Web SDK
@opennotification/web — tarayıcılar ve PWA'lar. Import anında hiçbir şey çalışmaz, SSR sırasında import etmek güvenlidir; @opennotification/core'dan yalnızca tipler gelir, paketinize doğrulama kütüphanesi girmez.
Bu çalışmadan önce HTTPS, manifest, VAPID çifti ve CORS_ORIGINS'te origin'iniz gerekir: Web platform kurulumu.
1 Kur
bun add @opennotification/web
ya da doğrudan CDN'den:
<script type="module">
import { init } from "https://cdn.jsdelivr.net/npm/@opennotification/web/+esm";
</script>
2 Service worker
Hazır derlenmiş paketi web kökünüze kopyalayın:
bun --filter @opennotification/web build:sw
cp packages/sdk-web/dist/sw.js public/sw.js
SDK onu /sw.js?api=<endpoint> olarak kaydeder — worker API taban URL'ini o query string'den okur, tek derleme her kuruluma uyar.
Zaten service worker'ınız mı var? Handler'ları içine alın:
// sw.ts
import { registerPushHandlers } from "@opennotification/web/sw";
registerPushHandlers({
apiUrl: "https://push.example.com",
defaults: { icon: "/icons/192.png", badge: "/icons/badge.png" },
});
Handler'lar şunları kapsar:
| Olay | Yapar |
|---|---|
push | Bildirimi gösterir (title, body, icon, image, badge, tag, actions, silent) ve delivered bildirir. |
notificationclick | opened (gövde) ya da clicked (aksiyon) bildirir, yeni sekme açmadan önce hedef URL'deki mevcut sekmeye odaklanır. |
notificationclose | dismissed bildirir. |
pushsubscriptionchange | Yeniden abone olur ve /v1/subscriptions/rotate çağırır; aboneliğini sessizce yenileyen tarayıcı aboneye mal olmaz. |
3 Başlat ve yeteneği kontrol et
import { init, mountInstallPrompt } from "@opennotification/web";
const on = init({
endpoint: "https://push.example.com",
appKey: "pk_live_…", // public anahtar
vapidKey: "BNcRd…", // uygulamanın VAPID public key'i
serviceWorkerPath: "/sw.js", // varsayılan
});
const cap = on.capability();
// { supported: true, reason: null, platform: "desktop" }
// { supported: false, reason: "IOS_NEEDS_INSTALL", platform: "ios" }
reason | Anlamı | Ne yapmalı |
|---|---|---|
null | Push çalışır. | Etkinleştir düğmenizi gösterin. |
IOS_NEEDS_INSTALL | iOS Safari sekmesi. Push yalnızca kurulu PWA'da var. | Kurulum rehberini gösterin (aşağıda). |
IOS_UNSUPPORTED_BROWSER | iOS'ta Chrome/Firefox/Edge — kuramayan WebKit kabukları. | Siteyi Safari'de açmalarını söyleyin. |
INSECURE_CONTEXT | HTTP üzerinden sunuluyor. | Hosting'i düzeltin. |
UNSUPPORTED_BROWSER | Service worker / Push API yok. | Düğmeyi gizleyin. |
iOS kurulum rehberi
const guidance = on.guidance("tr"); // "en" | "tr"
if (guidance?.actionable) mountInstallPrompt({ guidance });
guidance() Paylaş → Ana Ekrana Ekle adımlarını metin olarak döner; kendi sayfanızı yapmak istemiyorsanız mountInstallPrompt() küçük bir alt sayfa çizer. Kullanıcının düzeltemeyeceği nedenlerde (desteklenmeyen tarayıcı) actionable false'tur.
4 Abone ol — tıklamadan
document.querySelector("#enable")!.addEventListener("click", async () => {
const result = await on.subscribe({
externalId: "user_123",
tags: { plan: "premium" },
language: navigator.language,
});
if (!result.ok) {
switch (result.error.code) {
case "permission_denied": // bu origin için engelli — nasıl açılacağını gösterin
case "permission_dismissed": // istemi kapattı ya da hareket dışında çağrıldı
case "unsupported":
case "service_worker_failed":
case "push_subscribe_failed":
case "api_error":
case "network_error":
}
}
});
subscribe("user_123"), { externalId }'nin kısa hali. SDK worker'ı kaydeder, hazır olmasını bekler, izin ister, VAPID anahtarıyla abone olur ve aboneliği browser, standalone (PWA olarak kurulu), saat dilimi ve dille POST eder.
Bir tıklama/dokunma handler'ı dışında Notification.requestPermission() Safari'de sessizce reddedilir, Chrome'da giderek daha çok kısıtlanır. Bu olduğunda SDK permission_dismissed döner.
5 Kimlik, tag'ler, abonelikten çıkma
await on.login("user_456");
await on.setTags({ plan: "premium", streak: 42 });
await on.removeTags(["streak"]);
await on.setLanguage("tr");
await on.logout();
await on.unsubscribe(); // tarayıcıda abonelikten çıkar ve satırı siler
on.permission(); // "granted" | "denied" | "default" | "unsupported"
await on.isSubscribed();
on.subscriptionId(); // localStorage'dan
const stop = on.trackSessions(); // şimdi ve sekmeye her dönüşte oturum say
await on.trackSession(); // ya da bir kez kendiniz ping atın
Semantik: Kimlik. Oturumlar sunucuda tekilleştirilir (30 dakika boşluk kuralı). Sessiz (arka plan) kampanyalar tarayıcılara hiç ulaşmaz — fan-out web aboneliklerini atlar; hiçbir şey göstermeyen push aboneliği iptal ettirir.
Worker'ın aldığı yük
Referans olarak sunucunun her push'a şifrelediği:
{
"title": "Hafta sonu indirimi",
"body": "Pazar gecesine kadar her şeyde %30.",
"icon": "https://…/icon.png",
"image": "https://…/sale.jpg",
"badgeCount": 3,
"tag": "<collapseId>",
"actions": [{ "action": "shop", "title": "Alışverişe başla" }],
"data": { "msgId": "<imzalı>", "campaignId": "…", "url": "https://…/sale", "a:shop": "https://…/sale?cta=1", "screen": "sale" }
}
Buton hedefleri data["a:<id>"] olarak gider; tıklama handler'ı doğrusunu seçer. Kampanyanın sesi none ise silent: true eklenir; tag collapseId'den gelir, yeni push eskisini değiştirir.
Notlar
- Hatalar
Resultolarak döner, asla fırlatılmaz. - Abonelik kimliği
localStorage'da tutulur; site verisini temizlemek yeniden abone olmak demektir, bu da aynı endpoint'i upsert eder. - SDK'da VAPID private key'i okuyan ya da saklayan hiçbir şey yoktur — tarayıcıya yalnızca public olan ulaşır.