merge: origin/gece/p2-olcum~1 (gece vardiyası 2026-09-21)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-21 15:07:25 +03:00
12 changed files with 219 additions and 43 deletions

View File

@@ -3,8 +3,9 @@
*
* Script yalnızca prod build'de yükleniyor (bkz. app/layout.tsx) ve reklam
* engelleyiciler tarafından bloklanabiliyor; bu yüzden buradaki her çağrı
* `window.rybbit` yoksa sessizce hiçbir şey yapmaz — çağıran taraf asla
* varlık kontrolü yapmak zorunda kalmasın.
* `window.rybbit` yoksa kullanıcıya hiçbir şey hissettirmez (olaylar kısa
* süre kuyrukta bekler, script gelmezse atılır) — çağıran taraf asla varlık
* kontrolü yapmak zorunda kalmasın.
*
* KURAL: Öğrencinin ham başarı sıralaması analitiğe GÖNDERİLMEZ. Sıralama
* her zaman `siraKovasi()` ile bantlanır.
@@ -29,7 +30,13 @@ export type OlayAdi =
| "kilit_goruntulendi"
| "kredi_bitti"
| "tadimlik_goruntulendi"
| "kaydet_banner_goruntulendi";
| "kaydet_banner_goruntulendi"
| "cta_form_goruntulendi"
| "sira_form_hata"
| "sonuc_goruntulendi"
| "paket_goruntulendi"
| "liste_uretim_hata"
| "giris_hata";
export type Ozellikler = Record<string, string | number>;
@@ -48,16 +55,100 @@ declare global {
}
}
/** Özel olay gönderir. Script yoksa no-op. */
// Erken olay kuyruğu: script `afterInteractive` yüklendiği için sert sayfa
// yüklemesinde (OAuth dönüşü, e-postadaki link, yer imi) mount effect'leri
// `window.rybbit`'ten önce koşar. Mount anında atılan olaylar
// (sonuc_goruntulendi, paket_goruntulendi) kaybolmasın diye script gelene
// kadar burada bekletilir. Script hiç gelmezse (reklam engelleyici, dev)
// kuyruk 10 sn sonra sessizce atılır ve bir daha doldurulmaz.
const KUYRUK_SINIRI = 20;
const DENEME_ARALIGI_MS = 500;
const DENEME_SINIRI = 20;
let kuyruk: Array<[OlayAdi, Ozellikler | undefined]> = [];
let zamanlayici: ReturnType<typeof setInterval> | null = null;
let denemeSayisi = 0;
let kuyrukKapali = false;
function zamanlayiciyiDurdur(): void {
if (zamanlayici !== null) clearInterval(zamanlayici);
zamanlayici = null;
}
/** Bekleyen olayları geliş sırasıyla gönderir. */
function kuyruguBosalt(rybbit: RybbitApi): void {
const bekleyenler = kuyruk;
kuyruk = [];
zamanlayiciyiDurdur();
for (const [ad, ozellikler] of bekleyenler) {
try {
rybbit.event(ad, ozellikler);
} catch {
// yut
}
}
}
function kuyrugaEkle(ad: OlayAdi, ozellikler?: Ozellikler): void {
if (kuyrukKapali || kuyruk.length >= KUYRUK_SINIRI) return;
kuyruk.push([ad, ozellikler]);
if (zamanlayici !== null) return;
zamanlayici = setInterval(() => {
denemeSayisi += 1;
if (window.rybbit) {
kuyruguBosalt(window.rybbit);
} else if (denemeSayisi >= DENEME_SINIRI) {
// Script gelmedi: bekleyenleri at, sonraki olaylar eski no-op'a dönsün
kuyruk = [];
kuyrukKapali = true;
zamanlayiciyiDurdur();
}
}, DENEME_ARALIGI_MS);
}
/** Özel olay gönderir. Script henüz yoksa kısa süre kuyrukta bekletir. */
export function olay(ad: OlayAdi, ozellikler?: Ozellikler): void {
if (typeof window === "undefined") return;
try {
window.rybbit?.event(ad, ozellikler);
if (!window.rybbit) {
kuyrugaEkle(ad, ozellikler);
return;
}
// Sıra bozulmasın: bekleyen varsa önce onlar gider
if (kuyruk.length > 0) kuyruguBosalt(window.rybbit);
window.rybbit.event(ad, ozellikler);
} catch {
// Analitik hiçbir koşulda kullanıcı akışını bozmaz
}
}
/** Oturum başına tekilleştirme anahtarı (useGorunumOlayi ile ortak). */
export function olayAnahtari(ad: OlayAdi, anahtarEki?: string): string {
return `kolaytercih.olay.${ad}${anahtarEki ? `.${anahtarEki}` : ""}`;
}
/**
* Olayı oturum (sekme) başına en fazla 1 kez gönderir; "görüntülendi" türü
* olaylar yenileme ve geri-ileri gezinmede şişmesin diye. Tekilleştirme
* sessionStorage anahtarıyla yapılır.
*/
export function olayBirKez(
ad: OlayAdi,
ozellikler?: Ozellikler,
/** Aynı olayın farklı yüzeyleri/durumları için tekilleştirme eki. */
anahtarEki?: string,
): void {
if (typeof window === "undefined") return;
const anahtar = olayAnahtari(ad, anahtarEki);
try {
if (window.sessionStorage.getItem(anahtar)) return;
window.sessionStorage.setItem(anahtar, "1");
} catch {
// Storage engelliyse tekilleştirmeden vazgeç, olay yine atılır
}
olay(ad, ozellikler);
}
/**
* Ham sıralamayı analitik için bantlar. Panelde "hangi sıralama bandı
* dönüşüyor" sorusunu yanıtlar ama tekil öğrenciyi işaret etmez.