Neden yalnızca sayfayı yenileyince çalışıyordu?

Bildirim izni bileşeni ilk ziyarette görünmüyordu. Üç neden vardı: verinin geç gelmesi, eksik çerez ve aynı durumun farklı adlarla kaydedilmesi.

Bir kayıt bloğuna giden iki şeridin çizgi çizimi: bir push script jetonu ve değer kaydedilene kadar çizgide bekleyen bir widget jetonu

Bir ziyaretçi bildirimleri engellediğinde, site bir daha soramaz. Tarayıcı cevabı hatırlar ve izin API'sine (yazılımların birbirine istek göndermesini sağlayan arayüz) yapılan ikinci çağrı hiçbir şey göstermeden "denied" döner.

İzni yeniden açmak için ziyaretçinin elle işlem yapması gerekir. Ziyaretçi tarayıcısında site ayarlarını açıp anahtarı kendisi çevirmek zorundadır.

Revotas'ta tam bu durum için bir widget (sayfaya eklenen küçük işlevsel bileşen) var. Aralık 2024'te bir ekip arkadaşım yazdı: bildirimler engelliyse mağazanın mesajını ve bildirimleri yeniden açmak için numaralı adımları gösteriyor. 2025 baharında tuhaf bir raporla önüme geldi. Çalışıyordu, ama bazen. Sayfayı yenileyince çıkıyordu.

İlk seferde çıkması için iki script'e (belirli bir işi yapan kod dosyası) yayılan üç küçük düzeltme gerekti. Hepsi birlikte yüz satırın epey altında.

İki script, tek değer

Web push (uygulama açık olmasa da cihaza gönderilebilen bildirim) kullanan bir mağaza bizden iki script yüklüyor. Biri push'u yönetiyor: izin istiyor, aboneliği kaydediyor ve ziyaretçinin cevabını bir çerezde tutuyor. Diğeri, bu widget dahil, sitedeki widget'ları çalıştırıyor.

Widget, push script'inin sunucudan çektiği tek bir bilgiye ihtiyaç duyuyor: mağazanın push alan adı. Push script'i bunu istiyor ve cevap geldiğinde değeri localStorage'a yazıyor.

İlk ziyarette localStorage'da henüz bir şey yok. İki script de başlıyor. Widget script'i önce hazır oluyor, değere bakıyor ve hiçbir şey bulamıyor; widget görünmüyor. Bir an sonra push script'inin isteği dönüyor ve değer kaydediliyor, ama artık okuyacak kimse yok.

İkinci ziyarette değer zaten orada, o yüzden widget çalışıyor. "Bazen"in tamamı buydu.

Önce: widget, push script'i değeri kaydetmeden kayıtlı değere ulaşıyor ve hiçbir şey bulamıyor. Sonra: bekliyor ve push script'i değerin kaydedildiğini duyurduğunda çalışıyor.

Birinci düzeltme: değerin hazır olduğu haberini bekle

Widget yanlış şeyi bekliyordu. DOM (tarayıcının sayfadaki öğeleri temsil ettiği yapı) hazır olduğunda çalışıyordu; DOM'un hazır olması ise başka bir script'teki ağ isteği hakkında hiçbir şey söylemez.

İki ucu da değiştirdim. Push script'i artık değeri kaydettikten hemen sonra window üzerinde özel bir event (bir işlemin gerçekleştiğini bildiren olay) yayınlıyor. Widget önce storage'a bakıyor. Değer oradaysa çalışıyor. Değilse o event için bir dinleyici ekliyor ve event geldiğinde çalışıyor.

Zamanlayıcı yok, yeniden deneme döngüsü yok. Kontrol ilk ziyaretten sonraki bütün ziyaretleri, event ise ilk ziyareti karşılıyor.

Widget tarafı 18 Nisan 2025'te girdi. 8 Haziran'a kadar push script'i tarafı da ana hattaydı; event, yazma satırının hemen ardından yayınlanıyor.

İkinci düzeltme: çerez, izin değildir

Zamanlama düzeldikten sonra widget, bildirimleri açıkça engellemiş bazı ziyaretçilerde hâlâ gizli kalıyordu.

Widget'lar hedefleme kurallarıyla gösteriliyor ve bu widget'ın kuralı şunu soruyordu: web push engelli mi? Kontrol bizim çerezimizi okuyordu; push script'inin, ziyaretçi izin penceresine cevap verdiğinde yazdığı çerezi.

O çerez yalnızca ziyaretçi bu tarayıcıda bizim penceremize cevap verdiyse ve çerez hâlâ duruyorsa var. Bildirimleri tarayıcının kendi ayarlarından engelleyen ya da çerezi silinmiş, süresi dolmuş birinin hiç çerezi yoktu. Onlar için kontrol "hiçbir şey"i "engelli" ile karşılaştırıp kuralın eşleşmediğine karar veriyordu.

Tarayıcı gerçek cevabı zaten biliyor. Bu yüzden çerez yoksa kontrol artık Notification.permission değerini okuyor ve "denied"ı engelli sayıyor. Kontrol önce mevcut akışta kullanılan çerezi okuyor. Çerez yoksa tarayıcının doğrudan bildirdiği izin durumuna bakıyor.

Üçüncü düzeltme: tek durum, üç isim

Sonuncusu kelime meselesiydi. Çerez engelli durumunu false string'i (metin değeri) olarak saklıyor. Hedefleme kuralları aynı duruma blocked, bazı yapılandırmalarda da blocked_webpush diyordu.

Tek durumun üç yazımı, ikisi için asla doğru olamayacak bir string karşılaştırması demekti. Kuralın değerini karşılaştırmadan önce normalize ettim; artık üçü de aynı anlama geliyor.

Aynı değişiklikte push script'i doğrudan bir yol kazandı. İzin isteği reddedildiğinde, sayfanın yapılandırması istiyorsa, aynı "kontrol et, yoksa dinle" girişi üzerinden widget'ı kendisi çağırıyor. Bildirimleri az önce engelleyen ziyaretçi, geri dönüş yolunu en çok isteyebilecek kişi.

Hata aslında neyle ilgiliydi

Üçü de görünür olduktan sonra zor değildi. Raporu kafa karıştırıcı yapan, her birinin aynı belirtiyi, görünmeyen bir widget'ı, farklı bir ziyaretçi grubu için üretmesiydi:

  • ilk kez gelenler, zamanlama yüzünden,
  • çerezimiz olmayanlar, durumun okunduğu yer yüzünden,
  • kuralı başka bir kelime kullanan mağazalar, isimlendirme yüzünden.

Zamanlama düzeltmesi Nisan'da girdi. Diğer ikisi Haziran'da, ilk düzeltmenin ulaşmadığı ziyaretçiler için geldi.

Bundan aldıklarım:

  • "Yenileyince çalışıyor" bir sıralama hatasıdır. Bir şey yazılmadan önce okunuyor.
  • Tamamlanmayı tahmin etme, bildir. Yazma noktasındaki bir event tek satırdır ve yarışı ortadan kaldırır. Gecikme eklemek yalnızca yerini değiştirir.
  • Platformun durumunu, ondaki kopyana tercih et. Çerezimiz tarayıcıdaki iznin bir önbelleğiydi ve önbellekler kaybolur.
  • Bir duruma tek isim ver. İki sistem bir string üzerinde anlaşmak zorundaysa sınırda normalize et.

Bu yazı İngilizce aslından Türkçeye çevrilmiştir.

Çözmeye değer bir problemin mi var?Birlikte çözelim

Konuşmaya başla

Full-stack ve uygulamalı AI rollerine açığım, uzaktan veya hibrit.

Rolü, ekibi ve problemi anlat.