ความสามารถในการทำงานร่วมกันของ Web Push

โจ เมดเลย์
โจ้ เมดเลย์

ในช่วงแรก Chrome รองรับ Web Push API โดยใช้บริการพุชของ Firebase Cloud Messaging (FCM) ซึ่งก่อนหน้านี้เรียกว่าการรับส่งข้อความในระบบคลาวด์ของ Google (GCM) ซึ่งต้องใช้ API ที่เป็นกรรมสิทธิ์ของตนเอง การทำเช่นนี้ช่วยให้ Chrome ทำให้นักพัฒนาซอฟต์แวร์สามารถใช้ Web Push API ได้ในเวลาที่ยังเขียนข้อกำหนดของโปรโตคอล Web Push อยู่และให้มีการตรวจสอบสิทธิ์ในภายหลัง (หมายความว่าผู้ส่งข้อความเป็นบุคคลจริงตามที่อ้าง) ในช่วงเวลาที่โปรโตคอล Web Push ไม่มีโปรโตคอลดังกล่าว ข่าวดีก็คือทั้ง 2 อย่างนี้ไม่เป็นความจริงอีกต่อไป

FCM / GCM และ Chrome รองรับโปรโตคอล Web Push มาตรฐานแล้ว ในขณะที่การตรวจสอบสิทธิ์ผู้ส่งสามารถทำได้โดยใช้ VAPID ซึ่งหมายความว่าเว็บแอปไม่จำเป็นต้องใช้ "gcm_sender_id" อีกต่อไป

ในบทความนี้ ผมจะอธิบายวิธีแปลงโค้ดเซิร์ฟเวอร์ที่มีอยู่เพื่อใช้โปรโตคอล Web Push กับ FCM ก่อน ต่อไปผมจะแสดงวิธีใช้ VAPID ทั้งในโค้ดของไคลเอ็นต์และโค้ดเซิร์ฟเวอร์

FCM รองรับ Web Push Protocol

เรามาเริ่มที่บริบทกันสักเล็กน้อย เมื่อเว็บแอปพลิเคชันลงทะเบียนสำหรับการสมัครใช้บริการพุชจะได้รับ URL ของบริการพุช เซิร์ฟเวอร์จะใช้ปลายทางนี้เพื่อส่งข้อมูลไปยังผู้ใช้ผ่านเว็บแอป คุณจะได้รับปลายทาง FCM ใน Chrome หากสมัครใช้บริการผู้ใช้ที่ไม่มี VAPID (เราจะพูดถึง VAPID ในภายหลัง) ก่อนที่ FCM จะรองรับ Web Push Protocol คุณจะต้องดึงรหัสการลงทะเบียน FCM จากส่วนท้ายของ URL และใส่รหัสไว้ในส่วนหัวก่อนส่งคำขอ API ของ FCM เช่น ปลายทาง FCM ของ https://android.googleapis.com/gcm/send/ABCD1234 จะมีรหัสการลงทะเบียน "ABCD1234"

ตอนนี้ FCM รองรับ Web Push Protocol แล้ว คุณจึงปล่อยปลายทางไว้ตามเดิมและใช้ URL เป็นปลายทาง Web Push Protocol ได้ (ซึ่งจะทำให้สอดคล้องกับ Firefox และหวังว่าเบราว์เซอร์อื่นๆ ในอนาคต)

ก่อนจะเจาะลึกเรื่อง VAPID เราต้องตรวจสอบว่าโค้ดเซิร์ฟเวอร์ของเราจัดการกับปลายทาง FCM ได้อย่างถูกต้อง ด้านล่างคือตัวอย่างการส่งคำขอไปยังบริการพุชในโหนด สำหรับ FCM เราจะเพิ่มคีย์ API ในส่วนหัวของคำขอ ส่วนปลายทางบริการพุชอื่นๆ จะไม่จำเป็น สำหรับ Chrome เวอร์ชันก่อน 52, Opera Android และเบราว์เซอร์ Samsung คุณยังคงต้องใส่ "gcm_sender_id" ใน manifest.json ของเว็บแอป คีย์ API และรหัสผู้ส่งใช้เพื่อตรวจสอบว่าเซิร์ฟเวอร์ที่ส่งคำขอได้รับอนุญาตให้ส่งข้อความถึงผู้ใช้ที่เป็นผู้รับหรือไม่

const headers = new Headers();
// 12-hour notification time to live.
headers.append('TTL', 12 * 60 * 60);
// Assuming no data is going to be sent
headers.append('Content-Length', 0);

// Assuming you're not using VAPID (read on), this
// proprietary header is needed
if(subscription.endpoint
    .indexOf('https://android.googleapis.com/gcm/send/') === 0) {
    headers.append('Authorization', 'GCM_API_KEY');
}

fetch(subscription.endpoint, {
    method: 'POST',
    headers: headers
})
.then(response => {
    if (response.status !== 201) {
    throw new Error('Unable to send push message');
    }
});

โปรดทราบว่านี่เป็นการเปลี่ยนแปลงใน FCM / API ของ GCM คุณจึงไม่จำเป็นต้องอัปเดตการสมัครรับข้อมูล เพียงแค่เปลี่ยนรหัสเซิร์ฟเวอร์เพื่อกำหนดส่วนหัวดังที่แสดงด้านบน

ขอแนะนำ VAPID สำหรับการระบุเซิร์ฟเวอร์

VAPID เป็นชื่อย่อใหม่ที่น่าสนใจสำหรับ "รหัสเซิร์ฟเวอร์แอปพลิเคชันโดยสมัครใจ" ข้อกำหนดใหม่นี้จะกำหนดแฮนด์เชคระหว่างเซิร์ฟเวอร์แอปและบริการพุชเป็นหลัก และจะทำให้บริการพุชสามารถยืนยันได้ว่าเว็บไซต์ใดกำลังส่งข้อความอยู่ เมื่อใช้ VAPID คุณจะหลีกเลี่ยงขั้นตอนเฉพาะสำหรับ FCM ในการส่งข้อความพุชได้ คุณไม่จำเป็นต้องใช้โปรเจ็กต์ Firebase, gcm_sender_id หรือส่วนหัว Authorization อีกต่อไป

กระบวนการนี้ค่อนข้างง่าย

  1. แอปพลิเคชันเซิร์ฟเวอร์จะสร้างคู่คีย์สาธารณะ/ส่วนตัว เว็บแอปจะได้รับ คีย์สาธารณะ
  2. เมื่อผู้ใช้เลือกรับการพุช ให้เพิ่มคีย์สาธารณะไปยังออบเจ็กต์ตัวเลือกของการเรียกใช้ subscription()
  3. เมื่อเซิร์ฟเวอร์แอปส่งข้อความพุช ให้ใส่ JSON Web Token ที่ลงนามไว้พร้อมกับคีย์สาธารณะ

มาดูรายละเอียดของขั้นตอนเหล่านี้กันเลย

สร้างคู่คีย์สาธารณะ/ส่วนตัว

เราหละหลวมเรื่องการเข้ารหัส ดังนั้นมาดูส่วนที่เกี่ยวข้องจากข้อกำหนดเกี่ยวกับรูปแบบของคีย์สาธารณะ/คีย์ส่วนตัว VAPID กัน

เซิร์ฟเวอร์แอปพลิเคชันควรสร้างและดูแลรักษาคู่คีย์ Signing ที่ใช้ได้กับลายเซ็นดิจิทัล Elliptic Curve (ECDSA) เหนือเส้นโค้ง P-256

ดูวิธีดำเนินการดังกล่าวได้ในไลบรารีโหนด Web-push

function generateVAPIDKeys() {
    var curve = crypto.createECDH('prime256v1');
    curve.generateKeys();

    return {
    publicKey: curve.getPublicKey(),
    privateKey: curve.getPrivateKey(),
    };
}

การสมัครใช้บริการด้วยคีย์สาธารณะ

หากต้องการสมัครใช้บริการผู้ใช้ Chrome เพื่อพุชด้วยคีย์สาธารณะ VAPID คุณต้องส่งคีย์สาธารณะเป็น Uint8Array โดยใช้พารามิเตอร์ applicationServerKey ของเมธอด subscription()

const publicKey = new Uint8Array([0x4, 0x37, 0x77, 0xfe, …. ]);
serviceWorkerRegistration.pushManager.subscribe(
    {
    userVisibleOnly: true,
    applicationServerKey: publicKey
    }
);

คุณจะทราบว่าเวอร์ชันใช้งานได้โดยการตรวจสอบปลายทางในออบเจ็กต์การสมัครใช้บริการที่เป็นผลลัพธ์ หากต้นทางคือ fcm.googleapis.com แสดงว่าใช้งานได้

https://fcm.googleapis.com/fcm/send/ABCD1234

การส่งข้อความพุช

หากต้องการส่งข้อความโดยใช้ VAPID คุณต้องส่งคำขอโปรโตคอล Web Push แบบปกติที่มีส่วนหัว HTTP เพิ่มเติม 2 รายการ ได้แก่ ส่วนหัวการให้สิทธิ์และส่วนหัว Crypto-Key

ส่วนหัวการให้สิทธิ์

ส่วนหัว Authorization คือ JSON Web Token (JWT) ที่ลงนามโดยมี "WebPush" อยู่ด้านหน้า

JWT คือวิธีการแชร์ออบเจ็กต์ JSON กับบุคคลที่ 2 ในลักษณะที่ฝ่ายที่ส่งสามารถลงนามได้ และฝ่ายที่รับสามารถยืนยันได้ว่าลายเซ็นนั้นมาจากผู้ส่งที่คาดไว้ โครงสร้างของ JWT คือสตริงที่เข้ารหัส 3 สตริงและมีจุด 1 จุดคั่นระหว่างสตริง

<JWTHeader>.<Payload>.<Signature>

ส่วนหัว JWT

ส่วนหัว JWT มีชื่ออัลกอริทึมที่ใช้สำหรับการลงชื่อและประเภทของโทเค็น สำหรับ VAPID ค่านี้ต้องมีลักษณะดังนี้

{
    "typ": "JWT",
    "alg": "ES256"
}

จากนั้นจะเป็น URL แบบ Base64 ที่เข้ารหัสและกลายเป็นส่วนแรกของ JWT

เพย์โหลด

เพย์โหลดเป็นออบเจ็กต์ JSON อีกรายการที่มีข้อมูลต่อไปนี้

  • กลุ่มเป้าหมาย ("aud")
    • แหล่งที่มาของบริการพุช (ไม่ใช่ต้นทางของเว็บไซต์) ใน JavaScript คุณสามารถดำเนินการต่อไปนี้เพื่อให้ได้กลุ่มเป้าหมาย const audience = new URL(subscription.endpoint).origin
  • เวลาหมดอายุ ("exp")
    • ซึ่งเป็นจำนวนวินาทีที่ระบบจะพิจารณาว่าคำขอหมดอายุ ซึ่งต้องอยู่ภายใน 24 ชั่วโมงหลังจากส่งคำขอใน UTC
  • เรื่อง ("sub")
    • หัวเรื่องต้องเป็น URL หรือ URL mailto: ซึ่งจะระบุผู้ติดต่อในกรณีที่บริการพุชจำเป็นต้องติดต่อผู้ส่งข้อความ

ตัวอย่างเพย์โหลดอาจมีลักษณะดังนี้

{
    "aud": "http://push-service.example.com",
    "exp": Math.floor((Date.now() / 1000) + (12 * 60 * 60)),
    "sub": "mailto: my-email@some-url.com"
}

ออบเจ็กต์ JSON นี้เข้ารหัส URL base64 และสร้างส่วนที่สองของ JWT

ลายเซ็น

ลายเซ็นเกิดจากการรวมส่วนหัวและเพย์โหลดที่เข้ารหัสโดยมีจุด จากนั้นเข้ารหัสผลลัพธ์โดยใช้คีย์ส่วนตัว VAPID ที่คุณสร้างไว้ก่อนหน้านี้ ผลลัพธ์ควรจะมีจุดต่อท้ายส่วนหัว

เราจะไม่แสดงตัวอย่างโค้ดในเรื่องนี้เนื่องจากมีไลบรารีจำนวนหนึ่งที่จะรับค่าส่วนหัวและเพย์โหลดออบเจ็กต์ JSON และสร้างลายเซ็นนี้ให้คุณ

JWT ที่ลงชื่อแล้วจะถูกใช้เป็นส่วนหัว Authorization ที่มี "WebPush" นำหน้า และจะมีลักษณะดังนี้

WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2ZjbS5nb29nbGVhcGlzLmNvbSIsImV4cCI6MTQ2NjY2ODU5NCwic3ViIjoibWFpbHRvOnNpbXBsZS1wdXNoLWRlbW9AZ2F1bnRmYWNlLmNvLnVrIn0.Ec0VR8dtf5qb8Fb5Wk91br-evfho9sZT6jBRuQwxVMFyK5S8bhOjk8kuxvilLqTBmDXJM5l3uVrVOQirSsjq0A

โปรดสังเกต 2-3 อย่างเกี่ยวกับเรื่องนี้ อย่างแรก ส่วนหัวการให้สิทธิ์มีคำว่า "WebPush" และควรตามด้วยช่องว่างตามด้วย JWT นอกจากนี้ให้สังเกตจุดที่คั่นส่วนหัว JWT, เพย์โหลด และลายเซ็น

ส่วนหัว Crypto-Key

นอกเหนือจากส่วนหัวการให้สิทธิ์แล้ว คุณต้องเพิ่มคีย์สาธารณะ VAPID ไปยังส่วนหัว Crypto-Key เป็นสตริงที่เข้ารหัส URL ฐาน 64 โดยมี p256ecdsa= นำหน้า

p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaNndIo

เมื่อส่งการแจ้งเตือนที่มีข้อมูลที่เข้ารหัส คุณจะใช้ส่วนหัว Crypto-Key อยู่แล้ว ดังนั้นในการเพิ่มคีย์แอปพลิเคชันเซิร์ฟเวอร์ ก็แค่เพิ่มเครื่องหมายเซมิโคลอนก่อนเพิ่มเนื้อหาข้างต้น ซึ่งจะส่งผลให้เกิดสิ่งต่อไปนี้

dh=BGEw2wsHgLwzerjvnMTkbKrFRxdmwJ5S_k7zi7A1coR_sVjHmGrlvzYpAT1n4NPbioFlQkIrTNL8EH4V3ZZ4vJE;
p256ecdsa=BDd3_hVL9fZi9Ybo2UUzA284WG5FZR30_95YeZJsiApwXKpNcF1rRPF3foIiBHXRdJI2Qhumhf6_LFTeZaN

ความเป็นจริงของการเปลี่ยนแปลงเหล่านี้

เมื่อใช้ VAPID คุณจะไม่จำเป็นต้องลงชื่อสมัครใช้บัญชีกับ GCM เพื่อใช้ข้อความ Push ใน Chrome และสามารถใช้เส้นทางโค้ดเดียวกันในการสมัครรับข้อมูลให้กับผู้ใช้และส่งข้อความถึงผู้ใช้ทั้งใน Chrome และ Firefox ทั้ง 2 อย่างเป็นไปตามมาตรฐาน

สิ่งที่คุณต้องคำนึงถึงคือใน Chrome 51 และก่อนหน้านี้ Opera สำหรับเบราว์เซอร์ Android และ Samsung คุณยังคงต้องกำหนด gcm_sender_id ในไฟล์ Manifest ของเว็บแอปและคุณจะต้องเพิ่มส่วนหัวการให้สิทธิ์ไปยังปลายทาง FCM ที่จะส่งคืน

โดย VAPID มีข้อจำกัดจากข้อกำหนดที่เป็นกรรมสิทธิ์เหล่านี้ ถ้าคุณนำ VAPID มาใช้ก็จะทำงานได้กับทุกเบราว์เซอร์ที่รองรับพุชจากเว็บ เนื่องจากมีเบราว์เซอร์ที่รองรับ VAPID มากขึ้น คุณจึงตัดสินใจได้ว่าควรนำ gcm_sender_id ออกจากไฟล์ Manifest เมื่อใด