ใน Codelab นี้ คุณจะได้สร้างเซิร์ฟเวอร์ข้อความ Push เซิร์ฟเวอร์จะจัดการรายการการสมัครรับข้อมูล Push และส่งการแจ้งเตือนไปยังผู้ใช้
โค้ดไคลเอ็นต์เสร็จสมบูรณ์แล้ว ใน Codelab นี้คุณจะทำงานกับฟังก์ชันฝั่งเซิร์ฟเวอร์
รีมิกซ์แอปตัวอย่างและดูแอปในแท็บใหม่
ระบบจะบล็อกการแจ้งเตือนจากแอป Glitch ที่ฝังไว้โดยอัตโนมัติ คุณจึงไม่สามารถดูตัวอย่างแอปในหน้านี้ได้ แต่ให้ทำดังนี้
- คลิกรีมิกซ์เพื่อแก้ไขเพื่อให้โปรเจ็กต์แก้ไขได้
- หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกดเต็มหน้าจอ
แอปเวอร์ชันที่ใช้จริงจะเปิดในแท็บ Chrome ใหม่ ใน Glitch ที่ฝังไว้ ให้คลิกดูซอร์สโค้ดเพื่อแสดงโค้ดอีกครั้ง
ขณะทำ Codelab นี้ ให้ทำการเปลี่ยนแปลงโค้ดใน Glitch ที่ฝังอยู่ในหน้านี้ รีเฟรชแท็บใหม่ด้วยแอปที่เผยแพร่อยู่เพื่อดูการเปลี่ยนแปลง
ทำความคุ้นเคยกับแอปเริ่มต้นและโค้ดของแอป
เริ่มต้นด้วยการดูที่ UI ไคลเอ็นต์ของแอป
ในแท็บใหม่ของ Chrome
กดแป้น Control+Shift+J (หรือ Command+Option+J ใน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ คลิกแท็บคอนโซล
ลองคลิกปุ่มใน UI (ดูเอาต์พุตในคอนโซลสำหรับนักพัฒนาซอฟต์แวร์ของ Chrome)
ลงทะเบียน Service Worker จะลงทะเบียน Service Worker สำหรับขอบเขตของ URL โปรเจ็กต์ Glitch ยกเลิกการลงทะเบียน Service Worker จะนํา Service Worker ออก หากมีการสมัครใช้บริการ Push แนบอยู่ ระบบก็จะปิดใช้งานการสมัครใช้บริการ Push ด้วย
ติดตาม Push จะสร้างการติดตาม Push โดยจะใช้ได้เมื่อลงทะเบียน Service Worker แล้ว และมีค่าคงที่
VAPID_PUBLIC_KEY
ในรหัสไคลเอ็นต์ (ดูรายละเอียดเพิ่มเติมในภายหลัง) คุณจึงยังคลิกไม่ได้เมื่อคุณมีการสมัครใช้บริการ Push ที่ใช้งานอยู่ แจ้งการสมัครใช้บริการปัจจุบันจะขอให้เซิร์ฟเวอร์ส่งการแจ้งเตือนไปยังอุปกรณ์ปลายทาง
แจ้งเตือนการสมัครใช้บริการทั้งหมดจะบอกให้เซิร์ฟเวอร์ส่งการแจ้งเตือนไปยังปลายทางการสมัครใช้บริการทั้งหมดในฐานข้อมูล
โปรดทราบว่าปลายทางเหล่านี้บางรายการอาจไม่ได้ทำงานอยู่ การสมัครใช้บริการอาจหายไปเมื่อเซิร์ฟเวอร์ส่งการแจ้งเตือนถึง
มาดูสิ่งที่เกิดขึ้นในฝั่งเซิร์ฟเวอร์กัน หากต้องการดูข้อความจากโค้ดเซิร์ฟเวอร์ ให้ดูที่บันทึก 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 ง่ายๆ ที่บันทึกเหตุการณ์ Push และแสดงการแจ้งเตือน/views/index.html
มี UI ของแอป.env
มีตัวแปรสภาพแวดล้อมที่ Glitch โหลดลงในเซิร์ฟเวอร์แอปของคุณเมื่อเริ่มทำงาน คุณจะป้อนรายละเอียดการตรวจสอบสิทธิ์ใน.env
เพื่อส่งการแจ้งเตือนserver.js
คือไฟล์ที่คุณจะทำงานส่วนใหญ่ในระหว่าง Codelab นี้โค้ดเริ่มต้นจะสร้างเว็บเซิร์ฟเวอร์ Express แบบง่าย มีรายการสิ่งที่ต้องทํา 4 รายการสําหรับคุณ ซึ่งทําเครื่องหมายในความคิดเห็นโค้ดด้วย
TODO:
สิ่งที่ต้องทำในโค้ดแล็บนี้ คุณจะต้องทําตามรายการสิ่งที่ต้องทําทีละรายการ
สร้างและโหลดรายละเอียด VAPID
รายการสิ่งที่ต้องทําแรกคือสร้างรายละเอียด VAPID แล้วเพิ่มลงในตัวแปรสภาพแวดล้อม Node.js และอัปเดตโค้ดไคลเอ็นต์และเซิร์ฟเวอร์ด้วยค่าใหม่
ข้อมูลเบื้องต้น
เมื่อสมัครรับการแจ้งเตือน ผู้ใช้จะต้องเชื่อถือข้อมูลประจำตัวของแอปและเซิร์ฟเวอร์ของแอป นอกจากนี้ ผู้ใช้ต้องมั่นใจว่าเมื่อได้รับการแจ้งเตือน จะเป็นการแจ้งเตือนจากแอปเดียวกันกับที่ตั้งค่าการสมัครใช้บริการ นอกจากนี้ ผู้รับยังต้องมั่นใจว่าไม่มีใครอ่านเนื้อหาการแจ้งเตือนได้
โปรโตคอลที่ทำให้ข้อความ Push ปลอดภัยและเป็นส่วนตัวเรียกว่า Voluntary Application Server Identification 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/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);
});
จัดการการยกเลิกการสมัครใช้บริการ
ข้อมูลเบื้องต้น
เซิร์ฟเวอร์อาจไม่ทราบว่าการติดตามไม่ทำงานเมื่อใด เช่น การติดตามอาจถูกลบออกเมื่อเบราว์เซอร์ปิด Service Worker
อย่างไรก็ตาม เซิร์ฟเวอร์จะดูข้อมูลการสมัครใช้บริการที่ยกเลิกผ่าน UI ของแอปได้ ในขั้นตอนนี้ คุณจะใช้ฟังก์ชันการทำงานเพื่อนำการสมัครใช้บริการออกจากฐานข้อมูล
วิธีนี้จะช่วยให้เซิร์ฟเวอร์หลีกเลี่ยงการส่งการแจ้งเตือนจำนวนมากไปยังปลายทางที่ไม่มีอยู่จริง แน่นอนว่าเรื่องนี้ไม่สำคัญกับแอปทดสอบง่ายๆ แต่สำคัญเมื่อดำเนินการในวงกว้าง
การใช้งาน
คำขอยกเลิกการสมัครใช้บริการจะส่งไปยัง /remove-subscription
POST URL
เครื่องจัดการเส้นทาง 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);
});