Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Web Pushを(ちょっと)詳しく解説する #fukuokapwa
Search
Hiroyuki ANAI
March 29, 2018
Programming
690
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Web Pushを(ちょっと)詳しく解説する #fukuokapwa
福岡PWA勉強会 #01 - connpass
https://fukuoka-pwa.connpass.com/event/81842/
Hiroyuki ANAI
March 29, 2018
More Decks by Hiroyuki ANAI
See All by Hiroyuki ANAI
書き換えて学ぶTemporal #fukts
pirosikick
2
410
fukuoka.ts #3 社内でESLintの共通設定を配りたい2025年春版
pirosikick
3
480
compilerOptions、全部読んだ
pirosikick
1
300
Step Functionsの設計時に知っておいたほうがいいかもしれないこと
pirosikick
0
530
Go言語による並行処理「4.4 orチャネル」の図
pirosikick
0
480
サイボウズWebフロントエンド脱レガシーの今までとこれから
pirosikick
6
17k
@cybozu/eslint-configから学ぶ、全社共通ESLint configの運用
pirosikick
4
1.9k
Web Share Target API #w3fukuoka
pirosikick
0
740
Google I/O '19のWebをまとめる会
pirosikick
2
890
Other Decks in Programming
See All in Programming
そのテスト、説明できますか?~LWテスト戦略FW~のご紹介
nakahara
0
190
Haskell/Servantを通してWebミドルウェアを捉え直す
pizzacat83
0
320
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.7k
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
14
6.6k
分散システム、なんですぐ死んでしまうん?耐障害性を高めたいあなたのためのレジリエンスパターン入門
mshibuya
7
1.9k
AI 輔助遺留系統現代化的經驗分享
jame2408
1
1.2k
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
170
AIで効率化できた業務・日常
ochtum
0
160
LLM本来の能力を解き放つサンドボックス技術とAI民主化への適用
yukukotani
3
4.8k
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
12
4.6k
トークンをケチるな、設計しろ:GitHub Copilotを賢く使うコンテキスト戦略
ochtum
0
280
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
110
Featured
See All Featured
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
220
Amusing Abliteration
ianozsvald
1
220
Abbi's Birthday
coloredviolet
3
8.5k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6k
Everyday Curiosity
cassininazir
0
250
Statistics for Hackers
jakevdp
799
230k
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.7k
Being A Developer After 40
akosma
91
590k
How STYLIGHT went responsive
nonsquared
100
6.2k
Designing for Timeless Needs
cassininazir
1
270
Transcript
!QJSPTJLJDL Ԭ18"ษڧձ
w ݀Ҫ w IUUQTUXJUUFSDPNQJSPTJLJDL w ϠϑʔגࣜձࣾɹΤϯδχΞ w ϦονϥϘגࣜձࣾɹΤϯδχΞ w Ϡϑʔͷࢠձࣾ
w +BWB4DSJQUͱ͔͕͖Ͱ͢ !2
13 w 3FBDUೖͱ͍͏ຊΛ ॻ͖·ͨ͠ w 3FEVYʹ͍ͭͯ ͪΌΜͱॻ͍͍ͯΔຊ ͓ͦΒ͘͜Ε͚ͩ w Α͔ͬͨΒങ͍ͬͯͩ͘͞
13 w 8&# %#13&44Ͱ ʮͲΜͱ͍͜ϑϩϯτΤϯυ ։ൃʯͱ͍͏࿈ࡌΛ͍ͬͯ ·͢ w Α͔ͬͨΒಡΜͰ͍ͩ͘͞ʂ
ΑΖ͓͘͠ئ͍͠·͢ !
8FC1VTIΛ ʢͪΐͬͱʣৄ͘͠આ໌͢Δ
͢͜ͱ w 8&# %#13&44WPMͰ 18"ʹؔ͢ΔهࣄΛॻ͍ͨ w จྔͷؔͰɺ ୲ͨ͠8FC1VTIͷ෦Λ ͔ͳΓͬͨ w
͏ͪΐͬͱ͚ͩৄ͘͠ ղઆ͍ͤͯͩ͘͞͞
8FC1VTI w 8FCͰϓογϡ͕ग़དྷΔ w ϒϥβଆͷ"1*8$͕ɺ ϓογϡ͢Δଆͷϓϩτίϧ*&5'͕ඪ४Խ͍ͯ͠Δ w 18"$IFDLMJTUʹϓογϡ௨ͷ߲͕͋Γɺ 8FC1VTI18"ͷҰ෦ͱݴͬͯͳ͍ʂʂʂʂ ʢ18"ͬΆ͘ͳ͍ൃදͰେมਃ͠༁ͳ͍"ʣ
!8
ϒϥβଆͷ"1*
w TDSJQU w ߪಡͷ։࢝ w TFSWJDF8PSLFS3FHJTUSBUJPOTVCTDSJCF \ʜ^ w 4FSWJDF8PSLFS
w ϓογϡͷड৴ w TFMGBEE&WFOU-JTUFOFS QVTI ʜ !10
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
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
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
ϓογϡΛૹ৴͢Δ ॲཧ
w XFCQVTIMJCTʹϥΠϒϥϦ͕͋Δ w IUUQTHJUIVCDPNXFCQVTIMJCT w ࠓճ/PEFKT൛Ͱ࣮ w IUUQTHJUIVCDPNXFCQVTIMJCTXFCQVTI w OQNJOTUBMMTBWFXFCQVTI
03 ZBSOBEEXFCQVTI !15
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
1 // 送信するデータ 2 const message = { … }
3 4 // メッセージを送信 5 webpush.sendNotification( 6 // ブラウザから受け取ったPushSubscription 7 pushSubscription, 8 // 文字列 9 JSON.stringify(message) 10 ); !17
σϞ
͏ͪΐͬͱৄ͘͠
ͬ͘͟Γͱͨ͠શମ૾ 1VTI4FSWJDF 4FSWFS TVCTDSJCF 1VTI4VCTDSJQUJPO 1VTI4VCTDSJQUJPO 4FOENFTTBHF 4FOENFTTBHF "QQ !20
αʔόʔͷೝূ 1VTI4FSWJDF ਖ਼͍͠4FSWFS ѱ͍4FSWFS "QQ 4FOENFTTBHF 4FOENFTTBHF 0,# /($ !21
αʔόʔͷೝূ 1VTI4FSWJDF ਖ਼͍͠4FSWFS ॺ໊Λ༩ ॺ໊Ͱ ਖ਼͍͠4FSWFS͔Βͷ ϝοηʔδͰ͋Δ͜ͱΛ֬ೝ "QQ 0,# !22
αʔόʔͷೝূ 1VTI4FSWJDF ਖ਼͍͠4FSWFS ॺ໊Λ༩ ॺ໊Ͱ ਖ਼͍͠4FSWFS͔Βͷ ϝοηʔδͰ͋Δ͜ͱΛ֬ೝ "QQ 0,# 7"1*%
!23
7"1*% w IUUQTUPPMTJFUGPSHIUNMSGD w 7PMVOUBSZ"QQMJDBUJPO4FSWFS*EFOUJpDBUJPO GPS8FC1VTI w 8FC1VTIͷαʔόೝূ7"1*%Λࢼͯ͠ΈΔ چ($. ͷొ͕ෆཁʹͳͬͨ$ISPNFͷ8FC1VTIΛࢼͯ͠ΈΔ
2JJUB !24
1VTI4FSWJDF 4FSWFS "QQ ެ։伴 ൿີ伴 ᶃ伴ϖΞΛੜ ᶄࣄલʹެ։伴Λڞ༗ ᶅTVCTDSJCF࣌ʹ ϓογϡαʔϏεʹެ։伴Λૹ৴ ᶆൿີ伴Ͱॺ໊Λ࡞
+85 +85 ᶇຊจʴॺ໊ɺެ։伴Λૹ৴ ᶈೝূ͢Δ !25
ᶃ伴ϖΞΛੜ !26
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
ᶄࣄલʹެ։伴Λڞ༗ ᶅTVCTDSJCF࣌ʹ ɹϓογϡαʔϏεʹެ։伴Λૹ৴ !28
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
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
ϖΠϩʔυͷ҉߸Խ 1VTI4FSWJDF ਖ਼͍͠4FSWFS "QQ ҉߸Խ ෮߸ ҉߸Խ͞Ε͍ͯΔͷͰɺ 1VTI4FSWJDFதΛݟΕͳ͍ !31
ͳͥ҉߸Խ͕ඞཁ͔ʁ w 8FC1VTIΞϓϦέʔγϣϯ͕ϓογϡαʔϏεΛ ҙࣝ͠ͳ͍ͰΑ͍ઃܭʹͳ͍ͬͯΔ w ϓογϡαʔϏεͷ"1*͕ಉ͡ͳͷͰ w ໘ɺ͔ͨ͠͠Β৴པͰ͖ͳ͍ϓογϡαʔϏεʹ σʔλΛૹ৴͍ͯ͠Δ͔͠Εͳ͍ w
ΞϓϦͷΫϥΠΞϯτɾαʔόҎ֎ ϝοηʔδ͕ಡΊͳ͍Α͏ʹ͖͢ !32
҉߸Խʹؔ͢Δ༷ w .FTTBHF&ODSZQUJPOGPS8FC1VTI IUUQTUPPMTJFUGPSHIUNMSGD w 3'$ˣΛͬͯ҉߸Խ͢ΔखॱͳͲ w &ODSZQUFE$POUFOU&ODPEJOHGPS)551 IUUQTUPPMTJFUGPSHIUNMSGD w
)551্ͷϖΠϩʔυͷ҉߸Խɺ伴ަͳͲ !33
ͬͱৄ͘͠ w 5IF8FC1VTI1SPUPDPMc(PPHMF%FWFMPQFST IUUQTEFWFMPQFSTHPPHMFDPNXFCGVOEBNFOUBMT QVTIOPUJpDBUJPOTXFCQVTIQSPUPDPM w ӳޠ͕ͩίʔυ͖ͰΠϝʔδ͍͢͠ w <վగ൛>8FC1VTIͰϒϥβʹϓογϡ௨ΛૹͬͯΈ Δ2JJUB
w ༷ͷߋ৽ʹै͓ͯ͠Γɺྑهࣄ !34
͓ΘΓʹ
ϓϩτίϧʹ͍ͭͯ w ϓϩτίϧ͕γϯϓϧˍϥΠϒϥϦ͕͋ΔͷͰɺ 1BB4ͳͲʹཔΒͣͱࣗલͰͳΜͱ͔ͳΔ w ·༷͕ͩมΘΔՄೳੑ͕͋Γͦ͏ͳͷͰɺ ༷ϒϥβʔͷ࣮ঢ়گΛ͍ͳ͕ΒʹͳΔ !36
༷ΛͪΌΜͱௐͯΈͯ w ϓϩτίϧ͕γϯϓϧͳͷͰɺ ؤுΕશ෦ͷ༷ॻΛಡΊΔ ʢ࣮ࡍʹ̍िؒҐͰ͘Β͍Λ௨ͤͨʣ w ༷ॻΛಡΉͱʮಡΜͩͧʯͱ͍͏ؾ࣋ͪʹͳΓɺ ʢʹཱ͔ͭͱݴΘΕΔͱඍົ͕ͩʣṖͷຬײ͕͋Δ w ͕ɺ҉߸Խɾ伴पΓௐ࢝ΊΔͱ
ͲΜͲΜΘ͔Βͳ͍͜ͱ͕ग़ͯ͘ΔͷͰ ·ͱ·͕ͬͨ࣌ؒඞཁ͔͠Εͳ͍ !37
ऴ