diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js new file mode 100644 index 0000000..c9ce282 --- /dev/null +++ b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js @@ -0,0 +1 @@ +(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js new file mode 100644 index 0000000..c9ce282 --- /dev/null +++ b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js @@ -0,0 +1 @@ +(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/src/app/actions/page.tsx b/node/src/app/actions/page.tsx index e60a5e7..d9384cb 100644 --- a/node/src/app/actions/page.tsx +++ b/node/src/app/actions/page.tsx @@ -10,7 +10,7 @@ import axios, { AxiosError } from 'axios' import { useRouter } from 'next/navigation' import './page.css' -import { useIndexedDB } from '@/hooks' +import { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB } from '@/hooks' interface Notification { title: string @@ -81,18 +81,33 @@ setMessages(messages) if(messages.length > 0) setTemplateMessage(messages[0]) } - fetch('/accessKey') - .then(res => res.ok ? res.json() : Promise.reject()) - .then(({ accessKey: cookieKey }: { accessKey: string | null }) => { - let accessKey = cookieKey - if(!accessKey) { - const lsKey = localStorage.getItem('AccessKey') - if(lsKey && lsKey.length === 20) { - fetch('/accessKey', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accessKey: lsKey }) }) - localStorage.removeItem('AccessKey') - accessKey = lsKey + // アクセスキーは IndexedDB が primary store。無ければ旧保存先 (Cookie / localStorage) + // から IndexedDB へ移行する。 + loadAccessKeyFromIDB() + .then(async (idbKey): Promise => { + if (idbKey) return idbKey + try { + const res = await fetch('/accessKey') + if (res.ok) { + const { accessKey: cookieKey }: { accessKey: string | null } = await res.json() + if (cookieKey) { + await saveAccessKeyToIDB(cookieKey) + fetch('/accessKey', { method: 'DELETE' }) + return cookieKey + } } + } catch (e) { + console.error('GET /accessKey failed', e) } + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + return lsKey + } + return null + }) + .then(accessKey => { if(!accessKey) { setAccessKeyError('アクセスキーがありません') setIsLoading(false) diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js new file mode 100644 index 0000000..c9ce282 --- /dev/null +++ b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js @@ -0,0 +1 @@ +(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/src/app/actions/page.tsx b/node/src/app/actions/page.tsx index e60a5e7..d9384cb 100644 --- a/node/src/app/actions/page.tsx +++ b/node/src/app/actions/page.tsx @@ -10,7 +10,7 @@ import axios, { AxiosError } from 'axios' import { useRouter } from 'next/navigation' import './page.css' -import { useIndexedDB } from '@/hooks' +import { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB } from '@/hooks' interface Notification { title: string @@ -81,18 +81,33 @@ setMessages(messages) if(messages.length > 0) setTemplateMessage(messages[0]) } - fetch('/accessKey') - .then(res => res.ok ? res.json() : Promise.reject()) - .then(({ accessKey: cookieKey }: { accessKey: string | null }) => { - let accessKey = cookieKey - if(!accessKey) { - const lsKey = localStorage.getItem('AccessKey') - if(lsKey && lsKey.length === 20) { - fetch('/accessKey', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accessKey: lsKey }) }) - localStorage.removeItem('AccessKey') - accessKey = lsKey + // アクセスキーは IndexedDB が primary store。無ければ旧保存先 (Cookie / localStorage) + // から IndexedDB へ移行する。 + loadAccessKeyFromIDB() + .then(async (idbKey): Promise => { + if (idbKey) return idbKey + try { + const res = await fetch('/accessKey') + if (res.ok) { + const { accessKey: cookieKey }: { accessKey: string | null } = await res.json() + if (cookieKey) { + await saveAccessKeyToIDB(cookieKey) + fetch('/accessKey', { method: 'DELETE' }) + return cookieKey + } } + } catch (e) { + console.error('GET /accessKey failed', e) } + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + return lsKey + } + return null + }) + .then(accessKey => { if(!accessKey) { setAccessKeyError('アクセスキーがありません') setIsLoading(false) diff --git a/node/src/app/useWebpush.tsx b/node/src/app/useWebpush.tsx index 4a8dc66..09867dd 100644 --- a/node/src/app/useWebpush.tsx +++ b/node/src/app/useWebpush.tsx @@ -10,6 +10,7 @@ import { User } from '@/types' import jsSHA from "jssha" import { checkNotificationStatus, requestNotificationPermission } from '@/utils/ios' +import { saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from '@/hooks' function generateSHA256Hash(data: string): string { const shaObj = new jsSHA("SHA-256", "TEXT"); @@ -45,11 +46,9 @@ localStorage.setItem('Users', JSON.stringify(data.users)) setIsLoading(false) setRemoteHash(data.hash) - fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: newAccessKey }), - }) + // アクセスキーは IndexedDB を primary store として保存する。 + // Cookie は Android ホーム画面 PWA で消えるため使わない。 + saveAccessKeyToIDB(newAccessKey) return data.hash }) .catch(err => { @@ -67,46 +66,50 @@ // 初回読み込み時 useEffect(() => { - const saveKey = (key: string) => fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: key }), - }) + // アクセスキーは IndexedDB を primary store として管理する。 + // saveKey は IndexedDB への保存のみ (Cookie へは書かない)。 + const saveKey = (key: string) => saveAccessKeyToIDB(key) const init = async () => { - // 1. 現在のCookieキーを取得 - let cookieKey: string | null = null - try { - const res = await fetch('/accessKey') - if (res.ok) { - const data: { accessKey: string | null } = await res.json() - cookieKey = data.accessKey - } - } catch (e) { - console.error('GET /accessKey failed', e) - } + // 1. IndexedDB (primary) からアクセスキーを取得 + let idbKey: string | null = await loadAccessKeyFromIDB() - // 2. URLハッシュのキーを取得 (ハッシュは URL に残す。 - // deep link / bookmark / タブ復元で再適用できるようにするため。 + // 2. URLハッシュのキーを取得 (deep link / QR / タブ復元で再適用するため URL に残す。 // キー変更をキャンセルした時のみ後段で消去する)。 const hashMatch = window.location.hash.match(/[#&]accessKey=([^&]+)/) const hashKey = hashMatch ? decodeURIComponent(hashMatch[1]) : null - // 3. localStorageからCookieへ移行(CookieもURLハッシュもない場合) - let migratedKey: string | null = null - if (!cookieKey && !hashKey) { - const lsKey = localStorage.getItem('AccessKey') - if (lsKey && lsKey.length === 20) { - await saveKey(lsKey) - localStorage.removeItem('AccessKey') - migratedKey = lsKey + // 3. IndexedDB にもハッシュにも無ければ、旧保存先から IndexedDB へ移行する。 + // 3a: Cookie (__Host-raikyakun_access) → 移行後は Cookie を破棄 + // 3b: localStorage('AccessKey') + if (!idbKey && !hashKey) { + try { + const res = await fetch('/accessKey') + if (res.ok) { + const data: { accessKey: string | null } = await res.json() + if (data.accessKey) { + await saveAccessKeyToIDB(data.accessKey) + fetch('/accessKey', { method: 'DELETE' }) + idbKey = data.accessKey + } + } + } catch (e) { + console.error('GET /accessKey failed', e) + } + if (!idbKey) { + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + idbKey = lsKey + } } } // WebViewの場合 if (!('serviceWorker' in navigator)) { - const resolvedKey = hashKey ?? cookieKey ?? migratedKey - if (hashKey && hashKey !== cookieKey) await saveKey(hashKey) + const resolvedKey = hashKey ?? idbKey + if (hashKey && hashKey !== idbKey) await saveKey(hashKey) if (resolvedKey) setAccessKey(resolvedKey) setIsLoading(true) checkNotificationStatus() @@ -138,7 +141,7 @@ if (!registration.pushManager) { setIsSubscribed(false) setPushSupported(false) - const resolvedKey = hashKey ?? cookieKey ?? migratedKey + const resolvedKey = hashKey ?? idbKey if (hashKey) await saveKey(hashKey) if (resolvedKey) { setAccessKey(resolvedKey) @@ -159,13 +162,13 @@ } // 5. キー変更 + サブスク登録済み → ユーザーに確認 - const isKeyChange = hashKey !== null && hashKey !== cookieKey + const isKeyChange = hashKey !== null && hashKey !== idbKey let keyChangeCancelled = false - if (isKeyChange && subscription && cookieKey) { + if (isKeyChange && subscription && idbKey) { if (confirm('現在別のアクセスキーで登録中です。登録解除してアクセスキーを変更しますか?')) { // 確認: フル解除 → 新キーで継続 try { - await unsubscribe({ isLocalOnly: false, accessKey: cookieKey }) + await unsubscribe({ isLocalOnly: false, accessKey: idbKey }) } catch { setErrorMessage('登録解除に失敗しました。先に登録解除ボタンから解除してください。') return @@ -176,7 +179,7 @@ checkAccessKey(hashKey) return } - // キャンセル: 旧キーのまま継続(Cookie は cookieKey のまま)。 + // キャンセル: 旧キーのまま継続(IndexedDB は idbKey のまま)。 // 拒否したキーが URL ハッシュに残ると reload のたびに再プロンプトになるため、 // このケースだけハッシュを消去する。 history.replaceState(null, '', location.pathname + location.search) @@ -186,13 +189,13 @@ // 6. キーの確定 let resolvedKey: string | null if (keyChangeCancelled) { - resolvedKey = cookieKey + resolvedKey = idbKey } else if (hashKey) { // キー変更でサブスクなし、または同一キーの再アクセス await saveKey(hashKey) resolvedKey = hashKey } else { - resolvedKey = cookieKey ?? migratedKey + resolvedKey = idbKey } if (resolvedKey) setAccessKey(resolvedKey) @@ -380,7 +383,7 @@ } endAdornment={os === 'Other' || isStandalone ? - {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'})}} disabled={!!isSubscribed || isLoading}> + {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'});removeAccessKeyFromIDB()}} disabled={!!isSubscribed || isLoading}> diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js new file mode 100644 index 0000000..c9ce282 --- /dev/null +++ b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js @@ -0,0 +1 @@ +(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/src/app/actions/page.tsx b/node/src/app/actions/page.tsx index e60a5e7..d9384cb 100644 --- a/node/src/app/actions/page.tsx +++ b/node/src/app/actions/page.tsx @@ -10,7 +10,7 @@ import axios, { AxiosError } from 'axios' import { useRouter } from 'next/navigation' import './page.css' -import { useIndexedDB } from '@/hooks' +import { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB } from '@/hooks' interface Notification { title: string @@ -81,18 +81,33 @@ setMessages(messages) if(messages.length > 0) setTemplateMessage(messages[0]) } - fetch('/accessKey') - .then(res => res.ok ? res.json() : Promise.reject()) - .then(({ accessKey: cookieKey }: { accessKey: string | null }) => { - let accessKey = cookieKey - if(!accessKey) { - const lsKey = localStorage.getItem('AccessKey') - if(lsKey && lsKey.length === 20) { - fetch('/accessKey', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accessKey: lsKey }) }) - localStorage.removeItem('AccessKey') - accessKey = lsKey + // アクセスキーは IndexedDB が primary store。無ければ旧保存先 (Cookie / localStorage) + // から IndexedDB へ移行する。 + loadAccessKeyFromIDB() + .then(async (idbKey): Promise => { + if (idbKey) return idbKey + try { + const res = await fetch('/accessKey') + if (res.ok) { + const { accessKey: cookieKey }: { accessKey: string | null } = await res.json() + if (cookieKey) { + await saveAccessKeyToIDB(cookieKey) + fetch('/accessKey', { method: 'DELETE' }) + return cookieKey + } } + } catch (e) { + console.error('GET /accessKey failed', e) } + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + return lsKey + } + return null + }) + .then(accessKey => { if(!accessKey) { setAccessKeyError('アクセスキーがありません') setIsLoading(false) diff --git a/node/src/app/useWebpush.tsx b/node/src/app/useWebpush.tsx index 4a8dc66..09867dd 100644 --- a/node/src/app/useWebpush.tsx +++ b/node/src/app/useWebpush.tsx @@ -10,6 +10,7 @@ import { User } from '@/types' import jsSHA from "jssha" import { checkNotificationStatus, requestNotificationPermission } from '@/utils/ios' +import { saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from '@/hooks' function generateSHA256Hash(data: string): string { const shaObj = new jsSHA("SHA-256", "TEXT"); @@ -45,11 +46,9 @@ localStorage.setItem('Users', JSON.stringify(data.users)) setIsLoading(false) setRemoteHash(data.hash) - fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: newAccessKey }), - }) + // アクセスキーは IndexedDB を primary store として保存する。 + // Cookie は Android ホーム画面 PWA で消えるため使わない。 + saveAccessKeyToIDB(newAccessKey) return data.hash }) .catch(err => { @@ -67,46 +66,50 @@ // 初回読み込み時 useEffect(() => { - const saveKey = (key: string) => fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: key }), - }) + // アクセスキーは IndexedDB を primary store として管理する。 + // saveKey は IndexedDB への保存のみ (Cookie へは書かない)。 + const saveKey = (key: string) => saveAccessKeyToIDB(key) const init = async () => { - // 1. 現在のCookieキーを取得 - let cookieKey: string | null = null - try { - const res = await fetch('/accessKey') - if (res.ok) { - const data: { accessKey: string | null } = await res.json() - cookieKey = data.accessKey - } - } catch (e) { - console.error('GET /accessKey failed', e) - } + // 1. IndexedDB (primary) からアクセスキーを取得 + let idbKey: string | null = await loadAccessKeyFromIDB() - // 2. URLハッシュのキーを取得 (ハッシュは URL に残す。 - // deep link / bookmark / タブ復元で再適用できるようにするため。 + // 2. URLハッシュのキーを取得 (deep link / QR / タブ復元で再適用するため URL に残す。 // キー変更をキャンセルした時のみ後段で消去する)。 const hashMatch = window.location.hash.match(/[#&]accessKey=([^&]+)/) const hashKey = hashMatch ? decodeURIComponent(hashMatch[1]) : null - // 3. localStorageからCookieへ移行(CookieもURLハッシュもない場合) - let migratedKey: string | null = null - if (!cookieKey && !hashKey) { - const lsKey = localStorage.getItem('AccessKey') - if (lsKey && lsKey.length === 20) { - await saveKey(lsKey) - localStorage.removeItem('AccessKey') - migratedKey = lsKey + // 3. IndexedDB にもハッシュにも無ければ、旧保存先から IndexedDB へ移行する。 + // 3a: Cookie (__Host-raikyakun_access) → 移行後は Cookie を破棄 + // 3b: localStorage('AccessKey') + if (!idbKey && !hashKey) { + try { + const res = await fetch('/accessKey') + if (res.ok) { + const data: { accessKey: string | null } = await res.json() + if (data.accessKey) { + await saveAccessKeyToIDB(data.accessKey) + fetch('/accessKey', { method: 'DELETE' }) + idbKey = data.accessKey + } + } + } catch (e) { + console.error('GET /accessKey failed', e) + } + if (!idbKey) { + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + idbKey = lsKey + } } } // WebViewの場合 if (!('serviceWorker' in navigator)) { - const resolvedKey = hashKey ?? cookieKey ?? migratedKey - if (hashKey && hashKey !== cookieKey) await saveKey(hashKey) + const resolvedKey = hashKey ?? idbKey + if (hashKey && hashKey !== idbKey) await saveKey(hashKey) if (resolvedKey) setAccessKey(resolvedKey) setIsLoading(true) checkNotificationStatus() @@ -138,7 +141,7 @@ if (!registration.pushManager) { setIsSubscribed(false) setPushSupported(false) - const resolvedKey = hashKey ?? cookieKey ?? migratedKey + const resolvedKey = hashKey ?? idbKey if (hashKey) await saveKey(hashKey) if (resolvedKey) { setAccessKey(resolvedKey) @@ -159,13 +162,13 @@ } // 5. キー変更 + サブスク登録済み → ユーザーに確認 - const isKeyChange = hashKey !== null && hashKey !== cookieKey + const isKeyChange = hashKey !== null && hashKey !== idbKey let keyChangeCancelled = false - if (isKeyChange && subscription && cookieKey) { + if (isKeyChange && subscription && idbKey) { if (confirm('現在別のアクセスキーで登録中です。登録解除してアクセスキーを変更しますか?')) { // 確認: フル解除 → 新キーで継続 try { - await unsubscribe({ isLocalOnly: false, accessKey: cookieKey }) + await unsubscribe({ isLocalOnly: false, accessKey: idbKey }) } catch { setErrorMessage('登録解除に失敗しました。先に登録解除ボタンから解除してください。') return @@ -176,7 +179,7 @@ checkAccessKey(hashKey) return } - // キャンセル: 旧キーのまま継続(Cookie は cookieKey のまま)。 + // キャンセル: 旧キーのまま継続(IndexedDB は idbKey のまま)。 // 拒否したキーが URL ハッシュに残ると reload のたびに再プロンプトになるため、 // このケースだけハッシュを消去する。 history.replaceState(null, '', location.pathname + location.search) @@ -186,13 +189,13 @@ // 6. キーの確定 let resolvedKey: string | null if (keyChangeCancelled) { - resolvedKey = cookieKey + resolvedKey = idbKey } else if (hashKey) { // キー変更でサブスクなし、または同一キーの再アクセス await saveKey(hashKey) resolvedKey = hashKey } else { - resolvedKey = cookieKey ?? migratedKey + resolvedKey = idbKey } if (resolvedKey) setAccessKey(resolvedKey) @@ -380,7 +383,7 @@ } endAdornment={os === 'Other' || isStandalone ? - {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'})}} disabled={!!isSubscribed || isLoading}> + {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'});removeAccessKeyFromIDB()}} disabled={!!isSubscribed || isLoading}> diff --git a/node/src/hooks/index.ts b/node/src/hooks/index.ts old mode 100755 new mode 100644 index 281dae5..014d82d --- a/node/src/hooks/index.ts +++ b/node/src/hooks/index.ts @@ -1,2 +1,2 @@ -export { useIndexedDB } from './useIndexedDB' +export { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from './useIndexedDB' export { useWebRTC } from './useWebRTC' \ No newline at end of file diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js new file mode 100644 index 0000000..c9ce282 --- /dev/null +++ b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js @@ -0,0 +1 @@ +(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/src/app/actions/page.tsx b/node/src/app/actions/page.tsx index e60a5e7..d9384cb 100644 --- a/node/src/app/actions/page.tsx +++ b/node/src/app/actions/page.tsx @@ -10,7 +10,7 @@ import axios, { AxiosError } from 'axios' import { useRouter } from 'next/navigation' import './page.css' -import { useIndexedDB } from '@/hooks' +import { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB } from '@/hooks' interface Notification { title: string @@ -81,18 +81,33 @@ setMessages(messages) if(messages.length > 0) setTemplateMessage(messages[0]) } - fetch('/accessKey') - .then(res => res.ok ? res.json() : Promise.reject()) - .then(({ accessKey: cookieKey }: { accessKey: string | null }) => { - let accessKey = cookieKey - if(!accessKey) { - const lsKey = localStorage.getItem('AccessKey') - if(lsKey && lsKey.length === 20) { - fetch('/accessKey', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accessKey: lsKey }) }) - localStorage.removeItem('AccessKey') - accessKey = lsKey + // アクセスキーは IndexedDB が primary store。無ければ旧保存先 (Cookie / localStorage) + // から IndexedDB へ移行する。 + loadAccessKeyFromIDB() + .then(async (idbKey): Promise => { + if (idbKey) return idbKey + try { + const res = await fetch('/accessKey') + if (res.ok) { + const { accessKey: cookieKey }: { accessKey: string | null } = await res.json() + if (cookieKey) { + await saveAccessKeyToIDB(cookieKey) + fetch('/accessKey', { method: 'DELETE' }) + return cookieKey + } } + } catch (e) { + console.error('GET /accessKey failed', e) } + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + return lsKey + } + return null + }) + .then(accessKey => { if(!accessKey) { setAccessKeyError('アクセスキーがありません') setIsLoading(false) diff --git a/node/src/app/useWebpush.tsx b/node/src/app/useWebpush.tsx index 4a8dc66..09867dd 100644 --- a/node/src/app/useWebpush.tsx +++ b/node/src/app/useWebpush.tsx @@ -10,6 +10,7 @@ import { User } from '@/types' import jsSHA from "jssha" import { checkNotificationStatus, requestNotificationPermission } from '@/utils/ios' +import { saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from '@/hooks' function generateSHA256Hash(data: string): string { const shaObj = new jsSHA("SHA-256", "TEXT"); @@ -45,11 +46,9 @@ localStorage.setItem('Users', JSON.stringify(data.users)) setIsLoading(false) setRemoteHash(data.hash) - fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: newAccessKey }), - }) + // アクセスキーは IndexedDB を primary store として保存する。 + // Cookie は Android ホーム画面 PWA で消えるため使わない。 + saveAccessKeyToIDB(newAccessKey) return data.hash }) .catch(err => { @@ -67,46 +66,50 @@ // 初回読み込み時 useEffect(() => { - const saveKey = (key: string) => fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: key }), - }) + // アクセスキーは IndexedDB を primary store として管理する。 + // saveKey は IndexedDB への保存のみ (Cookie へは書かない)。 + const saveKey = (key: string) => saveAccessKeyToIDB(key) const init = async () => { - // 1. 現在のCookieキーを取得 - let cookieKey: string | null = null - try { - const res = await fetch('/accessKey') - if (res.ok) { - const data: { accessKey: string | null } = await res.json() - cookieKey = data.accessKey - } - } catch (e) { - console.error('GET /accessKey failed', e) - } + // 1. IndexedDB (primary) からアクセスキーを取得 + let idbKey: string | null = await loadAccessKeyFromIDB() - // 2. URLハッシュのキーを取得 (ハッシュは URL に残す。 - // deep link / bookmark / タブ復元で再適用できるようにするため。 + // 2. URLハッシュのキーを取得 (deep link / QR / タブ復元で再適用するため URL に残す。 // キー変更をキャンセルした時のみ後段で消去する)。 const hashMatch = window.location.hash.match(/[#&]accessKey=([^&]+)/) const hashKey = hashMatch ? decodeURIComponent(hashMatch[1]) : null - // 3. localStorageからCookieへ移行(CookieもURLハッシュもない場合) - let migratedKey: string | null = null - if (!cookieKey && !hashKey) { - const lsKey = localStorage.getItem('AccessKey') - if (lsKey && lsKey.length === 20) { - await saveKey(lsKey) - localStorage.removeItem('AccessKey') - migratedKey = lsKey + // 3. IndexedDB にもハッシュにも無ければ、旧保存先から IndexedDB へ移行する。 + // 3a: Cookie (__Host-raikyakun_access) → 移行後は Cookie を破棄 + // 3b: localStorage('AccessKey') + if (!idbKey && !hashKey) { + try { + const res = await fetch('/accessKey') + if (res.ok) { + const data: { accessKey: string | null } = await res.json() + if (data.accessKey) { + await saveAccessKeyToIDB(data.accessKey) + fetch('/accessKey', { method: 'DELETE' }) + idbKey = data.accessKey + } + } + } catch (e) { + console.error('GET /accessKey failed', e) + } + if (!idbKey) { + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + idbKey = lsKey + } } } // WebViewの場合 if (!('serviceWorker' in navigator)) { - const resolvedKey = hashKey ?? cookieKey ?? migratedKey - if (hashKey && hashKey !== cookieKey) await saveKey(hashKey) + const resolvedKey = hashKey ?? idbKey + if (hashKey && hashKey !== idbKey) await saveKey(hashKey) if (resolvedKey) setAccessKey(resolvedKey) setIsLoading(true) checkNotificationStatus() @@ -138,7 +141,7 @@ if (!registration.pushManager) { setIsSubscribed(false) setPushSupported(false) - const resolvedKey = hashKey ?? cookieKey ?? migratedKey + const resolvedKey = hashKey ?? idbKey if (hashKey) await saveKey(hashKey) if (resolvedKey) { setAccessKey(resolvedKey) @@ -159,13 +162,13 @@ } // 5. キー変更 + サブスク登録済み → ユーザーに確認 - const isKeyChange = hashKey !== null && hashKey !== cookieKey + const isKeyChange = hashKey !== null && hashKey !== idbKey let keyChangeCancelled = false - if (isKeyChange && subscription && cookieKey) { + if (isKeyChange && subscription && idbKey) { if (confirm('現在別のアクセスキーで登録中です。登録解除してアクセスキーを変更しますか?')) { // 確認: フル解除 → 新キーで継続 try { - await unsubscribe({ isLocalOnly: false, accessKey: cookieKey }) + await unsubscribe({ isLocalOnly: false, accessKey: idbKey }) } catch { setErrorMessage('登録解除に失敗しました。先に登録解除ボタンから解除してください。') return @@ -176,7 +179,7 @@ checkAccessKey(hashKey) return } - // キャンセル: 旧キーのまま継続(Cookie は cookieKey のまま)。 + // キャンセル: 旧キーのまま継続(IndexedDB は idbKey のまま)。 // 拒否したキーが URL ハッシュに残ると reload のたびに再プロンプトになるため、 // このケースだけハッシュを消去する。 history.replaceState(null, '', location.pathname + location.search) @@ -186,13 +189,13 @@ // 6. キーの確定 let resolvedKey: string | null if (keyChangeCancelled) { - resolvedKey = cookieKey + resolvedKey = idbKey } else if (hashKey) { // キー変更でサブスクなし、または同一キーの再アクセス await saveKey(hashKey) resolvedKey = hashKey } else { - resolvedKey = cookieKey ?? migratedKey + resolvedKey = idbKey } if (resolvedKey) setAccessKey(resolvedKey) @@ -380,7 +383,7 @@ } endAdornment={os === 'Other' || isStandalone ? - {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'})}} disabled={!!isSubscribed || isLoading}> + {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'});removeAccessKeyFromIDB()}} disabled={!!isSubscribed || isLoading}> diff --git a/node/src/hooks/index.ts b/node/src/hooks/index.ts old mode 100755 new mode 100644 index 281dae5..014d82d --- a/node/src/hooks/index.ts +++ b/node/src/hooks/index.ts @@ -1,2 +1,2 @@ -export { useIndexedDB } from './useIndexedDB' +export { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from './useIndexedDB' export { useWebRTC } from './useWebRTC' \ No newline at end of file diff --git a/node/src/hooks/useIndexedDB.tsx b/node/src/hooks/useIndexedDB.tsx old mode 100755 new mode 100644 index 948cb74..14d4c71 --- a/node/src/hooks/useIndexedDB.tsx +++ b/node/src/hooks/useIndexedDB.tsx @@ -4,6 +4,48 @@ const dbName = 'pwa-db' const storeName = 'subscriptions' +// アクセスキーのバックアップを通話履歴と同じ store に置くための予約 callId。 +// Cookie は Android ホーム画面 PWA で消えることがあるが、IndexedDB は SW が +// push のたびに書き込むため残る。その耐久性に相乗りしてキーを退避する。 +// update() の一覧には出さないようフィルタする。 +const ACCESS_KEY_ID = '__accessKey__' + +const openPwaDB = (): Promise => + new Promise((resolve, reject) => { + const openRequest = indexedDB.open(dbName, 1) + openRequest.onupgradeneeded = () => { + const db = openRequest.result + if (!db.objectStoreNames.contains(storeName)) { + db.createObjectStore(storeName, { keyPath: 'callId' }) + } + } + openRequest.onsuccess = () => resolve(openRequest.result) + openRequest.onerror = () => reject(openRequest.error) + }) + +export const saveAccessKeyToIDB = (accessKey: string): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite') + tx.objectStore(storeName).put({ callId: ACCESS_KEY_ID, accessKey }) + tx.oncomplete = () => resolve() + tx.onerror = () => resolve() + })).catch(() => {}) + +export const loadAccessKeyFromIDB = (): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const req = db.transaction(storeName, 'readonly').objectStore(storeName).get(ACCESS_KEY_ID) + req.onsuccess = () => resolve(req.result?.accessKey ?? null) + req.onerror = () => resolve(null) + })).catch(() => null) + +export const removeAccessKeyFromIDB = (): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite') + tx.objectStore(storeName).delete(ACCESS_KEY_ID) + tx.oncomplete = () => resolve() + tx.onerror = () => resolve() + })).catch(() => {}) + export const useIndexedDB = () => { const [latestCall, setLatestCall] = useState(null) @@ -28,6 +70,11 @@ request.onsuccess = event => { const cursor = request.result if (cursor) { + // アクセスキーのバックアップレコードは通話履歴ではないのでスキップ + if (cursor.value.callId === ACCESS_KEY_ID) { + cursor.continue() + return + } // 最初のレコードが最新のレコードになる setLatestCall(cursor.value) } else { @@ -41,7 +88,7 @@ cursorRequest.onsuccess = event => { const cursor = cursorRequest.result if (cursor) { - callListTemp.push(cursor.value) + if (cursor.value.callId !== ACCESS_KEY_ID) callListTemp.push(cursor.value) cursor.continue() } else { setCallList(callListTemp) diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js new file mode 100644 index 0000000..c9ce282 --- /dev/null +++ b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js @@ -0,0 +1 @@ +(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/src/app/actions/page.tsx b/node/src/app/actions/page.tsx index e60a5e7..d9384cb 100644 --- a/node/src/app/actions/page.tsx +++ b/node/src/app/actions/page.tsx @@ -10,7 +10,7 @@ import axios, { AxiosError } from 'axios' import { useRouter } from 'next/navigation' import './page.css' -import { useIndexedDB } from '@/hooks' +import { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB } from '@/hooks' interface Notification { title: string @@ -81,18 +81,33 @@ setMessages(messages) if(messages.length > 0) setTemplateMessage(messages[0]) } - fetch('/accessKey') - .then(res => res.ok ? res.json() : Promise.reject()) - .then(({ accessKey: cookieKey }: { accessKey: string | null }) => { - let accessKey = cookieKey - if(!accessKey) { - const lsKey = localStorage.getItem('AccessKey') - if(lsKey && lsKey.length === 20) { - fetch('/accessKey', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accessKey: lsKey }) }) - localStorage.removeItem('AccessKey') - accessKey = lsKey + // アクセスキーは IndexedDB が primary store。無ければ旧保存先 (Cookie / localStorage) + // から IndexedDB へ移行する。 + loadAccessKeyFromIDB() + .then(async (idbKey): Promise => { + if (idbKey) return idbKey + try { + const res = await fetch('/accessKey') + if (res.ok) { + const { accessKey: cookieKey }: { accessKey: string | null } = await res.json() + if (cookieKey) { + await saveAccessKeyToIDB(cookieKey) + fetch('/accessKey', { method: 'DELETE' }) + return cookieKey + } } + } catch (e) { + console.error('GET /accessKey failed', e) } + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + return lsKey + } + return null + }) + .then(accessKey => { if(!accessKey) { setAccessKeyError('アクセスキーがありません') setIsLoading(false) diff --git a/node/src/app/useWebpush.tsx b/node/src/app/useWebpush.tsx index 4a8dc66..09867dd 100644 --- a/node/src/app/useWebpush.tsx +++ b/node/src/app/useWebpush.tsx @@ -10,6 +10,7 @@ import { User } from '@/types' import jsSHA from "jssha" import { checkNotificationStatus, requestNotificationPermission } from '@/utils/ios' +import { saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from '@/hooks' function generateSHA256Hash(data: string): string { const shaObj = new jsSHA("SHA-256", "TEXT"); @@ -45,11 +46,9 @@ localStorage.setItem('Users', JSON.stringify(data.users)) setIsLoading(false) setRemoteHash(data.hash) - fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: newAccessKey }), - }) + // アクセスキーは IndexedDB を primary store として保存する。 + // Cookie は Android ホーム画面 PWA で消えるため使わない。 + saveAccessKeyToIDB(newAccessKey) return data.hash }) .catch(err => { @@ -67,46 +66,50 @@ // 初回読み込み時 useEffect(() => { - const saveKey = (key: string) => fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: key }), - }) + // アクセスキーは IndexedDB を primary store として管理する。 + // saveKey は IndexedDB への保存のみ (Cookie へは書かない)。 + const saveKey = (key: string) => saveAccessKeyToIDB(key) const init = async () => { - // 1. 現在のCookieキーを取得 - let cookieKey: string | null = null - try { - const res = await fetch('/accessKey') - if (res.ok) { - const data: { accessKey: string | null } = await res.json() - cookieKey = data.accessKey - } - } catch (e) { - console.error('GET /accessKey failed', e) - } + // 1. IndexedDB (primary) からアクセスキーを取得 + let idbKey: string | null = await loadAccessKeyFromIDB() - // 2. URLハッシュのキーを取得 (ハッシュは URL に残す。 - // deep link / bookmark / タブ復元で再適用できるようにするため。 + // 2. URLハッシュのキーを取得 (deep link / QR / タブ復元で再適用するため URL に残す。 // キー変更をキャンセルした時のみ後段で消去する)。 const hashMatch = window.location.hash.match(/[#&]accessKey=([^&]+)/) const hashKey = hashMatch ? decodeURIComponent(hashMatch[1]) : null - // 3. localStorageからCookieへ移行(CookieもURLハッシュもない場合) - let migratedKey: string | null = null - if (!cookieKey && !hashKey) { - const lsKey = localStorage.getItem('AccessKey') - if (lsKey && lsKey.length === 20) { - await saveKey(lsKey) - localStorage.removeItem('AccessKey') - migratedKey = lsKey + // 3. IndexedDB にもハッシュにも無ければ、旧保存先から IndexedDB へ移行する。 + // 3a: Cookie (__Host-raikyakun_access) → 移行後は Cookie を破棄 + // 3b: localStorage('AccessKey') + if (!idbKey && !hashKey) { + try { + const res = await fetch('/accessKey') + if (res.ok) { + const data: { accessKey: string | null } = await res.json() + if (data.accessKey) { + await saveAccessKeyToIDB(data.accessKey) + fetch('/accessKey', { method: 'DELETE' }) + idbKey = data.accessKey + } + } + } catch (e) { + console.error('GET /accessKey failed', e) + } + if (!idbKey) { + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + idbKey = lsKey + } } } // WebViewの場合 if (!('serviceWorker' in navigator)) { - const resolvedKey = hashKey ?? cookieKey ?? migratedKey - if (hashKey && hashKey !== cookieKey) await saveKey(hashKey) + const resolvedKey = hashKey ?? idbKey + if (hashKey && hashKey !== idbKey) await saveKey(hashKey) if (resolvedKey) setAccessKey(resolvedKey) setIsLoading(true) checkNotificationStatus() @@ -138,7 +141,7 @@ if (!registration.pushManager) { setIsSubscribed(false) setPushSupported(false) - const resolvedKey = hashKey ?? cookieKey ?? migratedKey + const resolvedKey = hashKey ?? idbKey if (hashKey) await saveKey(hashKey) if (resolvedKey) { setAccessKey(resolvedKey) @@ -159,13 +162,13 @@ } // 5. キー変更 + サブスク登録済み → ユーザーに確認 - const isKeyChange = hashKey !== null && hashKey !== cookieKey + const isKeyChange = hashKey !== null && hashKey !== idbKey let keyChangeCancelled = false - if (isKeyChange && subscription && cookieKey) { + if (isKeyChange && subscription && idbKey) { if (confirm('現在別のアクセスキーで登録中です。登録解除してアクセスキーを変更しますか?')) { // 確認: フル解除 → 新キーで継続 try { - await unsubscribe({ isLocalOnly: false, accessKey: cookieKey }) + await unsubscribe({ isLocalOnly: false, accessKey: idbKey }) } catch { setErrorMessage('登録解除に失敗しました。先に登録解除ボタンから解除してください。') return @@ -176,7 +179,7 @@ checkAccessKey(hashKey) return } - // キャンセル: 旧キーのまま継続(Cookie は cookieKey のまま)。 + // キャンセル: 旧キーのまま継続(IndexedDB は idbKey のまま)。 // 拒否したキーが URL ハッシュに残ると reload のたびに再プロンプトになるため、 // このケースだけハッシュを消去する。 history.replaceState(null, '', location.pathname + location.search) @@ -186,13 +189,13 @@ // 6. キーの確定 let resolvedKey: string | null if (keyChangeCancelled) { - resolvedKey = cookieKey + resolvedKey = idbKey } else if (hashKey) { // キー変更でサブスクなし、または同一キーの再アクセス await saveKey(hashKey) resolvedKey = hashKey } else { - resolvedKey = cookieKey ?? migratedKey + resolvedKey = idbKey } if (resolvedKey) setAccessKey(resolvedKey) @@ -380,7 +383,7 @@ } endAdornment={os === 'Other' || isStandalone ? - {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'})}} disabled={!!isSubscribed || isLoading}> + {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'});removeAccessKeyFromIDB()}} disabled={!!isSubscribed || isLoading}> diff --git a/node/src/hooks/index.ts b/node/src/hooks/index.ts old mode 100755 new mode 100644 index 281dae5..014d82d --- a/node/src/hooks/index.ts +++ b/node/src/hooks/index.ts @@ -1,2 +1,2 @@ -export { useIndexedDB } from './useIndexedDB' +export { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from './useIndexedDB' export { useWebRTC } from './useWebRTC' \ No newline at end of file diff --git a/node/src/hooks/useIndexedDB.tsx b/node/src/hooks/useIndexedDB.tsx old mode 100755 new mode 100644 index 948cb74..14d4c71 --- a/node/src/hooks/useIndexedDB.tsx +++ b/node/src/hooks/useIndexedDB.tsx @@ -4,6 +4,48 @@ const dbName = 'pwa-db' const storeName = 'subscriptions' +// アクセスキーのバックアップを通話履歴と同じ store に置くための予約 callId。 +// Cookie は Android ホーム画面 PWA で消えることがあるが、IndexedDB は SW が +// push のたびに書き込むため残る。その耐久性に相乗りしてキーを退避する。 +// update() の一覧には出さないようフィルタする。 +const ACCESS_KEY_ID = '__accessKey__' + +const openPwaDB = (): Promise => + new Promise((resolve, reject) => { + const openRequest = indexedDB.open(dbName, 1) + openRequest.onupgradeneeded = () => { + const db = openRequest.result + if (!db.objectStoreNames.contains(storeName)) { + db.createObjectStore(storeName, { keyPath: 'callId' }) + } + } + openRequest.onsuccess = () => resolve(openRequest.result) + openRequest.onerror = () => reject(openRequest.error) + }) + +export const saveAccessKeyToIDB = (accessKey: string): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite') + tx.objectStore(storeName).put({ callId: ACCESS_KEY_ID, accessKey }) + tx.oncomplete = () => resolve() + tx.onerror = () => resolve() + })).catch(() => {}) + +export const loadAccessKeyFromIDB = (): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const req = db.transaction(storeName, 'readonly').objectStore(storeName).get(ACCESS_KEY_ID) + req.onsuccess = () => resolve(req.result?.accessKey ?? null) + req.onerror = () => resolve(null) + })).catch(() => null) + +export const removeAccessKeyFromIDB = (): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite') + tx.objectStore(storeName).delete(ACCESS_KEY_ID) + tx.oncomplete = () => resolve() + tx.onerror = () => resolve() + })).catch(() => {}) + export const useIndexedDB = () => { const [latestCall, setLatestCall] = useState(null) @@ -28,6 +70,11 @@ request.onsuccess = event => { const cursor = request.result if (cursor) { + // アクセスキーのバックアップレコードは通話履歴ではないのでスキップ + if (cursor.value.callId === ACCESS_KEY_ID) { + cursor.continue() + return + } // 最初のレコードが最新のレコードになる setLatestCall(cursor.value) } else { @@ -41,7 +88,7 @@ cursorRequest.onsuccess = event => { const cursor = cursorRequest.result if (cursor) { - callListTemp.push(cursor.value) + if (cursor.value.callId !== ACCESS_KEY_ID) callListTemp.push(cursor.value) cursor.continue() } else { setCallList(callListTemp) diff --git a/public.tar.gz b/public.tar.gz index 616135a..b938bce 100644 --- a/public.tar.gz +++ b/public.tar.gz Binary files differ diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js new file mode 100644 index 0000000..c9ce282 --- /dev/null +++ b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js @@ -0,0 +1 @@ +(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/src/app/actions/page.tsx b/node/src/app/actions/page.tsx index e60a5e7..d9384cb 100644 --- a/node/src/app/actions/page.tsx +++ b/node/src/app/actions/page.tsx @@ -10,7 +10,7 @@ import axios, { AxiosError } from 'axios' import { useRouter } from 'next/navigation' import './page.css' -import { useIndexedDB } from '@/hooks' +import { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB } from '@/hooks' interface Notification { title: string @@ -81,18 +81,33 @@ setMessages(messages) if(messages.length > 0) setTemplateMessage(messages[0]) } - fetch('/accessKey') - .then(res => res.ok ? res.json() : Promise.reject()) - .then(({ accessKey: cookieKey }: { accessKey: string | null }) => { - let accessKey = cookieKey - if(!accessKey) { - const lsKey = localStorage.getItem('AccessKey') - if(lsKey && lsKey.length === 20) { - fetch('/accessKey', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accessKey: lsKey }) }) - localStorage.removeItem('AccessKey') - accessKey = lsKey + // アクセスキーは IndexedDB が primary store。無ければ旧保存先 (Cookie / localStorage) + // から IndexedDB へ移行する。 + loadAccessKeyFromIDB() + .then(async (idbKey): Promise => { + if (idbKey) return idbKey + try { + const res = await fetch('/accessKey') + if (res.ok) { + const { accessKey: cookieKey }: { accessKey: string | null } = await res.json() + if (cookieKey) { + await saveAccessKeyToIDB(cookieKey) + fetch('/accessKey', { method: 'DELETE' }) + return cookieKey + } } + } catch (e) { + console.error('GET /accessKey failed', e) } + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + return lsKey + } + return null + }) + .then(accessKey => { if(!accessKey) { setAccessKeyError('アクセスキーがありません') setIsLoading(false) diff --git a/node/src/app/useWebpush.tsx b/node/src/app/useWebpush.tsx index 4a8dc66..09867dd 100644 --- a/node/src/app/useWebpush.tsx +++ b/node/src/app/useWebpush.tsx @@ -10,6 +10,7 @@ import { User } from '@/types' import jsSHA from "jssha" import { checkNotificationStatus, requestNotificationPermission } from '@/utils/ios' +import { saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from '@/hooks' function generateSHA256Hash(data: string): string { const shaObj = new jsSHA("SHA-256", "TEXT"); @@ -45,11 +46,9 @@ localStorage.setItem('Users', JSON.stringify(data.users)) setIsLoading(false) setRemoteHash(data.hash) - fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: newAccessKey }), - }) + // アクセスキーは IndexedDB を primary store として保存する。 + // Cookie は Android ホーム画面 PWA で消えるため使わない。 + saveAccessKeyToIDB(newAccessKey) return data.hash }) .catch(err => { @@ -67,46 +66,50 @@ // 初回読み込み時 useEffect(() => { - const saveKey = (key: string) => fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: key }), - }) + // アクセスキーは IndexedDB を primary store として管理する。 + // saveKey は IndexedDB への保存のみ (Cookie へは書かない)。 + const saveKey = (key: string) => saveAccessKeyToIDB(key) const init = async () => { - // 1. 現在のCookieキーを取得 - let cookieKey: string | null = null - try { - const res = await fetch('/accessKey') - if (res.ok) { - const data: { accessKey: string | null } = await res.json() - cookieKey = data.accessKey - } - } catch (e) { - console.error('GET /accessKey failed', e) - } + // 1. IndexedDB (primary) からアクセスキーを取得 + let idbKey: string | null = await loadAccessKeyFromIDB() - // 2. URLハッシュのキーを取得 (ハッシュは URL に残す。 - // deep link / bookmark / タブ復元で再適用できるようにするため。 + // 2. URLハッシュのキーを取得 (deep link / QR / タブ復元で再適用するため URL に残す。 // キー変更をキャンセルした時のみ後段で消去する)。 const hashMatch = window.location.hash.match(/[#&]accessKey=([^&]+)/) const hashKey = hashMatch ? decodeURIComponent(hashMatch[1]) : null - // 3. localStorageからCookieへ移行(CookieもURLハッシュもない場合) - let migratedKey: string | null = null - if (!cookieKey && !hashKey) { - const lsKey = localStorage.getItem('AccessKey') - if (lsKey && lsKey.length === 20) { - await saveKey(lsKey) - localStorage.removeItem('AccessKey') - migratedKey = lsKey + // 3. IndexedDB にもハッシュにも無ければ、旧保存先から IndexedDB へ移行する。 + // 3a: Cookie (__Host-raikyakun_access) → 移行後は Cookie を破棄 + // 3b: localStorage('AccessKey') + if (!idbKey && !hashKey) { + try { + const res = await fetch('/accessKey') + if (res.ok) { + const data: { accessKey: string | null } = await res.json() + if (data.accessKey) { + await saveAccessKeyToIDB(data.accessKey) + fetch('/accessKey', { method: 'DELETE' }) + idbKey = data.accessKey + } + } + } catch (e) { + console.error('GET /accessKey failed', e) + } + if (!idbKey) { + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + idbKey = lsKey + } } } // WebViewの場合 if (!('serviceWorker' in navigator)) { - const resolvedKey = hashKey ?? cookieKey ?? migratedKey - if (hashKey && hashKey !== cookieKey) await saveKey(hashKey) + const resolvedKey = hashKey ?? idbKey + if (hashKey && hashKey !== idbKey) await saveKey(hashKey) if (resolvedKey) setAccessKey(resolvedKey) setIsLoading(true) checkNotificationStatus() @@ -138,7 +141,7 @@ if (!registration.pushManager) { setIsSubscribed(false) setPushSupported(false) - const resolvedKey = hashKey ?? cookieKey ?? migratedKey + const resolvedKey = hashKey ?? idbKey if (hashKey) await saveKey(hashKey) if (resolvedKey) { setAccessKey(resolvedKey) @@ -159,13 +162,13 @@ } // 5. キー変更 + サブスク登録済み → ユーザーに確認 - const isKeyChange = hashKey !== null && hashKey !== cookieKey + const isKeyChange = hashKey !== null && hashKey !== idbKey let keyChangeCancelled = false - if (isKeyChange && subscription && cookieKey) { + if (isKeyChange && subscription && idbKey) { if (confirm('現在別のアクセスキーで登録中です。登録解除してアクセスキーを変更しますか?')) { // 確認: フル解除 → 新キーで継続 try { - await unsubscribe({ isLocalOnly: false, accessKey: cookieKey }) + await unsubscribe({ isLocalOnly: false, accessKey: idbKey }) } catch { setErrorMessage('登録解除に失敗しました。先に登録解除ボタンから解除してください。') return @@ -176,7 +179,7 @@ checkAccessKey(hashKey) return } - // キャンセル: 旧キーのまま継続(Cookie は cookieKey のまま)。 + // キャンセル: 旧キーのまま継続(IndexedDB は idbKey のまま)。 // 拒否したキーが URL ハッシュに残ると reload のたびに再プロンプトになるため、 // このケースだけハッシュを消去する。 history.replaceState(null, '', location.pathname + location.search) @@ -186,13 +189,13 @@ // 6. キーの確定 let resolvedKey: string | null if (keyChangeCancelled) { - resolvedKey = cookieKey + resolvedKey = idbKey } else if (hashKey) { // キー変更でサブスクなし、または同一キーの再アクセス await saveKey(hashKey) resolvedKey = hashKey } else { - resolvedKey = cookieKey ?? migratedKey + resolvedKey = idbKey } if (resolvedKey) setAccessKey(resolvedKey) @@ -380,7 +383,7 @@ } endAdornment={os === 'Other' || isStandalone ? - {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'})}} disabled={!!isSubscribed || isLoading}> + {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'});removeAccessKeyFromIDB()}} disabled={!!isSubscribed || isLoading}> diff --git a/node/src/hooks/index.ts b/node/src/hooks/index.ts old mode 100755 new mode 100644 index 281dae5..014d82d --- a/node/src/hooks/index.ts +++ b/node/src/hooks/index.ts @@ -1,2 +1,2 @@ -export { useIndexedDB } from './useIndexedDB' +export { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from './useIndexedDB' export { useWebRTC } from './useWebRTC' \ No newline at end of file diff --git a/node/src/hooks/useIndexedDB.tsx b/node/src/hooks/useIndexedDB.tsx old mode 100755 new mode 100644 index 948cb74..14d4c71 --- a/node/src/hooks/useIndexedDB.tsx +++ b/node/src/hooks/useIndexedDB.tsx @@ -4,6 +4,48 @@ const dbName = 'pwa-db' const storeName = 'subscriptions' +// アクセスキーのバックアップを通話履歴と同じ store に置くための予約 callId。 +// Cookie は Android ホーム画面 PWA で消えることがあるが、IndexedDB は SW が +// push のたびに書き込むため残る。その耐久性に相乗りしてキーを退避する。 +// update() の一覧には出さないようフィルタする。 +const ACCESS_KEY_ID = '__accessKey__' + +const openPwaDB = (): Promise => + new Promise((resolve, reject) => { + const openRequest = indexedDB.open(dbName, 1) + openRequest.onupgradeneeded = () => { + const db = openRequest.result + if (!db.objectStoreNames.contains(storeName)) { + db.createObjectStore(storeName, { keyPath: 'callId' }) + } + } + openRequest.onsuccess = () => resolve(openRequest.result) + openRequest.onerror = () => reject(openRequest.error) + }) + +export const saveAccessKeyToIDB = (accessKey: string): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite') + tx.objectStore(storeName).put({ callId: ACCESS_KEY_ID, accessKey }) + tx.oncomplete = () => resolve() + tx.onerror = () => resolve() + })).catch(() => {}) + +export const loadAccessKeyFromIDB = (): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const req = db.transaction(storeName, 'readonly').objectStore(storeName).get(ACCESS_KEY_ID) + req.onsuccess = () => resolve(req.result?.accessKey ?? null) + req.onerror = () => resolve(null) + })).catch(() => null) + +export const removeAccessKeyFromIDB = (): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite') + tx.objectStore(storeName).delete(ACCESS_KEY_ID) + tx.oncomplete = () => resolve() + tx.onerror = () => resolve() + })).catch(() => {}) + export const useIndexedDB = () => { const [latestCall, setLatestCall] = useState(null) @@ -28,6 +70,11 @@ request.onsuccess = event => { const cursor = request.result if (cursor) { + // アクセスキーのバックアップレコードは通話履歴ではないのでスキップ + if (cursor.value.callId === ACCESS_KEY_ID) { + cursor.continue() + return + } // 最初のレコードが最新のレコードになる setLatestCall(cursor.value) } else { @@ -41,7 +88,7 @@ cursorRequest.onsuccess = event => { const cursor = cursorRequest.result if (cursor) { - callListTemp.push(cursor.value) + if (cursor.value.callId !== ACCESS_KEY_ID) callListTemp.push(cursor.value) cursor.continue() } else { setCallList(callListTemp) diff --git a/public.tar.gz b/public.tar.gz index 616135a..b938bce 100644 --- a/public.tar.gz +++ b/public.tar.gz Binary files differ diff --git a/standalone.tar.gz b/standalone.tar.gz index 324d820..227fa6b 100644 --- a/standalone.tar.gz +++ b/standalone.tar.gz Binary files differ diff --git a/node/public/sw.js b/node/public/sw.js index ec808d4..fb4daa4 100644 --- a/node/public/sw.js +++ b/node/public/sw.js @@ -1 +1 @@ -!function(){"use strict";console.log("WORKER"),[{'revision':'bba7affdc37f7bc18f5d4f695e7677d6','url':'/_next/app-build-manifest.json'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/Kf5msmo6le0bHgtWC2LPY/_ssgManifest.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/actions/page-26175c66c6099729.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/call/page-01095f368fda42cc.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/app/page-b9dcee04ff2d68a3.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'Kf5msmo6le0bHgtWC2LPY','url':'/_next/static/chunks/webpack-a1a07d2ee7f44621.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file +!function(){"use strict";console.log("WORKER"),[{'revision':'62c272c7bceaf98e1e7cf19f10c2aabe','url':'/_next/app-build-manifest.json'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/178-f486c82183c0a700.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/323-83a9c245249d1973.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/357-2d2b29d0e934ef78.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/779-1ccda44af730dfe9.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/891-4a6ec0223a0c9760.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/938-b6fe7d76045b425c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/_not-found/page-cdda61631cd23728.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/actions/page-b2f9cede2932e94f.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/call/page-82700347f5e4cd14.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/layout-d6e465feefc5e35e.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/app/page-08eb303ce6f64e15.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/fd9d1056-a6ea0b22fbc3ba2c.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/framework-aec844d2ccbe7592.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-443475fc6e69d3ba.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/main-app-163f24773b3433a5.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_app-72b849fbd24ac258.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/pages/_error-7ba65e1336b92748.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':'dIV_tNgVQ4foLVBAa3drX','url':'/_next/static/chunks/webpack-daf11a5238b3aae2.js'},{'revision':'130cc505dd240a84','url':'/_next/static/css/130cc505dd240a84.css'},{'revision':'2da0ae46fcd526f6','url':'/_next/static/css/2da0ae46fcd526f6.css'},{'revision':'77599c32099fcd80','url':'/_next/static/css/77599c32099fcd80.css'},{'revision':'7e779d9a5c1a15ac','url':'/_next/static/css/7e779d9a5c1a15ac.css'},{'revision':'c155cce658e53418dec34664328b51ac','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/dIV_tNgVQ4foLVBAa3drX/_ssgManifest.js'},{'revision':'a0c5b49eea2028b7fd6e3b0d0d1c8a0a','url':'/_next/static/media/001f750b538f7a9e-s.woff2'},{'revision':'9dda5cfc9a46f256d0e131bb535e46f8','url':'/_next/static/media/19cfc7226ec3afaa-s.woff2'},{'revision':'536359ff0fc970eef8be299490b3eaff','url':'/_next/static/media/1a634e73dfeff02c-s.woff2'},{'revision':'b7627e3c9663757d70121f2ad4c8d986','url':'/_next/static/media/1e41be92c43b3255-s.p.woff2'},{'revision':'4e2553027f1d60eff32898367dd4d541','url':'/_next/static/media/21350d82a1f187e9-s.woff2'},{'revision':'1e5f06cab9f9fe1f9df22e2e2aeae2e4','url':'/_next/static/media/4120b0a488381b31-s.woff2'},{'revision':'4409a8110fdf0ba9059a609f00deafbd','url':'/_next/static/media/4f48fe9100901594-s.woff2'},{'revision':'a721fb76b97a8ad2d71e6466a663e7d1','url':'/_next/static/media/5eae37b69937655e-s.woff2'},{'revision':'f852254ed0041481aaac038e94fb24dc','url':'/_next/static/media/80841ae24d03ed90-s.woff2'},{'revision':'01ba6c2a184b8cba08b0d57167664d75','url':'/_next/static/media/8e9860b6e62d6359-s.woff2'},{'revision':'c65df4878c04253139ed838edf774dee','url':'/_next/static/media/970d71e7dcbc144d-s.woff2'},{'revision':'7b8d2e8d1d6863bd8250cdfe9b2a583e','url':'/_next/static/media/b3f718d64f9a6dea-s.woff2'},{'revision':'9e494903d6b0ffec1a1e14d34427d44d','url':'/_next/static/media/ba9851c3c22cd980-s.woff2'},{'revision':'027a89e9ab733a145db70f09b8a18b42','url':'/_next/static/media/c5fe6dc8356a8c31-s.woff2'},{'revision':'d54db44de5ccb18886ece2fda72bdfe0','url':'/_next/static/media/df0a9ae256c0569c-s.woff2'},{'revision':'65850a373e258f1c897a2b3d75eb74de','url':'/_next/static/media/e4af272ccee01ff0-s.p.woff2'},{'revision':'fd0fd1665e816597c3b3b87ac1cd28bc','url':'/images/android-chrome.png'},{'revision':'6f3e3155a3f2321e5f7405f1842faaa7','url':'/images/icon_checkbox_accept.png'},{'revision':'33149b81595b8e5f2f567682590928cf','url':'/images/icon_checkbox_reject.png'},{'revision':'133b7f2dc4ea79de526bbe6a50736e45','url':'/images/install_for_ios.png'},{'revision':'e883b3448099ec5b00b0bd043c5a7430','url':'/images/install_for_ios26.svg'},{'revision':'e19320bec37641bf5b180e1ae046ee78','url':'/images/iphone-bell-icon.png'},{'revision':'88b2400e45bcf521514b7252cbb2d959','url':'/images/iphone-settings-icon.png'},{'revision':'7712da80ea749c4448626fa030a88416','url':'/images/maskable_icon_x128.png'},{'revision':'d17df9b05031ca9232898c1d18c9f800','url':'/images/maskable_icon_x144.png'},{'revision':'97d32f45fa7117f0ef91643cf12b1f2c','url':'/images/maskable_icon_x192.png'},{'revision':'8cc5c97b7006c09dfb4af4e2d9383cee','url':'/images/maskable_icon_x384.png'},{'revision':'3484c469af447168b6f232e40f1ef72e','url':'/images/maskable_icon_x512.png'},{'revision':'752bab2112e77141088d13e6e9dcf6df','url':'/images/らいきゃくんアプリ.png'},{'revision':'758c6c5ac011effba3c9d102f51356a6','url':'/sw.js'},{'revision':'facf02c3cd022e48ee4dcada88dd5d1f','url':'/url.png'}],self.addEventListener("push",function(o){console.log("data",o.data.text());let{message:n,callId:i,createdAt:r,visitor:s,dst:c}=JSON.parse(o.data.text()),a=t().then(t=>{var o;return o={callId:i,createdAt:r,visitor:s,dst:c},new Promise((n,i)=>{let r=t.transaction([e],"readwrite").objectStore(e).put(o);r.onerror=e=>i(e.target.errorCode),r.onsuccess=e=>n(e.target.result)})}),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:r,visitor:s,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});o.waitUntil(Promise.all([a,l]).catch(e=>{console.error("Error in one of the push event processes:",e)}))}),self.addEventListener("notificationclick",function(e){var t;console.log("notificationclick");let o=null==e?void 0:null===(t=e.notification)||void 0===t?void 0:t.data;e.notification.close(),e.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then(function(t){let n="/actions?data=".concat(encodeURIComponent(o),"&action=").concat(e.action);for(let e=0;e{let n=indexedDB.open("pwa-db",1);n.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},n.onerror=e=>o(e.target.errorCode),n.onsuccess=e=>t(e.target.result)})}async function o(e){try{console.log(e);let o=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:e})});if(!o.ok)throw Error("Failed to subscribe to push service.");let n=await o.json(),i=await t();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(e){console.error("Error in subscribePush:",e)}}async function n(e){if(console.log(e),!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:e})})).ok)throw Error("Failed to subscribe to push service.")}}(); \ No newline at end of file diff --git a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js b/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js deleted file mode 100644 index c9ce282..0000000 --- a/node/public/worker-Kf5msmo6le0bHgtWC2LPY.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js new file mode 100644 index 0000000..c9ce282 --- /dev/null +++ b/node/public/worker-dIV_tNgVQ4foLVBAa3drX.js @@ -0,0 +1 @@ +(()=>{"use strict";console.log("WORKER"),self.__WB_MANIFEST,self.addEventListener("push",(function(t){console.log("data",t.data.text());const{message:n,callId:i,createdAt:s,visitor:r,dst:c}=JSON.parse(t.data.text()),a=o().then((t=>function(t,o){return new Promise(((n,i)=>{const s=t.transaction([e],"readwrite").objectStore(e).put(o);s.onerror=t=>i(t.target.errorCode),s.onsuccess=t=>n(t.target.result)}))}(t,{callId:i,createdAt:s,visitor:r,dst:c}))),l=registration.showNotification(n,{body:"30秒以内に回答してください",icon:"/icons/android-chrome-192x192.png",data:JSON.stringify({callId:i,createdAt:s,visitor:r,dst:c}),actions:[{action:"accept",title:"対応可能"},{action:"reject",title:"対応不可"}]});t.waitUntil(Promise.all([a,l]).catch((t=>{console.error("Error in one of the push event processes:",t)})))})),self.addEventListener("notificationclick",(function(t){var e;console.log("notificationclick");const o=null==t||null===(e=t.notification)||void 0===e?void 0:e.data;t.notification.close(),t.waitUntil(clients.matchAll({type:"window",includeUncontrolled:!0,userVisibleOnly:!0}).then((function(e){let n=`/actions?data=${encodeURIComponent(o)}&action=${t.action}`;for(let t=0;t{const i=indexedDB.open(t,1);i.onupgradeneeded=t=>{t.target.result.createObjectStore(e,{keyPath:"id"})},i.onerror=t=>n(t.target.errorCode),i.onsuccess=t=>o(t.target.result)}))}async function n(t){try{console.log(t);const e=await fetch("/api/mobile",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({registration:t})});if(!e.ok)throw new Error("Failed to subscribe to push service.");const n=await e.json(),i=await o();await storeIdentifier(i,n.identifier),console.log("Identifier stored in IndexedDB:",n.identifier)}catch(t){console.error("Error in subscribePush:",t)}}async function i(t){console.log(t);if(!(await fetch("/api/mobile",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({oldSubscription:t})})).ok)throw new Error("Failed to subscribe to push service.")}})(); \ No newline at end of file diff --git a/node/src/app/actions/page.tsx b/node/src/app/actions/page.tsx index e60a5e7..d9384cb 100644 --- a/node/src/app/actions/page.tsx +++ b/node/src/app/actions/page.tsx @@ -10,7 +10,7 @@ import axios, { AxiosError } from 'axios' import { useRouter } from 'next/navigation' import './page.css' -import { useIndexedDB } from '@/hooks' +import { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB } from '@/hooks' interface Notification { title: string @@ -81,18 +81,33 @@ setMessages(messages) if(messages.length > 0) setTemplateMessage(messages[0]) } - fetch('/accessKey') - .then(res => res.ok ? res.json() : Promise.reject()) - .then(({ accessKey: cookieKey }: { accessKey: string | null }) => { - let accessKey = cookieKey - if(!accessKey) { - const lsKey = localStorage.getItem('AccessKey') - if(lsKey && lsKey.length === 20) { - fetch('/accessKey', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accessKey: lsKey }) }) - localStorage.removeItem('AccessKey') - accessKey = lsKey + // アクセスキーは IndexedDB が primary store。無ければ旧保存先 (Cookie / localStorage) + // から IndexedDB へ移行する。 + loadAccessKeyFromIDB() + .then(async (idbKey): Promise => { + if (idbKey) return idbKey + try { + const res = await fetch('/accessKey') + if (res.ok) { + const { accessKey: cookieKey }: { accessKey: string | null } = await res.json() + if (cookieKey) { + await saveAccessKeyToIDB(cookieKey) + fetch('/accessKey', { method: 'DELETE' }) + return cookieKey + } } + } catch (e) { + console.error('GET /accessKey failed', e) } + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + return lsKey + } + return null + }) + .then(accessKey => { if(!accessKey) { setAccessKeyError('アクセスキーがありません') setIsLoading(false) diff --git a/node/src/app/useWebpush.tsx b/node/src/app/useWebpush.tsx index 4a8dc66..09867dd 100644 --- a/node/src/app/useWebpush.tsx +++ b/node/src/app/useWebpush.tsx @@ -10,6 +10,7 @@ import { User } from '@/types' import jsSHA from "jssha" import { checkNotificationStatus, requestNotificationPermission } from '@/utils/ios' +import { saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from '@/hooks' function generateSHA256Hash(data: string): string { const shaObj = new jsSHA("SHA-256", "TEXT"); @@ -45,11 +46,9 @@ localStorage.setItem('Users', JSON.stringify(data.users)) setIsLoading(false) setRemoteHash(data.hash) - fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: newAccessKey }), - }) + // アクセスキーは IndexedDB を primary store として保存する。 + // Cookie は Android ホーム画面 PWA で消えるため使わない。 + saveAccessKeyToIDB(newAccessKey) return data.hash }) .catch(err => { @@ -67,46 +66,50 @@ // 初回読み込み時 useEffect(() => { - const saveKey = (key: string) => fetch('/accessKey', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accessKey: key }), - }) + // アクセスキーは IndexedDB を primary store として管理する。 + // saveKey は IndexedDB への保存のみ (Cookie へは書かない)。 + const saveKey = (key: string) => saveAccessKeyToIDB(key) const init = async () => { - // 1. 現在のCookieキーを取得 - let cookieKey: string | null = null - try { - const res = await fetch('/accessKey') - if (res.ok) { - const data: { accessKey: string | null } = await res.json() - cookieKey = data.accessKey - } - } catch (e) { - console.error('GET /accessKey failed', e) - } + // 1. IndexedDB (primary) からアクセスキーを取得 + let idbKey: string | null = await loadAccessKeyFromIDB() - // 2. URLハッシュのキーを取得 (ハッシュは URL に残す。 - // deep link / bookmark / タブ復元で再適用できるようにするため。 + // 2. URLハッシュのキーを取得 (deep link / QR / タブ復元で再適用するため URL に残す。 // キー変更をキャンセルした時のみ後段で消去する)。 const hashMatch = window.location.hash.match(/[#&]accessKey=([^&]+)/) const hashKey = hashMatch ? decodeURIComponent(hashMatch[1]) : null - // 3. localStorageからCookieへ移行(CookieもURLハッシュもない場合) - let migratedKey: string | null = null - if (!cookieKey && !hashKey) { - const lsKey = localStorage.getItem('AccessKey') - if (lsKey && lsKey.length === 20) { - await saveKey(lsKey) - localStorage.removeItem('AccessKey') - migratedKey = lsKey + // 3. IndexedDB にもハッシュにも無ければ、旧保存先から IndexedDB へ移行する。 + // 3a: Cookie (__Host-raikyakun_access) → 移行後は Cookie を破棄 + // 3b: localStorage('AccessKey') + if (!idbKey && !hashKey) { + try { + const res = await fetch('/accessKey') + if (res.ok) { + const data: { accessKey: string | null } = await res.json() + if (data.accessKey) { + await saveAccessKeyToIDB(data.accessKey) + fetch('/accessKey', { method: 'DELETE' }) + idbKey = data.accessKey + } + } + } catch (e) { + console.error('GET /accessKey failed', e) + } + if (!idbKey) { + const lsKey = localStorage.getItem('AccessKey') + if (lsKey && lsKey.length === 20) { + await saveAccessKeyToIDB(lsKey) + localStorage.removeItem('AccessKey') + idbKey = lsKey + } } } // WebViewの場合 if (!('serviceWorker' in navigator)) { - const resolvedKey = hashKey ?? cookieKey ?? migratedKey - if (hashKey && hashKey !== cookieKey) await saveKey(hashKey) + const resolvedKey = hashKey ?? idbKey + if (hashKey && hashKey !== idbKey) await saveKey(hashKey) if (resolvedKey) setAccessKey(resolvedKey) setIsLoading(true) checkNotificationStatus() @@ -138,7 +141,7 @@ if (!registration.pushManager) { setIsSubscribed(false) setPushSupported(false) - const resolvedKey = hashKey ?? cookieKey ?? migratedKey + const resolvedKey = hashKey ?? idbKey if (hashKey) await saveKey(hashKey) if (resolvedKey) { setAccessKey(resolvedKey) @@ -159,13 +162,13 @@ } // 5. キー変更 + サブスク登録済み → ユーザーに確認 - const isKeyChange = hashKey !== null && hashKey !== cookieKey + const isKeyChange = hashKey !== null && hashKey !== idbKey let keyChangeCancelled = false - if (isKeyChange && subscription && cookieKey) { + if (isKeyChange && subscription && idbKey) { if (confirm('現在別のアクセスキーで登録中です。登録解除してアクセスキーを変更しますか?')) { // 確認: フル解除 → 新キーで継続 try { - await unsubscribe({ isLocalOnly: false, accessKey: cookieKey }) + await unsubscribe({ isLocalOnly: false, accessKey: idbKey }) } catch { setErrorMessage('登録解除に失敗しました。先に登録解除ボタンから解除してください。') return @@ -176,7 +179,7 @@ checkAccessKey(hashKey) return } - // キャンセル: 旧キーのまま継続(Cookie は cookieKey のまま)。 + // キャンセル: 旧キーのまま継続(IndexedDB は idbKey のまま)。 // 拒否したキーが URL ハッシュに残ると reload のたびに再プロンプトになるため、 // このケースだけハッシュを消去する。 history.replaceState(null, '', location.pathname + location.search) @@ -186,13 +189,13 @@ // 6. キーの確定 let resolvedKey: string | null if (keyChangeCancelled) { - resolvedKey = cookieKey + resolvedKey = idbKey } else if (hashKey) { // キー変更でサブスクなし、または同一キーの再アクセス await saveKey(hashKey) resolvedKey = hashKey } else { - resolvedKey = cookieKey ?? migratedKey + resolvedKey = idbKey } if (resolvedKey) setAccessKey(resolvedKey) @@ -380,7 +383,7 @@ } endAdornment={os === 'Other' || isStandalone ? - {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'})}} disabled={!!isSubscribed || isLoading}> + {setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'});removeAccessKeyFromIDB()}} disabled={!!isSubscribed || isLoading}> diff --git a/node/src/hooks/index.ts b/node/src/hooks/index.ts old mode 100755 new mode 100644 index 281dae5..014d82d --- a/node/src/hooks/index.ts +++ b/node/src/hooks/index.ts @@ -1,2 +1,2 @@ -export { useIndexedDB } from './useIndexedDB' +export { useIndexedDB, saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from './useIndexedDB' export { useWebRTC } from './useWebRTC' \ No newline at end of file diff --git a/node/src/hooks/useIndexedDB.tsx b/node/src/hooks/useIndexedDB.tsx old mode 100755 new mode 100644 index 948cb74..14d4c71 --- a/node/src/hooks/useIndexedDB.tsx +++ b/node/src/hooks/useIndexedDB.tsx @@ -4,6 +4,48 @@ const dbName = 'pwa-db' const storeName = 'subscriptions' +// アクセスキーのバックアップを通話履歴と同じ store に置くための予約 callId。 +// Cookie は Android ホーム画面 PWA で消えることがあるが、IndexedDB は SW が +// push のたびに書き込むため残る。その耐久性に相乗りしてキーを退避する。 +// update() の一覧には出さないようフィルタする。 +const ACCESS_KEY_ID = '__accessKey__' + +const openPwaDB = (): Promise => + new Promise((resolve, reject) => { + const openRequest = indexedDB.open(dbName, 1) + openRequest.onupgradeneeded = () => { + const db = openRequest.result + if (!db.objectStoreNames.contains(storeName)) { + db.createObjectStore(storeName, { keyPath: 'callId' }) + } + } + openRequest.onsuccess = () => resolve(openRequest.result) + openRequest.onerror = () => reject(openRequest.error) + }) + +export const saveAccessKeyToIDB = (accessKey: string): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite') + tx.objectStore(storeName).put({ callId: ACCESS_KEY_ID, accessKey }) + tx.oncomplete = () => resolve() + tx.onerror = () => resolve() + })).catch(() => {}) + +export const loadAccessKeyFromIDB = (): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const req = db.transaction(storeName, 'readonly').objectStore(storeName).get(ACCESS_KEY_ID) + req.onsuccess = () => resolve(req.result?.accessKey ?? null) + req.onerror = () => resolve(null) + })).catch(() => null) + +export const removeAccessKeyFromIDB = (): Promise => + openPwaDB().then(db => new Promise((resolve) => { + const tx = db.transaction(storeName, 'readwrite') + tx.objectStore(storeName).delete(ACCESS_KEY_ID) + tx.oncomplete = () => resolve() + tx.onerror = () => resolve() + })).catch(() => {}) + export const useIndexedDB = () => { const [latestCall, setLatestCall] = useState(null) @@ -28,6 +70,11 @@ request.onsuccess = event => { const cursor = request.result if (cursor) { + // アクセスキーのバックアップレコードは通話履歴ではないのでスキップ + if (cursor.value.callId === ACCESS_KEY_ID) { + cursor.continue() + return + } // 最初のレコードが最新のレコードになる setLatestCall(cursor.value) } else { @@ -41,7 +88,7 @@ cursorRequest.onsuccess = event => { const cursor = cursorRequest.result if (cursor) { - callListTemp.push(cursor.value) + if (cursor.value.callId !== ACCESS_KEY_ID) callListTemp.push(cursor.value) cursor.continue() } else { setCallList(callListTemp) diff --git a/public.tar.gz b/public.tar.gz index 616135a..b938bce 100644 --- a/public.tar.gz +++ b/public.tar.gz Binary files differ diff --git a/standalone.tar.gz b/standalone.tar.gz index 324d820..227fa6b 100644 --- a/standalone.tar.gz +++ b/standalone.tar.gz Binary files differ diff --git a/static.tar.gz b/static.tar.gz index 0c4cdc9..80fdc08 100644 --- a/static.tar.gz +++ b/static.tar.gz Binary files differ