Slide 1

Slide 1 text

!QJSPTJLJDL ෱Ԭ18"ษڧձ

Slide 2

Slide 2 text

w ݀Ҫ޺޾ w IUUQTUXJUUFSDPNQJSPTJLJDL w ϠϑʔגࣜձࣾɹΤϯδχΞ w ϦονϥϘגࣜձࣾɹΤϯδχΞ w Ϡϑʔͷࢠձࣾ w +BWB4DSJQUͱ͔͕޷͖Ͱ͢ !2

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

ΑΖ͓͘͠ئ͍͠·͢ !

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

࿩͢͜ͱ w 8%#13&44WPMͰ
 18"ʹؔ͢ΔهࣄΛॻ͍ͨ w จྔͷؔ܎Ͱɺ
 ୲౰ͨ͠8FC1VTIͷ෦෼Λ
 ͔ͳΓ࡟ͬͨ w ΋͏ͪΐͬͱ͚ͩৄ͘͠
 ղઆ͍ͤͯͩ͘͞͞

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

ϒϥ΢βଆͷ"1*

Slide 10

Slide 10 text

w TDSJQU w ߪಡͷ։࢝ w TFSWJDF8PSLFS3FHJTUSBUJPOTVCTDSJCF \ʜ^ w 4FSWJDF8PSLFS w ϓογϡͷड৴ w TFMGBEE&WFOU-JTUFOFS QVTI ʜ !10

Slide 11

Slide 11 text

1 // でロード・実行される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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

ϓογϡΛૹ৴͢Δ ॲཧ

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

1 // 送信するデータ 2 const message = { … } 3 4 // メッセージを送信 5 webpush.sendNotification( 6 // ブラウザから受け取ったPushSubscription 7 pushSubscription, 8 // 文字列 9 JSON.stringify(message) 10 ); !17

Slide 18

Slide 18 text

σϞ

Slide 19

Slide 19 text

΋͏ͪΐͬͱৄ͘͠

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

1VTI4FSWJDF 4FSWFS "QQ ެ։伴 ൿີ伴 ᶃ伴ϖΞΛੜ੒ ᶄࣄલʹެ։伴Λڞ༗ ᶅTVCTDSJCF࣌ʹ ϓογϡαʔϏεʹެ։伴Λૹ৴ ᶆൿີ伴Ͱॺ໊Λ࡞੒ +85 +85 ᶇຊจʴॺ໊ɺެ։伴Λૹ৴ ᶈೝূ͢Δ !25

Slide 26

Slide 26 text

ᶃ伴ϖΞΛੜ੒ !26

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

ᶄࣄલʹެ։伴Λڞ༗ ᶅTVCTDSJCF࣌ʹ ɹϓογϡαʔϏεʹެ։伴Λૹ৴ !28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

ϖΠϩʔυͷ҉߸Խ 1VTI4FSWJDF ਖ਼͍͠4FSWFS "QQ ҉߸Խ ෮߸ ҉߸Խ͞Ε͍ͯΔͷͰɺ 1VTI4FSWJDF͸த਎ΛݟΕͳ͍ !31

Slide 32

Slide 32 text

ͳͥ҉߸Խ͕ඞཁ͔ʁ w 8FC1VTI͸ΞϓϦέʔγϣϯ͕ϓογϡαʔϏεΛ
 ҙࣝ͠ͳ͍ͰΑ͍ઃܭʹͳ͍ͬͯΔ w ϓογϡαʔϏεͷ"1*͕ಉ͡ͳͷͰ w ൓໘ɺ΋͔ͨ͠͠Β৴པͰ͖ͳ͍ϓογϡαʔϏεʹ
 σʔλΛૹ৴͍ͯ͠Δ͔΋͠Εͳ͍ w ΞϓϦͷΫϥΠΞϯτɾαʔόҎ֎͸
 ϝοηʔδ͕ಡΊͳ͍Α͏ʹ͢΂͖ !32

Slide 33

Slide 33 text

҉߸Խʹؔ͢Δ࢓༷ w .FTTBHF&ODSZQUJPOGPS8FC1VTI
 IUUQTUPPMTJFUGPSHIUNMSGD w 3'$ˣΛ࢖ͬͯ҉߸Խ͢ΔखॱͳͲ w &ODSZQUFE$POUFOU&ODPEJOHGPS)551
 IUUQTUPPMTJFUGPSHIUNMSGD w )551্ͷϖΠϩʔυͷ҉߸Խɺ伴ަ׵ͳͲ !33

Slide 34

Slide 34 text

΋ͬͱৄ͘͠ w 5IF8FC1VTI1SPUPDPMc(PPHMF%FWFMPQFST
 IUUQTEFWFMPQFSTHPPHMFDPNXFCGVOEBNFOUBMT QVTIOPUJpDBUJPOTXFCQVTIQSPUPDPM w ӳޠ͕ͩίʔυ෇͖ͰΠϝʔδ͠΍͍͢ w <վగ൛>8FC1VTIͰϒϥ΢βʹϓογϡ௨஌ΛૹͬͯΈ Δ2JJUB w ࢓༷ͷߋ৽ʹ΋௥ै͓ͯ͠Γɺ௒ྑهࣄ !34

Slide 35

Slide 35 text

͓ΘΓʹ

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text