Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Web Pushを(ちょっと)詳しく解説する #fukuokapwa

Web Pushを(ちょっと)詳しく解説する #fukuokapwa

福岡PWA勉強会 #01 - connpass
https://fukuoka-pwa.connpass.com/event/81842/

Hiroyuki ANAI

March 29, 2018
Tweet

More Decks by Hiroyuki ANAI

Other Decks in Programming

Transcript

  1. w TDSJQU w ߪಡͷ։࢝ w TFSWJDF8PSLFS3FHJTUSBUJPOTVCTDSJCF \ʜ^  w 4FSWJDF8PSLFS

    w ϓογϡͷड৴ w TFMGBEE&WFOU-JTUFOFS QVTI ʜ  !10
  2. 1 // <script>でロード・実行されるJavaScript 2 3 // サポート判定 4 if (!('serviceWorker'

    in navigator && 'PushManager' in window)) { 5 console.log('Web push is not supported in this browser'); 6 return; 7 } 8 9 // ServiceWorkerの登録 10 // ServiceWorkerRegistrationを返す 11 const reg 12 = await navigator.serviceWorker.register('./sw.js'); 13 14 // 購読開始 15 // ユーザに許可を求めるダイアログが表示され、 16 // 許可されればPushSubscriptionを返す 17 const pushSubscription = await reg.pushManager.subscribe({ 18 // プッシュサーバーの公開鍵 19 applicationServerKey, 20 21 // "返されたプッシュサブスクリプションの効果が 22 // ユーザーに表示するメッセージにだけ使われるかを示す" 23 // https://mzl.la/2GgFKbs 24 userVisibleOnly: true 25 }); !11
  3. 1 // PushSubscription 2 3 // プッシュサービスのエンドポイントURL 4 pushSubscription.endpoint; 5

    6 // クライアントで生成した鍵情報 7 pushSubscription.getKey('auth'); 8 pushSubscription.getKey('p256dh'); 9 10 // JSON.stringifyでシリアライズ可能 11 console.log(JSON.stringify(pushSubscription)); 12 // => {"endpoint":"…",{"keys":{…}}} 13 14 // プッシュを送信するサーバーにPushSubscriptionを送る 15 fetch('/send-to-push-server', { 16 method: 'POST', 17 headers: { 18 'Content-Type': 'application/json' 19 }, 20 body: JSON.stringify(pushSubscription) 21 }); !12
  4. 1 // ServiceWorker側のコード 2 3 // pushイベント: プッシュを受信した時に発火 4 self.addEventListener('push',

    event => { 5 // プッシュされたデータの取得 6 event.data.text(); 7 event.data.json(); 8 9 // 通知の表示 10 return event.waitUntil( 11 self.registration.showNotification( 12 'タイトル', 13 { 14 body: '本文', 15 icon: 'アイコン画像URL', 16 … 17 } 18 ) 19 ); 20 }); !13
  5. 1 // Node.js 2 const webpush = require('web-push'); 3 4

    // 鍵を生成(最初の一度だけでよい) 5 const vapidKeys = webpush.generateVAPIDKeys(); 6 7 // base64url形式の文字列 8 // 公開鍵 9 // ブラウザ側のsubscribe関数呼出し時に渡す(applicationServerKey) 10 vapidKeys.publicKey; 11 // 秘密鍵 12 vapidKeys.privatekey; 13 14 // 設定 15 webpush.setVapidDetails( 16 // subject 17 // プッシュサービスが送信者に連絡を取る時に使う 18 // URLかメールアドレス 19 "mailto:[email protected]", 20 // 生成した鍵ペア 21 vapidKeys.publicKey, 22 vapidKeys.privateKey 23 ); !16
  6. 1 // 送信するデータ 2 const message = { … }

    3 4 // メッセージを送信 5 webpush.sendNotification( 6 // ブラウザから受け取ったPushSubscription 7 pushSubscription, 8 // 文字列 9 JSON.stringify(message) 10 ); !17
  7. 1 // webpush.generateVAPIDKeysの中身 2 function generateVAPIDKeys() { 3 // crptoモジュールでECDHの鍵ペアを生成

    4 const curve = crypto.createECDH('prime256v1'); 5 curve.generateKeys(); 6 7 let publicKeyBuffer = curve.getPublicKey(); 8 let privateKeyBuffer = curve.getPrivateKey(); 9 10 // Bufferをbase64url形式に変換する処理など 11 … 12 } 13 !27
  8. 1 // web-push/src/vapid-helper.js 2 3 function getVapidHeaders(audience, subject, publicKey, privateKey,

    …) { 4 // …省略… 5 6 const header = { 7 typ: 'JWT', 8 alg: 'ES256' 9 }; 10 11 const jwtPayload = { 12 aud: audience, 13 exp: expiration, 14 sub: subject 15 }; 16 17 // 署名:JWT(JSON Web Token)を生成 18 const jwt = jws.sign({ 19 header: header, 20 payload: jwtPayload, 21 // 生成した秘密鍵で署名 22 privateKey: toPEM(privateKey) 23 }); 24 ᶆൿີ伴Ͱॺ໊Λ࡞੒ !29
  9. 1 // web-push/src/vapid-helper.js 2 3 function getVapidHeaders(audience, subject, publicKey, privateKey,

    …) { 4 // …省略… 5 6 if (/* Content-Encodingがaes128gcmの場合 */) { 7 return { 8 Authorization: 'vapid t=' + jwt + ', k=' + urlBase64.encode(publicKey) 9 }; 10 } else if (/* Content-Encodingがaes128の場合 */) { 11 return { 12 Authorization: 'WebPush ' + jwt, 13 'Crypto-Key': 'p256ecdsa=' + urlBase64.encode(publicKey) 14 }; 15 } 16 17 throw new Error(…); 18 } 19 ᶇຊจʴॺ໊ɺެ։伴Λૹ৴ ˞ࡉ͔͍ॲཧ͸ஔ͍͓͍ͯͯɺ ɹIFBEFSʹॺ໊ɺެ։伴ΛؚΊͯૹ৴͢Δͱ͍͏͜ͱ͕ݴ͍͔ͨͬͨ !30