ข้ามไปยังเนื้อหา

Push API

Push API ช่วยให้ server ของคุณส่งข้อความไปยังอุปกรณ์ของผู้ใช้ได้แม้ผู้ใช้จะไม่ได้ใช้แอปอยู่ — ไม่ต้องมีแท็บเปิดอยู่ browser รักษาการเชื่อมต่อถาวรกับ push service (ที่ดูแลโดย browser vendor) server ของคุณพูดคุยกับ push service นั้น และ push service ส่งข้อความไปยังอุปกรณ์ของผู้ใช้ที่ service worker ของคุณรับข้อความนั้น

sequenceDiagram
  participant Page as Page (JS)
  participant SW as Service Worker
  participant PS as Push Service (browser vendor)
  participant Srv as Your Server

  Page->>SW: navigator.serviceWorker.ready
  Page->>PS: pushManager.subscribe({ applicationServerKey })
  PS-->>Page: PushSubscription (endpoint + keys)
  Page->>Srv: POST /subscribe (subscription JSON)
  Note over Srv: Stores subscription in DB

  Srv->>PS: POST endpoint with encrypted payload + VAPID auth
  PS->>SW: push event with data
  SW->>SW: event.waitUntil(showNotification(...))
Push notification flow: subscribe, send, deliver

VAPID (Voluntary Application Server Identification) keys ใช้ยืนยันตัวตน server ของคุณกับ push service สร้างครั้งเดียวและเก็บอย่างปลอดภัย:

Terminal window
# Using the web-push npm package
npx web-push generate-vapid-keys

ซึ่งจะให้ public key (แชร์กับ browser) และ private key (เก็บไว้บน server เท่านั้น — อย่าเปิดเผย)

// main.js — page code
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready;
// Convert your VAPID public key (base64url) to a Uint8Array
const applicationServerKey = urlBase64ToUint8Array(VAPID_PUBLIC_KEY);
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true, // required: every push must show a notification
applicationServerKey,
});
// Send the subscription object to your backend
await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription),
});
console.log('Subscribed:', subscription.endpoint);
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const rawData = atob(base64);
return Uint8Array.from([...rawData].map((c) => c.charCodeAt(0)));
}

userVisibleOnly: true เป็นข้อบังคับ — browser กำหนดให้ push message ทุกรายการต้องแสดง notification ที่มองเห็นได้

Server ของคุณรับ subscription JSON (ที่มี endpoint, keys.auth, keys.p256dh) และเก็บไว้ใน database โดยใช้ user เป็น key

// server.js (Node.js with web-push)
import webpush from 'web-push';
webpush.setVapidDetails(
process.env.VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY,
);
async function sendPush(subscription, payload) {
await webpush.sendNotification(subscription, JSON.stringify(payload));
}
// Example
sendPush(storedSubscription, {
title: 'Your order shipped!',
body: 'It will arrive by Thursday.',
});
// sw.js — service worker
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : { title: 'Update', body: '' };
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
data,
})
);
});

event.waitUntil() ทำให้ SW ยังคงทำงานต่อจนกว่า notification จะถูกแสดง ถ้า SW terminate ก่อนที่ showNotification จะ resolve notification อาจสูญหายได้

ก่อน subscribe ให้ตรวจสอบว่ามี subscription อยู่แล้วหรือไม่เพื่อหลีกเลี่ยงการซ้ำกัน:

const existing = await registration.pushManager.getSubscription();
if (existing) {
console.log('Already subscribed:', existing.endpoint);
return existing;
}
const subscription = await registration.pushManager.getSubscription();
if (subscription) {
await subscription.unsubscribe();
// Also notify your server to remove the stored subscription
await fetch('/api/unsubscribe', {
method: 'POST',
body: JSON.stringify({ endpoint: subscription.endpoint }),
});
}

บทเรียนแบบ code-only: Push ต้องการ backend server ที่มี VAPID keys, database และ push service ตัวอย่างด้านบนสมบูรณ์และถูกต้อง แต่ไม่สามารถรันใน in-browser playground ได้ คัดลอกลงในโปรเจ็กต์จริง เช่น Node.js server ที่มี web-push และ frontend framework ใดก็ได้

ตัวเลือกBenefitCost
Push API (re-engage ผู้ใช้ที่ไม่ได้เปิดแอป)ดึงผู้ใช้กลับมาได้แม้ปิดแอปไปนาน เพิ่ม retentionต้องมี push server/VAPID key และจัดการ subscription lifecycle เอง
ไม่ใช้ push เลย พึ่งแค่ในแอปไม่ต้องดูแล infrastructure ฝั่ง push server เลยเสียโอกาส re-engage ผู้ใช้ที่ไม่ได้เปิดแอปเป็นระยะ
  • ไม่ handle subscription expiry/rotation ทำให้ push ที่ส่งไปเงียบหายเพราะ endpoint เก่าใช้ไม่ได้แล้ว
  • ส่ง push notification บ่อยเกินไปจนผู้ใช้ revoke permission ทิ้งทั้งหมด
  • ไม่ encrypt payload ตามสเปก Web Push (VAPID + payload encryption) ทำให้ push provider บาง raw ปฏิเสธ request

💡 ตัวอย่างจากของจริง

Facebook web push — ใช้ Push API แจ้งเตือนข้อความและ activity ให้ผู้ใช้กลับมาที่เว็บแม้ปิด tab ไปแล้ว

สายการบิน (check-in reminder) — ส่ง push แจ้งเตือนเวลา check-in ล่วงหน้า ทำให้ผู้ใช้กลับเข้าแอปโดยไม่ต้องเปิดเองมาเช็ค

pushManager.subscribe() return อะไร?
ทำไม subscribe options จึงต้องมี userVisibleOnly: true?
ควรเก็บ VAPID private key ไว้ที่ไหน?
event ใดของ service worker ที่ทำงานเมื่อ server ส่ง push message?