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
400
fukuoka.ts #3 社内でESLintの共通設定を配りたい2025年春版
pirosikick
3
470
compilerOptions、全部読んだ
pirosikick
1
290
Step Functionsの設計時に知っておいたほうがいいかもしれないこと
pirosikick
0
520
Go言語による並行処理「4.4 orチャネル」の図
pirosikick
0
470
サイボウズWebフロントエンド脱レガシーの今までとこれから
pirosikick
6
17k
@cybozu/eslint-configから学ぶ、全社共通ESLint configの運用
pirosikick
4
1.9k
Web Share Target API #w3fukuoka
pirosikick
0
730
Google I/O '19のWebをまとめる会
pirosikick
2
890
Other Decks in Programming
See All in Programming
プロパティの順序で型推論が壊れる!? TypeScript6.0の修正からContext-Sensitivityの仕組みを追う
bicstone
2
1.3k
net-httpのHTTP/2対応について
naruse
0
450
AIとRubyの静的型付け
ukin0k0
0
550
AIエージェントと協働するCLI開発 — BunとOpenClawで学んだこと
yoshikouki
1
240
Technical Debt: Understanding it Rightly, Engaging it Rightly #LaravelLiveJP
shogogg
0
200
Webフレームワークの ベンチマークについて
yusukebe
0
150
Inside Stream API
skrb
1
660
代数的データ型って何が嬉しいの? #frontend_phpcon_do
kajitack
8
3.3k
AIチームを指揮するOSS「TAKT」活用術 / How to Use “TAKT,” an OSS Tool for Orchestrating AI Teams
nrslib
6
850
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
110
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
320
Dataformのリポジトリを立ち上げるときにまずやること / dataform-day0-2026
snhryt
0
120
Featured
See All Featured
Paper Plane
katiecoart
PRO
1
51k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
720
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
230
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
940
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.5k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
300
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
The browser strikes back
jonoalderson
0
1.2k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
200
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Mobile First: as difficult as doing things right
swwweet
225
10k
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
ऴ