ใน Codelab นี้ คุณจะสร้างเซิร์ฟเวอร์ข้อความ 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 ออก หากมีการแนบการสมัครใช้บริการพุชอยู่ ระบบจะปิดใช้งานการสมัครใช้บริการพุชดังกล่าวด้วย
ติดตามเพื่อพุชจะสร้างการติดตามแบบพุช โดยจะใช้ได้เมื่อมีการลงทะเบียน 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 ID for Web Push (VAPID) VAPID ใช้วิทยาการเข้ารหัสคีย์สาธารณะเพื่อยืนยันตัวตนของแอป เซิร์ฟเวอร์ และปลายทางการสมัครใช้บริการ รวมถึงเข้ารหัสเนื้อหาการแจ้งเตือน
ในแอปนี้ คุณจะใช้แพ็กเกจ Web-push npm เพื่อสร้างคีย์ VAPID รวมถึงเข้ารหัสและส่งการแจ้งเตือน
การใช้งาน
ในขั้นตอนนี้ ให้สร้างคีย์ VAPID 1 คู่สำหรับแอป แล้วเพิ่มลงในตัวแปรสภาพแวดล้อม โหลดตัวแปรสภาพแวดล้อมในเซิร์ฟเวอร์และเพิ่มคีย์สาธารณะเป็นค่าคงที่ในโค้ดไคลเอ็นต์
ใช้ฟังก์ชัน
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 จะรีสตาร์ทแอปทุกครั้งที่คุณแก้ไขโค้ด ดังนั้นคีย์คู่แรกที่คุณสร้างอาจเลื่อนออกจากมุมมองเมื่อมีผลลัพธ์เพิ่มเติมตามมา
คัดลอกและวางคีย์ VAPID ใน .env ใส่คีย์ในเครื่องหมายคำพูดคู่ (
"..."
)สำหรับ
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
เดียวกันกับที่คัดลอกลงในไฟล์ .env ดังนี้public/index.js
// Copy from .env const VAPID_PUBLIC_KEY = ''; const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...'; ````
ใช้ฟังก์ชันเพื่อส่งการแจ้งเตือน
ที่มา
ในแอปนี้ คุณจะใช้แพ็กเกจ Web-push npm เพื่อส่งการแจ้งเตือน
แพ็กเกจนี้จะเข้ารหัสการแจ้งเตือนโดยอัตโนมัติเมื่อมีการเรียกใช้ webpush.sendNotification()
คุณจึงไม่ต้องเป็นห่วง
Web-push ยอมรับตัวเลือกการแจ้งเตือนมากมาย เช่น คุณสามารถแนบส่วนหัวไปกับข้อความและระบุการเข้ารหัสเนื้อหา
ใน 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 ของแอปได้ ในขั้นตอนนี้ คุณจะได้ใช้ฟังก์ชันในการนำการสมัครใช้บริการออกจากฐานข้อมูล
วิธีนี้จะทำให้เซิร์ฟเวอร์หลีกเลี่ยงการส่งการแจ้งเตือนจำนวนมากไปยังปลายทางที่ไม่มีอยู่จริง เห็นได้ชัดว่าเรื่องนี้ไม่สำคัญมากนักสำหรับแอปทดสอบทั่วไป แต่กลายเป็นสิ่งสำคัญในระดับที่ใหญ่ขึ้น
การใช้งาน
คำขอยกเลิกการสมัครใช้บริการจะอยู่ใน URL POST ของ /remove-subscription
ตัวแฮนเดิลเส้นทางของ 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);
});