{"id":13248,"date":"2024-10-08T09:40:25","date_gmt":"2024-10-08T01:40:25","guid":{"rendered":"https:\/\/blog.hoyo.idv.tw\/?p=13248"},"modified":"2024-12-18T14:27:24","modified_gmt":"2024-12-18T06:27:24","slug":"pwa-%e6%bc%b8%e9%80%b2%e5%bc%8f%e7%b6%b2%e8%b7%af%e6%87%89%e7%94%a8%e7%a8%8b%e5%bc%8f-2-%e4%bd%bf%e7%94%a8-fcm-%e6%8e%a8%e6%92%ad%e9%80%9a%e7%9f%a5","status":"publish","type":"post","link":"https:\/\/blog.hoyo.idv.tw\/?p=13248","title":{"rendered":"PWA \u6f38\u9032\u5f0f\u7db2\u8def\u61c9\u7528\u7a0b\u5f0f - 2. \u4f7f\u7528 FCM \u63a8\u64ad\u901a\u77e5"},"content":{"rendered":"<p>--<\/p>\n<h2>\u524d\u8a00<\/h2>\n<p>PWA \u4f5c\u70ba\u7db2\u7ad9\u904e\u5ea6\u5230 App \u7684\u61c9\u7528\uff0c\u7184\u5c4f\u3001\u7d05\u9ede\u901a\u77e5\u3001\u63a8\u64ad\u9ede\u9078\u9032\u5165\u76f8\u5c0d\u61c9\u529f\u80fd\u4e5f\u90fd\u53ef\u4ee5\u6b63\u5e38\u4f7f\u7528\uff0c\u5982\u679c\u4e0d\u662f\u81ea\u5df1\u5b89\u88dd\u548c App \u4f7f\u7528\u9ad4\u9a57\u6c92\u6709\u4e0d\u540c\u3002<\/p>\n<p>--<\/p>\n<h2>\u652f\u63f4\u6027<\/h2>\n<ul>\n<li><a href=\"https:\/\/iosref.com\/ios#iphone\" target=\"_blank\" rel=\"noopener\">iOS version by device \u2014 iOS Ref<\/a><\/li>\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Notification\" target=\"_blank\" rel=\"noopener\">Notification - Web APIs | MDN<\/a><\/li>\n<\/ul>\n<p>iOS \u5fc5\u9808\u8981 16.4 \u4ee5\u4e0a\u7684\u7248\u672c\uff0c\u4e5f\u5c31\u662f iPhone 14 \u4ee5\u4e0b\u90fd\u5fc5\u9808\u5347\u7d1a\u7cfb\u7d71\u624d\u53ef\u4ee5\u652f\u63f4<\/p>\n<p><img loading=\"lazy\" class=\"alignnone size-full wp-image-13705\" src=\"https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-27-08.png\" alt=\"\" width=\"767\" height=\"397\" srcset=\"https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-27-08.png 767w, https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-27-08-300x155.png 300w, https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-27-08-600x311.png 600w\" sizes=\"(max-width: 767px) 100vw, 767px\" \/><\/p>\n<p><img loading=\"lazy\" class=\"alignnone size-full wp-image-13704\" src=\"https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-25-06.png\" alt=\"\" width=\"878\" height=\"368\" srcset=\"https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-25-06.png 878w, https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-25-06-300x126.png 300w, https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-25-06-600x251.png 600w, https:\/\/blog.hoyo.idv.tw\/wp-content\/uploads\/2024\/10\/2024-12-18-14-25-06-768x322.png 768w\" sizes=\"(max-width: 878px) 100vw, 878px\" \/><\/p>\n<p>--<\/p>\n<h2>\u53c3\u8003\u8cc7\u6e90<\/h2>\n<ul>\n<li><a href=\"https:\/\/ithelp.ithome.com.tw\/articles\/10288738\" target=\"_blank\" rel=\"noopener\">\u4f60\u77e5\u9053\u9019\u662f\u4ec0\u9ebc\u55ce\uff1f Chrome extension MV3 With Vite - Day8 Service Worker \u8a02\u95b1\u63a8\u64ad - iT \u90a6\u5e6b\u5fd9<\/a><\/li>\n<li><a href=\"https:\/\/ithelp.ithome.com.tw\/articles\/10196898\" target=\"_blank\" rel=\"noopener\">Day27-Push Notification\u4e4b\u6210\u70ba\u8a02\u95b1\u7528\u6236(Firebase\u5be6\u4f5c) - iT \u90a6\u5e6b\u5fd9<\/a><\/li>\n<li><a href=\"https:\/\/ithelp.ithome.com.tw\/articles\/10197056\" target=\"_blank\" rel=\"noopener\">Day28-Push Notification\u4f3a\u670d\u5668\u63a8\u64ad\u8a0a\u606f\u5be6\u4f5c - iT \u90a6\u5e6b\u5fd9<\/a><\/li>\n<\/ul>\n<p>--<\/p>\n<h2>\u7db2\u9801\u8a3b\u518a FCM \u53d6\u5f97\u8eab\u4efd<\/h2>\n<pre class=\"lang:js decode:true\">\/\/ \u7cfb\u7d71\u662f\u5426\u652f\u63f4\r\nif (!(\"Notification\" in window)) {\r\n    console.log(\"\u4e0d\u652f\u63f4\u63a8\u9001\u901a\u77e5\");\r\n}\r\n\r\n\/\/ service Worker\r\nfunction urlB64ToUint8Array(base64String) {\r\n    const padding = '='.repeat((4 - base64String.length % 4) % 4);\r\n    const base64 = (base64String + padding)\r\n        .replace(\/\\-\/g, '+')\r\n        .replace(\/_\/g, '\/');\r\n\r\n    const rawData = window.atob(base64);\r\n    const outputArray = new Uint8Array(rawData.length);\r\n\r\n    for (let i = 0; i &lt; rawData.length; ++i) {\r\n        outputArray[i] = rawData.charCodeAt(i);\r\n    }\r\n    return outputArray;\r\n}\r\n\r\n\/\/ \u63a5\u6536\u767c\u9001\u7684\u8eab\u4efd\r\nconst applicationServerPublicKey = 'BGQsM1ZSgzMCvBeHEhhcwrBALSsRvFOyK6ErMYZmJoXHiIdJVwuffhTa5HUnmaTiVs6jWLUnUA-x05JVn2uKi38';\r\nconst applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);\r\n\r\n\/\/ \u8a3b\u518a sw.js\r\nfunction serviceWorker_register(){\r\n    if ('serviceWorker' in navigator) {\r\n        navigator.serviceWorker\r\n            .register('.\/sw.js')\r\n            .then(reg =&gt; {\r\n                return reg.pushManager.subscribe({\r\n                    userVisibleOnly: true,\r\n                    applicationServerKey: applicationServerKey\r\n                });\r\n            })\r\n            .then(subscription =&gt; {\r\n                \/\/ \u4e0a\u50b3\u4f7f\u7528\u8005\u8a3b\u518a\u8eab\u4efd\r\n                \/\/ ...\r\n            })\r\n            .catch(err =&gt; {\r\n                console.log('Failed to subscribe the user: ', err);\r\n            });\r\n    }\r\n}\r\n\r\nfunction requestPermission(){\r\n    Notification.requestPermission().then((permission) =&gt; {\r\n        \/\/ \u53d6\u5f97\u6388\u6b0a\u5f8c\u57f7\u884c\u52d5\u4f5c\r\n        serviceWorker_register();\r\n    });\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>--<\/p>\n<h2>sw.js<\/h2>\n<pre class=\"lang:js decode:true \">const cacheVersion = 'v2';\r\nconst cacheName = \"upad12\";\r\n\r\nconst filesToCache = [\r\n    '\/application\/views\/app\/style.css',\r\n    '\/application\/views\/app\/user_bootstrap5.css',\r\n    '\/public\/3rd\/php.js',\r\n];\r\n\r\nself.addEventListener('install', event =&gt; {\r\n    \/\/ console.log(\"[Service Worker] Install\");\r\n    event.waitUntil(\r\n        (async () =&gt; {\r\n            const cache = await caches.open(cacheName);\r\n            await cache.addAll(filesToCache);\r\n        })(),\r\n    );\r\n});\r\n\r\nself.addEventListener('fetch', event =&gt; {\r\n    \/\/ console.log('[ServiceWorker] fetch', event.request);\r\n    event.respondWith(\r\n        caches.match(event.request).then((response) =&gt; {\r\n            return response || fetch(event.request);\r\n        })\r\n    );\r\n});\r\n\r\nself.addEventListener('push', function(event) {\r\n    \/\/ console.log(event.data.json());\r\n    const payload = event.data.json();\r\n\r\n    let options = {\r\n        body: payload.message,\r\n        icon: payload.icon,\r\n        badge: payload.icon,\r\n        \/\/ tag: \"\",\r\n        \/\/ renotify: true,\r\n        data: {\r\n            role : payload.role,\r\n            source : payload.source,\r\n            child_id : payload.child_id,\r\n        },\r\n    };\r\n    event.waitUntil(\r\n        self.registration.showNotification(payload.title, options)\r\n    );\r\n});\r\n\r\n\/\/\r\nself.addEventListener(\"notificationclick\", (event) =&gt; {\r\n    const push_data = event.notification.data;\r\n    console.log(\"On notification click: \", push_data);\r\n\r\n    event.waitUntil(\r\n        clients\r\n            .matchAll({\r\n                type: \"window\",\r\n            })\r\n            .then((clientList) =&gt; {\r\n                let url = '\/app';\r\n\r\n                if ( push_data.source === '\u5237\u5361' ){\r\n                    if ( push_data.role === '11' ) url = '\/app\/Boss\/EmployeeAttendance';\r\n                    if ( push_data.role === '21' ) url = '\/app\/Student\/Attendance';\r\n                    if ( push_data.role === '30' ) url = '\/app\/Parents\/ChildCalendar\/?child_id=' + push_data.child_id;\r\n                }\r\n\r\n                return clients.openWindow(url);\r\n            }),\r\n    );\r\n\r\n    event.notification.close();\r\n});\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>--<\/p>\n<h2>\u767c\u9001\u7aef\uff1a\u5b89\u88dd web-push<\/h2>\n<p>\u5b89\u88dd\u6642\u52a0\u4e0a -g \u70ba\u4f7f\u7528\u547d\u4ee4\u5217<\/p>\n<pre class=\"lang:default decode:true\">npm install web-push -g<\/pre>\n<p>\u53d6\u5f97\u767c\u9001\u8005\u8eab\u4efd\uff0c\u53ea\u8981\u6709\u8eab\u4efd\u5c31\u53ef\u4ee5\u767c\u9001\uff0c\u4e0d\u540c\u96fb\u8166\u4e5f\u53ef\u4ee5\u5171\u7528<\/p>\n<pre class=\"lang:default decode:true\">web-push generate-vapid-keys<\/pre>\n<pre class=\"lang:default decode:true\">=======================================\r\n\r\nPublic Key:\r\nBPrtEl0VOWdEdP_hwl-P1xnLkuzD9qiNfUBaOzO5KUxBXeE2VnkWwzKP0PSm4cjprR6nK9Jg3ziBnINqAFKHf9k\r\n\r\nPrivate Key:\r\nG8y_V-PUwP188uPCpKn0Io8XPdzEEbfhKJ2AGw24dhk\r\n\r\n=======================================<\/pre>\n<p>\u63a8\u9001\u8a0a\u606f<\/p>\n<pre class=\"lang:default decode:true\">web-push send-notification\r\n--endpoint=https:\/\/fcm.googleapis.com\/fcm\/send\/eIz-0oEdA88:APA91bF_A-eANS-pfHzvQ6ixWeJQdrEODeiwV9seclwqSiLd7nYT7-CKnqSJpE66cLyV3yYlJxNzClCeLbnpGq00JixEcd6lDUkcalFJVFLQjPq7R2ufdAVkfGBINHT1rXaQc8KZvoEQ\r\n--key=BFhr8ZmNlw_rBUxc4d8oLEwYjkabBb6sXQ1SW-tEogsPrV3mmvGhVhABMOuzi7vrhkAn8saLcQ81WsWpuyjQ9TE\r\n--auth=e-CL1SS37TSvRe7Swx4idA\r\n--vapid-pubkey=BGQsM1ZSgzMCvBeHEhhcwrBALSsRvFOyK6ErMYZmJoXHiIdJVwuffhTa5HUnmaTiVs6jWLUnUA-x05JVn2uKi38\r\n--vapid-pvtkey=CWROTKckpwAgt02x2l8WWK1Vq2kTMduODZz5oJ1ICYo\r\n--vapid-subject=https:\/\/hoyo.idv.tw\r\n--payload=\"{\\\"message\\\":\\\"This is a message\\\",\\\"title\\\":\\\"this is a title\\\",\\\"icon\\\":\\\"https:\/\/encrypted-tbn0.gstatic.com\/images?q=tbn:ANd9GcQFQpoz6_MxzG3k_NCSWrhcW-H3nU5GoDhCysqPfXUEzmCQGpsuIQ\\\"}\"<\/pre>\n<ul>\n<li>--endpoint \u4f7f\u7528\u8005\u8a3b\u518a\u53d6\u5f97\u7684 endpoint\uff0c\u63a5\u6536\u63a8\u64ad\u7684\u8a0a\u606f\u767c\u9001\u7684\u5e73\u53f0\uff0c\u4e0d\u540c\u7684\u88dd\u7f6e\u3001\u700f\u89bd\u5668 endpoint \u90fd\u4e0d\u540c\uff0c\u4f8b\u5982 Google Chrome \u4f7f\u7528 Google \u7684 FCM (Firebase Cloud Messaging)\uff0cMicrosoft Edge \u4f7f\u7528 WNS (Windows Push Notification Service)<\/li>\n<li>--key \u4f7f\u7528\u8005\u8a3b\u518a\u53d6\u5f97 keys \u7684 p256dh<\/li>\n<li>--auth \u4f7f\u7528\u8005\u8a3b\u518a\u53d6\u5f97 keys \u7684 auth<\/li>\n<li>--vapid-pubkey \u4f7f\u7528 generate-vapid-keys \u53d6\u5f97\u7684 Public Key<\/li>\n<li>--vapid-pvtkey\u4f7f\u7528 generate-vapid-keys \u53d6\u5f97\u7684 Private Key<\/li>\n<li>--vapid-subject \u4f7f\u7528 email (mailto:your@email.com) \u6216\u662f\u7db2\u5740 (https:\/\/your-website.com) \u7533\u660e\u8eab\u4efd<\/li>\n<li>--payload \u767c\u9001\u5167\u5bb9\uff0c\u7d14\u6587\u5b57\u683c\u5f0f\u96a8\u610f\uff0c\u53ea\u8981\u63a5\u6536\u5f8c\u53ef\u8655\u7406\uff0c\u4e00\u822c\u90fd\u662f\u4f7f\u7528 JSON<\/li>\n<\/ul>\n<p>--<\/p>\n<h2>\u652f\u63f4\u6027<\/h2>\n<p>\u63a8\u64ad\u5404\u5bb6\u5fc5\u9808\u4f7f\u7528\u81ea\u5df1\u7684\u4e3b\u6a5f\u9032\u884c\uff0c\u56e0\u6b64 endpoint \u5c31\u4e0d\u76f8\u540c<\/p>\n<ul>\n<li>Chrome\uff1ahttps:\/\/fcm.googleapis.com\/fcm\/send\/<\/li>\n<li>Edge\uff1ahttps:\/\/wns2-ln2p.notify.windows.com\/w\/?token=<\/li>\n<li>Firefox\uff1ahttps:\/\/updates.push.services.mozilla.com\/wpush\/v2\/<\/li>\n<li>Safari\uff1ahttps:\/\/web.push.apple.com\/<\/li>\n<\/ul>\n<p>\u56e0\u70ba endpoint \u4e3b\u6a5f\u90fd\u662f\u5f9e\u5404\u81ea\u700f\u89bd\u5668\u63d0\u4f9b\uff0c\u6240\u4ee5\u53cd\u61c9\u901f\u5ea6\u53ef\u80fd\u6703\u6709\u4e0d\u540c<\/p>\n<p>--<\/p>\n<h2>\u6ce8\u610f\u4e8b\u9805<\/h2>\n<ul>\n<li>\u4fee\u6539 sw.js \u5fc5\u9808\u91cd\u65b0\u555f\u52d5\u700f\u89bd\u5668<\/li>\n<\/ul>\n<p>--<\/p>\n<h2>\u7bc4\u4f8b<\/h2>\n<ul>\n<li><a href=\"https:\/\/pushpad.xyz\/service-worker.js\" target=\"_blank\" rel=\"noopener\">pushpad.xyz\/service-worker.js<\/a><\/li>\n<li><a href=\"https:\/\/www.ecpay.com.tw\/Scripts\/BrowserMessage\/BrowserMessage.js\" target=\"_blank\" rel=\"noopener\">ecpay.com.tw\/Scripts\/BrowserMessage\/BrowserMessage.js<\/a><\/li>\n<li><a href=\"https:\/\/www.ecpay.com.tw\/\/Scripts\/BrowserMessage\/service-worker.js\" target=\"_blank\" rel=\"noopener\">ecpay.com.tw\/\/Scripts\/BrowserMessage\/service-worker.js<\/a><\/li>\n<\/ul>\n<p>--<\/p>\n<div class=\"pvc_clear\"><\/div>\n<p class=\"pvc_stats all \" data-element-id=\"13248\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> &nbsp;2,469&nbsp;total views<\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"excerpt":{"rendered":"<p>-- \u524d\u8a00 PWA \u4f5c\u70ba\u7db2\u7ad9\u904e...<\/p>\n<div class=\"pvc_clear\"><\/div>\n<p class=\"pvc_stats all \" data-element-id=\"13248\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> &nbsp;2,469&nbsp;total views<\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[262],"tags":[],"_links":{"self":[{"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=\/wp\/v2\/posts\/13248"}],"collection":[{"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=13248"}],"version-history":[{"count":14,"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=\/wp\/v2\/posts\/13248\/revisions"}],"predecessor-version":[{"id":13706,"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=\/wp\/v2\/posts\/13248\/revisions\/13706"}],"wp:attachment":[{"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=13248"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=13248"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.hoyo.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=13248"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}