สร้างเซิร์ฟเวอร์ข้อความ Push

ใน Codelab นี้ คุณจะสร้างเซิร์ฟเวอร์ข้อความ Push เซิร์ฟเวอร์จะจัดการรายการการสมัครใช้บริการพุชและส่งการแจ้งเตือนไปยังการสมัครสมาชิก

โค้ดไคลเอ็นต์เสร็จสมบูรณ์แล้ว ใน Codelab นี้คุณจะทำงานกับฟังก์ชันฝั่งเซิร์ฟเวอร์

รีมิกซ์แอปตัวอย่างแล้วดูในแท็บใหม่

ระบบจะบล็อกการแจ้งเตือนจากแอป Glitch ที่ฝังโดยอัตโนมัติ คุณจึงดูตัวอย่างแอปในหน้านี้ไม่ได้ แต่ให้ทำดังนี้

  1. คลิกรีมิกซ์เพื่อแก้ไขเพื่อทำให้โปรเจ็กต์แก้ไขได้
  2. หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกด เต็มหน้าจอ เต็มหน้าจอ

แอปที่เผยแพร่อยู่จะเปิดขึ้นในแท็บ Chrome ใหม่ ในข้อบกพร่องที่ฝัง ให้คลิกดูแหล่งที่มาเพื่อแสดงโค้ดอีกครั้ง

ขณะดำเนินการใน Codelab นี้ ให้ทำการเปลี่ยนแปลงโค้ดใน Glitch ที่ฝังอยู่ในหน้านี้ รีเฟรชแท็บใหม่ด้วยแอปที่เผยแพร่อยู่เพื่อดูการเปลี่ยนแปลง

ทำความคุ้นเคยกับแอปเริ่มต้นและโค้ดของแอป

เริ่มต้นด้วยการดูที่ UI ไคลเอ็นต์ของแอป

ในแท็บ Chrome ใหม่ ให้ทำดังนี้

  1. กด "Control+Shift+J" (หรือ "Command+Option+J" ใน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาเว็บ คลิกแท็บคอนโซล

  2. ลองคลิกปุ่มใน 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 สำหรับแอปแล้วเพิ่มลงในตัวแปรสภาพแวดล้อม โหลดตัวแปรสภาพแวดล้อมในเซิร์ฟเวอร์และเพิ่มคีย์สาธารณะเป็นค่าคงที่ในโค้ดไคลเอ็นต์

  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);
    
  2. หลังจากที่ Glitch รีสตาร์ทแอป แอปจะแสดงคีย์ที่สร้างขึ้นไปยังบันทึก Node.js ภายในอินเทอร์เฟซของ Glitch (ไม่ใช่ในคอนโซล Chrome) หากต้องการดูคีย์ VAPID ให้เลือก เครื่องมือ -> บันทึกในอินเทอร์เฟซ Glitch

    โปรดตรวจสอบว่าคุณคัดลอกคีย์สาธารณะและคีย์ส่วนตัวจากคู่คีย์เดียวกันแล้ว

    Glitch จะรีสตาร์ทแอปทุกครั้งที่คุณแก้ไขโค้ด ดังนั้นคีย์คู่แรกที่คุณสร้างอาจเลื่อนออกจากมุมมองเมื่อมีเอาต์พุตมากขึ้น

  3. ใน .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"
    
  4. ใน 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);
    
  5. ใน 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
    }
    
  6. คัดลอกและวางคีย์ Public ลงในโค้ดไคลเอ็นต์ด้วย

    ใน public/index.js ให้ป้อนค่าเดียวกันกับ VAPID_PUBLIC_KEY ที่คัดลอกไปไว้ในไฟล์ .env

    public/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

  1. ผู้ใช้คลิกติดตามเพื่อพุช

  2. ไคลเอ็นต์ใช้ค่าคงที่ VAPID_PUBLIC_KEY (คีย์ VAPID สาธารณะของเซิร์ฟเวอร์) เพื่อสร้างออบเจ็กต์ subscription เฉพาะเซิร์ฟเวอร์ที่ไม่ซ้ำกัน ออบเจ็กต์ subscription มีลักษณะดังนี้

       {
         "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
         "expirationTime": null,
         "keys":
         {
           "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
           "auth": "0IyyvUGNJ9RxJc83poo3bA"
         }
       }
    
  3. ไคลเอ็นต์จะส่งคำขอ POST ไปยัง URL ของ /add-subscription โดยรวมการสมัครใช้บริการเป็น JSON ที่มีสตริงในเนื้อหา

  4. เซิร์ฟเวอร์จะเรียก 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);
  });