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/

Dc52871050369929247b69f3767e8737?s=128

Hiroyuki ANAI

March 29, 2018
Tweet

Transcript

  1. !QJSPTJLJDL ෱Ԭ18"ษڧձ

  2. w ݀Ҫ޺޾ w IUUQTUXJUUFSDPNQJSPTJLJDL w ϠϑʔגࣜձࣾɹΤϯδχΞ w ϦονϥϘגࣜձࣾɹΤϯδχΞ w Ϡϑʔͷࢠձࣾ

    w +BWB4DSJQUͱ͔͕޷͖Ͱ͢ !2
  3. 13 w 3FBDUೖ໳ͱ͍͏ຊΛ
 ॻ͖·ͨ͠ w 3FEVYʹ͍ͭͯ
 ͪΌΜͱॻ͍͍ͯΔຊ͸
 ͓ͦΒ͘͜Ε͚ͩ w Α͔ͬͨΒങ͍ͬͯͩ͘͞

     
  4. 13 w 8&# %#13&44Ͱ
 ʮͲΜͱ͍͜ϑϩϯτΤϯυ ։ൃʯͱ͍͏࿈ࡌΛ΍͍ͬͯ ·͢ w Α͔ͬͨΒಡΜͰ͍ͩ͘͞ʂ 

    
  5. ΑΖ͓͘͠ئ͍͠·͢ !

  6. 8FC1VTIΛ ʢͪΐͬͱʣৄ͘͠આ໌͢Δ

  7. ࿩͢͜ͱ w 8&# %#13&44WPMͰ
 18"ʹؔ͢ΔهࣄΛॻ͍ͨ w จྔͷؔ܎Ͱɺ
 ୲౰ͨ͠8FC1VTIͷ෦෼Λ
 ͔ͳΓ࡟ͬͨ w

    ΋͏ͪΐͬͱ͚ͩৄ͘͠
 ղઆ͍ͤͯͩ͘͞͞  
  8. 8FC1VTI w 8FCͰϓογϡ͕ग़དྷΔ w ϒϥ΢βଆͷ"1*͸8$͕ɺ
 ϓογϡ͢Δଆͷϓϩτίϧ͸*&5'͕ඪ४Խ͍ͯ͠Δ w 18"$IFDLMJTUʹ͸ϓογϡ௨஌ͷ߲໨͕͋Γɺ
 8FC1VTI΋18"ͷҰ෦ͱݴͬͯ΋໰୊ͳ͍ʂʂʂʂ
 ʢ18"ͬΆ͘ͳ͍ൃදͰେมਃ͠༁ͳ͍"ʣ

    !8
  9. ϒϥ΢βଆͷ"1*

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

    w ϓογϡͷड৴ w TFMGBEE&WFOU-JTUFOFS QVTI ʜ  !10
  11. 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
  12. 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
  13. 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
  14. ϓογϡΛૹ৴͢Δ ॲཧ

  15. w XFCQVTIMJCTʹϥΠϒϥϦ͕͋Δ w IUUQTHJUIVCDPNXFCQVTIMJCT w ࠓճ͸/PEFKT൛Ͱ࣮૷ w IUUQTHJUIVCDPNXFCQVTIMJCTXFCQVTI w OQNJOTUBMMTBWFXFCQVTI


    03
 ZBSOBEEXFCQVTI !15
  16. 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:pirosikick@gmail.com", 20 // 生成した鍵ペア 21 vapidKeys.publicKey, 22 vapidKeys.privateKey 23 ); !16
  17. 1 // 送信するデータ 2 const message = { … }

    3 4 // メッセージを送信 5 webpush.sendNotification( 6 // ブラウザから受け取ったPushSubscription 7 pushSubscription, 8 // 文字列 9 JSON.stringify(message) 10 ); !17
  18. σϞ

  19. ΋͏ͪΐͬͱৄ͘͠

  20. ͬ͘͟Γͱͨ͠શମ૾ 1VTI4FSWJDF 4FSWFS TVCTDSJCF 1VTI4VCTDSJQUJPO 1VTI4VCTDSJQUJPO 4FOENFTTBHF 4FOENFTTBHF "QQ !20

  21. αʔόʔͷೝূ 1VTI4FSWJDF ਖ਼͍͠4FSWFS ѱ͍4FSWFS "QQ 4FOENFTTBHF 4FOENFTTBHF 0,# /($ !21

  22. αʔόʔͷೝূ 1VTI4FSWJDF ਖ਼͍͠4FSWFS ॺ໊Λ෇༩ ॺ໊Ͱ ਖ਼͍͠4FSWFS͔Βͷ ϝοηʔδͰ͋Δ͜ͱΛ֬ೝ "QQ 0,# !22

  23. αʔόʔͷೝূ 1VTI4FSWJDF ਖ਼͍͠4FSWFS ॺ໊Λ෇༩ ॺ໊Ͱ ਖ਼͍͠4FSWFS͔Βͷ ϝοηʔδͰ͋Δ͜ͱΛ֬ೝ "QQ 0,# 7"1*%

    !23
  24. 7"1*% w IUUQTUPPMTJFUGPSHIUNMSGD w 7PMVOUBSZ"QQMJDBUJPO4FSWFS*EFOUJpDBUJPO
 GPS8FC1VTI w 8FC1VTIͷαʔόೝূ7"1*%Λࢼͯ͠ΈΔ چ୊($. ͷొ࿥͕ෆཁʹͳͬͨ$ISPNFͷ8FC1VTIΛࢼͯ͠ΈΔ

     2JJUB !24
  25. 1VTI4FSWJDF 4FSWFS "QQ ެ։伴 ൿີ伴 ᶃ伴ϖΞΛੜ੒ ᶄࣄલʹެ։伴Λڞ༗ ᶅTVCTDSJCF࣌ʹ ϓογϡαʔϏεʹެ։伴Λૹ৴ ᶆൿີ伴Ͱॺ໊Λ࡞੒

    +85 +85 ᶇຊจʴॺ໊ɺެ։伴Λૹ৴ ᶈೝূ͢Δ !25
  26. ᶃ伴ϖΞΛੜ੒ !26

  27. 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
  28. ᶄࣄલʹެ։伴Λڞ༗ ᶅTVCTDSJCF࣌ʹ ɹϓογϡαʔϏεʹެ։伴Λૹ৴ !28

  29. 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
  30. 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
  31. ϖΠϩʔυͷ҉߸Խ 1VTI4FSWJDF ਖ਼͍͠4FSWFS "QQ ҉߸Խ ෮߸ ҉߸Խ͞Ε͍ͯΔͷͰɺ 1VTI4FSWJDF͸த਎ΛݟΕͳ͍ !31

  32. ͳͥ҉߸Խ͕ඞཁ͔ʁ w 8FC1VTI͸ΞϓϦέʔγϣϯ͕ϓογϡαʔϏεΛ
 ҙࣝ͠ͳ͍ͰΑ͍ઃܭʹͳ͍ͬͯΔ w ϓογϡαʔϏεͷ"1*͕ಉ͡ͳͷͰ w ൓໘ɺ΋͔ͨ͠͠Β৴པͰ͖ͳ͍ϓογϡαʔϏεʹ
 σʔλΛૹ৴͍ͯ͠Δ͔΋͠Εͳ͍ w

    ΞϓϦͷΫϥΠΞϯτɾαʔόҎ֎͸
 ϝοηʔδ͕ಡΊͳ͍Α͏ʹ͢΂͖ !32
  33. ҉߸Խʹؔ͢Δ࢓༷ w .FTTBHF&ODSZQUJPOGPS8FC1VTI
 IUUQTUPPMTJFUGPSHIUNMSGD w 3'$ˣΛ࢖ͬͯ҉߸Խ͢ΔखॱͳͲ w &ODSZQUFE$POUFOU&ODPEJOHGPS)551
 IUUQTUPPMTJFUGPSHIUNMSGD w

    )551্ͷϖΠϩʔυͷ҉߸Խɺ伴ަ׵ͳͲ !33
  34. ΋ͬͱৄ͘͠ w 5IF8FC1VTI1SPUPDPMc(PPHMF%FWFMPQFST
 IUUQTEFWFMPQFSTHPPHMFDPNXFCGVOEBNFOUBMT QVTIOPUJpDBUJPOTXFCQVTIQSPUPDPM w ӳޠ͕ͩίʔυ෇͖ͰΠϝʔδ͠΍͍͢ w <վగ൛>8FC1VTIͰϒϥ΢βʹϓογϡ௨஌ΛૹͬͯΈ Δ2JJUB

    w ࢓༷ͷߋ৽ʹ΋௥ै͓ͯ͠Γɺ௒ྑهࣄ !34
  35. ͓ΘΓʹ

  36. ϓϩτίϧʹ͍ͭͯ w ϓϩτίϧ͕γϯϓϧˍϥΠϒϥϦ͕͋ΔͷͰɺ
 1BB4ͳͲʹཔΒͣͱ΋ࣗલͰͳΜͱ͔ͳΔ w ·ͩ࢓༷͕มΘΔՄೳੑ͕͋Γͦ͏ͳͷͰɺ
 ࢓༷΍ϒϥ΢βʔͷ࣮૷ঢ়گΛ௥͍ͳ͕Βʹ͸ͳΔ !36

  37. ࢓༷ΛͪΌΜͱௐ΂ͯΈͯ w ϓϩτίϧ͕γϯϓϧͳͷͰɺ
 ؤுΕ͹શ෦ͷ࢓༷ॻΛಡΊΔ
 ʢ࣮ࡍʹ̍िؒҐͰ͘Β͍͸໨Λ௨ͤͨʣ w ࢓༷ॻΛಡΉͱʮಡΜͩͧʯͱ͍͏ؾ࣋ͪʹͳΓɺ
 ʢ໾ʹཱ͔ͭͱݴΘΕΔͱඍົ͕ͩʣṖͷຬ଍ײ͕͋Δ w ͕ɺ҉߸Խɾ伴पΓ͸ௐ΂࢝ΊΔͱ


    ͲΜͲΜΘ͔Βͳ͍͜ͱ͕ग़ͯ͘ΔͷͰ
 ·ͱ·͕ͬͨ࣌ؒඞཁ͔΋͠Εͳ͍ !37