Files

221 lines
8.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
NUMEX KAŞE STÜDYO — SUNUCU KÖPRÜSÜ (api.js)
------------------------------------------------------------
Arayüz ile yerel Node sunucusu arasındaki TEK temas noktası.
Sunucu uçları (server/server.js ile birebir sözleşmeli):
GET /api/saglik → { durum, uygulama, surum, tabla }
GET /api/malzemeler → { malzemeler: [...] }
POST /api/kase-onizleme → { plan: { sureSn, yolMm, satirlar, ... } }
POST /api/gcode-kaydet → 201 { tamam, dosya, bayt }
GET /api/gcode-liste → { adet, dosyalar:[{ad,bayt,...}] }
GET /api/gcode-indir?ad= → text/plain G-code
KURALLAR
- Sunucu yoksa arayüz ÇÖKMEZ: her çağrı nazik bir hata nesnesi döner.
- Ağ hatalarında otomatik 1 kez yeniden dener (geçici kopmalar için).
- Hiçbir yerde alert() yok; hatalar dönen değerle bildirilir.
============================================================ */
'use strict';
(function (global) {
/* Sunucu aynı kökenden servis edildiği için göreli yollar yeterli.
file:// ile açılırsa localhost'a düşülür (kullanıcı sunucuyu çalıştırmışsa çalışır). */
/* Sunucu aynı kökenden servis edildiğinde (tarayıcı, http/https) göreli
yol yeterlidir; ancak Node/vitest gibi göreli URL'i çözemeyen
bağlamlarda location.host'tan TAM taban üretilir. file:// ile açılırsa
localhost'a düşülür (kullanıcı sunucuyu çalıştırmışsa çalışır). */
const TABAN = (function () {
const kon = global.location;
if (!kon) return 'http://localhost:4175';
const protokol = kon.protocol || '';
if (/^https?:$/.test(protokol)) {
/* host varsa (127.0.0.1:PORT) tam köken kullan — göreli fetch'in
Node tarafında çözülememesi sorununu ortadan kaldırır. */
if (kon.host) return protokol + '//' + kon.host;
return '';
}
return 'http://localhost:4175';
})();
const VARSAYILAN_ZAMAN_ASIMI = 12000;
/* ---------- Alt düzey istek yardımcısı ---------- */
/* fetch'i ÇAĞRI ANINDA çöz: tarayıcıda/Node'da global fetch sonradan
değiştirilebilir (test stub'ları, polyfill). Yükleme anında sabitlenmiş
bir referans, sahte/stub fetch'i gölgeleyip yanlış sonuç üretirdi. */
/* new Function ile yüklenen bu köprüde "globalThis" parametre olarak
gölgelenebilir (test yardımcıları sahte bir global geçer). Gerçek global
nesneye erişmek için dolaylı Function çağrısı kullanılır; bu, enjekte
edilen sahte globalden BAĞIMSIZ olarak canlı fetch'i bulur. */
function gercekGlobal() {
try {
/* eslint-disable-next-line no-new-func */
return Function('return this')();
} catch (e) {
return typeof globalThis !== 'undefined' ? globalThis : global;
}
}
function fetchBul() {
/* Öncelik: GERÇEK global (tarayıcıda window, Node/vitest'te globalThis).
vi.stubGlobal fetch'i burada ezer; sabitlenmiş referans yerine daima
canlı değere bak ki test stub'ları doğru yakalansın. */
const kok = gercekGlobal();
if (kok && typeof kok.fetch === 'function') return kok.fetch.bind(kok);
if (global && typeof global.fetch === 'function') return global.fetch.bind(global);
throw new Error('fetch bulunamadı');
}
function istek(yol, secenek) {
secenek = secenek || {};
const yontem = (secenek.yontem || 'GET').toUpperCase();
const urlAdres = TABAN + yol;
const cfg = {
method: yontem,
headers: Object.assign({ 'Accept': 'application/json' }, secenek.basliklar || {})
};
if (secenek.govde !== undefined && secenek.govde !== null) {
cfg.headers['Content-Type'] = 'application/json';
cfg.body = typeof secenek.govde === 'string' ? secenek.govde : JSON.stringify(secenek.govde);
}
const kontrolcu = typeof AbortController !== 'undefined' ? new AbortController() : null;
if (kontrolcu) cfg.signal = kontrolcu.signal;
const zamanAsimi = setTimeout(function () {
if (kontrolcu) kontrolcu.abort();
}, secenek.zamanAsimi || VARSAYILAN_ZAMAN_ASIMI);
return fetchBul()(urlAdres, cfg)
.then(function (yanit) {
clearTimeout(zamanAsimi);
const icerikTipi = yanit.headers.get('content-type') || '';
const okuyucu = icerikTipi.indexOf('application/json') >= 0
? yanit.json()
: yanit.text();
return okuyucu.then(function (govde) {
return { tamam: yanit.ok, kod: yanit.status, veri: govde, basliklar: yanit.headers };
});
})
.catch(function (hata) {
clearTimeout(zamanAsimi);
const iptal = hata && (hata.name === 'AbortError');
throw new Error(
iptal
? 'Sunucu zamanında yanıt vermedi (zaman aşımı).'
: 'Sunucuya ulaşılamadı — "npm start" ile çalıştığından emin olun.'
);
});
}
/* ---------- Yeniden denemeli istek ---------- */
function istekTekrar(yol, secenek, deneme) {
deneme = deneme === undefined ? 1 : deneme;
return istek(yol, secenek).catch(function (hata) {
if (deneme > 0) {
return new Promise(function (r) { setTimeout(r, 350); })
.then(function () { return istekTekrar(yol, secenek, deneme - 1); });
}
throw hata;
});
}
/* ---------- Genel sonuç sarmalayıcı ----------
Arayüz hiçbir zaman "throw" görmez; hep { tamam, veri, hata } alır. */
function guvenli(promiseFn) {
return promiseFn()
.then(function (r) {
if (!r.tamam) {
const mesaj = (r.veri && (r.veri.hata || r.veri.mesaj)) || ('Sunucu hatası (HTTP ' + r.kod + ')');
return { tamam: false, kod: r.kod, hata: mesaj, veri: r.veri };
}
return { tamam: true, kod: r.kod, veri: r.veri, hata: null };
})
.catch(function (hata) {
return { tamam: false, kod: 0, hata: hata.message, veri: null };
});
}
/* ============================================================
DIŞA AÇIK API
============================================================ */
const KaseAPI = {
taban: TABAN,
/** Sunucu ayakta mı + sürüm/tabla bilgisi */
saglik: function () {
return guvenli(function () { return istekTekrar('/api/saglik', {}, 1); });
},
/** Lazer parametre presetleri */
malzemeler: function () {
return guvenli(function () { return istekTekrar('/api/malzemeler', {}, 1); });
},
/**
* İşleme önizlemesi (süre, toplam yol, satır kırılımı).
* @param {object} govde { metin, font, boy, guc, hiz, gecis, x, y }
*/
onizleme: function (govde) {
return guvenli(function () {
return istek('/api/kase-onizleme', { yontem: 'POST', govde: govde });
});
},
/** Üretilen G-code'u cikti/ klasörüne yazar. */
gcodeKaydet: function (govde) {
return guvenli(function () {
return istek('/api/gcode-kaydet', { yontem: 'POST', govde: govde });
});
},
/** Kaydedilmiş G-code dosyalarını listeler. */
gcodeListe: function () {
return guvenli(function () { return istek('/api/gcode-liste', {}, 1); });
},
/** Kaydedilmiş bir dosyayı metin olarak indirir. */
gcodeIndir: function (ad) {
return guvenli(function () {
return istek('/api/gcode-indir?ad=' + encodeURIComponent(ad));
});
},
/**
* Tarayıcıdan dosya indirmeyi tetikler (kaydedilmiş .nc dosyası).
* Sunucudan metni çekip Blob ile indirir — böylece indirme
* davranışı da sunucu sözleşmesine bağlı kalır.
*/
gcodeIndirTetikle: function (ad) {
return KaseAPI.gcodeIndir(ad).then(function (sonuc) {
if (!sonuc.tamam) return sonuc;
try {
const blob = new Blob([sonuc.veri], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = ad;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(function () { URL.revokeObjectURL(url); }, 1500);
} catch (e) {
return { tamam: false, kod: 0, hata: 'İndirme başlatılamadı: ' + e.message, veri: null };
}
return sonuc;
});
},
/** Ham istek (test/ileri düzey kullanım için) */
hamIstek: istek
};
global.KaseAPI = KaseAPI;
})(typeof window !== 'undefined' ? window : globalThis);
/* Node tarafında (vitest) require edilebilirlik — tarayıcıda etkisiz. */
if (typeof module !== 'undefined' && module.exports) {
module.exports = typeof window !== 'undefined' ? window.KaseAPI : globalThis.KaseAPI;
}