Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Butun oqim
- 2.1 To'rt ishtirokchi
- 2.2 Obuna ichida nima bor
- 3. Ruxsat: qachon va qanday so'rash
- 3.1 Uch holat va bitta imkoniyat
- 3.2 "Yumshoq so'rash" naqshi
- 4. VAPID: "bu xabarni men yubordim"
- 4.1 Kalit juftligi
- 4.2 Brauzer kalitni qanday oladi
- 4.3 Imzo: JWT
- 5. Service Worker'da qabul qilish
- 5.1 push hodisasi
- 5.2 notificationclick: ochiq oynani topish
- 6. Platformalar: FCM va iOS
- 6.1 Firebase Cloud Messaging
- 6.2 iPhone va iPad
- 7. Biz sinab ko'rdik: nima ishladi, nima ishlamadi
- 7.1 Nega vazifalar da push yo'q
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): taklifni qachon ko'rsatish
- 3-mashq (qiyin): oyna tanlash — eng aniq moslik
- 4-mashq: tajriba qadami (ixtiyoriy)
- 11. Real ishda
- Xulosa
- Manbalar
Web push bildirishnomalar: ruxsat, obuna, VAPID va Service Worker'da qabul qilish
Qisqacha: Web push — sayt yopiq bo'lsa ham foydalanuvchiga bildirishnoma yuborish. Oqim: sahifa ruxsat so'raydi (faqat bosishdan keyin) va
pushManager.subscribebilan obuna oladi. Obuna serverga beriladi; server xabarni brauzerning push xizmatiga (Chrome — Google FCM) VAPID kaliti bilan imzolab yuboradi; push xizmati SW'ni uyg'otadi, SW esashowNotificationqiladi. iPhone'da faqat bosh ekranga qo'shilgan PWA'da. Halol holat: kanon VAPID kalitini va imzoni lokal yaratdi, lekin push'ni lokal yetkaza olmadi — bu darsdagi oqim hujjat bo'yicha,vazifalarga kirmadi.
Bu darsda
- Push oqimining to'rt ishtirokchisini va har qadamda nima ketishini chiza olasiz.
- Ruxsatni "yumshoq so'rash" naqshi bilan, to'g'ri vaqtda so'raydigan komponent yozasiz.
- VAPID kalit juftligi,
applicationServerKeyva JWT imzosini Node'da yaratib ko'rasiz. - SW'dagi
pushvanotificationclickni ochiq oynaga fokus beradigan qilib yozasiz. - FCM, iOS cheklovlari va obuna eskirganda (410) nima qilishni bilasiz.
Oldin bilishingiz kerak: Permissions, Notifications va Geolocation, PWA: o'rnatiladigan veb ilova (push asoslari), Service Worker hayot sikli, PWA React loyihada.
1. Nega bu kerak?
«Bahor» oshxonasida buyurtma tayyor bo'lganda mehmon telefonida "Oshingiz tayyor!" chiqishi kerak. Mehmon ilovani yopib, Telegram'da o'tiribdi. Notifications darsidagi new Notification(…) bu yerda yordam bermaydi — u faqat sahifa ochiq bo'lganda ishlaydi.
PWA darsida push'ning asosini ko'rgan edik: obuna, VAPID, SW'dagi push hodisasi. Bu darsda butun zanjirni quramiz va uch savolga javob beramiz: ruxsatni qachon so'rash (eng ko'p xato shu yerda), kalitlar qanday ishlaydi, va iPhone hamda Android'da nima farq qiladi.
Hayotiy o'xshatish: shifoxona navbati. Siz ro'yxatdan o'tasiz (obuna), qabulxona sizning raqamingizni yozadi (server saqlaydi). Navbatingiz kelganda qabulxona to'g'ridan-to'g'ri sizga emas, pochta xizmatiga xat beradi (push xizmati). Pochta sizni qayerda bo'lsangiz ham topadi.
Bitta halol ogohlantirish oldindan. Push uchun brauzer kompaniyasining serveri kerak — Chrome uchun Google'ning FCM'i. Kursning qoidasi: hech qanday hisob va kalit yaratmaymiz. Kanon kalitlarni va imzoni lokal yaratdi, lekin xabarni lokal yetkazish urinishi natija bermadi. Shuning uchun bu darsdagi to'liq oqim — hujjatlar bo'yicha. Qaysi qism o'lchangani har joyda aytiladi.
2. Butun oqim
2.1 To'rt ishtirokchi
sequenceDiagram
participant P as Sahifa
participant W as Service Worker
participant S as Server («Bahor»)
participant X as Push xizmati (FCM)
P->>P: tugma bosildi → requestPermission
P->>X: subscribe(applicationServerKey)
X-->>P: obuna: endpoint + kalitlar
P->>S: POST /obuna (obuna JSON)
Note over S: osh tayyor!
S->>X: shifrlangan xabar + VAPID JWT
X->>W: push hodisasi (sahifa yopiq bo'lsa ham)
W->>W: showNotification
Note over W: bosildi → notificationclickDiagrammaga qarang: server hech qachon telefonga to'g'ridan-to'g'ri ulanmaydi. U faqat push xizmatini biladi. Push xizmati esa brauzer bilan doimiy aloqada — telefon uxlab yotganda ham.
2.2 Obuna ichida nima bor
pushManager.subscribe qaytargan obunani JSON.stringify qilsangiz, hujjat bo'yicha shunday shakl:
{
"endpoint": "https://fcm.googleapis.com/fcm/send/…",
"expirationTime": null,
"keys": {
"p256dh": "BN…(base64url, 65 bayt)",
"auth": "…(base64url, 16 bayt)"
}
}endpoint— push xizmatidagi shu brauzerning "pochta qutisi". Bu manzilni bilgan har kim unga xabar yubormoqchi bo'ladi — shuning uchun u maxfiy saqlanadi va VAPID imzosi talab qilinadi.p256dhvaauth— xabarni shifrlash uchun brauzerning kalitlari. Server xabarni shu kalitlar bilan shifrlaydi (RFC 8291). Push xizmati — Google ham — xabar matnini o'qiy olmaydi, faqat yetkazadi.
Bu JSON — bitta foydalanuvchi, bitta brauzer. Foydalanuvchining telefoni va kompyuteri — ikki obuna. Server ularni foydalanuvchiga bog'lab saqlaydi (25-qism).
3. Ruxsat: qachon va qanday so'rash
3.1 Uch holat va bitta imkoniyat
Notification.permission uch qiymat oladi (Permissions darsida ko'rganmiz): "default" (so'ralmagan), "granted", "denied". Eng muhim fakt: "denied" dan keyin sayt qayta so'ray olmaydi. requestPermission() darhol "denied" qaytaradi, oyna chiqmaydi. Foydalanuvchi faqat brauzer sozlamalaridan o'zi qaytarishi mumkin.
Ya'ni brauzerning ruxsat oynasi — bitta imkoniyat. Uni sahifa ochilishi bilan, mehmon hali nima uchunligini bilmay turib ko'rsatsangiz — "Bloklash" ni bosadi va imkoniyat yo'qoladi. Chrome hujjatiga ko'ra, ruxsat so'rovlari ko'p rad etiladigan saytlarda brauzer oynani "jimroq" ko'rinishga o'tkazadi — manzil satridagi kichik belgi.
3.2 "Yumshoq so'rash" naqshi
To'g'ri yo'l — ikki qadam:
- O'z UI'ingiz — foydali paytda: mehmon buyurtma berdi. "Tayyor bo'lganda xabar beraylikmi?" va ikki tugma. Bu — sizning tugmangiz; «Yo'q» bosilsa, hech narsa yo'qolmaydi, keyinroq qayta taklif qilish mumkin.
- Brauzer oynasi — faqat «Ha» bosilgandan keyin. Foydalanuvchi nima uchunligini biladi va "Ruxsat berish" ehtimoli yuqori.
Natija oynasida haqiqiy ruxsat oynasi yo'q (sandbox iframe). Shuning uchun brauzer oynasini soraRuxsat funksiyasi bilan taqlid qilamiz — haqiqiy ilovada uning o'rnida Notification.requestPermission() turadi:
import { useState } from "react";
// Taqlid: brauzer oynasi. Ilovada — Notification.requestPermission()
const soraRuxsat = () =>
new Promise((hal) => setTimeout(() => hal("granted"), 100));
function EslatmaTaklifi() {
const [holat, setHolat] = useState("default");
const [yopildi, setYopildi] = useState(false);
async function yoq() {
const javob = await soraRuxsat();
console.log("brauzer javobi:", javob);
setHolat(javob);
// granted bo'lsa — keyingi qadam: pushManager.subscribe
}
if (holat === "granted") {
return <p>Tayyor bo'lganda xabar beramiz.</p>;
}
if (holat === "denied") {
return (
<p>
Bildirishnomalar o'chiq. Yoqish: manzil satridagi qulf
belgisi → Bildirishnomalar → Ruxsat.
</p>
);
}
if (yopildi) return null;
return (
<section aria-label="Eslatma taklifi">
<p>Oshingiz tayyor bo'lganda telefonga xabar beraylikmi?</p>
<button onClick={yoq}>Ha, xabar bering</button>{" "}
<button onClick={() => setYopildi(true)}>Hozir emas</button>
</section>
);
}
export default function App() {
return (
<>
<p>Buyurtma qabul qilindi: osh, 1 porsiya.</p>
<EslatmaTaklifi />
</>
);
}Konsolda:
brauzer javobi: grantedEkranda:
Buyurtma qabul qilindi: osh, 1 porsiya.
Tayyor bo'lganda xabar beramiz.Uch holat — uch xil UI: taklif, "yoqildi", va denied da — yo'riqnoma (qayta so'rash befoyda). «Hozir emas» brauzer oynasini umuman ochmaydi — imkoniyat saqlanib qoladi.
Real ilovada yana ikki narsa: taklif qachon rad etilganini localStorage da eslab qolish (har sahifada qayta chiqmasin) va sahifa ochilganda Notification.permission ni o'qib, boshlang'ich holatni olish.
Tekshirib ko'ring: Jasur aka shunday kod yozdi:
useEffect(() => { Notification.requestPermission(); }, []). Nima noto'g'ri, va iPhone'da nima bo'ladi?
Javob
Ikki muammo. Birinchisi — ruxsat sahifa ochilishi bilan, sababsiz so'raladi: ko'p odam "Bloklash" ni bosadi va bu denied qaytarib bo'lmaydi. Ikkinchisi — iOS'da (va Firefox, Safari'da) ruxsat faqat foydalanuvchi harakati (bosish) ichida so'ralishi mumkin; effektdan chaqirilgan so'rov oynani umuman ko'rsatmaydi. To'g'ri joy — tugmaning onClick i.
4. VAPID: "bu xabarni men yubordim"
4.1 Kalit juftligi
Push xizmati endpoint ga kelgan har xabarni yetkazavermaydi. U so'raydi: "obunani yaratgan server sizmisiz?". Javob — VAPID (Voluntary Application Server Identification, RFC 8292): server kalit juftligiga ega. Ochiq kalit obuna paytida brauzerga beriladi (applicationServerKey), yopiq kalit faqat serverda turadi va har xabarni imzolaydi.
Kalit — P-256 egri chizig'idagi ECDSA juftligi (crypto: shifrlash va imzo — serverda chuqur). Node'ning o'zida yaratsa bo'ladi — kanon tajribasi aynan shunday qildi:
import { generateKeyPairSync, sign, verify } from "node:crypto";
const { publicKey, privateKey } = generateKeyPairSync("ec", {
namedCurve: "prime256v1", // P-256
});
// Ochiq kalit: 0x04 + x (32 bayt) + y (32 bayt) — "siqilmagan nuqta"
const jwk = publicKey.export({ format: "jwk" });
const ochiq = Buffer.concat([
Buffer.from([4]),
Buffer.from(jwk.x, "base64url"),
Buffer.from(jwk.y, "base64url"),
]);
const applicationServerKey = ochiq.toString("base64url");
console.log("ochiq kalit:", ochiq.length, "bayt", ochiq[0]);
console.log("base64url uzunligi:", applicationServerKey.length);Konsolda:
ochiq kalit: 65 bayt 4
base64url uzunligi: 87Kalitning o'zi har safar boshqa (tasodifiy), shuning uchun faqat uzunliklarni va birinchi baytni (4) chiqardik. 65 bayt — kanon o'lchovida ham aynan shu: VAPID ochiq kalit (applicationServerKey, base64url, 65 bayt). Yopiq kalitni kanon faylga yozmadi — u serverning maxfiy sozlamasi (.env), git'ga hech qachon tushmaydi.
Amalda kalitni web-push npm paketi bitta buyruq bilan yaratadi: npx web-push generate-vapid-keys. Kalit bir marta yaratiladi va o'zgartirilmaydi: o'zgarsa, eski obunalarning hammasi ishlamay qoladi.
4.2 Brauzer kalitni qanday oladi
subscribe ga kalit Uint8Array sifatida beriladi, server esa base64url satr beradi. Shuning uchun deyarli har loyihada shu kichik funksiya bor:
function base64urlBaytlarga(matn) {
const toldirish = "=".repeat((4 - (matn.length % 4)) % 4);
const base64 = (matn + toldirish)
.replaceAll("-", "+")
.replaceAll("_", "/");
const xom = atob(base64);
return Uint8Array.from(xom, (harf) => harf.charCodeAt(0));
}
const baytlar = base64urlBaytlarga("BAEC_-8");
console.log(baytlar.length, [...baytlar]);Konsolda:
5 [ 4, 1, 2, 255, 239 ]base64url — oddiy base64 ning manzilga mos varianti: + o'rniga -, / o'rniga _, oxiridagi = lar yo'q. atob faqat oddiy base64 ni tushunadi — shuning uchun avval qaytaramiz. _-8 → /+8 → 255, 239.
Obuna kodi esa (PWA darsidagidek), hujjat bo'yicha:
const reg = await navigator.serviceWorker.ready;
const obuna = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: base64urlBaytlarga(VAPID_OCHIQ_KALIT),
});
await fetch("/api/obuna", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(obuna),
});userVisibleOnly: true — Chrome talabi: har push ko'rinadigan bildirishnoma bilan tugaydi. "Jim" push (foydalanuvchiga bildirmasdan fonda ishlash) ruxsat etilmaydi.
4.3 Imzo: JWT
Server har xabarga Authorization sarlavhasini qo'shadi: vapid t=<JWT>, k=<ochiq kalit>. JWT — uch qismli satr: sarlavha, tana, imzo (JWT anatomiyasi — serverda chuqur). Uni Node'da qo'lda yasab, imzoni tekshiramiz:
import { generateKeyPairSync, sign, verify } from "node:crypto";
const { publicKey, privateKey } = generateKeyPairSync("ec", {
namedCurve: "prime256v1",
});
const b64u = (x) => Buffer.from(x).toString("base64url");
const sarlavha = b64u(JSON.stringify({ typ: "JWT", alg: "ES256" }));
const tana = b64u(
JSON.stringify({
aud: "https://fcm.googleapis.com", // push xizmati origin'i
exp: Math.floor(Date.now() / 1000) + 12 * 60 * 60, // 12 soat
sub: "mailto:admin@example.com", // muammo bo'lsa — kimga yozish
}),
);
const imzo = sign("sha256", Buffer.from(`${sarlavha}.${tana}`), {
key: privateKey,
dsaEncoding: "ieee-p1363", // JWT uchun: r ‖ s, 64 bayt
});
const jwt = `${sarlavha}.${tana}.${b64u(imzo)}`;
console.log("qismlar:", jwt.split(".").length);
console.log("imzo:", imzo.length, "bayt,", b64u(imzo).length);
const togri = verify(
"sha256",
Buffer.from(`${sarlavha}.${tana}`),
{ key: publicKey, dsaEncoding: "ieee-p1363" },
imzo,
);
console.log("imzo to'g'ri:", togri);Konsolda:
qismlar: 3
imzo: 64 bayt, 86
imzo to'g'ri: truePush xizmati aynan shu tekshiruvni qiladi: ochiq kalit (k=) bilan imzoni tekshiradi va u obunadagi applicationServerKey ga tengmi — solishtiradi. Kanon tajribasida ham imzo 86 belgi edi (natijalar/tajriba/30-web-push.txt). aud — push xizmati (endpoint origin'i), exp — 24 soatdan oshmasligi kerak (RFC 8292).
Amalda bularni web-push paketi o'zi qiladi — shifrlash bilan birga. Server qismi (hujjat bo'yicha; push yuborish — 29-qismda):
import webpush from "web-push";
webpush.setVapidDetails(
"mailto:admin@example.com",
process.env.VAPID_OCHIQ,
process.env.VAPID_YOPIQ, // faqat serverda!
);
try {
await webpush.sendNotification(
obuna, // brauzerdan kelgan JSON
JSON.stringify({ sarlavha: "«Bahor»", matn: "Oshingiz tayyor!" }),
{ TTL: 60 * 60 }, // 1 soatda yetkazilmasa — tashla
);
} catch (xato) {
if (xato.statusCode === 404 || xato.statusCode === 410) {
await obunaniOchir(obuna.endpoint); // obuna endi yo'q
}
}Uchta tafsilot: TTL — telefon o'chiq bo'lsa push xizmati xabarni qancha saqlashi (RFC 8030); eskirgan "osh tayyor" ertalab kelsa, foydasi yo'q. 410 Gone — foydalanuvchi ruxsatni olib tashladi yoki brauzer obunani yangiladi: bunday obunani bazadan o'chiring, aks holda yuborishlar o'sib boradi. Hajm — RFC 8030 push xizmatlaridan kamida 4096 bayt xabarni yetkazishni talab qiladi; katta ma'lumotni xabarga emas, SW yuklab oladigan manzilga qo'ying.
Tekshirib ko'ring: Nega VAPID yopiq kaliti
VITE_o'zgaruvchiga yozilmasligi kerak, ochiq kalit esa bemalol brauzerga beriladi?
Javob
Ochiq kalit — "kim imzolaganini tekshirish" uchun: uni bilish xabar yuborishga imkon bermaydi. Yopiq kalit esa imzolash uchun. U VITE_ bilan build'ga tushsa (Vite env), har kim JS fayldan oladi va sizning obunachilaringizning hammasiga sizning nomingizdan push yubora oladi — endpoint lar sizdan o'g'irlansa yoki taxmin qilinsa. Bu — AI chat darsidagi API kalit qoidasining aynan o'zi.
5. Service Worker'da qabul qilish
5.1 push hodisasi
Kanon tajribasidagi SW (tajriba/push/sw.js) — push qismi (sinov uchun sahifaga ham xabar yuborgan qismi olib tashlandi, bir qatorli try telefon uchun ochildi):
self.addEventListener("push", (event) => {
let x = { sarlavha: "Vazifalar", matn: "Yangi eslatma" };
try {
x = { ...x, ...event.data.json() };
} catch {
/* matn bo'lishi mumkin */
}
event.waitUntil(
self.registration.showNotification(x.sarlavha, {
body: x.matn,
tag: "eslatma",
data: { url: "./" },
}),
);
});event.data.json()— xabar tanasi (push xizmati shifrni brauzerda ochgan). Xabar JSON bo'lmasa —trysukut matnini qoldiradi.event.waitUntil— bildirishnoma chiqmaguncha SW uxlamasin (hayot sikli). Busiz Chrome "sayt fonda yangilandi" degan o'z bildirishnomasini ko'rsatishi mumkin —userVisibleOnlyva'dasi buzilgan bo'ladi.tag— bir xil tegli bildirishnomalar bir-birini almashtiradi. «Bahor» uch marta "buyurtma holati" yuborsa, ekranda uchta emas, bitta (oxirgisi) turadi.data— bosilganda kerak bo'ladigan ma'lumot (qaysi sahifani ochish).
Boshqa foydali parametrlar (MDN bo'yicha): icon (katta rasm), badge (Android holat qatoridagi kichik oq-qora belgi), renotify: true (bir xil tag da qayta tovush), requireInteraction (o'zi yopilmasin), actions (tugmalar — hamma platformada emas).
5.2 notificationclick: ochiq oynani topish
Kanon tajribasi bosilganda doim yangi oyna ochardi:
self.addEventListener("notificationclick", (event) => {
event.notification.close();
event.waitUntil(self.clients.openWindow(event.notification.data.url));
});Mehmonda ilova allaqachon ochiq bo'lsa — ikkinchi tab ochiladi. Yaxshiroq yo'l: avval ochiq oynalarni qidirish va topilsa — unga fokus berish. Qaror mantiqini sof funksiyaga ajratib, Node'da sinaymiz (3-mashq shu yerdan):
function oynaTanla(oynalar, manzil) {
const aniq = oynalar.find((o) => o.url === manzil);
if (aniq) return { amal: "fokus", url: aniq.url };
const ilova = oynalar[0];
if (ilova) return { amal: "o'tish", url: manzil };
return { amal: "ochish", url: manzil };
}
const ilova = "https://example.com/vazifalar/";
console.log(oynaTanla([{ url: ilova }], ilova));
console.log(oynaTanla([{ url: ilova + "statistika" }], ilova));
console.log(oynaTanla([], ilova));Konsolda:
{ amal: 'fokus', url: 'https://example.com/vazifalar/' }
{ amal: "o'tish", url: 'https://example.com/vazifalar/' }
{ amal: 'ochish', url: 'https://example.com/vazifalar/' }SW'da uni shunday ulaysiz (hujjat bo'yicha: clients.matchAll, client.focus(), client.navigate()):
self.addEventListener("notificationclick", (event) => {
event.notification.close();
const { url } = event.notification.data;
const manzil = new URL(url, self.location).href;
event.waitUntil(
(async () => {
const oynalar = await self.clients.matchAll({ type: "window" });
const qaror = oynaTanla(oynalar, manzil);
if (qaror.amal === "ochish") {
return self.clients.openWindow(manzil);
}
const oyna =
oynalar.find((o) => o.url === manzil) ?? oynalar[0];
if (qaror.amal === "o'tish") await oyna.navigate(manzil);
return oyna.focus();
})(),
);
});clients.openWindow va focus faqat notificationclick ichida ishlaydi — brauzer "foydalanuvchi bosdi" deb hisoblaydigan qisqa vaqt ichida.
6. Platformalar: FCM va iOS
6.1 Firebase Cloud Messaging
Chrome'da endpoint doim fcm.googleapis.com — ya'ni Google'ning FCM'i. Lekin buning uchun Firebase hisobi shart emas: VAPID bilan standart Web Push to'g'ridan-to'g'ri FCM endpoint'iga ishlaydi. Firebase'ning o'z SDK'si (firebase/messaging) — boshqa yo'l: u getToken(messaging, { vapidKey, serviceWorkerRegistration }) bilan "token" beradi, server esa Firebase Admin SDK orqali yuboradi. Afzalligi — Android/iOS ilovalari bilan bitta tizim. Narxi — Firebase hisobi, loyiha sozlamasi va qo'shimcha SDK. Kanon Firebase loyihasi yaratmadi (topshiriq: hisob yo'q) — bu yo'l faqat hujjat bo'yicha.
| Brauzer | Push xizmati |
|---|---|
| Chrome, Edge (Android ham) | Google FCM / Microsoft (Edge) |
| Firefox | Mozilla Push Service |
| Safari (macOS, iOS) | Apple Push Notification service |
Standart Web Push (VAPID + RFC 8291 shifrlash) uchalasida ham bir xil kod bilan ishlaydi — farqni web-push paketi o'zi hal qiladi.
6.2 iPhone va iPad
PWA darsidagi ogohlantirish bu yerda asosiy talab:
- iOS 16.4 dan, va faqat bosh ekranga qo'shilgan PWA'da. Safari tabida ochilgan saytga push kelmaydi (WebKit blogi bo'yicha).
- Ruxsat faqat foydalanuvchi bosishi ichida.
- Manifestda
display: "standalone"(yokifullscreen) — o'rnatilgan ilova shunday ochiladi.
Amalda bu shunday UI degani: iPhone'da, o'rnatilmagan sahifada «Eslatmalarni yoqish» tugmasi o'rniga «Avval bosh ekranga qo'shing: Ulashish → Bosh ekranga» yo'riqnomasi. Tekshiruv — "PushManager" in window va matchMedia("(display-mode: standalone)") (Ilova ichidamizmi?).
7. Biz sinab ko'rdik: nima ishladi, nima ishlamadi
Kanon tajribasi (tajriba/push/, faqat lokal, tashqi push xizmatisiz) natijasi — xom fayl aynan (JWT qisqartirilgan):
# 21/#30 web push tajribasi — 2026-10-17T17:57
VAPID ochiq kalit (applicationServerKey, base64url, 65 bayt): BEKRjE8nBpi2tQ2-gzEMqvLD… (yopiq kalit — faqat serverda, faylga yozilmadi)
VAPID JWT (Authorization: vapid t=…, k=…): eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guZXhhbXBsZS5pbnZhbGlkIiwiZXhwIjoxNzkyMzAzMDI5LCJzdWIiOiJtYWlsdG86bG9naW5AZXhhbXBsZS5jb20ifQ.h9vfrhCXFB… (86 belgi imzo, ES256)
Chrome/155.0.8059.82
ruxsat (so'ralmagan): default; PushManager bor: true
bosilgach:
registrationId: null
CDP push → SW: sahifaga xabar null; getNotifications: [] (headless'da bildirishnoma ro'yxati bo'sh bo'lishi mumkin)
konsol xatolari: Failed to load resource: the server responded with a status of 404 (Not Found)Qatorma-qator, halol:
- Ishladi: VAPID kalit (65 bayt) va ES256 JWT imzosi (86 belgi) —
node:cryptobilan. Yuqoridagi Node bloklari xuddi shuni takrorlaydi. - Ishladi: sahifa ochilganda ruxsat
default,PushManagermavjud — ruxsat so'ralmagan, sahifa uni o'zi so'ramadi. - Chaqirilmadi:
pushManager.subscribe— Chrome'da u Google FCM'ga ulanadi, bu esa tashqi xizmat. Topshiriq bo'yicha ataylab chaqirilmadi. - Ishlamadi: push'ni lokal yetkazish. Reja — Chrome DevTools Protocol'ning
ServiceWorker.deliverPushMessagebuyrug'i bilan SW'ga to'g'ridan-to'g'ri xabar berish edi. Lekin SW'ningregistrationIdsi olinmadi (null), xabar yuborilmadi. «bosilgach:» qatori ham bo'sh — ruxsat tugmasi natijasi yozilmadi. Bu sinov vositasining muammosi, push'ning emas. Biz "push ishlaydi, sinadik" demaymiz.
Push'ni qo'lda, o'z kompyuteringizda sinash yo'li bor — PWA darsida ko'rgan DevTools → Application → Service Workers → Push maydoni. U SW'ga sinov xabarini beradi. Bu push ishlovchisini tekshiradi, lekin obuna, VAPID va push xizmatini emas.
7.1 Nega vazifalar da push yo'q
Push uchun server kerak: obunalarni saqlash, VAPID yopiq kaliti, yuborish. vazifalar ning o'z backend'i hali yo'q (u «Bahor» mashq API'si bilan ishlaydi). Push o'z serverimiz (25-qism) va push yuborish darsidan keyin qo'shiladi. Bu darsning kodi o'shanda tayyor turadi: ruxsat taklifi, base64urlBaytlarga, SW ishlovchilari.
8. Hujumchi nigohi
- Yopiq kalit sizdi — hujumchi obunachilaringizga sizning nomingizdan "Hisobingiz bloklandi, shu havolani bosing" yuboradi. Kalit — faqat server
.envida; sizsa — yangisini yarating (eski obunalar yo'qoladi, qayta so'raysiz). - Bildirishnomadagi havola —
notificationclickdagidata.urlni tekshiring: faqat o'z origin'ingiz (ochiq yo'naltirish). Yuqoridagi koddanew URL(…, self.location)nisbiy manzilni origin'ga bog'laydi; tashqi manzil bo'lsa — ochmang. - Obuna = shaxsiy ma'lumot.
endpointbrauzerni aniqlaydi. Uni foydalanuvchi hisobiga bog'lab saqlang, chiqishda (logout) o'chiring — aks holda kompyuterdan chiqib ketgan odamga xabarlar kelaveradi. - Spam — "ruxsat berdi" — "hamma narsani yuborsa bo'ladi" degani emas. Foydalanuvchi bildirishnoma turlarini tanlay olsin (buyurtma holati — ha, aksiya — yo'q).
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| ruxsat oynasi chiqmaydi | denied yoki bosishsiz so'rov |
holatni tekshiring; faqat onClick ichida |
subscribe — applicationServerKey xatosi |
kalit satr, Uint8Array emas |
base64urlBaytlarga |
| Chrome o'zi "sayt fonda yangilandi" deydi | waitUntil siz showNotification |
event.waitUntil(…) |
| uchta bir xil bildirishnoma | tag yo'q |
bir xil tag |
| bosilganda ikkinchi tab | doim openWindow |
avval clients.matchAll |
| server yuborishlari sekinlashib boradi | 410 obunalar o'chirilmagan | 404/410 — bazadan o'chirish |
iPhone'da PushManager yo'q |
bosh ekranga qo'shilmagan | o'rnatish yo'riqnomasi |
10. Mashqlar
Mashqlar kurs/mashqlar/21/30-web-push/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Rad etilgan ruxsat holati —
- Obunadagi push xizmati manzili —
- Server imzosi uchun kalit standarti —
- Bir xil bildirishnomalarni almashtiradigan parametr —
- Obuna endi yo'qligini bildiruvchi HTTP holat kodi —
Yechim
- denied — qayta so'rab bo'lmaydi, faqat sozlamalardan.
- endpoint — har brauzer uchun alohida.
- VAPID — RFC 8292; yopiq kalit serverda.
- tag — oxirgisi qoladi.
- 410 (Gone) — obunani bazadan o'chiring.
2-mashq (o'rta): taklifni qachon ko'rsatish
Funksiya taklifKorsatilsinmi(h) yozing. h — { ruxsat, buyurtmalar, rad, bugun }: ruxsat — "default" | "granted" | "denied", buyurtmalar — mehmonning buyurtmalari soni, rad — taklif oxirgi marta «Hozir emas» qilingan sana ("YYYY-MM-DD" yoki null), bugun — bugungi sana. Qoidalar:
- Ruxsat
defaultbo'lmasa —false(berilgan yoki qaytarib bo'lmaydi). - Kamida 1 ta buyurtma bo'lsin (foydali payt).
- Rad etilganidan beri kamida 7 kun o'tsin.
Ishora: sanalar orasidagi kun — (new Date(b) - new Date(a)) / 86_400_000 (Date).
Yechim
const KUN = 86_400_000;
function taklifKorsatilsinmi({ ruxsat, buyurtmalar, rad, bugun }) {
if (ruxsat !== "default") return false;
if (buyurtmalar < 1) return false;
if (rad !== null && (new Date(bugun) - new Date(rad)) / KUN < 7) {
return false;
}
return true;
}
const asos = {
ruxsat: "default",
buyurtmalar: 1,
rad: null,
bugun: "2026-11-02",
};
const sina = (o) =>
console.log(taklifKorsatilsinmi({ ...asos, ...o }));
sina({});
sina({ ruxsat: "denied" });
sina({ buyurtmalar: 0 });
sina({ rad: "2026-10-30" });
sina({ rad: "2026-10-26" });Konsolda:
true
false
false
false
trueUchinchi holat — buyurtma yo'q (foydali payt kelmagan); to'rtinchisi — rad etilganiga 3 kun o'tgan, kutamiz; beshinchisi — 7 kun, yana taklif. denied uchun taklif ko'rsatish befoyda: «Ha» bosilsa ham brauzer oynasi chiqmaydi.
3-mashq (qiyin): oyna tanlash — eng aniq moslik
«notificationclick» bo'limidagi oynaTanla ni yaxshilang:
- Bir nechta oyna ochiq bo'lsa, fokusdagi (
focused: true) oynani afzal ko'rsin (aniq moslik bo'lmaganda). - Boshqa origin'dagi manzil (
https://bilan boshlanib, ilova origin'iga teng bo'lmagan) —{ amal: "rad" }. - Konsolda to'rt holatni tekshiring.
Ishora: origin — new URL(manzil).origin (URL).
Yechim
function oynaTanla(oynalar, manzil, origin) {
if (new URL(manzil).origin !== origin) return { amal: "rad" };
const aniq = oynalar.find((o) => o.url === manzil);
if (aniq) return { amal: "fokus", url: aniq.url };
const oyna = oynalar.find((o) => o.focused) ?? oynalar[0];
if (oyna) return { amal: "o'tish", dan: oyna.url, url: manzil };
return { amal: "ochish", url: manzil };
}
const o = "https://example.com";
const oynalar = [
{ url: `${o}/vazifalar/statistika`, focused: false },
{ url: `${o}/vazifalar/sozlamalar`, focused: true },
];
console.log(oynaTanla(oynalar, `${o}/vazifalar/statistika`, o).amal);
console.log(oynaTanla(oynalar, `${o}/vazifalar/`, o).dan);
console.log(oynaTanla([], `${o}/vazifalar/`, o).amal);
console.log(oynaTanla(oynalar, "https://boshqa.example/", o).amal);Konsolda:
fokus
https://example.com/vazifalar/sozlamalar
ochish
radIkkinchi holatda aniq moslik yo'q — fokusdagi sozlamalar oynasi tanlandi (statistika birinchi tursa ham). To'rtinchisi — boshqa origin: bildirishnoma hujumchi havolasi bilan kelgan bo'lsa ham ochilmaydi.
4-mashq: tajriba qadami (ixtiyoriy)
O'z kompyuteringizda: kichik sahifa (index.html + sw.js) yarating, SW'ga «Service Worker'da qabul qilish» bo'limidagi push va yaxshilangan notificationclick ni qo'ying. npx serve kabi lokal server bilan oching, tugma orqali ruxsat bering. DevTools → Application → Service Workers → Push maydoniga {"sarlavha":"«Bahor»","matn":"Osh tayyor"} yozib yuboring. Hisob va kalit kerak emas.
Yechim
Kutiladigan natija:
- Push maydonidan yuborilganda — tizim bildirishnomasi ««Bahor»» / «Osh tayyor».
- Ikki marta yuborsangiz — bitta bildirishnoma (bir xil
tag). - Sahifa ochiq turganda bossangiz — yangi tab ochilmaydi, mavjudiga fokus.
- Matn JSON bo'lmasa (
salom) — sukut «Vazifalar» / «Yangi eslatma». - Bu sinov
subscribe, VAPID va push xizmatini chetlab o'tadi — u faqat SW ishlovchisini tekshiradi.
11. Real ishda
- Qayerda: buyurtma va yetkazish holati, chat xabarlari, bank tranzaksiyalari, kalendar eslatmalari. Telegram Web, Gmail, YouTube — hammasi Web Push ishlatadi.
- Mahsulot ko'rsatkichi — ruxsat berish foizi. Yumshoq so'rash naqshi uni bir necha baravar oshiradi va
deniedni kamaytiradi. Bu ko'pincha dasturchiga beriladigan aniq vazifa. - Intervyu savollari: "push oqimi qanday?", "VAPID nima uchun kerak?", "ruxsatni qachon so'raysiz?", "iOS'da qanday cheklovlar bor?", "410 kelsa nima qilasiz?".
Xulosa
- Web push: sahifa → ruxsat →
subscribe→ server saqlaydi → server push xizmatiga (VAPID imzo, shifrlangan) → SWpush→showNotification. - Ruxsat — bitta imkoniyat: faqat bosishdan keyin, foydali paytda, avval o'z UI'ingiz bilan ("yumshoq so'rash").
denied— faqat sozlamalardan. - VAPID: ochiq kalit (65 bayt) — brauzerga, yopiq — faqat serverda; har xabar ES256 JWT bilan imzolanadi.
- SW:
waitUntil,tag,data; bosilganda — ochiq oynaga fokus, faqat o'z origin'ingiz. - iOS — faqat bosh ekrandagi PWA. 404/410 obunani o'chiring.
- Kanon: kalit va imzo lokal yaratildi; lokal yetkazish ishlamadi —
vazifalarga push o'z server bilan keladi.
Keyingi dars: Yuklanishni optimallashtirish: JS, shriftlar va resurslar — 21-qismda 225 KB gacha o'sgan birinchi yuklashni sahifalarni kech yuklash bilan qisqartiramiz va Lighthouse bilan o'lchaymiz.
Manbalar
- MDN: Push API,
PushManager.subscribe(),PushSubscription,ServiceWorkerRegistration.showNotification(),notificationclick,Clients.matchAll(),WindowClient.focus() - RFC 8030 (Generic Event Delivery Using HTTP Push), RFC 8291 (Message Encryption for Web Push), RFC 8292 (VAPID)
- web-push npm paketi:
generateVAPIDKeys,setVapidDetails,sendNotification - Firebase hujjati: Cloud Messaging — Web (
getToken,vapidKey) - WebKit blogi: "Web Push for Web Apps on iOS and iPadOS" (iOS 16.4)
- developer.chrome.com: permission UX va jimroq bildirishnoma so'rovlari
- Kanon:
tajriba/push/(sw.js,sinov.mjs),natijalar/tajriba/30-web-push.txt(2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!