ใน Codelab นี้ คุณจะสร้างเซิร์ฟเวอร์ข้อความ Push เซิร์ฟเวอร์จะจัดการรายการการสมัครใช้บริการพุชและส่งการแจ้งเตือนไปยังการสมัครสมาชิก
โค้ดไคลเอ็นต์เสร็จสมบูรณ์แล้ว ใน Codelab นี้คุณจะทำงานกับฟังก์ชันฝั่งเซิร์ฟเวอร์
รีมิกซ์แอปตัวอย่างแล้วดูในแท็บใหม่
ระบบจะบล็อกการแจ้งเตือนจากแอป Glitch ที่ฝังโดยอัตโนมัติ คุณจึงดูตัวอย่างแอปในหน้านี้ไม่ได้ แต่ให้ทำดังนี้
- คลิกรีมิกซ์เพื่อแก้ไขเพื่อทำให้โปรเจ็กต์แก้ไขได้
- หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกด
เต็มหน้าจอ
แอปที่เผยแพร่อยู่จะเปิดขึ้นในแท็บ Chrome ใหม่ ในข้อบกพร่องที่ฝัง ให้คลิกดูแหล่งที่มาเพื่อแสดงโค้ดอีกครั้ง
ขณะดำเนินการใน Codelab นี้ ให้ทำการเปลี่ยนแปลงโค้ดใน Glitch ที่ฝังอยู่ในหน้านี้ รีเฟรชแท็บใหม่ด้วยแอปที่เผยแพร่อยู่เพื่อดูการเปลี่ยนแปลง
ทำความคุ้นเคยกับแอปเริ่มต้นและโค้ดของแอป
เริ่มต้นด้วยการดูที่ UI ไคลเอ็นต์ของแอป
ในแท็บ Chrome ใหม่ ให้ทำดังนี้
กด "Control+Shift+J" (หรือ "Command+Option+J" ใน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาเว็บ คลิกแท็บคอนโซล
ลองคลิกปุ่มใน UI (ตรวจสอบเอาต์พุตที่คอนโซลของนักพัฒนาซอฟต์แวร์ Chrome)
ลงทะเบียน Service Worker จะลงทะเบียน Service Worker สำหรับขอบเขต URL โปรเจ็กต์ Glitch ของคุณ ยกเลิกการลงทะเบียน Service Worker จะนำโปรแกรมทำงานของบริการออก หากมีการสมัครใช้บริการพุชอยู่ด้วย ระบบจะปิดใช้งานการสมัครใช้บริการพุชด้วย
ตัวเลือกติดตามเพื่อพุชจะสร้างการสมัครใช้บริการพุช โดยจะใช้ได้เมื่อลงทะเบียน Service Worker แล้ว และมีค่าคงที่
VAPID_PUBLIC_KEY
ในรหัสไคลเอ็นต์ (ดูรายละเอียดเพิ่มเติมในภายหลัง) คุณจึงยังคลิกไม่ได้เมื่อมีการสมัครใช้บริการพุชที่ใช้งานอยู่ ให้แจ้งการสมัครใช้บริการปัจจุบันให้เซิร์ฟเวอร์ส่งการแจ้งเตือนไปยังปลายทาง
แจ้งเตือนการสมัครใช้บริการทั้งหมดจะบอกให้เซิร์ฟเวอร์ส่งการแจ้งเตือนไปยังปลายทางการสมัครใช้บริการทั้งหมดในฐานข้อมูล
โปรดทราบว่าปลายทางบางรายการอาจไม่ได้ทำงานอยู่ การสมัครรับข้อมูลนั้นจะหายไปทุกครั้งที่เซิร์ฟเวอร์ส่งการแจ้งเตือนไปยังการสมัครรับข้อมูล
มาดูกันว่ามีอะไรเกิดขึ้นในฝั่งเซิร์ฟเวอร์บ้าง หากต้องการดูข้อความจากโค้ดเซิร์ฟเวอร์ ให้ดูที่บันทึก Node.js ภายในอินเทอร์เฟซของ Glitch
ในแอป Glitch ให้คลิกเครื่องมือ -> บันทึก
คุณอาจเห็นข้อความที่คล้ายกับ
Listening on port 3000
หากลองคลิกแจ้งเตือนการสมัครใช้บริการปัจจุบันหรือแจ้งเตือนการติดตามทั้งหมดใน UI ของแอปที่เผยแพร่อยู่ คุณจะเห็นข้อความต่อไปนี้ด้วย
TODO: Implement sendNotifications() Endpoints to send to: []
ลองมาดูโค้ดบางอย่างกัน
public/index.js
มีรหัสไคลเอ็นต์ที่เสร็จสมบูรณ์ โดยจะดำเนินการตรวจหาฟีเจอร์ ลงทะเบียนและยกเลิกการลงทะเบียน Service Worker และควบคุมการสมัครใช้บริการของผู้ใช้สำหรับข้อความ Push นอกจากนี้ยังส่งข้อมูลเกี่ยวกับการสมัครใช้บริการใหม่และรายการที่ถูกลบไปยังเซิร์ฟเวอร์ด้วยเนื่องจากคุณต้องทำงานกับฟังก์ชันของเซิร์ฟเวอร์เท่านั้น จึงจะไม่ได้แก้ไขไฟล์นี้ (นอกเหนือจากการเพิ่มค่าคงที่
VAPID_PUBLIC_KEY
)public/service-worker.js
เป็น Service Worker ที่ใช้งานง่ายซึ่งจะบันทึกเหตุการณ์พุชและแสดงการแจ้งเตือน/views/index.html
มี UI ของแอป.env
มีตัวแปรสภาพแวดล้อมที่ Glitch โหลดลงในเซิร์ฟเวอร์แอปของคุณเมื่อเริ่มทำงาน คุณจะป้อนข้อมูลใน.env
ด้วยรายละเอียดการตรวจสอบสิทธิ์สำหรับส่งการแจ้งเตือนserver.js
คือไฟล์ที่คุณจะทำงานส่วนใหญ่ในระหว่าง Codelab นี้โค้ดเริ่มต้นจะสร้างเว็บเซิร์ฟเวอร์ Express แบบง่ายๆ มีสิ่งที่ต้องทำ 4 รายการให้คุณ ทำเครื่องหมายในความคิดเห็นเกี่ยวกับโค้ดด้วย
TODO:
สิ่งที่ต้องทำใน Codelab นี้ คุณจะศึกษารายการสิ่งที่ต้องทำทีละรายการ
สร้างและโหลดรายละเอียด VAPID
สิ่งที่ต้องทำรายการแรกคือการสร้างรายละเอียด VAPID เพิ่มลงในตัวแปรสภาพแวดล้อม Node.js แล้วอัปเดตโค้ดไคลเอ็นต์และเซิร์ฟเวอร์ด้วยค่าใหม่
ข้อมูลเบื้องต้น
เมื่อสมัครรับการแจ้งเตือน ผู้ใช้จะต้องเชื่อถือข้อมูลประจำตัวของแอปและเซิร์ฟเวอร์ของแอป นอกจากนี้ ผู้ใช้ยังต้องมั่นใจว่าเมื่อได้รับการแจ้งเตือน การแจ้งเตือนนั้นมาจากแอปเดียวกับที่ตั้งค่าการสมัครใช้บริการนั้น และต้องไว้วางใจว่าไม่มีใครอ่านเนื้อหาการแจ้งเตือนได้
โปรโตคอลที่ทำให้ข้อความ Push มีความปลอดภัยและเป็นส่วนตัวเรียกว่า Voluntary Application Server Verification for Web Push (VAPID) VAPID ใช้วิทยาการเข้ารหัสคีย์สาธารณะเพื่อยืนยันตัวตนของแอป เซิร์ฟเวอร์ และอุปกรณ์ปลายทางของการสมัครใช้บริการ รวมถึงเพื่อเข้ารหัสเนื้อหาของการแจ้งเตือน
ในแอปนี้ คุณจะใช้แพ็กเกจ npm ของ Web-push เพื่อสร้างคีย์ VAPID รวมถึงเข้ารหัสและส่งการแจ้งเตือน
การใช้งาน
ในขั้นตอนนี้ ให้สร้างคู่ของคีย์ VAPID สำหรับแอปแล้วเพิ่มลงในตัวแปรสภาพแวดล้อม โหลดตัวแปรสภาพแวดล้อมในเซิร์ฟเวอร์และเพิ่มคีย์สาธารณะเป็นค่าคงที่ในโค้ดไคลเอ็นต์
ใช้ฟังก์ชัน
generateVAPIDKeys
ของไลบรารีweb-push
เพื่อสร้างคู่ของคีย์ VAPIDใน server.js ให้นําความคิดเห็นออกจากบรรทัดโค้ดต่อไปนี้
server.js
// Generate VAPID keys (only do this once). /* * const vapidKeys = webpush.generateVAPIDKeys(); * console.log(vapidKeys); */ const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys);
หลังจากที่ Glitch รีสตาร์ทแอป แอปจะแสดงคีย์ที่สร้างขึ้นไปยังบันทึก Node.js ภายในอินเทอร์เฟซของ Glitch (ไม่ใช่ในคอนโซล Chrome) หากต้องการดูคีย์ VAPID ให้เลือก เครื่องมือ -> บันทึกในอินเทอร์เฟซ Glitch
โปรดตรวจสอบว่าคุณคัดลอกคีย์สาธารณะและคีย์ส่วนตัวจากคู่คีย์เดียวกันแล้ว
Glitch จะรีสตาร์ทแอปทุกครั้งที่คุณแก้ไขโค้ด ดังนั้นคีย์คู่แรกที่คุณสร้างอาจเลื่อนออกจากมุมมองเมื่อมีเอาต์พุตมากขึ้น
ใน .env ให้คัดลอกและวางคีย์ VAPID ใส่คีย์ไว้ในเครื่องหมายคำพูดคู่ (
"..."
)สำหรับ
VAPID_SUBJECT
คุณสามารถป้อน"mailto:test@test.test"
.env
# process.env.SECRET VAPID_PUBLIC_KEY= VAPID_PRIVATE_KEY= VAPID_SUBJECT= VAPID_PUBLIC_KEY="BN3tWzHp3L3rBh03lGLlLlsq..." VAPID_PRIVATE_KEY="I_lM7JMIXRhOk6HN..." VAPID_SUBJECT="mailto:test@test.test"
ใน server.js ให้แสดงความคิดเห็นของโค้ด 2 บรรทัดนี้อีกครั้ง เนื่องจากคุณจะสร้างคีย์ VAPID เพียงครั้งเดียวเท่านั้น
server.js
// Generate VAPID keys (only do this once). /* const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys); */ const vapidKeys = webpush.generateVAPIDKeys(); console.log(vapidKeys);
ใน server.js ให้โหลดรายละเอียด VAPID จากตัวแปรสภาพแวดล้อม
server.js
const vapidDetails = { // TODO: Load VAPID details from environment variables. publicKey: process.env.VAPID_PUBLIC_KEY, privateKey: process.env.VAPID_PRIVATE_KEY, subject: process.env.VAPID_SUBJECT }
คัดลอกและวางคีย์ Public ลงในโค้ดไคลเอ็นต์ด้วย
ใน public/index.js ให้ป้อนค่าเดียวกันกับ
VAPID_PUBLIC_KEY
ที่คัดลอกไปไว้ในไฟล์ .envpublic/index.js
// Copy from .env const VAPID_PUBLIC_KEY = ''; const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...'; ````
ใช้ฟังก์ชันการทำงานเพื่อส่งการแจ้งเตือน
ข้อมูลเบื้องต้น
ในแอปนี้ คุณจะใช้แพ็กเกจ npm ของ Web-push เพื่อส่งการแจ้งเตือน
แพ็กเกจนี้จะเข้ารหัสการแจ้งเตือนโดยอัตโนมัติเมื่อมีการเรียกใช้ webpush.sendNotification()
คุณจึงไม่ต้องกังวลเกี่ยวกับเรื่องนี้
เว็บพุชยอมรับตัวเลือกการแจ้งเตือนหลายรายการ เช่น คุณสามารถแนบส่วนหัวไปกับข้อความและระบุการเข้ารหัสเนื้อหาได้
ใน Codelab นี้ คุณจะใช้เพียง 2 ตัวเลือกเท่านั้น โดยกำหนดด้วยบรรทัดโค้ดต่อไปนี้
let options = {
TTL: 10000; // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails; // VAPID keys from .env
};
ตัวเลือก TTL
(Time to Live) จะตั้งค่าระยะหมดเวลาหมดอายุของการแจ้งเตือน ซึ่งเป็นวิธีที่เซิร์ฟเวอร์จะหลีกเลี่ยงการส่งการแจ้งเตือนไปยังผู้ใช้หลังจากการแจ้งเตือนที่ไม่เกี่ยวข้องแล้ว
ตัวเลือก vapidDetails
มีคีย์ VAPID ที่คุณโหลดจากตัวแปรสภาพแวดล้อม
การใช้งาน
ใน server.js ให้แก้ไขฟังก์ชัน sendNotifications
ดังนี้
server.js
function sendNotifications(database, endpoints) {
// TODO: Implement functionality to send notifications.
console.log('TODO: Implement sendNotifications()');
console.log('Endpoints to send to: ', endpoints);
let notification = JSON.stringify(createNotification());
let options = {
TTL: 10000, // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails // VAPID keys from .env
};
endpoints.map(endpoint => {
let subscription = database[endpoint];
webpush.sendNotification(subscription, notification, options);
});
}
เนื่องจาก webpush.sendNotification()
แสดงคำมั่นสัญญา คุณจึงเพิ่มการจัดการข้อผิดพลาดได้อย่างง่ายดาย
ใน server.js ให้แก้ไขฟังก์ชัน sendNotifications
อีกครั้ง
server.js
function sendNotifications(database, endpoints) {
let notification = JSON.stringify(createNotification());
let options = {
TTL: 10000; // Time-to-live. Notifications expire after this.
vapidDetails: vapidDetails; // VAPID keys from .env
};
endpoints.map(endpoint => {
let subscription = database[endpoint];
webpush.sendNotification(subscription, notification, options);
let id = endpoint.substr((endpoint.length - 8), endpoint.length);
webpush.sendNotification(subscription, notification, options)
.then(result => {
console.log(`Endpoint ID: ${id}`);
console.log(`Result: ${result.statusCode} `);
})
.catch(error => {
console.log(`Endpoint ID: ${id}`);
console.log(`Error: ${error.body} `);
});
});
}
จัดการการสมัครใช้บริการใหม่
ข้อมูลเบื้องต้น
ต่อไปนี้คือสิ่งที่จะเกิดขึ้นเมื่อผู้ใช้สมัครรับข้อความ Push
ผู้ใช้คลิกติดตามเพื่อพุช
ไคลเอ็นต์ใช้ค่าคงที่
VAPID_PUBLIC_KEY
(คีย์ VAPID สาธารณะของเซิร์ฟเวอร์) เพื่อสร้างออบเจ็กต์subscription
เฉพาะเซิร์ฟเวอร์ที่ไม่ซ้ำกัน ออบเจ็กต์subscription
มีลักษณะดังนี้{ "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...", "expirationTime": null, "keys": { "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...", "auth": "0IyyvUGNJ9RxJc83poo3bA" } }
ไคลเอ็นต์จะส่งคำขอ
POST
ไปยัง URL ของ/add-subscription
โดยรวมการสมัครใช้บริการเป็น JSON ที่มีสตริงในเนื้อหาเซิร์ฟเวอร์จะเรียก
subscription
ที่เป็นสตริงจากเนื้อหาของคำขอ POST แยกวิเคราะห์กลับไปยัง JSON และเพิ่มไปยังฐานข้อมูลการสมัครรับข้อมูลฐานข้อมูลจะจัดเก็บการสมัครใช้บริการโดยใช้อุปกรณ์ปลายทางของตนเองเป็นคีย์ ดังนี้
{
"https://fcm...1234": {
endpoint: "https://fcm...1234",
expirationTime: ...,
keys: { ... }
},
"https://fcm...abcd": {
endpoint: "https://fcm...abcd",
expirationTime: ...,
keys: { ... }
},
"https://fcm...zxcv": {
endpoint: "https://fcm...zxcv",
expirationTime: ...,
keys: { ... }
},
}
ตอนนี้การสมัครใช้บริการใหม่พร้อมให้เซิร์ฟเวอร์ส่งการแจ้งเตือนแล้ว
การใช้งาน
คำขอสำหรับการติดตามใหม่จะอยู่ในเส้นทาง /add-subscription
ซึ่งเป็น URL ของ POST คุณจะเห็นเครื่องจัดการเส้นทาง Stub ใน server.js ดังนี้
server.js
app.post('/add-subscription', (request, response) => {
// TODO: implement handler for /add-subscription
console.log('TODO: Implement handler for /add-subscription');
console.log('Request body: ', request.body);
response.sendStatus(200);
});
ในการนำไปใช้งาน เครื่องจัดการนี้ต้องมีลักษณะดังนี้
- ดึงข้อมูลการสมัครรับข้อมูลใหม่จากเนื้อหาของคำขอ
- เข้าถึงฐานข้อมูลของการสมัครใช้บริการที่ใช้งานอยู่
- เพิ่มการสมัครใช้บริการใหม่ลงในรายการการสมัครใช้บริการที่ใช้งานอยู่
วิธีจัดการการสมัครใช้บริการใหม่
ใน server.js ให้แก้ไขเครื่องจัดการเส้นทางสำหรับ
/add-subscription
ดังนี้server.js
app.post('/add-subscription', (request, response) => {
// TODO: implement handler for /add-subscription
console.log('TODO: Implement handler for /add-subscription');
console.log('Request body: ', request.body);
let subscriptions = Object.assign({}, request.session.subscriptions);
subscriptions[request.body.endpoint] = request.body;
request.session.subscriptions = subscriptions;
response.sendStatus(200);
});
จัดการการยกเลิกการสมัครใช้บริการ
ข้อมูลเบื้องต้น
เซิร์ฟเวอร์จะไม่ทราบเสมอไปเมื่อการสมัครใช้บริการกลายเป็นไม่มีการใช้งาน เช่น ระบบอาจล้างข้อมูลการสมัครใช้บริการเมื่อเบราว์เซอร์ปิดโปรแกรมทำงานของบริการ
อย่างไรก็ตาม เซิร์ฟเวอร์จะค้นหาการสมัครใช้บริการที่ถูกยกเลิกผ่าน UI ของแอปได้ ในขั้นตอนนี้ คุณจะใช้ฟังก์ชันการทำงานเพื่อนำการสมัครใช้บริการออกจากฐานข้อมูล
วิธีนี้จะช่วยให้เซิร์ฟเวอร์หลีกเลี่ยงการส่งการแจ้งเตือนจำนวนมากไปยังปลายทางที่ไม่มีอยู่จริง แน่นอนว่าการใช้แอปทดสอบแบบง่ายๆ นั้นไม่ใช่สิ่งสำคัญอะไรเลย แต่กลับกลายเป็นสิ่งสำคัญสำหรับคนในวงกว้างมากขึ้น
การใช้งาน
คำขอยกเลิกการสมัครใช้บริการจะอยู่ใน URL ของ /remove-subscription
POST
เครื่องจัดการเส้นทาง Stub ใน server.js มีลักษณะดังนี้
server.js
app.post('/remove-subscription', (request, response) => {
// TODO: implement handler for /remove-subscription
console.log('TODO: Implement handler for /remove-subscription');
console.log('Request body: ', request.body);
response.sendStatus(200);
});
ในการนำไปใช้งาน เครื่องจัดการนี้ต้องมีลักษณะดังนี้
- ดึงข้อมูลปลายทางของการสมัครใช้บริการที่ยกเลิกแล้วจากเนื้อหาของคำขอ
- เข้าถึงฐานข้อมูลของการสมัครใช้บริการที่ใช้งานอยู่
- นำการสมัครใช้บริการที่ยกเลิกออกจากรายการการสมัครใช้บริการที่ใช้งานอยู่
เนื้อหาของคำขอ POST จากไคลเอ็นต์มีปลายทางที่คุณต้องนำออก
{
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}
วิธีจัดการการยกเลิกการสมัครใช้บริการ
ใน server.js ให้แก้ไขเครื่องจัดการเส้นทางสำหรับ
/remove-subscription
ดังนี้server.js
app.post('/remove-subscription', (request, response) => {
// TODO: implement handler for /remove-subscription
console.log('TODO: Implement handler for /remove-subscription');
console.log('Request body: ', request.body);
let subscriptions = Object.assign({}, request.session.subscriptions);
delete subscriptions[request.body.endpoint];
request.session.subscriptions = subscriptions;
response.sendStatus(200);
});