Mundarija (28)
- Bu darsda
- 1. Nega bu kerak?
- 2. Server qismi
- 2.1 Obuna ichida nima bor
- 2.2 Yuborish oqimi
- 2.3 web-push paketi
- 3. VAPID: "bu xabarni men yubordim"
- 3.1 JWT'ni qo'lda yasash
- 3.2 Nima uchun kerak
- 4. Shifrlash: +103 bayt
- 4.1 Nima bo'ladi
- 4.2 Yuk limiti
- 5. Javoblar va eskirgan obunalar
- 5.1 To'rt holat
- 5.2 Nega o'chirish muhim
- 5.3 TTL, Urgency, Topic
- 6. Kanon topilmalari
- 7. FCM va mobil ilovalar
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): to'g'ri yoki noto'g'ri
- 2-mashq (o'rta): obunani qabul qilishdan oldin tekshirish
- 3-mashq (qiyin): bir foydalanuvchining hamma qurilmalariga
- 11. Loyiha qadami: webpush.ts moduli
- 12. Real ishda
- Xulosa
- Manbalar
Push bildirishnomalar serverda: Web Push, VAPID va FCM
Qisqacha: Server push'ni telefon yoki brauzerga emas, obunadagi
endpointmanziliga — brauzer kompaniyasining push xizmatiga (Chrome — FCM, Firefox — Mozilla) yuboradi. Node'da buniweb-pushpaketi qiladi: xabarni brauzer kalitlari bilan shifrlaydi (aes128gcm, +103 bayt) va VAPID yopiq kaliti bilan imzolaydi (ES256 JWT). Javobga qarab:201— qabul qilindi,404/410— obuna o'lgan, bazadan o'chiring,429/5xx— keyinroq qayta. Mobil ilovalar uchun Firebase Admin SDK (FCM) — alohida yo'l, u kanonda sinalmagan.
Bu darsda
- Push oqimining server qismini va push xizmatining rolini tushuntirasiz.
- VAPID JWT'ni Web Crypto bilan o'zingiz yasab, imzosini tekshirasiz.
- Shifrlash nega xabarga aniq 103 bayt qo'shishini va yuk limitini hisoblaysiz.
- Push xizmati javobini to'rt holatga ajratasiz va eskirgan obunani tozalaysiz.
- Kanon
webpush.tsmodulini lokal soxta push xizmati bilan sinaysiz.
Oldin bilishingiz kerak: Web push bildirishnomalar (brauzer tomoni: ruxsat, subscribe, Service Worker), crypto: hash, shifrlash va imzo, JWT anatomiyasi (28-qism), SMS provayderlar: Eskiz va PlayMobile.
Web push bildirishnomalar darsida brauzer tomonini qurdingiz: ruxsat so'rash, obuna olish, Service Worker'da push hodisasi. O'sha dars halol aytgan edi: push'ni serversiz yetkazib bo'lmaydi va bu qism "server darsida" bo'ladi. Mana o'sha dars.
Diqqat: Haqiqiy push xizmatlari (Chrome → FCM, Firefox → Mozilla autopush), Firebase Admin SDK va Expo push kanonda sinalmagan (2026-10-27): Google loyihasi va kalit kerak. Kanon o'rniga brauzer kabi kalit yaratadigan, imzoni tekshiradigan va xabarni haqiqatan ochadigan lokal soxta push xizmati yozdi.
1. Nega bu kerak?
Email — sekin o'qiladi, SMS — pullik. Push esa bepul va darhol: Aziz brauzerni yopgan bo'lsa ham, "Non olish — 18:00" bildirishnomasi ekran burchagida chiqadi.
Server push yuborganda uch muammoga duch keladi:
- Kimga yuborish? Foydalanuvchining IP'si yo'q, brauzeri uxlab yotibdi. Server faqat obunani biladi.
- Ishonch. Push xizmati begona serverdan kelgan xabarni nega yetkazsin? Spamchi ham yuborishi mumkin.
- Maxfiylik. Xabar push xizmati (Google, Mozilla) orqali o'tadi. Ular matnni o'qimasligi kerak.
O'xshatish: pochta qutisi (abonent qutisi). Aziz pochtadan quti oldi va kalitning nusxasini sizga berdi. Siz xatni qutiga solasiz, lekin xat qulfli qutichada — kalit faqat Azizda (shifrlash). Konvertda sizning muhringiz bor (VAPID imzo) — pochta xat haqiqatan sizdan ekanini tekshiradi.
2. Server qismi
2.1 Obuna ichida nima bor
Brauzer pushManager.subscribe dan keyin serverga shunday JSON beradi (Web push darsida ko'rgansiz):
{
"endpoint": "https://fcm.googleapis.com/fcm/send/abc…",
"keys": {
"p256dh": "BEKr…",
"auth": "k3T0…"
}
}| Maydon | Nima | Server nima qiladi |
|---|---|---|
endpoint |
push xizmatidagi shu brauzer "qutisi" | xabarni shu URL'ga POST qiladi |
p256dh |
brauzerning ochiq ECDH kaliti (P-256, 65 bayt) |
xabarni shifrlash uchun |
auth |
16 baytli maxfiy son | shifrlash kalitiga qo'shiladi |
Server obunani bazada saqlaydi. Bir foydalanuvchining bir nechta obunasi bo'lishi mumkin: telefon, ish kompyuteri, uy kompyuteri. Kanon jadvali push_obunalar da endpoint noyob (UNIQUE) va faqat https:// bilan boshlanadi — buni Bildirishnoma tizimi arxitekturasi darsida ko'ramiz.
2.2 Yuborish oqimi
sequenceDiagram
participant S as Server (web-push)
participant P as Push xizmati (FCM)
participant B as Brauzer (SW)
S->>S: shifrlash (p256dh, auth)
S->>S: VAPID JWT imzosi
S->>P: POST endpoint, TTL, Urgency
P-->>S: 201 Created
P->>B: xabar (brauzer uyg'onadi)
B->>B: ochish va showNotificationDiagrammaga qarang: server faqat push xizmatini biladi va 201 olgach ishini tugatadi. Brauzer o'chiq bo'lsa, xizmat xabarni TTL soniya saqlab turadi.
2.3 web-push paketi
npm i web-push@3.6.7import webpush from "web-push";
// bir marta: kalitlar; ochiq — brauzerga, yopiq — .env ga
const { publicKey, privateKey } = webpush.generateVAPIDKeys();
const javob = await webpush.sendNotification(
obuna, // bazadan: { endpoint, keys: { p256dh, auth } }
JSON.stringify({ sarlavha: "Eslatma", matn: "Non olish — 18:00" }),
{
vapidDetails: {
subject: "mailto:admin@vazifalar.example.uz",
publicKey: process.env.VAPID_OCHIQ,
privateKey: process.env.VAPID_YOPIQ,
},
TTL: 3600,
urgency: "normal",
topic: "vazifa-42",
},
);
console.log(javob.statusCode); // 201Bu bitta chaqiruv ichida uch ish bor: shifrlash, imzolash va HTTPS so'rov. Keyingi ikki bo'limda ularni ochamiz.
3. VAPID: "bu xabarni men yubordim"
3.1 JWT'ni qo'lda yasash
VAPID (RFC 8292) — server o'zini push xizmatiga tanitadigan usul. Har so'rovda Authorization: vapid t=<JWT>, k=<ochiq kalit> sarlavhasi boradi. JWT — uch qism: sarlavha, tana (aud — push xizmati manzili, exp — muddat, sub — aloqa uchun manzil) va ES256 imzo (JWT anatomiyasi 28-qismda chuqur). Uni brauzerdagi Web Crypto bilan o'zimiz yasaymiz:
const b64u = (baytlar) =>
btoa(String.fromCharCode(...new Uint8Array(baytlar)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const matn = (obj) =>
b64u(new TextEncoder().encode(JSON.stringify(obj)));
const kalit = await crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"],
);
const ochiq = await crypto.subtle.exportKey("raw", kalit.publicKey);
const sarlavha = matn({ typ: "JWT", alg: "ES256" });
const tana = matn({
aud: "https://fcm.googleapis.com",
exp: 1793174400,
sub: "mailto:admin@vazifalar.example.uz",
});
const imzo = await crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-256" }, kalit.privateKey,
new TextEncoder().encode(`${sarlavha}.${tana}`),
);
const jwt = `${sarlavha}.${tana}.${b64u(imzo)}`;
console.log(ochiq.byteLength, new Uint8Array(ochiq)[0]);
console.log(imzo.byteLength);
console.log(jwt.split(".").map((q) => q.length));
const ochiqTana = tana.replace(/-/g, "+").replace(/_/g, "/");
console.log(JSON.parse(atob(ochiqTana)));
const togri = await crypto.subtle.verify(
{ name: "ECDSA", hash: "SHA-256" }, kalit.publicKey, imzo,
new TextEncoder().encode(`${sarlavha}.${tana}`),
);
console.log("imzo to'g'ri:", togri);Konsolda:
65 4
64
[ 36, 127, 86 ]
{
aud: 'https://fcm.googleapis.com',
exp: 1793174400,
sub: 'mailto:admin@vazifalar.example.uz'
}
imzo to'g'ri: trueKalit har safar yangi, shuning uchun faqat uzunliklarni chiqardik:
- Ochiq kalit 65 bayt, birinchi bayti
4— "siqilmagan" nuqta (x va y koordinata, har biri 32 bayt). Bu brauzerdagiapplicationServerKey. - ES256 imzo — 64 bayt (r va s, har biri 32). Base64url'da 86 belgi.
aud— push xizmatining origin'i (yo'lsiz). Kanon soxta xizmati aynan buni tekshiradi:audboshqa bo'lsa — 403.exp— 24 soatdan uzoq bo'lmasin; kanon xizmati buni ham tekshiradi.
3.2 Nima uchun kerak
Push xizmati imzoni k= dagi ochiq kalit bilan tekshiradi va bu kalitni obuna yaratilgandagi applicationServerKey bilan solishtiradi. Mos bo'lsa — xabar shu obunaning egasidan. Demak endpoint URL'ini o'g'irlagan begona odam yopiq kalitsiz unga xabar yubora olmaydi. Yopiq kalit sizsa — teskarisi: hujumchi hamma obunachilaringizga sizning nomingizdan yozadi.
Kanon testi qiziq natijani ham qayd etdi: "boshqa VAPID kaliti — xizmat rad etmaydi". Kanonning soxta xizmati kalitni obuna bilan bog'lamaydi (u faqat imzo to'g'riligini tekshiradi). Haqiqiy xizmatlarda bog'lanish bor — bu hujjat bo'yicha, sinalmagan.
4. Shifrlash: +103 bayt
4.1 Nima bo'ladi
Xabar tanasi aes128gcm (AES shifri, 128 bitli kalit, GCM rejimi) bilan shifrlanadi (RFC 8291). Uch qadam:
- Server har xabar uchun yangi vaqtinchalik kalit juftligi yaratadi va brauzerning
p256dhkaliti bilan umumiy sir hisoblaydi. Bu ECDH usuli: ikki tomon faqat ochiq kalitlarni almashib, bir xil sirga keladi — sirning o'zi tarmoqdan o'tmaydi. - Sirga
authqo'shiladi va HKDF (sirdan kerakli uzunlikdagi kalit yasaydigan funksiya) AES kalitini chiqaradi. - Shu kalit bilan tana shifrlanadi.
Push xizmati faqat shifrlangan baytlarni ko'radi. Ochish kaliti faqat brauzerda.
Kanon web-push bilan uch xil hajmdagi xabarni shifrladi. Har qatorda yuk va tana farqiga qarang:
yuk 36 bayt → tana 139 bayt (+103), sarlavhalar: TTL, Content-Length, Content-Type, Content-Encoding, Authorization, Urgency
yuk 526 bayt → tana 629 bayt (+103), sarlavhalar: TTL, Content-Length, Content-Type, Content-Encoding, Authorization, Urgency
yuk 3026 bayt → tana 3129 bayt (+103), sarlavhalar: TTL, Content-Length, Content-Type, Content-Encoding, Authorization, Urgency
100 ta yuborish (ECDH + HKDF + AES-GCM + ES256 JWT + HTTPS keepAlive): mediana 2.49 ms, p95 6.37 ms; qabul 100Hajm qanday bo'lmasin — doim aynan +103 bayt.
4.2 Yuk limiti
Push xizmatlari 4096 baytgacha tanani qabul qilishi kafolatlangan (RFC 8030). 103 qayerdan kelishini hisoblaymiz:
const yozuv = 4096;
const sarlavha = 16 + 4 + 1 + 65; // salt, rs, idlen, kalit
const teg = 16; // AES-GCM
const ajratgich = 1;
console.log(yozuv - sarlavha - teg - ajratgich);
console.log(sarlavha + teg + ajratgich);Konsolda:
3993
103- 86 bayt — shifrlash sarlavhasi: tasodifiy tuz (salt), yozuv hajmi, kalit uzunligi va serverning vaqtinchalik ochiq kaliti (65 bayt).
- 16 bayt — AES-GCM autentifikatsiya tegi: kimdir baytni o'zgartirsa, ochishda bilinadi.
- 1 bayt — to'ldirish ajratgichi.
Demak foydali yuk — 3 993 bayt. Kanon modulida MAKS_YUK = 4096 - 103. Amalda push'ga to'liq ma'lumot emas, qisqa matn va havola qo'ying ("Yangi izoh: /vazifalar/42"). Batafsilini ilova ochilganda API'dan oladi.
Tekshirib ko'ring: Xabar shifrlangan. Demak push matniga parolni tiklash kodini qo'ysa bo'ladimi?
Javob
Yo'q. Yo'lda shifrlangan, lekin manzilda — ochiq: bildirishnoma qulflangan telefon ekranida ham ko'rinadi, uni yonidagi odam o'qiydi. Shifrlash push xizmatidan himoya qiladi, ekrandan emas. Maxfiy ma'lumot push'da bo'lmasin — faqat "Yangi xabar bor".
5. Javoblar va eskirgan obunalar
5.1 To'rt holat
Push xizmati javobini kanon modul to'rt turga ajratadi. Mantiqning JS nusxasi:
function tasnifla(status, retryAfter) {
if (status >= 200 && status < 300) return { tur: "yetkazildi" };
if (status === 404 || status === 410) return { tur: "eskirgan" };
if (status === 429 || status >= 500) {
const s = Number(retryAfter);
return Number.isFinite(s) && s > 0
? { tur: "qayta", kutishSoniya: s }
: { tur: "qayta" };
}
return { tur: "xato" };
}
const holatlar = [[201], [410], [404], [429, "30"], [503], [413]];
for (const [st, ra] of holatlar) {
console.log(st, tasnifla(st, ra));
}Konsolda:
201 { tur: 'yetkazildi' }
410 { tur: 'eskirgan' }
404 { tur: 'eskirgan' }
429 { tur: 'qayta', kutishSoniya: 30 }
503 { tur: 'qayta' }
413 { tur: 'xato' }410 Gone/404— foydalanuvchi ruxsatni olib tashladi, brauzerni o'chirdi yoki obuna muddati tugadi. Bu obunaga boshqa hech qachon yubormang — bazadan o'chiring.429— juda ko'p so'rov;Retry-Aftersarlavhasi necha soniya kutishni aytadi.413— yuk juda katta. Qayta urinish foydasiz.
201 "yetkazildi" degani emas, "push xizmati qabul qildi" degani. Email va SMS'dagidek: topshirish va yetkazish — ikki xil voqea.
5.2 Nega o'chirish muhim
Eskirgan obunalar to'planib qoladi. Bir yildan keyin bazadagi obunalarning katta qismi o'lik bo'lishi mumkin. Har yuborishda ularga ham so'rov ketadi — vaqt, tarmoq va push xizmati oldidagi obro'. 410 kelganda darhol o'chirish — eng arzon tozalash. Kanon bildirishnoma markazi buni avtomatik qiladi: push natijasi eskirgan bo'lsa, push_obunalar dan qator o'chadi.
5.3 TTL, Urgency, Topic
| Sarlavha | Ma'nosi | vazifalar da |
|---|---|---|
TTL |
brauzer o'chiq bo'lsa xizmat necha soniya saqlaydi | 3600 (eskirgan eslatma kerak emas) |
Urgency |
very-low … high; telefon batareyani tejashga qaraydi |
normal; muddat o'tsa high |
Topic |
bir xil mavzuli yangi xabar eskisini almashtiradi | vazifa-42 |
Topic qulay: vazifa muddati uch marta o'zgarsa, oflayn brauzer qaytganda uchta emas, oxirgi bitta bildirishnoma keladi. Kanon testida soxta xizmat TTL: 600, Urgency: high, Topic: vazifa-42 sarlavhalarini qabul qilib, tanani ochdi va yukni aynan qaytardi.
6. Kanon topilmalari
Kanon modulni yozayotganda ikkita narsa topdi:
web-push3.6.7 faqat HTTPS'ga yuboradi. U ichidahttps.requestishlatadi —http://127.0.0.1dagi lokal soxta xizmatga umuman ulana olmadi. Kanon soxta xizmatni HTTPS (o'z-o'zini imzolagan sertifikat) qilib, modulgaagentparametrini qo'shdi — test o'z sertifikat markazi bilan ishlaydi. Proddaagentberilmaydi: haqiqiy xizmatlar doim HTTPS.- Buzilgan obuna kaliti "qayta" deb tasniflangan edi.
p256dhnoto'g'ri bo'lsa,web-pushso'rovni umuman yubormaydi vastatusCodesiz xato tashlaydi. Dastlabki kodstatus 0ni tarmoq uzilishi deb,qaytaqaytarardi — bunday obunaga worker abadiy qayta urinardi. Tuzatish:status === 0faqat xatoda tarmoq kodi (ECONNRESET,ETIMEDOUT— satrcode) bo'lsaqayta, aks holdaxato.
Ikkinchi topilma umumiy saboq: "noma'lum xato — qayta urin" qoidasi xavfli. Qayta urinish faqat vaqtinchalik ekani aniq bo'lgan xatolar uchun.
7. FCM va mobil ilovalar
Web Push — brauzerlar uchun standart. Android va iOS ilovalari uchun esa platforma xizmatlari: FCM (Firebase Cloud Messaging) va Apple'ning APNs. Ko'p loyiha ikkalasini FCM orqali boshqaradi. Bu bo'lim Firebase hujjati bo'yicha (2026-10-28 holatiga) — kanonda sinalmagan, kod va chiqish ko'rsatilmaydi.
- Firebase Admin SDK (
firebase-adminpaketi) — server tomoni. Kalit — Google service account JSON fayli (sir,.envkabi saqlanadi). - FCM token — mobil ilova olgan qurilma identifikatori; Web Push'dagi obunaning o'rnida.
- Notification va data xabar.
notification— tizim o'zi ko'rsatadi (ilova yopiq bo'lsa ham);data— faqat kalit-qiymat, ilova o'zi qayta ishlaydi. - Topic (FCM ma'nosida) — obunachilar guruhi: "yangiliklar" mavzusiga bitta yuborish hammaga ketadi. Web Push'dagi
Topicsarlavhasi bilan adashtirmang. - Multicast — bitta xabarni ko'p tokenga bir chaqiruvda.
- Eskirgan token — FCM "ro'yxatdan o'tmagan token" xatosini qaytaradi; Web Push'dagi
410kabi — bazadan o'chirish. - Expo push — Expo (React Native) ilovalari uchun o'z xizmati, ichida FCM va APNs.
Qoida bir xil: token/obuna — bazada, eskirgani — o'chiriladi, yuborish — navbat orqali, natija — to'rt holatdan biri.
8. Hujumchi nigohi
| Hujum | Natijasi | Himoya |
|---|---|---|
obunaga endpoint: "http://169.254.169.254/…" |
server ichki tarmoqqa so'rov yuboradi (SSRF) | faqat https, ma'lum push xizmatlari ro'yxati |
| VAPID yopiq kaliti sizdi | hamma obunachilarga soxta xabar | kalit .env da; yangisi — hamma qayta obuna |
| boshqa foydalanuvchi obunasini o'z hisobiga yozish | begona odam push'larni oladi | obuna — tizimga kirgan foydalanuvchiga, endpoint noyob |
| push matnida maxfiy ma'lumot | qulf ekranida o'qiladi | faqat qisqa matn, tafsilot — ilovada |
Birinchi qator ko'pincha unutiladi. endpoint — foydalanuvchi yuborgan URL, server esa unga POST qiladi. Hujumchi brauzer o'rniga o'zi JSON yuborib, endpoint ga serveringizning ichki manzilini yozadi. Bu SSRF — 28-qismda chuqur. Kanon sxemasi endpoint ~ '^https://' bilan birinchi qatlamni qo'ydi. Ikkinchi qatlam — 2-mashqda.
9. Ko'p uchraydigan xatolar
| Xato | Natija | To'g'risi |
|---|---|---|
410 dan keyin obunani o'chirmaslik |
o'lik obunalar to'planadi | darhol o'chirish |
| har xatoda qayta urinish | buzilgan obunaga abadiy sikl | faqat 429, 5xx, tarmoq |
| har deploy'da yangi VAPID kaliti | hamma obunalar ishdan chiqadi | kalit bir marta yaratiladi, .env da |
| katta yuk (rasm, butun vazifa) | 413 |
qisqa matn va havola |
TTL: 0 eslatma uchun |
brauzer o'chiq bo'lsa — yo'qoladi | mantiqiy TTL |
http lokal mock bilan web-push |
ulanmaydi | HTTPS mock + agent |
10. Mashqlar
Mashqlar kurs/mashqlar/29/17-push/ da.
1-mashq (oson): to'g'ri yoki noto'g'ri
- Server push'ni to'g'ridan brauzerning IP manziliga yuboradi.
- Push xizmati xabar matnini o'qiy olmaydi.
201javobi — bildirishnoma foydalanuvchiga ko'rindi degani.410kelgan obunaga ertaga qayta urinish kerak.- Shifrlash yukka doim 103 bayt qo'shadi.
Yechim
- Noto'g'ri —
endpointga, ya'ni push xizmatiga. - To'g'ri — aes128gcm, kalit faqat brauzerda.
- Noto'g'ri — xizmat qabul qildi; brauzer o'chiq bo'lsa
TTLdavomida kutadi. - Noto'g'ri — obuna o'lgan; o'chirish kerak.
- To'g'ri — kanon: 36, 526, 3026 baytda ham +103.
2-mashq (o'rta): obunani qabul qilishdan oldin tekshirish
obunaTekshir(ob) yozing: "ok" yoki sabab qaytarsin. Qoidalar: endpoint URL bo'lsin (new URL), protokol https:, host ma'lum xizmatlardan biri (fcm.googleapis.com, updates.push.services.mozilla.com, web.push.apple.com yoki .notify.windows.com bilan tugaydi); p256dh base64url'dan ochilganda 65 bayt va birinchisi 4; auth — 16 bayt. Ishora: base64url → base64 — - → +, _ → /, keyin atob (URL va URLSearchParams).
Yechim
const XIZMATLAR = [
"fcm.googleapis.com",
"updates.push.services.mozilla.com",
"web.push.apple.com",
];
const baytlar = (b64u) =>
Uint8Array.from(atob(b64u.replace(/-/g, "+").replace(/_/g, "/")),
(c) => c.charCodeAt(0));
function obunaTekshir(ob) {
let url;
try {
url = new URL(ob.endpoint);
} catch {
return "endpoint URL emas";
}
if (url.protocol !== "https:") return "faqat https";
const host = url.hostname;
const tanish = XIZMATLAR.includes(host)
|| host.endsWith(".notify.windows.com");
if (!tanish) return `notanish xizmat: ${host}`;
try {
const p = baytlar(ob.keys.p256dh);
if (p.length !== 65 || p[0] !== 4) return "p256dh noto'g'ri";
if (baytlar(ob.keys.auth).length !== 16) return "auth noto'g'ri";
} catch {
return "kalit base64url emas";
}
return "ok";
}
const p256dh = btoa(String.fromCharCode(4, ...new Array(64).fill(7)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const auth = "AAAAAAAAAAAAAAAAAAAAAA";
const fcm = "https://fcm.googleapis.com/fcm/send/abc";
console.log(obunaTekshir({ endpoint: fcm, keys: { p256dh, auth } }));
console.log(obunaTekshir({
endpoint: "http://169.254.169.254/latest", keys: { p256dh, auth },
}));
console.log(obunaTekshir({
endpoint: "https://ichki.example.uz/x", keys: { p256dh, auth },
}));
const yomonKalit = { p256dh: "AAAA", auth };
console.log(obunaTekshir({ endpoint: fcm, keys: yomonKalit }));
const togriKalit = { p256dh, auth };
console.log(obunaTekshir({ endpoint: "salom", keys: togriKalit }));Konsolda:
ok
faqat https
notanish xizmat: ichki.example.uz
p256dh noto'g'ri
endpoint URL emasIkkinchi qator — klassik SSRF urinishi (bulut serverlarining ichki metama'lumot manzili). endsWith(".notify.windows.com") — nuqta bilan: aks holda yomonnotify.windows.com ham o'tardi. Xizmatlar ro'yxati vaqt o'tishi bilan o'zgarishi mumkin — u brauzer hujjatlaridan olinadi va yangilanadi. 22 ta A — 16 bayt (har 4 belgi 3 bayt, to'ldirishsiz).
3-mashq (qiyin): bir foydalanuvchining hamma qurilmalariga
Foydalanuvchining beshta obunasi bor. hammagaYubor(endpointlar, chegara) yozing: bir vaqtda ko'pi bilan chegara ta so'rov; natija — { yetkazildi, ochirish, qayta } ro'yxatlari (404/410 — ochirish, 429/5xx — qayta). Soxta yubor(endpoint) 20 ms kutib status qaytaradi va eng ko'p parallel so'rovni sanaydi. Ishora: umumiy navbatdan shift() bilan oladigan chegara ta "ishchi" funksiya va Promise.all (Promise kombinatorlari).
Yechim
const STATUS = { a: 201, b: 410, c: 503, d: 201, e: 404 };
let parallel = 0;
let eng = 0;
async function yubor(endpoint) {
parallel++;
eng = Math.max(eng, parallel);
await new Promise((r) => setTimeout(r, 20));
parallel--;
return STATUS[endpoint];
}
async function hammagaYubor(endpointlar, chegara) {
const natija = { yetkazildi: [], ochirish: [], qayta: [] };
const navbat = [...endpointlar];
async function ishchi() {
while (navbat.length > 0) {
const ep = navbat.shift();
const st = await yubor(ep);
if (st === 404 || st === 410) natija.ochirish.push(ep);
else if (st === 429 || st >= 500) natija.qayta.push(ep);
else natija.yetkazildi.push(ep);
}
}
await Promise.all(Array.from({ length: chegara }, ishchi));
return natija;
}
const n = await hammagaYubor(["a", "b", "c", "d", "e"], 2);
console.log(n);
console.log("eng ko'p parallel:", eng);Konsolda:
{ yetkazildi: [ 'a', 'd' ], ochirish: [ 'b', 'e' ], qayta: [ 'c' ] }
eng ko'p parallel: 2Promise.all(endpointlar.map(yubor)) beshtasini birdan yuborardi; 10 000 obunada bu 10 000 ta bir vaqtdagi so'rov. Ishchilar naqshi chegarani ushlaydi. JavaScript bitta oqimli bo'lgani uchun navbat.shift() ikki ishchi orasida "poyga" bermaydi (Event loop). Natijadagi ochirish ro'yxati — bazadan o'chirish uchun, qayta — navbatga kechiktirib qaytarish uchun.
11. Loyiha qadami: webpush.ts moduli
Kanon push'ni modullar/src/push/webpush.ts ga yig'di. Telefon uchun uzun qatorlarni bo'ldik, ma'nosi kanondagidek (web-push 3.6.7, TypeScript 6.0.3):
// webpush.ts — Web Push (RFC 8030/8291/8292): VAPID bilan imzolangan,
// aes128gcm shifrlangan xabar. Natija turlari: yetkazildi / eskirgan
// obuna (404, 410 → bazadan o'chirish) / qayta urinish (429, 5xx).
// FCM (Firebase Admin) — hujjat bo'yicha, SINALMAGAN.
import type { Agent } from "node:https";
import webpush from "web-push";
export type Obuna = {
endpoint: string;
keys: { p256dh: string; auth: string };
};
export type PushNatija =
| { tur: "yetkazildi"; status: number }
| { tur: "eskirgan"; status: number }
| { tur: "qayta"; status: number; kutishSoniya?: number }
| { tur: "xato"; status: number; sabab: string };
// aes128gcm: 4096 bayt yozuv − 86 bayt sarlavha (salt 16 + rs 4 +
// idlen 1 + kalit 65) − 16 tag − 1 ajratgich
export const MAKS_YUK = 4096 - 103;
type Vapid = {
subject: string;
publicKey: string;
privateKey: string;
};
type Xabar = {
sarlavha: string;
matn: string;
url?: string;
teg?: string;
};
type Shoshilinch = "very-low" | "low" | "normal" | "high";
export class WebPush {
readonly #vapid: Vapid;
readonly #agent: Agent | undefined;
/** web-push faqat HTTPS endpoint'ga yuboradi (`https.request`);
* `agent` — test/mock uchun o'z CA si bilan. */
constructor(vapid: Vapid, agent?: Agent) {
this.#vapid = vapid;
this.#agent = agent;
}
static kalitlarYarat(): { publicKey: string; privateKey: string } {
return webpush.generateVAPIDKeys();
}
async yubor(
obuna: Obuna,
xabar: Xabar,
o: { ttl?: number; shoshilinch?: Shoshilinch } = {},
): Promise<PushNatija> {
const yuk = JSON.stringify(xabar);
const hajm = Buffer.byteLength(yuk);
if (hajm > MAKS_YUK) {
const sabab = `yuk ${String(hajm)} bayt > ${String(MAKS_YUK)}`;
return { tur: "xato", status: 0, sabab };
}
// Topic: bir xil teg — eskisini almashtiradi (RFC 8030 §5.4)
const teg = xabar.teg;
const topic = teg?.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 32);
try {
const r = await webpush.sendNotification(obuna, yuk, {
vapidDetails: this.#vapid,
TTL: o.ttl ?? 3600,
urgency: o.shoshilinch ?? "normal",
...(teg ? { topic } : {}),
timeout: 10_000,
...(this.#agent ? { agent: this.#agent } : {}),
});
return { tur: "yetkazildi", status: r.statusCode };
} catch (e) {
const status = (e as { statusCode?: number }).statusCode ?? 0;
// ECONNRESET, ETIMEDOUT … — vaqtinchalik
const kod = (e as { code?: unknown }).code;
const tarmoq = typeof kod === "string";
if (status === 404 || status === 410) {
return { tur: "eskirgan", status };
}
const vaqtinchalik =
status === 429 || status >= 500 || (status === 0 && tarmoq);
if (vaqtinchalik) {
const h = (e as { headers?: Record<string, string> }).headers;
const ra = Number(h?.["retry-after"]);
return Number.isFinite(ra) && ra > 0
? { tur: "qayta", status, kutishSoniya: ra }
: { tur: "qayta", status };
}
const sabab = e instanceof Error ? e.message : String(e);
return { tur: "xato", status, sabab };
}
}
}PushNatija— to'rt holatli birlashma tur; chaqiruvchiswitch (n.tur)bilan hammasini ko'rib chiqishga majbur.- Yuk limiti so'rovdan oldin tekshiriladi —
413ni kutmasdan. teg→Topic: faqat ruxsat etilgan belgilar, 32 tagacha.catch— darsdagi to'rt holat va kanon topilmasi (status 0+ tarmoq kodi).
Testlar — modullar/test/push.test.ts, to'rtta, lokal soxta push xizmati (test/soxta/push-xizmati.ts) bilan. U brauzer qiladigan ishni qiladi: P-256 ECDH kalit va 16 baytli auth yaratib obuna beradi. Har so'rovda VAPID JWT'ni (ES256 imzo, aud = origin, exp ≤ 24 soat) tekshiradi va tanani http_ece bilan haqiqatan ochadi. Soxta xizmat HTTPS — sertifikatni o'zimiz yaratdik (openssl, faqat 127.0.0.1). Biz ishga tushirdik (formatlangan modul nusxasi, tsc va eslint 0, Node 24.21.0):
KANON_TLS=<sertifikat papkasi> node --test test/push.test.ts▶ Web Push (soxta push xizmati, HTTPS)
✔ VAPID kalitlari: public 65 bayt (uncompressed P-256), private 32 bayt (1.4463ms)
✔ yuborish: xizmat JWT ni tekshiradi va aes128gcm tanani ochadi; TTL, Urgency, Topic (43.0268ms)
✔ 410/404 → eskirgan; 429 → qayta (Retry-After); boshqa VAPID kaliti — xizmat rad etmaydi, imzo to'g'ri (45.3359ms)
✔ yuk limiti va buzilgan obuna kaliti (1.3044ms)
✔ Web Push (soxta push xizmati, HTTPS) (134.5263ms)
ℹ tests 4
ℹ suites 1
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 376.4584To'rt test o'tdi: VAPID kalit o'lchamlari, shifrlangan yukni xizmat ochgani, 410/404/429/503 tasnifi va yuk limiti bilan buzilgan kalit. Commit:
git commit -m "feat(push): Web Push — VAPID, aes128gcm, eskirgan obuna"vazifalar-api ga ulash 28-qism tugagach: obunani qabul qiladigan marshrut tizimga kirgan foydalanuvchiga bog'lanadi, yuborish esa bildirishnoma markazi orqali.
12. Real ishda
- Web Push — yangiliklar saytlari, chat va loyiha boshqaruv ilovalari (eslatma, izoh). Server qismi deyarli doim
web-pushyoki uning boshqa tillardagi muqobili. - FCM — mobil ilovalarda amaldagi standart; Android'da boshqa yo'l amalda yo'q, iOS uchun APNs ham FCM orqali boshqariladi.
- Tayyor xizmatlar (OneSignal, Novu) obuna saqlash va yuborishni o'zi qiladi — narxi va bog'liqligi bilan.
- Intervyuda: "Web Push qanday ishlaydi?", "VAPID nima uchun?", "Eskirgan push tokenlarni qanday tozalaysiz?"
Xulosa
- Server push'ni obunadagi
endpoint— push xizmatiga yuboradi; brauzerni xizmat uyg'otadi. - VAPID — ES256 JWT: ochiq kalit 65 bayt, imzo 64 bayt;
aud— xizmat origin'i,exp≤ 24 soat. - aes128gcm shifrlash doim +103 bayt; foydali yuk ≤ 3 993 bayt.
201— qabul,404/410— obunani o'chirish,429/5xx— qayta (Retry-After), qolgani — xato.- Kanon topilmalari:
web-pushfaqat HTTPS; noma'lum xatoni "qayta" deyish — abadiy sikl. endpoint— foydalanuvchi bergan URL: SSRF'ga qarshi faqat ma'lum xizmatlar.
Keyingi dars: Bildirishnoma tizimi arxitekturasi — email, SMS, push va Telegram'ni bitta markazga yig'amiz: foydalanuvchi afzalliklari, dedup, soatlik limit va ikki worker bitta xabarni ikki marta yubormasligi.
Manbalar
- RFC 8030 — Web Push protokoli (TTL, Urgency, Topic): https://www.rfc-editor.org/rfc/rfc8030
- RFC 8291 — Web Push shifrlash (aes128gcm): https://www.rfc-editor.org/rfc/rfc8291
- RFC 8292 — VAPID: https://www.rfc-editor.org/rfc/rfc8292
- web-push (Node): https://github.com/web-push-libs/web-push
- Firebase Cloud Messaging (Admin SDK, xabar turlari): https://firebase.google.com/docs/cloud-messaging — sinalmagan
- Expo push: https://docs.expo.dev/push-notifications/overview/ — sinalmagan
- Kanon tajribasi (IlmHamroh, 2026-10-27): web-push 3.6.7, http_ece 1.2.1, lokal HTTPS soxta push xizmati, Node 24.21.0. VAPID JWT, modul testlari va mashq yechimlari — biz ishga tushirdik (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!