aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-10-03 02:25:55 -0700
committerPinapelz <yukais@pinapelz.com>2025-10-03 02:27:57 -0700
commit3d1d33c2aac15e07c3b840a1fb9428e3feda8330 (patch)
tree84a16e5b620acde37cc9cf1eba811d1f53426704
parent19fc6861e567ac8ca56476152edf11d1abb15661 (diff)
initial firebase fcm implementation
-rw-r--r--site/package.json1
-rw-r--r--site/pnpm-lock.yaml837
-rw-r--r--site/public/firebase-messaging-sw.js143
-rw-r--r--site/src/components/NotificationButton.tsx196
-rw-r--r--site/src/firebase.ts81
-rw-r--r--site/src/pages/Homepage.tsx125
6 files changed, 1325 insertions, 58 deletions
diff --git a/site/package.json b/site/package.json
index 82723ca..86cc0ef 100644
--- a/site/package.json
+++ b/site/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.1.13",
"@vercel/analytics": "^1.5.0",
+ "firebase": "^12.3.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.9.1",
diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml
index c5b7308..cb3d7c4 100644
--- a/site/pnpm-lock.yaml
+++ b/site/pnpm-lock.yaml
@@ -10,10 +10,13 @@ importers:
dependencies:
'@tailwindcss/vite':
specifier: ^4.1.13
- version: 4.1.13(vite@7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))
+ version: 4.1.13(vite@7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))
'@vercel/analytics':
specifier: ^1.5.0
version: 1.5.0(react@19.1.1)
+ firebase:
+ specifier: ^12.3.0
+ version: 12.3.0
react:
specifier: ^19.1.1
version: 19.1.1
@@ -41,7 +44,7 @@ importers:
version: 19.1.9(@types/react@19.1.13)
'@vitejs/plugin-react-swc':
specifier: ^4.1.0
- version: 4.1.0(vite@7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))
+ version: 4.1.0(vite@7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))
eslint:
specifier: ^9.35.0
version: 9.35.0(jiti@2.5.1)
@@ -62,10 +65,10 @@ importers:
version: 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)
vite:
specifier: ^7.1.6
- version: 7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)
+ version: 7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)
vite-plugin-pwa:
specifier: ^1.0.3
- version: 1.0.3(vite@7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))(workbox-build@7.3.0)(workbox-window@7.3.0)
+ version: 1.0.3(vite@7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))(workbox-build@7.3.0)(workbox-window@7.3.0)
packages:
@@ -764,6 +767,225 @@ packages:
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@firebase/ai@2.3.0':
+ resolution: {integrity: sha512-rVZgf4FszXPSFVIeWLE8ruLU2JDmPXw4XgghcC0x/lK9veGJIyu+DvyumjreVhW/RwD3E5cNPWxQunzylhf/6w==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app': 0.x
+ '@firebase/app-types': 0.x
+
+ '@firebase/analytics-compat@0.2.24':
+ resolution: {integrity: sha512-jE+kJnPG86XSqGQGhXXYt1tpTbCTED8OQJ/PQ90SEw14CuxRxx/H+lFbWA1rlFtFSsTCptAJtgyRBwr/f00vsw==}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/analytics-types@0.8.3':
+ resolution: {integrity: sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==}
+
+ '@firebase/analytics@0.10.18':
+ resolution: {integrity: sha512-iN7IgLvM06iFk8BeFoWqvVpRFW3Z70f+Qe2PfCJ7vPIgLPjHXDE774DhCT5Y2/ZU/ZbXPDPD60x/XPWEoZLNdg==}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/app-check-compat@0.4.0':
+ resolution: {integrity: sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/app-check-interop-types@0.3.3':
+ resolution: {integrity: sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==}
+
+ '@firebase/app-check-types@0.5.3':
+ resolution: {integrity: sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==}
+
+ '@firebase/app-check@0.11.0':
+ resolution: {integrity: sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/app-compat@0.5.3':
+ resolution: {integrity: sha512-rRK9YOvgsAU/+edjgubL1q1FyCMjBZZs+fAWtD36tklawkh6WZV07sNLVSceuni+a21oby6xoad+3R8dfztOrA==}
+ engines: {node: '>=20.0.0'}
+
+ '@firebase/app-types@0.9.3':
+ resolution: {integrity: sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==}
+
+ '@firebase/app@0.14.3':
+ resolution: {integrity: sha512-by1leTfZkwGycPKRWpc+p5/IhpnOj8zaScVi4RRm9fMoFYS3IE87Wzx1Yf/ruVYowXOEuLqYY3VmJw5tU3+0Bg==}
+ engines: {node: '>=20.0.0'}
+
+ '@firebase/auth-compat@0.6.0':
+ resolution: {integrity: sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/auth-interop-types@0.2.4':
+ resolution: {integrity: sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==}
+
+ '@firebase/auth-types@0.13.0':
+ resolution: {integrity: sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==}
+ peerDependencies:
+ '@firebase/app-types': 0.x
+ '@firebase/util': 1.x
+
+ '@firebase/auth@1.11.0':
+ resolution: {integrity: sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app': 0.x
+ '@react-native-async-storage/async-storage': ^1.18.1
+ peerDependenciesMeta:
+ '@react-native-async-storage/async-storage':
+ optional: true
+
+ '@firebase/component@0.7.0':
+ resolution: {integrity: sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==}
+ engines: {node: '>=20.0.0'}
+
+ '@firebase/data-connect@0.3.11':
+ resolution: {integrity: sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw==}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/database-compat@2.1.0':
+ resolution: {integrity: sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==}
+ engines: {node: '>=20.0.0'}
+
+ '@firebase/database-types@1.0.16':
+ resolution: {integrity: sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==}
+
+ '@firebase/database@1.1.0':
+ resolution: {integrity: sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==}
+ engines: {node: '>=20.0.0'}
+
+ '@firebase/firestore-compat@0.4.2':
+ resolution: {integrity: sha512-cy7ov6SpFBx+PHwFdOOjbI7kH00uNKmIFurAn560WiPCZXy9EMnil1SOG7VF4hHZKdenC+AHtL4r3fNpirpm0w==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/firestore-types@3.0.3':
+ resolution: {integrity: sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==}
+ peerDependencies:
+ '@firebase/app-types': 0.x
+ '@firebase/util': 1.x
+
+ '@firebase/firestore@4.9.2':
+ resolution: {integrity: sha512-iuA5+nVr/IV/Thm0Luoqf2mERUvK9g791FZpUJV1ZGXO6RL2/i/WFJUj5ZTVXy5pRjpWYO+ZzPcReNrlilmztA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/functions-compat@0.4.1':
+ resolution: {integrity: sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/functions-types@0.6.3':
+ resolution: {integrity: sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==}
+
+ '@firebase/functions@0.13.1':
+ resolution: {integrity: sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/installations-compat@0.2.19':
+ resolution: {integrity: sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/installations-types@0.5.3':
+ resolution: {integrity: sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==}
+ peerDependencies:
+ '@firebase/app-types': 0.x
+
+ '@firebase/installations@0.6.19':
+ resolution: {integrity: sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/logger@0.5.0':
+ resolution: {integrity: sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==}
+ engines: {node: '>=20.0.0'}
+
+ '@firebase/messaging-compat@0.2.23':
+ resolution: {integrity: sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/messaging-interop-types@0.2.3':
+ resolution: {integrity: sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==}
+
+ '@firebase/messaging@0.12.23':
+ resolution: {integrity: sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/performance-compat@0.2.22':
+ resolution: {integrity: sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/performance-types@0.2.3':
+ resolution: {integrity: sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==}
+
+ '@firebase/performance@0.7.9':
+ resolution: {integrity: sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/remote-config-compat@0.2.20':
+ resolution: {integrity: sha512-P/ULS9vU35EL9maG7xp66uljkZgcPMQOxLj3Zx2F289baTKSInE6+YIkgHEi1TwHoddC/AFePXPpshPlEFkbgg==}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/remote-config-types@0.5.0':
+ resolution: {integrity: sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==}
+
+ '@firebase/remote-config@0.7.0':
+ resolution: {integrity: sha512-dX95X6WlW7QlgNd7aaGdjAIZUiQkgWgNS+aKNu4Wv92H1T8Ue/NDUjZHd9xb8fHxLXIHNZeco9/qbZzr500MjQ==}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/storage-compat@0.4.0':
+ resolution: {integrity: sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app-compat': 0.x
+
+ '@firebase/storage-types@0.8.3':
+ resolution: {integrity: sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==}
+ peerDependencies:
+ '@firebase/app-types': 0.x
+ '@firebase/util': 1.x
+
+ '@firebase/storage@0.14.0':
+ resolution: {integrity: sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@firebase/app': 0.x
+
+ '@firebase/util@1.13.0':
+ resolution: {integrity: sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@firebase/webchannel-wrapper@1.0.5':
+ resolution: {integrity: sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==}
+
+ '@grpc/grpc-js@1.9.15':
+ resolution: {integrity: sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==}
+ engines: {node: ^8.13.0 || >=10.10.0}
+
+ '@grpc/proto-loader@0.7.15':
+ resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==}
+ engines: {node: '>=6'}
+ hasBin: true
+
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -815,6 +1037,36 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
+ '@protobufjs/aspromise@1.1.2':
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
+
+ '@protobufjs/base64@1.1.2':
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
+
+ '@protobufjs/codegen@2.0.4':
+ resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
+
+ '@protobufjs/eventemitter@1.1.0':
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
+
+ '@protobufjs/fetch@1.1.0':
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
+
+ '@protobufjs/float@1.0.2':
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
+
+ '@protobufjs/inquire@1.1.0':
+ resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
+
+ '@protobufjs/path@1.1.2':
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
+
+ '@protobufjs/pool@1.1.0':
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
+
+ '@protobufjs/utf8@1.1.0':
+ resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
+
'@rolldown/pluginutils@1.0.0-beta.35':
resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==}
@@ -1149,6 +1401,9 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@types/node@24.6.2':
+ resolution: {integrity: sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==}
+
'@types/react-dom@19.1.9':
resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==}
peerDependencies:
@@ -1270,6 +1525,10 @@ packages:
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -1367,6 +1626,10 @@ packages:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1457,6 +1720,9 @@ packages:
electron-to-chromium@1.5.221:
resolution: {integrity: sha512-/1hFJ39wkW01ogqSyYoA4goOXOtMRy6B+yvA1u42nnsEGtHzIzmk93aPISumVQeblj47JUHLC9coCjUxb1EvtQ==}
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
enhanced-resolve@5.18.3:
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'}
@@ -1576,6 +1842,10 @@ packages:
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
+ faye-websocket@0.11.4:
+ resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
+ engines: {node: '>=0.8.0'}
+
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -1600,6 +1870,9 @@ packages:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
+ firebase@12.3.0:
+ resolution: {integrity: sha512-/JVja0IDO8zPETGv4TvvBwo7RwcQFz+RQ3JBETNtUSeqsDdI9G7fhRTkCy1sPKnLzW0xpm/kL8GOj6ncndTT3g==}
+
flat-cache@4.0.1:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
@@ -1637,6 +1910,10 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -1713,6 +1990,9 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ http-parser-js@0.5.10:
+ resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==}
+
idb@7.1.1:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
@@ -1783,6 +2063,10 @@ packages:
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
engines: {node: '>= 0.4'}
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
is-generator-function@1.1.0:
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
engines: {node: '>= 0.4'}
@@ -1996,6 +2280,9 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
+ lodash.camelcase@4.3.0:
+ resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
+
lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
@@ -2008,6 +2295,9 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+ long@5.3.2:
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -2148,6 +2438,10 @@ packages:
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
engines: {node: ^14.13.1 || >=16.0.0}
+ protobufjs@7.5.4:
+ resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
+ engines: {node: '>=12.0.0'}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -2217,6 +2511,10 @@ packages:
resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==}
hasBin: true
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -2343,6 +2641,10 @@ packages:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
string.prototype.matchall@4.0.12:
resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
engines: {node: '>= 0.4'}
@@ -2363,6 +2665,10 @@ packages:
resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
engines: {node: '>=4'}
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
strip-comments@2.0.1:
resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==}
engines: {node: '>=10'}
@@ -2420,6 +2726,9 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -2460,6 +2769,9 @@ packages:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
+ undici-types@7.13.0:
+ resolution: {integrity: sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==}
+
unicode-canonical-property-names-ecmascript@2.0.1:
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
engines: {node: '>=4'}
@@ -2549,9 +2861,20 @@ packages:
yaml:
optional: true
+ web-vitals@4.2.4:
+ resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
+
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
+ websocket-driver@0.7.4:
+ resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==}
+ engines: {node: '>=0.8.0'}
+
+ websocket-extensions@0.1.4:
+ resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
+ engines: {node: '>=0.8.0'}
+
whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
@@ -2629,9 +2952,17 @@ packages:
workbox-window@7.3.0:
resolution: {integrity: sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA==}
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -2639,6 +2970,14 @@ packages:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -3428,6 +3767,336 @@ snapshots:
'@eslint/core': 0.15.2
levn: 0.4.1
+ '@firebase/ai@2.3.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/app-check-interop-types': 0.3.3
+ '@firebase/app-types': 0.9.3
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/analytics-compat@0.2.24(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/analytics': 0.10.18(@firebase/app@0.14.3)
+ '@firebase/analytics-types': 0.8.3
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+
+ '@firebase/analytics-types@0.8.3': {}
+
+ '@firebase/analytics@0.10.18(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/installations': 0.6.19(@firebase/app@0.14.3)
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/app-check-compat@0.4.0(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-check': 0.11.0(@firebase/app@0.14.3)
+ '@firebase/app-check-types': 0.5.3
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+
+ '@firebase/app-check-interop-types@0.3.3': {}
+
+ '@firebase/app-check-types@0.5.3': {}
+
+ '@firebase/app-check@0.11.0(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/app-compat@0.5.3':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/app-types@0.9.3': {}
+
+ '@firebase/app@0.14.3':
+ dependencies:
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ idb: 7.1.1
+ tslib: 2.8.1
+
+ '@firebase/auth-compat@0.6.0(@firebase/app-compat@0.5.3)(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-compat': 0.5.3
+ '@firebase/auth': 1.11.0(@firebase/app@0.14.3)
+ '@firebase/auth-types': 0.13.0(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)
+ '@firebase/component': 0.7.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+ - '@firebase/app-types'
+ - '@react-native-async-storage/async-storage'
+
+ '@firebase/auth-interop-types@0.2.4': {}
+
+ '@firebase/auth-types@0.13.0(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)':
+ dependencies:
+ '@firebase/app-types': 0.9.3
+ '@firebase/util': 1.13.0
+
+ '@firebase/auth@1.11.0(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/component@0.7.0':
+ dependencies:
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/data-connect@0.3.11(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/auth-interop-types': 0.2.4
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/database-compat@2.1.0':
+ dependencies:
+ '@firebase/component': 0.7.0
+ '@firebase/database': 1.1.0
+ '@firebase/database-types': 1.0.16
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/database-types@1.0.16':
+ dependencies:
+ '@firebase/app-types': 0.9.3
+ '@firebase/util': 1.13.0
+
+ '@firebase/database@1.1.0':
+ dependencies:
+ '@firebase/app-check-interop-types': 0.3.3
+ '@firebase/auth-interop-types': 0.2.4
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ faye-websocket: 0.11.4
+ tslib: 2.8.1
+
+ '@firebase/firestore-compat@0.4.2(@firebase/app-compat@0.5.3)(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/firestore': 4.9.2(@firebase/app@0.14.3)
+ '@firebase/firestore-types': 3.0.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+ - '@firebase/app-types'
+
+ '@firebase/firestore-types@3.0.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)':
+ dependencies:
+ '@firebase/app-types': 0.9.3
+ '@firebase/util': 1.13.0
+
+ '@firebase/firestore@4.9.2(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ '@firebase/webchannel-wrapper': 1.0.5
+ '@grpc/grpc-js': 1.9.15
+ '@grpc/proto-loader': 0.7.15
+ tslib: 2.8.1
+
+ '@firebase/functions-compat@0.4.1(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/functions': 0.13.1(@firebase/app@0.14.3)
+ '@firebase/functions-types': 0.6.3
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+
+ '@firebase/functions-types@0.6.3': {}
+
+ '@firebase/functions@0.13.1(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/app-check-interop-types': 0.3.3
+ '@firebase/auth-interop-types': 0.2.4
+ '@firebase/component': 0.7.0
+ '@firebase/messaging-interop-types': 0.2.3
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/installations-compat@0.2.19(@firebase/app-compat@0.5.3)(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/installations': 0.6.19(@firebase/app@0.14.3)
+ '@firebase/installations-types': 0.5.3(@firebase/app-types@0.9.3)
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+ - '@firebase/app-types'
+
+ '@firebase/installations-types@0.5.3(@firebase/app-types@0.9.3)':
+ dependencies:
+ '@firebase/app-types': 0.9.3
+
+ '@firebase/installations@0.6.19(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/util': 1.13.0
+ idb: 7.1.1
+ tslib: 2.8.1
+
+ '@firebase/logger@0.5.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@firebase/messaging-compat@0.2.23(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/messaging': 0.12.23(@firebase/app@0.14.3)
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+
+ '@firebase/messaging-interop-types@0.2.3': {}
+
+ '@firebase/messaging@0.12.23(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/installations': 0.6.19(@firebase/app@0.14.3)
+ '@firebase/messaging-interop-types': 0.2.3
+ '@firebase/util': 1.13.0
+ idb: 7.1.1
+ tslib: 2.8.1
+
+ '@firebase/performance-compat@0.2.22(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/performance': 0.7.9(@firebase/app@0.14.3)
+ '@firebase/performance-types': 0.2.3
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+
+ '@firebase/performance-types@0.2.3': {}
+
+ '@firebase/performance@0.7.9(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/installations': 0.6.19(@firebase/app@0.14.3)
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ web-vitals: 4.2.4
+
+ '@firebase/remote-config-compat@0.2.20(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/logger': 0.5.0
+ '@firebase/remote-config': 0.7.0(@firebase/app@0.14.3)
+ '@firebase/remote-config-types': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+
+ '@firebase/remote-config-types@0.5.0': {}
+
+ '@firebase/remote-config@0.7.0(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/installations': 0.6.19(@firebase/app@0.14.3)
+ '@firebase/logger': 0.5.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/storage-compat@0.4.0(@firebase/app-compat@0.5.3)(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app-compat': 0.5.3
+ '@firebase/component': 0.7.0
+ '@firebase/storage': 0.14.0(@firebase/app@0.14.3)
+ '@firebase/storage-types': 0.8.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@firebase/app'
+ - '@firebase/app-types'
+
+ '@firebase/storage-types@0.8.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)':
+ dependencies:
+ '@firebase/app-types': 0.9.3
+ '@firebase/util': 1.13.0
+
+ '@firebase/storage@0.14.0(@firebase/app@0.14.3)':
+ dependencies:
+ '@firebase/app': 0.14.3
+ '@firebase/component': 0.7.0
+ '@firebase/util': 1.13.0
+ tslib: 2.8.1
+
+ '@firebase/util@1.13.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@firebase/webchannel-wrapper@1.0.5': {}
+
+ '@grpc/grpc-js@1.9.15':
+ dependencies:
+ '@grpc/proto-loader': 0.7.15
+ '@types/node': 24.6.2
+
+ '@grpc/proto-loader@0.7.15':
+ dependencies:
+ lodash.camelcase: 4.3.0
+ long: 5.3.2
+ protobufjs: 7.5.4
+ yargs: 17.7.2
+
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.7':
@@ -3479,6 +4148,29 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
+ '@protobufjs/aspromise@1.1.2': {}
+
+ '@protobufjs/base64@1.1.2': {}
+
+ '@protobufjs/codegen@2.0.4': {}
+
+ '@protobufjs/eventemitter@1.1.0': {}
+
+ '@protobufjs/fetch@1.1.0':
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/inquire': 1.1.0
+
+ '@protobufjs/float@1.0.2': {}
+
+ '@protobufjs/inquire@1.1.0': {}
+
+ '@protobufjs/path@1.1.2': {}
+
+ '@protobufjs/pool@1.1.0': {}
+
+ '@protobufjs/utf8@1.1.0': {}
+
'@rolldown/pluginutils@1.0.0-beta.35': {}
'@rollup/plugin-babel@5.3.1(@babel/core@7.28.4)(rollup@2.79.2)':
@@ -3715,12 +4407,12 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.13
'@tailwindcss/oxide-win32-x64-msvc': 4.1.13
- '@tailwindcss/vite@4.1.13(vite@7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))':
+ '@tailwindcss/vite@4.1.13(vite@7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))':
dependencies:
'@tailwindcss/node': 4.1.13
'@tailwindcss/oxide': 4.1.13
tailwindcss: 4.1.13
- vite: 7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)
+ vite: 7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)
'@types/estree@0.0.39': {}
@@ -3728,6 +4420,10 @@ snapshots:
'@types/json-schema@7.0.15': {}
+ '@types/node@24.6.2':
+ dependencies:
+ undici-types: 7.13.0
+
'@types/react-dom@19.1.9(@types/react@19.1.13)':
dependencies:
'@types/react': 19.1.13
@@ -3837,11 +4533,11 @@ snapshots:
optionalDependencies:
react: 19.1.1
- '@vitejs/plugin-react-swc@4.1.0(vite@7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))':
+ '@vitejs/plugin-react-swc@4.1.0(vite@7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.35
'@swc/core': 1.13.5
- vite: 7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)
+ vite: 7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)
transitivePeerDependencies:
- '@swc/helpers'
@@ -3865,6 +4561,8 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
+ ansi-regex@5.0.1: {}
+
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
@@ -3975,6 +4673,12 @@ snapshots:
chownr@3.0.0: {}
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -4057,6 +4761,8 @@ snapshots:
electron-to-chromium@1.5.221: {}
+ emoji-regex@8.0.0: {}
+
enhanced-resolve@5.18.3:
dependencies:
graceful-fs: 4.2.11
@@ -4274,6 +4980,10 @@ snapshots:
dependencies:
reusify: 1.1.0
+ faye-websocket@0.11.4:
+ dependencies:
+ websocket-driver: 0.7.4
+
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
@@ -4295,6 +5005,39 @@ snapshots:
locate-path: 6.0.0
path-exists: 4.0.0
+ firebase@12.3.0:
+ dependencies:
+ '@firebase/ai': 2.3.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)
+ '@firebase/analytics': 0.10.18(@firebase/app@0.14.3)
+ '@firebase/analytics-compat': 0.2.24(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)
+ '@firebase/app': 0.14.3
+ '@firebase/app-check': 0.11.0(@firebase/app@0.14.3)
+ '@firebase/app-check-compat': 0.4.0(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)
+ '@firebase/app-compat': 0.5.3
+ '@firebase/app-types': 0.9.3
+ '@firebase/auth': 1.11.0(@firebase/app@0.14.3)
+ '@firebase/auth-compat': 0.6.0(@firebase/app-compat@0.5.3)(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)
+ '@firebase/data-connect': 0.3.11(@firebase/app@0.14.3)
+ '@firebase/database': 1.1.0
+ '@firebase/database-compat': 2.1.0
+ '@firebase/firestore': 4.9.2(@firebase/app@0.14.3)
+ '@firebase/firestore-compat': 0.4.2(@firebase/app-compat@0.5.3)(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)
+ '@firebase/functions': 0.13.1(@firebase/app@0.14.3)
+ '@firebase/functions-compat': 0.4.1(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)
+ '@firebase/installations': 0.6.19(@firebase/app@0.14.3)
+ '@firebase/installations-compat': 0.2.19(@firebase/app-compat@0.5.3)(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)
+ '@firebase/messaging': 0.12.23(@firebase/app@0.14.3)
+ '@firebase/messaging-compat': 0.2.23(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)
+ '@firebase/performance': 0.7.9(@firebase/app@0.14.3)
+ '@firebase/performance-compat': 0.2.22(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)
+ '@firebase/remote-config': 0.7.0(@firebase/app@0.14.3)
+ '@firebase/remote-config-compat': 0.2.20(@firebase/app-compat@0.5.3)(@firebase/app@0.14.3)
+ '@firebase/storage': 0.14.0(@firebase/app@0.14.3)
+ '@firebase/storage-compat': 0.4.0(@firebase/app-compat@0.5.3)(@firebase/app-types@0.9.3)(@firebase/app@0.14.3)
+ '@firebase/util': 1.13.0
+ transitivePeerDependencies:
+ - '@react-native-async-storage/async-storage'
+
flat-cache@4.0.1:
dependencies:
flatted: 3.3.3
@@ -4333,6 +5076,8 @@ snapshots:
gensync@1.0.0-beta.2: {}
+ get-caller-file@2.0.5: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -4413,6 +5158,8 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ http-parser-js@0.5.10: {}
+
idb@7.1.1: {}
ignore@5.3.2: {}
@@ -4485,6 +5232,8 @@ snapshots:
dependencies:
call-bound: 1.0.4
+ is-fullwidth-code-point@3.0.0: {}
+
is-generator-function@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -4656,6 +5405,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ lodash.camelcase@4.3.0: {}
+
lodash.debounce@4.0.8: {}
lodash.merge@4.6.2: {}
@@ -4664,6 +5415,8 @@ snapshots:
lodash@4.17.21: {}
+ long@5.3.2: {}
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -4785,6 +5538,21 @@ snapshots:
pretty-bytes@6.1.1: {}
+ protobufjs@7.5.4:
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/base64': 1.1.2
+ '@protobufjs/codegen': 2.0.4
+ '@protobufjs/eventemitter': 1.1.0
+ '@protobufjs/fetch': 1.1.0
+ '@protobufjs/float': 1.0.2
+ '@protobufjs/inquire': 1.1.0
+ '@protobufjs/path': 1.1.2
+ '@protobufjs/pool': 1.1.0
+ '@protobufjs/utf8': 1.1.0
+ '@types/node': 24.6.2
+ long: 5.3.2
+
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
@@ -4860,6 +5628,8 @@ snapshots:
dependencies:
jsesc: 3.0.2
+ require-directory@2.1.1: {}
+
require-from-string@2.0.2: {}
resolve-from@4.0.0: {}
@@ -5018,6 +5788,12 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
string.prototype.matchall@4.0.12:
dependencies:
call-bind: 1.0.8
@@ -5063,6 +5839,10 @@ snapshots:
is-obj: 1.0.1
is-regexp: 1.0.0
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
strip-comments@2.0.1: {}
strip-json-comments@3.1.1: {}
@@ -5119,6 +5899,8 @@ snapshots:
dependencies:
typescript: 5.9.2
+ tslib@2.8.1: {}
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
@@ -5178,6 +5960,8 @@ snapshots:
has-symbols: 1.1.0
which-boxed-primitive: 1.1.1
+ undici-types@7.13.0: {}
+
unicode-canonical-property-names-ecmascript@2.0.1: {}
unicode-match-property-ecmascript@2.0.0:
@@ -5207,18 +5991,18 @@ snapshots:
dependencies:
punycode: 2.3.1
- vite-plugin-pwa@1.0.3(vite@7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))(workbox-build@7.3.0)(workbox-window@7.3.0):
+ vite-plugin-pwa@1.0.3(vite@7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))(workbox-build@7.3.0)(workbox-window@7.3.0):
dependencies:
debug: 4.4.3
pretty-bytes: 6.1.1
tinyglobby: 0.2.15
- vite: 7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)
+ vite: 7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)
workbox-build: 7.3.0
workbox-window: 7.3.0
transitivePeerDependencies:
- supports-color
- vite@7.1.6(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0):
+ vite@7.1.6(@types/node@24.6.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0):
dependencies:
esbuild: 0.25.10
fdir: 6.5.0(picomatch@4.0.3)
@@ -5227,13 +6011,24 @@ snapshots:
rollup: 4.50.2
tinyglobby: 0.2.15
optionalDependencies:
+ '@types/node': 24.6.2
fsevents: 2.3.3
jiti: 2.5.1
lightningcss: 1.30.1
terser: 5.44.0
+ web-vitals@4.2.4: {}
+
webidl-conversions@4.0.2: {}
+ websocket-driver@0.7.4:
+ dependencies:
+ http-parser-js: 0.5.10
+ safe-buffer: 5.2.1
+ websocket-extensions: 0.1.4
+
+ websocket-extensions@0.1.4: {}
+
whatwg-url@7.1.0:
dependencies:
lodash.sortby: 4.7.0
@@ -5400,10 +6195,30 @@ snapshots:
'@types/trusted-types': 2.0.7
workbox-core: 7.3.0
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
wrappy@1.0.2: {}
+ y18n@5.0.8: {}
+
yallist@3.1.1: {}
yallist@5.0.0: {}
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
yocto-queue@0.1.0: {}
diff --git a/site/public/firebase-messaging-sw.js b/site/public/firebase-messaging-sw.js
new file mode 100644
index 0000000..056ce2b
--- /dev/null
+++ b/site/public/firebase-messaging-sw.js
@@ -0,0 +1,143 @@
+importScripts('https://www.gstatic.com/firebasejs/9.23.0/firebase-app-compat.js');
+importScripts('https://www.gstatic.com/firebasejs/9.23.0/firebase-messaging-compat.js');
+
+firebase.initializeApp({
+ apiKey: "AIzaSyAkxH71PlZJxhD7vuN_Q8kn3TtNnB09_cU",
+ authDomain: "updates-9eab8.firebaseapp.com",
+ projectId: "updates-9eab8",
+ storageBucket: "updates-9eab8.firebasestorage.app",
+ messagingSenderId: "347275855103",
+ appId: "1:347275855103:web:fb59a7504792c2736538ca"
+});
+
+const messaging = firebase.messaging();
+
+// Handle background messages
+messaging.onBackgroundMessage(function(payload) {
+ console.log('[firebase-messaging-sw.js] Received background message', payload);
+
+ // Extract notification data
+ const notificationTitle = payload.notification?.title || 'New Update';
+ const notificationBody = payload.notification?.body || 'You have a new notification';
+
+ // Build notification options with enhanced features
+ const notificationOptions = {
+ body: notificationBody,
+ icon: payload.notification?.icon || '/android/android-launchericon-192-192.png',
+ badge: '/android/android-launchericon-72-72.png',
+ vibrate: [200, 100, 200],
+ tag: payload.data?.tag || 'default-tag',
+ requireInteraction: payload.data?.requireInteraction === 'true',
+ renotify: true,
+ silent: false,
+ timestamp: Date.now(),
+ data: {
+ url: payload.data?.url || '/',
+ gameId: payload.data?.gameId,
+ ...payload.data
+ }
+ };
+
+ // Add image if provided
+ if (payload.notification?.image) {
+ notificationOptions.image = payload.notification.image;
+ }
+
+ // Add actions if provided
+ if (payload.data?.actions) {
+ try {
+ notificationOptions.actions = JSON.parse(payload.data.actions);
+ } catch (e) {
+ console.error('Failed to parse notification actions:', e);
+ }
+ }
+
+ // Show the notification
+ return self.registration.showNotification(notificationTitle, notificationOptions);
+});
+
+// Handle notification clicks
+self.addEventListener('notificationclick', function(event) {
+ console.log('[firebase-messaging-sw.js] Notification click received.', event);
+
+ event.notification.close();
+
+ // Handle action clicks
+ if (event.action) {
+ console.log('Action clicked:', event.action);
+ // You can handle different actions here
+ if (event.action === 'view') {
+ event.waitUntil(
+ clients.openWindow(event.notification.data?.url || '/')
+ );
+ } else if (event.action === 'dismiss') {
+ // Just close the notification
+ return;
+ }
+ } else {
+ // Default click behavior - open the URL
+ const clickUrl = event.notification.data?.url || '/';
+
+ event.waitUntil(
+ clients.matchAll({
+ type: 'window',
+ includeUncontrolled: true
+ }).then(function(clientList) {
+ // Check if there's already a window/tab open with the target URL
+ for (const client of clientList) {
+ if (client.url === clickUrl && 'focus' in client) {
+ return client.focus();
+ }
+ }
+ // If no existing window/tab, open a new one
+ if (clients.openWindow) {
+ return clients.openWindow(clickUrl);
+ }
+ })
+ );
+ }
+});
+
+// Handle notification close
+self.addEventListener('notificationclose', function(event) {
+ console.log('[firebase-messaging-sw.js] Notification was closed', event);
+ // You can track notification dismissals here if needed
+});
+
+// Handle service worker installation
+self.addEventListener('install', function(event) {
+ console.log('[firebase-messaging-sw.js] Service Worker installing.');
+ self.skipWaiting();
+});
+
+// Handle service worker activation
+self.addEventListener('activate', function(event) {
+ console.log('[firebase-messaging-sw.js] Service Worker activated.');
+ event.waitUntil(clients.claim());
+});
+
+// Handle push events (for debugging)
+self.addEventListener('push', function(event) {
+ console.log('[firebase-messaging-sw.js] Push event received', event);
+
+ if (event.data) {
+ try {
+ const data = event.data.json();
+ console.log('[firebase-messaging-sw.js] Push data:', data);
+ } catch (e) {
+ console.log('[firebase-messaging-sw.js] Push data text:', event.data.text());
+ }
+ }
+});
+
+// Error handling
+self.addEventListener('error', function(event) {
+ console.error('[firebase-messaging-sw.js] Service Worker error:', event);
+});
+
+// Fetch event handler for offline support (optional)
+self.addEventListener('fetch', function(event) {
+ // You can add offline caching strategies here if needed
+ // For now, just pass through the request
+ return;
+}); \ No newline at end of file
diff --git a/site/src/components/NotificationButton.tsx b/site/src/components/NotificationButton.tsx
new file mode 100644
index 0000000..8f4fb61
--- /dev/null
+++ b/site/src/components/NotificationButton.tsx
@@ -0,0 +1,196 @@
+import { useState, useEffect } from "react";
+import { messaging, initializeForegroundNotifications } from "../firebase.ts";
+import { getToken, deleteToken } from "firebase/messaging";
+
+const VAPID_KEY =
+ "BK7tpLF5Loy8Ew8bKxhTi-vOEJdxJSnu-jPyagWecLdD_SrEAt_OQS7nu0Xu3hR7AQpn0cOmgcdeeQd5zq5-Gyo";
+
+interface NotificationButtonProps {
+ className?: string;
+ isMoe?: boolean;
+}
+
+export default function NotificationButton({ className = "", isMoe = false }: NotificationButtonProps) {
+ const [permission, setPermission] = useState<NotificationPermission>("default");
+ const [isRegistered, setIsRegistered] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+ useEffect(() => {
+ // Check initial permission status
+ setPermission(Notification.permission);
+
+ // Check if service worker is registered
+ const checkRegistration = async () => {
+ if ('serviceWorker' in navigator) {
+ const registration = await navigator.serviceWorker.getRegistration('/firebase-messaging-sw.js');
+ setIsRegistered(!!registration);
+
+ // Initialize foreground notifications if already registered
+ if (registration && Notification.permission === "granted") {
+ initializeForegroundNotifications();
+ }
+ }
+ };
+
+ checkRegistration();
+ }, []);
+
+ const handleEnableNotifications = async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const permissionResult = await Notification.requestPermission();
+ setPermission(permissionResult);
+
+ if (permissionResult === "granted") {
+ // Register service worker
+ const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js');
+ console.log("Service Worker registered:", registration);
+ const token = await getToken(messaging, { vapidKey: VAPID_KEY });
+ console.log("FCM Token:", token);
+ // Store token locally (you might want to send this to your server)
+ localStorage.setItem('fcm_token', token);
+
+ // Initialize foreground notification handler
+ initializeForegroundNotifications();
+
+ setIsRegistered(true);
+ } else {
+ setError("Notification permission was denied");
+ }
+ } catch (err) {
+ console.error("Error enabling notifications:", err);
+ setError("Failed to enable notifications. Please try again.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleDisableNotifications = async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ await deleteToken(messaging);
+ console.log("FCM token deleted");
+ localStorage.removeItem('fcm_token');
+ if ('serviceWorker' in navigator) {
+ const registration = await navigator.serviceWorker.getRegistration('/firebase-messaging-sw.js');
+ if (registration) {
+ await registration.unregister();
+ console.log("Service Worker unregistered");
+ }
+ }
+
+ setIsRegistered(false);
+ } catch (err) {
+ console.error("Error disabling notifications:", err);
+ setError("Failed to disable notifications. Please try again.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // Determine button state and action
+ const getButtonContent = () => {
+ if (loading) {
+ return (
+ <>
+ <svg className="animate-spin h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
+ </svg>
+ {isRegistered ? "Disabling..." : "Enabling..."}
+ </>
+ );
+ }
+
+ if (permission === "denied") {
+ return (
+ <>
+ <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
+ </svg>
+ Notifications Blocked
+ </>
+ );
+ }
+
+ if (isRegistered && permission === "granted") {
+ return (
+ <>
+ <svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
+ <path d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
+ </svg>
+ Disable Notifications
+ </>
+ );
+ }
+
+ return (
+ <>
+ <svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
+ </svg>
+ Enable Notifications
+ </>
+ );
+ };
+
+ const handleClick = () => {
+ if (permission === "denied") {
+ // Can't re-request permission if denied
+ alert("Notifications are blocked. Please enable them in your browser settings.");
+ return;
+ }
+
+ if (isRegistered && permission === "granted") {
+ handleDisableNotifications();
+ } else {
+ handleEnableNotifications();
+ }
+ };
+
+ // Determine button styles
+ const getButtonStyles = () => {
+ if (loading || permission === "denied") {
+ return isMoe
+ ? `bg-pink-300 cursor-not-allowed opacity-60`
+ : `bg-gray-600 cursor-not-allowed opacity-60`;
+ }
+
+ if (isMoe) {
+ return isRegistered
+ ? `bg-pink-600 text-white hover:bg-pink-700`
+ : `bg-pink-500 text-white hover:bg-pink-600`;
+ } else {
+ return isRegistered
+ ? `bg-purple-700 text-white hover:bg-purple-800`
+ : `bg-purple-600 text-white hover:bg-purple-700`;
+ }
+ };
+
+ return (
+ <div className="flex flex-col items-center gap-2">
+ <button
+ onClick={handleClick}
+ disabled={loading || permission === "denied"}
+ className={`flex items-center justify-center px-4 py-2 rounded-lg font-semibold transition-colors ${getButtonStyles()} ${className}`}
+ >
+ {getButtonContent()}
+ </button>
+ {error && (
+ <p className={`text-sm ${isMoe ? "text-pink-600" : "text-red-500"}`}>
+ {error}
+ </p>
+ )}
+ {permission === "denied" && (
+ <p className={`text-xs ${isMoe ? "text-pink-600" : "text-gray-400"}`}>
+ To enable notifications, update your browser settings
+ </p>
+ )}
+ </div>
+ );
+}
diff --git a/site/src/firebase.ts b/site/src/firebase.ts
new file mode 100644
index 0000000..e908e58
--- /dev/null
+++ b/site/src/firebase.ts
@@ -0,0 +1,81 @@
+import { initializeApp } from "firebase/app";
+import { getMessaging, Messaging, onMessage } from "firebase/messaging";
+
+const firebaseConfig = {
+ apiKey: "AIzaSyAkxH71PlZJxhD7vuN_Q8kn3TtNnB09_cU",
+ authDomain: "updates-9eab8.firebaseapp.com",
+ projectId: "updates-9eab8",
+ storageBucket: "updates-9eab8.firebasestorage.app",
+ messagingSenderId: "347275855103",
+ appId: "1:347275855103:web:fb59a7504792c2736538ca"
+};
+
+const app = initializeApp(firebaseConfig);
+
+export const messaging: Messaging = getMessaging(app);
+
+// Handle foreground messages
+export const initializeForegroundNotifications = () => {
+ onMessage(messaging, (payload) => {
+ console.log('[firebase.ts] Message received in foreground:', payload);
+
+ // Check if browser supports notifications
+ if (!("Notification" in window)) {
+ console.log("This browser does not support desktop notifications");
+ return;
+ }
+
+ // Check notification permission
+ if (Notification.permission === "granted") {
+ // Create notification
+ const notificationTitle = payload.notification?.title || 'New Update';
+ const notificationOptions: NotificationOptions = {
+ body: payload.notification?.body || 'You have a new notification',
+ icon: payload.notification?.icon || '/android/android-launchericon-192-192.png',
+ badge: '/android/android-launchericon-72-72.png',
+ tag: payload.data?.tag || 'default-tag',
+ requireInteraction: payload.data?.requireInteraction === 'true',
+ silent: false,
+ data: {
+ url: payload.data?.url || '/',
+ gameId: payload.data?.gameId,
+ ...payload.data
+ }
+ };
+
+ // Add image if provided
+ if (payload.notification?.image) {
+ notificationOptions.badge = payload.notification.image;
+ }
+
+ // Create and show the notification
+ const notification = new Notification(notificationTitle, notificationOptions);
+
+ // Handle notification click
+ notification.onclick = (event) => {
+ event.preventDefault();
+ notification.close();
+
+ // Navigate to the URL if provided
+ const url = payload.data?.url || '/';
+ window.open(url, '_blank');
+ };
+
+ // Handle notification error
+ notification.onerror = (event) => {
+ console.error('[firebase.ts] Notification error:', event);
+ };
+
+ // Auto-close notification after 10 seconds if not require interaction
+ if (payload.data?.requireInteraction !== 'true') {
+ setTimeout(() => {
+ notification.close();
+ }, 10000);
+ }
+ } else {
+ console.log('[firebase.ts] Notification permission not granted');
+ }
+ });
+
+ console.log('[firebase.ts] Foreground notification handler initialized');
+};
diff --git a/site/src/pages/Homepage.tsx b/site/src/pages/Homepage.tsx
index 2f35280..db658ca 100644
--- a/site/src/pages/Homepage.tsx
+++ b/site/src/pages/Homepage.tsx
@@ -4,6 +4,7 @@ import { useParams, useSearchParams } from "react-router-dom";
import { getGameTitle } from "../utils.ts";
import TitleBar from "../components/TitleBar";
import { GameNotes } from "../components/GameNotes";
+import NotificationButton from "../components/NotificationButton";
interface ArcadeNewsAPIData {
fetch_time: number;
@@ -14,8 +15,9 @@ export default function Home() {
const { gameId } = useParams<{ gameId?: string }>();
const [searchParams] = useSearchParams();
const isMoe = searchParams.has("moe");
- const rssFeedUrl = import.meta.env.VITE_NEWS_BASE_URL + "/" +gameId + "_news.xml";
- const newsAPIBase = import.meta.env.VITE_NEWS_BASE_URL
+ const rssFeedUrl =
+ import.meta.env.VITE_NEWS_BASE_URL + "/" + gameId + "_news.xml";
+ const newsAPIBase = import.meta.env.VITE_NEWS_BASE_URL;
const [newsFeedData, setNewsFeedData] = useState<ArcadeNewsAPIData | null>(
null,
@@ -29,9 +31,7 @@ export default function Home() {
setError(false);
const newsDataFileName = gameId ? `${gameId}_news.json` : "news.json";
try {
- const response = await fetch(
- newsAPIBase+"/" + `${newsDataFileName}`,
- );
+ const response = await fetch(newsAPIBase + "/" + `${newsDataFileName}`);
if (!response.ok) {
throw new Error(`Failed to fetch news: ${response.statusText}`);
}
@@ -70,21 +70,31 @@ export default function Home() {
className={`${isMoe ? "bg-pink-100" : "bg-gray-950"} min-h-screen flex items-center justify-center`}
>
<div className="text-center px-4">
- <div className={`${isMoe ? "text-pink-500" : "text-purple-500"} text-8xl font-bold mb-4`}>
+ <div
+ className={`${isMoe ? "text-pink-500" : "text-purple-500"} text-8xl font-bold mb-4`}
+ >
404
</div>
- <h1 className={`${isMoe ? "text-pink-900" : "text-white"} text-3xl font-bold mb-4`}>
+ <h1
+ className={`${isMoe ? "text-pink-900" : "text-white"} text-3xl font-bold mb-4`}
+ >
News Not Found
</h1>
- <p className={`${isMoe ? "text-pink-700" : "text-gray-400"} text-lg mb-8`}>
+ <p
+ className={`${isMoe ? "text-pink-700" : "text-gray-400"} text-lg mb-8`}
+ >
{gameId
? `Unable to fetch news for ${getGameTitle(gameId)}`
- : "Unable to fetch news feed"
- }
+ : "Unable to fetch news feed"}
</p>
- <div className={`${isMoe ? "bg-pink-200" : "bg-gray-800"} rounded-lg p-6 max-w-md mx-auto`}>
- <p className={`${isMoe ? "text-pink-800" : "text-gray-300"} mb-4`}>
- The news feed you're looking for might be temporarily unavailable or doesn't exist.
+ <div
+ className={`${isMoe ? "bg-pink-200" : "bg-gray-800"} rounded-lg p-6 max-w-md mx-auto`}
+ >
+ <p
+ className={`${isMoe ? "text-pink-800" : "text-gray-300"} mb-4`}
+ >
+ The news feed you're looking for might be temporarily
+ unavailable or doesn't exist.
</p>
<a
href="/"
@@ -127,43 +137,64 @@ export default function Home() {
</div>
</div>
) : (
- <div
- className={`${isMoe ? "bg-pink-200 text-pink-900" : "bg-gray-800 text-white"} rounded-lg p-6 text-center shadow-lg`}
- >
- <h1 className="text-2xl font-bold">Welcome to 573-UPDATES</h1>
- <h2
- className={`text-2xl font-extrabold mb-4 tracking-widest text-center uppercase glow-neon ${
- isMoe ? "text-pink-500" : "text-[#00FF00]"
- }`}
+ <>
+ <div
+ className={`${isMoe ? "bg-pink-200 text-pink-900" : "bg-gray-800 text-white"} rounded-lg p-6 text-center shadow-lg`}
>
- SECOND STYLE
- </h2>
- <div className="floating">
- <img
- src="/liris.webp"
- className="w-48 mx-auto mb-2 object-contain rounded-2xl"
- />
- </div>
- <p>
- News and Information for various arcade games is aggregated
- here!
- </p>
- <p className="mt-2">
- RSS feeds are available on each game's individual page
- </p>
- <p className="mt-2">
- Please see the{" "}
- <a
- href="https://github.com/pinapelz/573-updates"
- className="text-blue-500 hover:underline"
+ <h1 className="text-2xl font-bold">Welcome to 573-UPDATES</h1>
+ <h2
+ className={`text-2xl font-extrabold mb-4 tracking-widest text-center uppercase glow-neon ${
+ isMoe ? "text-pink-500" : "text-[#00FF00]"
+ }`}
>
- GitHub
- </a>{" "}
- for API information
- </p>
- </div>
+ SECOND STYLE
+ </h2>
+ <div className="floating">
+ <img
+ src="/liris.webp"
+ className="w-48 mx-auto mb-2 object-contain rounded-2xl"
+ />
+ </div>
+ <p>
+ News and Information for various arcade games is aggregated
+ here!
+ </p>
+ <p className="mt-2">
+ RSS feeds are available on each game's individual page
+ </p>
+ <p className="mt-2">
+ Please see the{" "}
+ <a
+ href="https://github.com/pinapelz/573-updates"
+ className="text-blue-500 hover:underline"
+ >
+ GitHub
+ </a>{" "}
+ for API information
+ </p>
+ <div className="mt-6">
+ <details className="rounded-lg">
+ <summary
+ className={`cursor-pointer text-lg font-semibold ${
+ isMoe
+ ? "text-pink-700 hover:text-pink-500"
+ : "text-gray-300 hover:text-white"
+ }`}
+ >
+ Receive Notifications
+ </summary>
+ <div className="mt-4">
+ <NotificationButton isMoe={isMoe} />
+ <p className="mt-2">
+ Enables notifications for the main feed
+ </p>
+ </div>
+ </details>
+ </div>
+ </div>
+ </>
)}
- <NewsFeed newsItems={newsFeedData.news_posts}/>
+ <NewsFeed newsItems={newsFeedData.news_posts} />
</div>
<footer
className={`mt-8 text-center text-sm ${isMoe ? "text-pink-800" : "text-gray-400"}`}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage