From 2d4b8ea9cf88d25b0520ff4222a99cae6d016b76 Mon Sep 17 00:00:00 2001 From: Vadim Date: Fri, 16 Oct 2026 15:30:35 +0300 Subject: [PATCH] firebase implementation --- package-lock.json | 1048 ++++++++++++++++++++++- package.json | 1 + public/firebase-messaging-sw.js | 23 + src/App.vue | 16 + src/api/notification.api.ts | 31 + src/components/NotificationSettings.vue | 153 ++++ src/components/ProjectMenu.vue | 2 +- src/components/PushEnable.vue | 40 + src/components/SnackbarQueue.vue | 38 + src/composables/usePushNotifications.ts | 71 ++ src/layouts/DefaultLayout.vue | 14 +- src/lib/api-client.ts | 4 +- src/lib/firebase.ts | 81 ++ src/router/index.ts | 2 +- src/stores/snackbar.ts | 22 + src/views/HomeView.vue | 3 - src/views/users/ProfileView.vue | 5 + 17 files changed, 1542 insertions(+), 12 deletions(-) create mode 100644 public/firebase-messaging-sw.js create mode 100644 src/api/notification.api.ts create mode 100644 src/components/NotificationSettings.vue create mode 100644 src/components/PushEnable.vue create mode 100644 src/components/SnackbarQueue.vue create mode 100644 src/composables/usePushNotifications.ts create mode 100644 src/lib/firebase.ts create mode 100644 src/stores/snackbar.ts diff --git a/package-lock.json b/package-lock.json index 3cd89a6..857c0b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@vueuse/core": "^14.4.0", "better-auth": "^1.7.1", "chart.js": "^4.5.1", + "firebase": "^13.0.0", "pinia": "^4.0.3", "vue": "^3.5.41", "vue-chartjs": "^5.3.4", @@ -777,6 +778,640 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@firebase/ai": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-3.0.0.tgz", + "integrity": "sha512-66jJAi6JUmyXna89xaDONmfvupO1wNc3f2BegPwUImagbbpKdVeycE3kMqbU4jiDLL14N5PHzadovvxpaqD2hw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.5", + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.25", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.25.tgz", + "integrity": "sha512-oDagV3PHeNXBdxemksZCdj+GmUT19eCnTOcLdQePIIovFXlm3zWy3Gjeaokt9Nqx24kc4ljK8lU0XGcGmmKZ1A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/installations": "0.6.24", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.31.tgz", + "integrity": "sha512-msTlG9REujk5rjtOHNLngiuPmvaWGQte8ugL4/F6ZCMb+92Ue7SgjSEpNxINkM8ZtTrbGS83j5SZkt0r+M1Tjw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.25", + "@firebase/analytics-types": "0.8.5", + "@firebase/component": "0.7.5", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.5.tgz", + "integrity": "sha512-kdnooE7Bis2jEnsqcerRwn/UQpH5D3uvHpku7OdUM9TJN3omlu6iYtbyAQ6XkAJRgJtO0aNf9OOkW8J6D59oCA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.16.3.tgz", + "integrity": "sha512-L2mYqVV2ilnek+W4JG7yasA97TsXCwx+f7/abweX5uSp3dkPa5Luiglt3s9YWfh1fBR3SWH/AkRgAwUpDGyCjg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.13.1.tgz", + "integrity": "sha512-l8y3dmnhodXks/APAwx4tWqRl3tk8b9874KF1FJyKg1DIU+kMD0l52iz7S9/MW+eK8k6cjJg0G0iJ5KQAsQpow==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.7.tgz", + "integrity": "sha512-fBb/xSMyIKv1nDmccfzz3IAGBzx/cQOxmZai66rJzifb1hMMkztf824Xx7LhpTUe7HNUbXwY90IvJ66fi8q6yg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.13.1", + "@firebase/app-check-types": "0.5.5", + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.5.tgz", + "integrity": "sha512-qId34pVZ2CXTmtu4ofW5leiI93DvnOvIcr/5GT7MZPw5WXAi6nZoU+g9cNRgl7K90UJSbK0cXxten4meYMPyZg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.5.tgz", + "integrity": "sha512-+DF4gzFrlwGFyky38o4T/YN/r51l70yCtJ2HTkwmRj8FCMwPjm4hLH4fQbj6BUvzkiFqliBkitFsRpf1/YZkWA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-compat": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.19.tgz", + "integrity": "sha512-pdT+THcv3h43LcawVJ+MUTzofL7umBaCvCgdbr01fnTXrbQU2RSHb0Po/YsF7w+tgjCycg84zCgZbd51NmoIaA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.16.3", + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.6.tgz", + "integrity": "sha512-yPLahy7Esfu2w/yme3msVK4xTkDXQqq6szfQn8yVOQpCKiT5GVFjqNpLbuz6NkX0WuTwUidkmPewW3r4xkvpeg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/logger": "0.5.2" + } + }, + "node_modules/@firebase/auth": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.13.7.tgz", + "integrity": "sha512-00wn8cMrh1ZyEPF6RO5G081Xq1b57FN0QxChiAda0hy/D3Q5TrMk30qhWszRWCgXPRipw6yM6qtqUew3u3qEjA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.6.12", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.12.tgz", + "integrity": "sha512-Vspo2zx1RR6LB7zuM7TduD1clMLT/x3Wr97u2jf9s2JrhYArJAFKeEu8bRWF1DoX54z6W9N77SdPRbPAiLKPpg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "1.13.7", + "@firebase/auth-types": "0.13.2", + "@firebase/component": "0.7.5", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.6.tgz", + "integrity": "sha512-FgwZqDrBqgK0BHI70QTv1v/5wmuDF9f8fsJFvKSxoRlK2PPfSQtZAk2jhXu4pe9BZzFi/e4UYusU/bEQHdf6Qg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-types": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.2.tgz", + "integrity": "sha512-OU+miuoSxIWYN7GT291d2ylVJBL3k/OePo7/JDSoQtkFQoEiGBc4AHuMjj/seqFIsHrqnjrAfRCk4pfufoJolQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.5.tgz", + "integrity": "sha512-vuFDcL91Q+2ZuBJkyOh86T4q0B4ffNTDjc/A38tybO56odQABxRTFLTIowCWqAKeIcgo37GowWMVgFF73gD8Qw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.7.4.tgz", + "integrity": "sha512-su1aGWlzhxb+xtggCUSsufJn1FDa06SBDK71y+fpQ+g2zMhMExik6FUv/odkKzOF/xfWVjabCbWBcjJY3MceDA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.6", + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.5.tgz", + "integrity": "sha512-/JGpvszLoNXNgzilRXocigGfFF4hbcPA9wN1i1kjJx6oKkXgkHteZYl3lQs1lJX3ETDf6bv7zI15lPMVUp4sAQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.5", + "@firebase/auth-interop-types": "0.2.6", + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.7.tgz", + "integrity": "sha512-lBq9sJm8MnJINKJnkAKSOj2MbC66xGoSVCcGkPtstl+lkoKEBtD46qHLbuDgW/WbLPoKVm17RIN8BxHj+Z2F2w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/database": "1.1.5", + "@firebase/database-types": "1.0.22", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + }, + "peerDependenciesMeta": { + "@firebase/app": { + "optional": true + }, + "@firebase/app-compat": { + "optional": true + } + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.22.tgz", + "integrity": "sha512-YAZNXsjY9EQQ+pKw/3ax8n5FgolHC7Qew7EY5RceYdl0R2ZP+kCv1O0DkKlcceue8uQCOreHCNN7PWVFpY1Nug==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.6", + "@firebase/util": "1.15.3" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.18.0.tgz", + "integrity": "sha512-28aKMUeM+L4fuxx5hurvQK31KLh/KRutaP1544avVWuR4mNTAC0FQoHg8gbkN5NoJj3yN9/ZFGSFSVPR8/x/BQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "@firebase/webchannel-wrapper": "1.0.7", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "re2js": "^2.8.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.15.tgz", + "integrity": "sha512-/hWibRTNFsWkZZi1dbDS1hiJIMezYUMPrRjMmBXevel5b368XWY+NvfOaSxgy3+fDb6I+uZ7SKGFyL06gW3vTA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/firestore": "4.18.0", + "@firebase/firestore-types": "3.0.5", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.5.tgz", + "integrity": "sha512-dbdMAQkMd5dwWc48eupz/Y6/E9ruat3+gY5lhVKscvvT/HnDBEEMzJW38zdKhgdnglZHGk2vUsJMOHAphMHGMA==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.14.0.tgz", + "integrity": "sha512-DhuYFr0eMhp+s/PNEk6SiMsYkc00+XVOUeKbrl8MOzQNZI3SKQpqoDR1d+keNDAutwmHRWTUAdXBoVPD+xxVUw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.5", + "@firebase/auth-interop-types": "0.2.6", + "@firebase/component": "0.7.5", + "@firebase/messaging-interop-types": "0.2.6", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.5.0.tgz", + "integrity": "sha512-T3BDIToESZUHt7438wyKYMkRjKg3m1xAIux5fVpNvTRQlDOFLukWniQWBDhJ2znpU9kxMTR6+e31TVo8P15PZw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/functions": "0.14.0", + "@firebase/functions-types": "0.6.5", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.5.tgz", + "integrity": "sha512-Zc0pURjthHXzSj54ZivCkzKDSV1r/wIpnmHdhq82q2yFFPVoncG/ZJjnVMiANvQfeww/QnElhzODpfwUucVwdA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/installations": { + "version": "0.6.24", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.24.tgz", + "integrity": "sha512-Ui52ey8wHoWqkBbXRKJEKYWylI0JZogZmoLS+o8Anh1bxWtK27ZYjLla1UJAAdC5DCKLJ2qAp0VpJOjg8VOX1g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/util": "1.15.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.24", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.24.tgz", + "integrity": "sha512-8M5nlcWwYt881x83COP2odq5vgf+NgwJh+RMd4LRSv8JI1pxwDfgrDJOERrjTgfC9J5Z0vnQX4b1pdN914e0Zw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/installations": "0.6.24", + "@firebase/installations-types": "0.5.5", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.5.tgz", + "integrity": "sha512-e9UYcju3puDl1vdrcKIi5dExzHLameOT/Tc61Q48PYwxtsM1NzZh/ikGbdBQTsbRgg0EMZqdPr0/m5ODUBobrg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.2.tgz", + "integrity": "sha512-J2VO4NFTc0xQFrxV1B/lm5balicm9cwuX2acR9Yn41fN8KgUeQFo+VJV222IqW2FPSXKDu9uo5WdQFWf9TPbYg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.13.3.tgz", + "integrity": "sha512-nRXPAp+z0kA3KziJtRY9kktHECvRwaaONzHvislFgllhCwJMscW7icWNQA5IjyP8Uc2+GS/4px8Uz6rx6ftTag==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/installations": "0.6.24", + "@firebase/messaging-interop-types": "0.2.6", + "@firebase/util": "1.15.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.30", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.30.tgz", + "integrity": "sha512-m1DPT2ET5x0qYqXLoHwKMAyf1mgZZOhuoE6H6/H4OPsp8zSxMIDEFPaW7MaffPJsCA5J7kHzo+BHVNXK4ZoV+w==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/messaging": "0.13.3", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.6.tgz", + "integrity": "sha512-MVzvkKe2V4H2dHu5oOxRfeKQcfTwWmCgnzsC4V1q3ixun5iiL8riCZ2qI35rDhCL4glPGiu0jHxDbpVNuTKfow==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance": { + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.14.tgz", + "integrity": "sha512-9PH1XEZVHErxGdbXluvGz4Uyjw4W955H8v7Mv9rHIybRrg5F2Ac2tvf5+mSf5EtnxWNmxrD2WLnvMFaZP4sMrQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/installations": "0.6.24", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.27", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.27.tgz", + "integrity": "sha512-O/ozTf/EbChN94Pk7bd1eUC0PAC36726AwsaiJyC0bZzSBWfLGSWASGPH5pebUWXQPJtGxIJYRkkbX5l0Qa+Hw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/performance": "0.7.14", + "@firebase/performance-types": "0.2.5", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.5.tgz", + "integrity": "sha512-PRzOgB+/M+6AKlEkY8a9xy5Ff5SfZPIh4iShXhn2WAcL/euSbK7bdLqWysHduunNJhGFsXa22Ag3ixqL6rQtYg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/remote-config": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.9.3.tgz", + "integrity": "sha512-9DbuFNAFeUbVJQZmwLZfdoLRNFWV43V4UqaRpDkaSIdTmVWhzctPeISGkWe4sKTsqpoyldRumquS0gqWZg05fQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/installations": "0.6.24", + "@firebase/logger": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.30", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.30.tgz", + "integrity": "sha512-Q4sQKhQ8TPEMOcCUvUBfGeAP1DfcG7FGBfA9yFUn/XRr4vSToY0DTmK28Nh7lgLQYzs6D5Ng6knSoXH+aGn1Tg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/logger": "0.5.2", + "@firebase/remote-config": "0.9.3", + "@firebase/remote-config-types": "0.5.2", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.2.tgz", + "integrity": "sha512-i8k1omVfoAnaT1ZPv2FFjxMZrATYsO/GnFgHEj5+7fAJLzi8wLnGGv1WK06oEAD6ltrWXSO1HzeNyajn/NjMWw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/storage": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.6.tgz", + "integrity": "sha512-9WjohxpjbfcySjCp8kIbd1ZhtmCQFQLqPtDQ+wmZ5PVXimTDjDsSx/RZjwwK89MjCTXB9fCdmvZkJxplZn4W1A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.6.tgz", + "integrity": "sha512-te0IsqX1bM+OZCJF7jNSKXcMqCA2yYmpshRHjX4axy0lI/P9ZP9GLjEZMVL8wrjUWF4e3cZxMZTbck3Zd5E1hA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.7.5", + "@firebase/storage": "0.14.6", + "@firebase/storage-types": "0.8.5", + "@firebase/util": "1.15.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=24.12.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.5.tgz", + "integrity": "sha512-GEDs5P+rNUfNcS+wxIdOLAHficife2YXtvTJnyi3ssrX11AAtBg+nDUdbYh8vuBzWehVQZPmztGUUnud4L+4yg==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.3.tgz", + "integrity": "sha512-c/z/gaIlaaLZEuGbE6sLUuJ61tskg1JghvhcNQzW948ASBinbVBBRnZTC4b4yt4LaEtJQkYlyzqLHcutFwEIvA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.7.tgz", + "integrity": "sha512-phBFwieDLvkZGYN9CE9ZFNEIoBVksprzsnCzQejCmCHtgwCXReeuRpoEGN9C4EbhONztv8NRV1tau6Rb9pONwQ==", + "license": "Apache-2.0" + }, "node_modules/@fontsource/roboto": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.3.0.tgz", @@ -786,6 +1421,37 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.16.tgz", + "integrity": "sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1665,6 +2331,63 @@ "dev": true, "license": "MIT" }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz", @@ -1989,7 +2712,6 @@ "version": "26.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.3.0.tgz", "integrity": "sha512-L3fgrnchriRC2ExBflb8j4uZZURHZfQsmQeyVzhjcHW4kkwVyo8/0h1B2MVzMTrYUJYu6G7EWs14hW/L9putqw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~8.3.0" @@ -2727,6 +3449,15 @@ "dev": true, "license": "MIT" }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-7.0.0.tgz", @@ -3080,6 +3811,38 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/colorjs.io": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.7.1.tgz", @@ -3243,6 +4006,12 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/entities": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", @@ -3269,7 +4038,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3603,6 +4371,18 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3663,6 +4443,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-13.0.0.tgz", + "integrity": "sha512-4cChZajVtuOijT/t7Vw1GZRTuohCxw+aoc8DAwhF+CSbS+zhqHE+i5PzEVttIySw7oKerBDRZRyDt4FynrWecg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/ai": "3.0.0", + "@firebase/analytics": "0.10.25", + "@firebase/analytics-compat": "0.2.31", + "@firebase/app": "0.16.3", + "@firebase/app-check": "0.13.1", + "@firebase/app-check-compat": "0.4.7", + "@firebase/app-compat": "0.5.19", + "@firebase/app-types": "0.9.6", + "@firebase/auth": "1.13.7", + "@firebase/auth-compat": "0.6.12", + "@firebase/data-connect": "0.7.4", + "@firebase/database": "1.1.5", + "@firebase/database-compat": "2.1.7", + "@firebase/firestore": "4.18.0", + "@firebase/firestore-compat": "0.4.15", + "@firebase/functions": "0.14.0", + "@firebase/functions-compat": "0.5.0", + "@firebase/installations": "0.6.24", + "@firebase/installations-compat": "0.2.24", + "@firebase/messaging": "0.13.3", + "@firebase/messaging-compat": "0.2.30", + "@firebase/performance": "0.7.14", + "@firebase/performance-compat": "0.2.27", + "@firebase/remote-config": "0.9.3", + "@firebase/remote-config-compat": "0.2.30", + "@firebase/storage": "0.14.6", + "@firebase/storage-compat": "0.4.6", + "@firebase/util": "1.15.3" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -3770,6 +4586,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3799,6 +4624,18 @@ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "license": "MIT" }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3852,6 +4689,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4364,6 +5210,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5043,6 +5901,29 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5090,6 +5971,15 @@ ], "license": "MIT" }, + "node_modules/re2js": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/re2js/-/re2js-2.8.6.tgz", + "integrity": "sha512-xLgQil4kIUCrAzVk9fRSkxkFNwmygLFjVxXrLc65aE1F0+Zsb8rxumFBy4XKyvgMCTL6kilDq3EZ0piE2dP/Dg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/read-package-json-fast": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-6.0.0.tgz", @@ -5127,6 +6017,15 @@ "regexp-tree": "bin/regexp-tree" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -5225,6 +6124,26 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/sass": { "version": "1.103.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.103.1.tgz", @@ -5688,6 +6607,32 @@ "node": ">=0.10.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -5802,7 +6747,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/type-check": { @@ -5873,7 +6817,6 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, "license": "MIT" }, "node_modules/unplugin": { @@ -6501,12 +7444,41 @@ } } }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0" + }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "license": "MIT" }, + "node_modules/websocket-driver": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6533,6 +7505,38 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wsl-utils": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", @@ -6560,6 +7564,15 @@ "node": ">=18" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -6582,6 +7595,33 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 7f34f2e..4d3ecee 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@vueuse/core": "^14.4.0", "better-auth": "^1.7.1", "chart.js": "^4.5.1", + "firebase": "^13.0.0", "pinia": "^4.0.3", "vue": "^3.5.41", "vue-chartjs": "^5.3.4", diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js new file mode 100644 index 0000000..bb2dfbe --- /dev/null +++ b/public/firebase-messaging-sw.js @@ -0,0 +1,23 @@ +importScripts('https://www.gstatic.com/firebasejs/13.0.0/firebase-app-compat.js') +importScripts('https://www.gstatic.com/firebasejs/13.0.0/firebase-messaging-compat.js') + +firebase.initializeApp({ + apiKey: 'AIzaSyAKY4nPht7-YsIQfhcECxVwZFx7iAnCQSM', + authDomain: 'ulitka-c811b.firebaseapp.com', + projectId: 'ulitka-c811b', + storageBucket: 'ulitka-c811b.firebasestorage.app', + messagingSenderId: '82243223520', + appId: '1:82243223520:web:2c0e68358f8d5f2d2a7585', + measurementId: 'G-CMDB21T1WG', +}) + +const messaging = firebase.messaging() + +messaging.onBackgroundMessage((payload) => { + // Если в payload есть блок "notification", SDK покажет уведомление сам — + // без этой проверки получится дубль "Новое сообщение" с пустым телом. + if (payload.notification) return + // Для data-only сообщений показываем вручную: + const { title, body } = payload.data || {} + self.registration.showNotification(title || 'Новое сообщение', { body }) +}) diff --git a/src/App.vue b/src/App.vue index 5df2415..f4a8614 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,11 +6,17 @@ import { useRoute } from 'vue-router' import { useStatusStore } from '@/stores/status.ts' import { useIssueStore } from '@/stores/issue.ts' import { useProjectStore } from '@/stores/project.ts' +import { authClient } from '@/lib/auth-client.ts' +import { usePushNotifications } from '@/composables/usePushNotifications.ts' +import SnackbarQueue from '@/components/SnackbarQueue.vue' const route = useRoute() const issueStore = useIssueStore() const statusStore = useStatusStore() const projectStore = useProjectStore() +const session = authClient.useSession() +const push = usePushNotifications() + const { currentProjectId } = storeToRefs(projectStore) interface IssueSseEvent { @@ -39,6 +45,15 @@ watch(event, () => { } }) +// FID сохраняется на бэкенде под текущим пользователем, поэтому стартуем только с сессией +watch( + () => session.value.data?.user.id, + (userId) => { + if (userId) push.autoStart().catch((e) => console.error('Failed to start push', e)) + }, + { immediate: true }, +) + const layout = computed(() => { const layout = route?.meta?.layout return layout ? `${layout}Layout` : 'DefaultLayout' @@ -58,6 +73,7 @@ onMounted(async () => { + diff --git a/src/api/notification.api.ts b/src/api/notification.api.ts new file mode 100644 index 0000000..c024423 --- /dev/null +++ b/src/api/notification.api.ts @@ -0,0 +1,31 @@ +import { apiClient } from '@/lib/api-client.ts' + +export type DevicePlatform = 'ios' | 'android' | 'web' +export type NotificationChannel = 'EMAIL' | 'PUSH' + +export interface NotificationPreference { + channel: NotificationChannel + enabled: boolean +} + +// CLIENT REQUEST + +export async function deviceTokenRegister(token: string, platform: DevicePlatform = 'web') { + return apiClient.post('/api/notifications/device-tokens', { token, platform }) +} + +export async function deviceTokenRemove(token: string) { + return apiClient.delete('/api/notifications/device-tokens', { token }) +} + +export async function preferencesList() { + return apiClient.get('/api/notifications/preferences') +} + +export async function preferenceSet(channel: NotificationChannel, enabled: boolean) { + return apiClient.put(`/api/notifications/preferences/${channel}`, { enabled }) +} + +export async function testPushSend(title?: string, body?: string) { + return apiClient.post('/api/notifications/test-push', { title, body }) +} diff --git a/src/components/NotificationSettings.vue b/src/components/NotificationSettings.vue new file mode 100644 index 0000000..3fbe870 --- /dev/null +++ b/src/components/NotificationSettings.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/src/components/ProjectMenu.vue b/src/components/ProjectMenu.vue index cff1fd7..9c39e5f 100644 --- a/src/components/ProjectMenu.vue +++ b/src/components/ProjectMenu.vue @@ -31,7 +31,7 @@ onMounted(async () => {