Offline-First ve Background Sync (PWA)
Kısaca: Kullanıcı eylemleri (örneğin “sepete ekle”) internet bağlantısı yokken atılmaz, tarayıcıda yerel olarak saklanır ve bağlantı geri gelir gelmez otomatik olarak yeniden oynatılır.
Biraz daha ayrıntılı: localStorage yerine IndexedDB kullanılır, çünkü bir service worker ona arka planda, asıl sayfa hiç açık değilken bile erişebilmelidir — localStorage’a service worker bağlamından ulaşılamaz. Background Sync API’si (yalnızca Chrome/Edge/Android), sayfa kapalıyken bile service worker’ın yeniden oynatmayı tetiklemesine olanak tanır; evrensel bir yedek olarak (Safari/iOS’ta da çalışır) ek olarak basit bir 'online' tarayıcı olay dinleyicisi kullanılır.
Bizim bağlamımız: Emzett’te alışveriş sepeti için app/utils/offlineQueue.ts (CART_QUEUE_STORE) — /api/cart/sync üzerinden idempotent bir eşitlemeyle, böylece yinelenen bir eşitleme denemesi (Background Sync VE 'online' olayı) çift girdi oluşturmaz.
Ayrıntılı
Temel gereksinim olarak idempotency
“Idempotent” buradaki anahtar sözcüktür: hem Background Sync API’si hem de 'online' olay yedeği aynı eşitlemeyi tetikleyebileceğinden (tarayıcı tam o anda çevrimiçi olursa kuramsal olarak ikisi aynı anda bile çalışabilir), sunucu aynı eylemin iki kez gönderilmesinin ÇİFT etkisi olmayacak şekilde kurulmalıdır — tipik olarak eylem başına istemcinin ürettiği benzersiz bir kimlikle (genellikle eylem oluşturulurken yerel olarak üretilen bir UUID); sunucu bunu ikinci gelişte tanır ve ürünü ikinci kez sepete eklemek yerine yok sayar. Bu kimlik asıl eşitlemeden ÖNCE (yalnızca gönderirken değil) üretilmelidir, böylece her eşitleme denemesinde aynı kalır — her denemede yeni bir kimlik üretilseydi idempotency denetimi etkisiz olurdu.
Tipik akış
Kullanıcı bağlantısız “sepete ekle”ye tıklar → eylem hemen IndexedDB kuyruğuna düşer VE arayüzde iyimser biçimde sanki çoktan geçmiş gibi gösterilir → navigator.onLine tekrar true olur olmaz (ya da service worker Background Sync ile uyandırıldığında) bir eşitleme işleyicisi kuyruğu sırayla işler ve her eylemi sunucuya gönderir → başarıda kuyruk girdisi silinir, başarısızlıkta sonraki bir deneme için tutulur. İyimser arayüz güncellemesi bilinçli bir tercihtir: çevrimdışı alışveriş yapan bir kullanıcı uygulamanın “bozuk” olduğu hissine kapılmamalıdır — bunun yerine eylem hemen başarılı görünür, gerçekten bir şey başarısız olursa ince bir ipucuyla (“tekrar çevrimiçi olunca eşitlenecek”).
self.addEventListener("sync", (event) => {
if (event.tag === "cart-sync") {
event.waitUntil(processQueuedCartActions());
}
});Neden localStorage yerine IndexedDB
localStorage senkrondur ve ana iş parçacığını kısa süre engeller; bu, daha büyük veri miktarlarında fark edilir takılmalara yol açabilir; IndexedDB ise asenkrondur ve yapılandırılmış, daha büyük veri miktarları için tasarlanmıştır (dizinli nesne depoları, işlemler). Ancak buradaki belirleyici nokta farklıdır: localStorage belgenin ana iş parçacığına bağlıdır ve bir service worker bağlamından basitçe ulaşılamaz — bir service worker belgeden bağımsız kendi iş parçacığında çalışır, tam da sayfanın kendisi hiç açık değilken de çalışabilmesi için. IndexedDB, her iki bağlamdan (belge VE service worker) eşit ölçüde erişilebilen birkaç depolama API’sinden biridir.
Background Sync API’sinin sınırları
Background Sync API’si bilinçli olarak anında yürütme garantisi değildir — eşitlemeyi gerçekten ne zaman tetikleyeceğine tarayıcı kendisi karar verir (ağ kalitesine, pil düzeyine vb. bağlı olarak), ancak tipik olarak yeniden bağlandıktan birkaç saniye içinde. Tarayıcı desteği de tutarsızdır: Safari (ve dolayısıyla iOS’taki tüm tarayıcılar, çünkü Apple orada kendi WebKit motorunu zorunlu kılar) API’yi hâlâ desteklemez — bu yüzden 'online' olay yedeği isteğe bağlı bir ek değil, platformlar arası güvenilirlik için kesinlikle gereklidir.
Ayrıca bak: proxy.ts, Bağlantısız protokol