From 04bf4e9833b80e29c73d36c2c86545cb797db0f7 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Thu, 27 Jul 2023 12:54:54 +0530 Subject: [PATCH 01/32] added PWA notifications --- .env.example | 11 + package.json | 1 + public/firebase-messaging-sw.js | 19 + .../notifications/NotificationContent.tsx | 34 +- src/services/firebase/config.ts | 26 + src/services/firebase/messaging.ts | 28 + src/utils/env/client.ts | 56 ++ yarn.lock | 536 +++++++++++++++++- 8 files changed, 700 insertions(+), 11 deletions(-) create mode 100644 public/firebase-messaging-sw.js create mode 100644 src/services/firebase/config.ts create mode 100644 src/services/firebase/messaging.ts diff --git a/.env.example b/.env.example index b05bc8152..e75b27ae0 100644 --- a/.env.example +++ b/.env.example @@ -35,3 +35,14 @@ NEXT_PUBLIC_GA_ID= NEXT_PUBLIC_SQUID_URL= COVALENT_API_KEY= + + +# Firebase API Keys +NEXT_PUBLIC_NOTIFICATION_APP_ID= +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= +NEXT_PUBLIC_FIREBASE_PROJECT_ID= +NEXT_PUBLIC_FIREBASE_API_KEY= +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= +NEXT_PUBLIC_FIREBASE_MESSAGING_ID= +NEXT_PUBLIC_FIREBASE_APP_ID= +NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID= diff --git a/package.json b/package.json index 75d20c9bd..9ee8d938d 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "eslint": "8.34.0", "eslint-config-next": "13.2.1", "ethers": "^6.3.0", + "firebase": "^10.1.0", "formidable": "^3.4.0", "graphql": "^16.6.0", "graphql-request": "^6.0.0", diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js new file mode 100644 index 000000000..af9fdeef2 --- /dev/null +++ b/public/firebase-messaging-sw.js @@ -0,0 +1,19 @@ +importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js') +importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js') + +if ('serviceWorker' in navigator) { + navigator.serviceWorker + .register('../firebase-messaging-sw.js') + .then(function (registration) { + console.log('Registration successful, scope is:', registration.scope) + }) + .catch(function (err) { + console.log('Service worker registration failed, error:', err) + }) +} + +firebase.initializeApp({ + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_ID, // Firebase Message Sender Config. +}) + +const initMessaging = firebase.messaging() diff --git a/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx index ce23626ae..9ae428050 100644 --- a/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx @@ -3,14 +3,26 @@ import MailIcon from '@/assets/icons/mail.svg' import DotBlinkingNotification from '@/components/DotBlinkingNotification' import MenuList from '@/components/MenuList' import useFirstVisitNotification from '@/hooks/useFirstVisitNotification' +import { getMessageToken } from '@/services/firebase/messaging' +import { useMyAccount } from '@/stores/my-account' import { cx } from '@/utils/class-names' import { FaDiscord, FaTelegram } from 'react-icons/fa' import { ContentProps } from '../../types' export default function NotificationContent({ setCurrentState }: ContentProps) { - const { showNotification, closeNotification } = useFirstVisitNotification( - 'telegram-notification' - ) + const address = useMyAccount((state) => state.address) + + const pwa = useFirstVisitNotification('pwa-notification') + const telegram = useFirstVisitNotification('telegram-notification') + + const enablePushNotification = async () => { + const token = await getMessageToken() + + if (token) { + // TODO: Send backend request to store mapping between token & address. + console.log('fcm token', token, 'User Address: ', address) + } + } return ( Telegram Bot - {showNotification && } + {telegram.showNotification && } ), iconClassName: cx('text-[#A3ACBE]'), icon: FaTelegram, onClick: () => { - closeNotification() + telegram.closeNotification() setCurrentState('telegram-notifications') }, }, { - text: , + text: ( + + Push Notifications + {pwa.showNotification && } + + ), iconClassName: cx('text-[#A3ACBE]'), icon: BellIcon, - disabled: true, + onClick: () => { + pwa.closeNotification() + enablePushNotification() + }, }, { text: , diff --git a/src/services/firebase/config.ts b/src/services/firebase/config.ts new file mode 100644 index 000000000..6ab521dd6 --- /dev/null +++ b/src/services/firebase/config.ts @@ -0,0 +1,26 @@ +// firebaseConfig.js +import { + getFirebaseApiKey, + getFirebaseAppId, + getFirebaseAuthDomain, + getFirebaseMeasurementId, + getFirebaseMessagingId, + getFirebaseProjectId, + getFirebaseStorageBucket, +} from '@/utils/env/client' +import * as firebase from 'firebase/app' +import 'firebase/firestore' // Import any Firebase services you plan to use + +const firebaseConfig = { + apiKey: getFirebaseApiKey(), + authDomain: getFirebaseAuthDomain(), + projectId: getFirebaseProjectId(), + storageBucket: getFirebaseStorageBucket(), + messagingSenderId: getFirebaseMessagingId(), + appId: getFirebaseAppId(), + measurementId: getFirebaseMeasurementId(), +} + +const firebaseApp = firebase.initializeApp(firebaseConfig) + +export default firebaseApp diff --git a/src/services/firebase/messaging.ts b/src/services/firebase/messaging.ts new file mode 100644 index 000000000..f6c48c85e --- /dev/null +++ b/src/services/firebase/messaging.ts @@ -0,0 +1,28 @@ +import { getFirebaseNotificationAppId } from '@/utils/env/client' +import { getMessaging, getToken } from 'firebase/messaging' +import firebaseApp from './config' + +export const getMessageToken = async (): Promise => { + const messaging = getMessaging(firebaseApp) + + const permission = Notification.permission + + if (permission === 'granted') { + // The user has already granted permission. + const token = await getToken(messaging, { + vapidKey: getFirebaseNotificationAppId(), + }) + + alert(`Copy the FCM token: ${token}`) + return token + } else if (permission === 'denied') { + // The user has denied permission. + console.log('Permission denied by the user.') + } else { + // The user has not yet been asked for permission. + Notification.requestPermission().then(async () => { + // The user has granted or denied permission. + await getMessageToken() + }) + } +} diff --git a/src/utils/env/client.ts b/src/utils/env/client.ts index 0189dfda4..176607c45 100644 --- a/src/utils/env/client.ts +++ b/src/utils/env/client.ts @@ -45,3 +45,59 @@ export function getGaId() { export function getSquidUrl() { return checkEnv(process.env.NEXT_PUBLIC_SQUID_URL, 'NEXT_PUBLIC_SQUID_URL') } + +export function getFirebaseApiKey() { + return checkEnv( + process.env.NEXT_PUBLIC_FIREBASE_API_KEY, + 'NEXT_PUBLIC_FIREBASE_API_KEY' + ) +} + +export function getFirebaseNotificationAppId() { + return checkEnv( + process.env.NEXT_PUBLIC_NOTIFICATION_APP_ID, + 'NEXT_PUBLIC_NOTIFICATION_APP_ID' + ) +} + +export function getFirebaseAuthDomain() { + return checkEnv( + process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, + 'NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN' + ) +} + +export function getFirebaseProjectId() { + return checkEnv( + process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, + 'NEXT_PUBLIC_FIREBASE_PROJECT_ID' + ) +} + +export function getFirebaseStorageBucket() { + return checkEnv( + process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, + 'NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET' + ) +} + +export function getFirebaseMessagingId() { + return checkEnv( + process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_ID, + 'NEXT_PUBLIC_FIREBASE_MESSAGING_ID' + ) +} + +export function getFirebaseAppId() { + return checkEnv( + process.env.NEXT_PUBLIC_FIREBASE_APP_ID, + 'NEXT_PUBLIC_FIREBASE_APP_ID' + ) +} + +export function getFirebaseMeasurementId() { + return checkEnv( + process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, + 'NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID' + ) +} diff --git a/yarn.lock b/yarn.lock index ea163227c..d877c793f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2469,6 +2469,379 @@ dependencies: keccak "^3.0.0" +"@firebase/analytics-compat@0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz#50063978c42f13eb800e037e96ac4b17236841f4" + integrity sha512-4MqpVLFkGK7NJf/5wPEEP7ePBJatwYpyjgJ+wQHQGHfzaCDgntOnl9rL2vbVGGKCnRqWtZDIWhctB86UWXaX2Q== + dependencies: + "@firebase/analytics" "0.10.0" + "@firebase/analytics-types" "0.8.0" + "@firebase/component" "0.6.4" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/analytics-types@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.0.tgz#551e744a29adbc07f557306530a2ec86add6d410" + integrity sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw== + +"@firebase/analytics@0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.0.tgz#9c6986acd573c6c6189ffb52d0fd63c775db26d7" + integrity sha512-Locv8gAqx0e+GX/0SI3dzmBY5e9kjVDtD+3zCFLJ0tH2hJwuCAiL+5WkHuxKj92rqQj/rvkBUCfA1ewlX2hehg== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/installations" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/app-check-compat@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.7.tgz#e150f61d653a0f2043a34dcb995616a717161839" + integrity sha512-cW682AxsyP1G+Z0/P7pO/WT2CzYlNxoNe5QejVarW2o5ZxeWSSPAiVEwpEpQR/bUlUmdeWThYTMvBWaopdBsqw== + dependencies: + "@firebase/app-check" "0.8.0" + "@firebase/app-check-types" "0.5.0" + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/app-check-interop-types@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz#b27ea1397cb80427f729e4bbf3a562f2052955c4" + integrity sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg== + +"@firebase/app-check-types@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.0.tgz#1b02826213d7ce6a1cf773c329b46ea1c67064f4" + integrity sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ== + +"@firebase/app-check@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.0.tgz#b531ec40900af9c3cf1ec63de9094a0ddd733d6a" + integrity sha512-dRDnhkcaC2FspMiRK/Vbp+PfsOAEP6ZElGm9iGFJ9fDqHoPs0HOPn7dwpJ51lCFi1+2/7n5pRPGhqF/F03I97g== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/app-compat@0.2.15": + version "0.2.15" + resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.15.tgz#06a932311d340dd94666b9e9cb15ca5fc8bdc434" + integrity sha512-ttEbOEtO1SSz27cRPrwXAmrqDjdQ33sQc7rqqQuSMUuPRdYCQEcYdqzpkbvqgdkzGksx2kfH4JqQ6R/hI12nDw== + dependencies: + "@firebase/app" "0.9.15" + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/app-types@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.0.tgz#35b5c568341e9e263b29b3d2ba0e9cfc9ec7f01e" + integrity sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q== + +"@firebase/app@0.9.15": + version "0.9.15" + resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.9.15.tgz#8c5b7a85c6f856f3292c1fcc2a029c12a63b9ad9" + integrity sha512-xxQi6mkhRjtXeFUwleSF4zU7lwEH+beNhLE7VmkzEzjEsjAS14QPQPZ35gpgSD+/NigOeho7wgEXd4C/bOkRfA== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/auth-compat@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.4.4.tgz#062dd397a508c7a442f36c014133ded4d29c62bb" + integrity sha512-B2DctJDJ05djBwebNEdC3zbKWzKdIdxpbca8u9P/NSjqaJNSFq3fhz8h8bjlS9ufSrxaQWFSJMMH3dRmx3FlEA== + dependencies: + "@firebase/auth" "1.1.0" + "@firebase/auth-types" "0.12.0" + "@firebase/component" "0.6.4" + "@firebase/util" "1.9.3" + node-fetch "2.6.7" + tslib "^2.1.0" + +"@firebase/auth-interop-types@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz#78884f24fa539e34a06c03612c75f222fcc33742" + integrity sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg== + +"@firebase/auth-types@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.12.0.tgz#f28e1b68ac3b208ad02a15854c585be6da3e8e79" + integrity sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA== + +"@firebase/auth@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.1.0.tgz#106cad08f977245e641642ac9b7c3a2dae46400d" + integrity sha512-5RJQMXG0p/tSvtqpfM8jA+heELjVCgHHASq3F7NglAa/CWUGCAE4g2F4YDPW5stDkvtKKRez0WYAWnbcuQ5P4w== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + "@react-native-async-storage/async-storage" "^1.18.1" + node-fetch "2.6.7" + tslib "^2.1.0" + +"@firebase/component@0.6.4": + version "0.6.4" + resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.4.tgz#8981a6818bd730a7554aa5e0516ffc9b1ae3f33d" + integrity sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA== + dependencies: + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/database-compat@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-1.0.1.tgz#ab0acbbfb0031080cc16504cef6d00c95cf27ff1" + integrity sha512-ky82yLIboLxtAIWyW/52a6HLMVTzD2kpZlEilVDok73pNPLjkJYowj8iaIWK5nTy7+6Gxt7d00zfjL6zckGdXQ== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/database" "1.0.1" + "@firebase/database-types" "1.0.0" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/database-types@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.0.tgz#3f7f71c2c3fd1e29d15fce513f14dae2e7543f2a" + integrity sha512-SjnXStoE0Q56HcFgNQ+9SsmJc0c8TqGARdI/T44KXy+Ets3r6x/ivhQozT66bMnCEjJRywYoxNurRTMlZF8VNg== + dependencies: + "@firebase/app-types" "0.9.0" + "@firebase/util" "1.9.3" + +"@firebase/database@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.1.tgz#28830f1d0c05ec2f7014658a3165129cec891bcb" + integrity sha512-VAhF7gYwunW4Lw/+RQZvW8dlsf2r0YYqV9W0Gi2Mz8+0TGg1mBJWoUtsHfOr8kPJXhcLsC4eP/z3x6L/Fvjk/A== + dependencies: + "@firebase/auth-interop-types" "0.2.1" + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + faye-websocket "0.11.4" + tslib "^2.1.0" + +"@firebase/firestore-compat@0.3.14": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.14.tgz#f1ceac5a85da52c6b5b9d65136456e3eaec7b227" + integrity sha512-sOjaYefSPXJXdFH6qyxSwJVakEqAAote6jjrJk/ZCoiX90rs9r3yYV90wP4gmaTKyXjkt8EMlwuapekgGsE5Tw== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/firestore" "4.1.0" + "@firebase/firestore-types" "3.0.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/firestore-types@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.0.tgz#f3440d5a1cc2a722d361b24cefb62ca8b3577af3" + integrity sha512-Meg4cIezHo9zLamw0ymFYBD4SMjLb+ZXIbuN7T7ddXN6MGoICmOTq3/ltdCGoDCS2u+H1XJs2u/cYp75jsX9Qw== + +"@firebase/firestore@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.1.0.tgz#fcdd4e033c258fccbe4d47dadf625faa1f62272f" + integrity sha512-FEd+4R0QL9RAJVcdqXgbdIuQYpvzkeKNBVxNM5qcWDPMurjNpja8VaWpVZmT3JXG8FfO+NGTnHJtsW/nWO7XiQ== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + "@firebase/webchannel-wrapper" "0.10.1" + "@grpc/grpc-js" "~1.8.17" + "@grpc/proto-loader" "^0.6.13" + node-fetch "2.6.7" + tslib "^2.1.0" + +"@firebase/functions-compat@0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.5.tgz#7a532d3a9764c6d5fbc1ec5541a989a704326647" + integrity sha512-uD4jwgwVqdWf6uc3NRKF8cSZ0JwGqSlyhPgackyUPe+GAtnERpS4+Vr66g0b3Gge0ezG4iyHo/EXW/Hjx7QhHw== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/functions" "0.10.0" + "@firebase/functions-types" "0.6.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/functions-types@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.0.tgz#ccd7000dc6fc668f5acb4e6a6a042a877a555ef2" + integrity sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw== + +"@firebase/functions@0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.10.0.tgz#c630ddf12cdf941c25bc8d554e30c3226cd560f6" + integrity sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA== + dependencies: + "@firebase/app-check-interop-types" "0.3.0" + "@firebase/auth-interop-types" "0.2.1" + "@firebase/component" "0.6.4" + "@firebase/messaging-interop-types" "0.2.0" + "@firebase/util" "1.9.3" + node-fetch "2.6.7" + tslib "^2.1.0" + +"@firebase/installations-compat@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.4.tgz#b5557c897b4cd3635a59887a8bf69c3731aaa952" + integrity sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/installations" "0.6.4" + "@firebase/installations-types" "0.5.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/installations-types@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.0.tgz#2adad64755cd33648519b573ec7ec30f21fb5354" + integrity sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg== + +"@firebase/installations@0.6.4": + version "0.6.4" + resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.4.tgz#20382e33e6062ac5eff4bede8e468ed4c367609e" + integrity sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/util" "1.9.3" + idb "7.0.1" + tslib "^2.1.0" + +"@firebase/logger@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.4.0.tgz#15ecc03c452525f9d47318ad9491b81d1810f113" + integrity sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA== + dependencies: + tslib "^2.1.0" + +"@firebase/messaging-compat@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz#323ca48deef77065b4fcda3cfd662c4337dffcfd" + integrity sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/messaging" "0.12.4" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/messaging-interop-types@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz#6056f8904a696bf0f7fdcf5f2ca8f008e8f6b064" + integrity sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ== + +"@firebase/messaging@0.12.4": + version "0.12.4" + resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.4.tgz#ccb49df5ab97d5650c9cf5b8c77ddc34daafcfe0" + integrity sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/installations" "0.6.4" + "@firebase/messaging-interop-types" "0.2.0" + "@firebase/util" "1.9.3" + idb "7.0.1" + tslib "^2.1.0" + +"@firebase/performance-compat@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.4.tgz#95cbf32057b5d9f0c75d804bc50e6ed3ba486274" + integrity sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/performance" "0.6.4" + "@firebase/performance-types" "0.2.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/performance-types@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.0.tgz#400685f7a3455970817136d9b48ce07a4b9562ff" + integrity sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA== + +"@firebase/performance@0.6.4": + version "0.6.4" + resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.4.tgz#0ad766bfcfab4f386f4fe0bef43bbcf505015069" + integrity sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/installations" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/remote-config-compat@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz#1f494c81a6c9560b1f9ca1b4fbd4bbbe47cf4776" + integrity sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/remote-config" "0.4.4" + "@firebase/remote-config-types" "0.3.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/remote-config-types@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz#689900dcdb3e5c059e8499b29db393e4e51314b4" + integrity sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA== + +"@firebase/remote-config@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.4.4.tgz#6a496117054de58744bc9f382d2a6d1e14060c65" + integrity sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/installations" "0.6.4" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/storage-compat@0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.2.tgz#51a97170fd652a516f729f82b97af369e5a2f8d7" + integrity sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/storage" "0.11.2" + "@firebase/storage-types" "0.8.0" + "@firebase/util" "1.9.3" + tslib "^2.1.0" + +"@firebase/storage-types@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.0.tgz#f1e40a5361d59240b6e84fac7fbbbb622bfaf707" + integrity sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg== + +"@firebase/storage@0.11.2": + version "0.11.2" + resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.11.2.tgz#c5e0316543fe1c4026b8e3910f85ad73f5b77571" + integrity sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA== + dependencies: + "@firebase/component" "0.6.4" + "@firebase/util" "1.9.3" + node-fetch "2.6.7" + tslib "^2.1.0" + +"@firebase/util@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.9.3.tgz#45458dd5cd02d90e55c656e84adf6f3decf4b7ed" + integrity sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA== + dependencies: + tslib "^2.1.0" + +"@firebase/webchannel-wrapper@0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.1.tgz#60bb2aaf129f9e00621f8d698722ddba6ee1f8ac" + integrity sha512-Dq5rYfEpdeel0bLVN+nfD1VWmzCkK+pJbSjIawGE+RY4+NIJqhbUDDQjvV0NUK84fMfwxvtFoCtEe70HfZjFcw== + "@floating-ui/core@^1.2.6": version "1.2.6" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.6.tgz#d21ace437cc919cdd8f1640302fa8851e65e75c0" @@ -3028,6 +3401,36 @@ resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== +"@grpc/grpc-js@~1.8.17": + version "1.8.18" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.8.18.tgz#fdbf2728064fd3db7e72e970372db28bd0f6fafb" + integrity sha512-2uWPtxhsXmVgd8WzDhfamSjHpZDXfMjMDciY6VRTq4Sn7rFzazyf0LLDa0oav+61UHIoEZb4KKaAV6S7NuJFbQ== + dependencies: + "@grpc/proto-loader" "^0.7.0" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.6.13": + version "0.6.13" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.13.tgz#008f989b72a40c60c96cd4088522f09b05ac66bc" + integrity sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g== + dependencies: + "@types/long" "^4.0.1" + lodash.camelcase "^4.3.0" + long "^4.0.0" + protobufjs "^6.11.3" + yargs "^16.2.0" + +"@grpc/proto-loader@^0.7.0": + version "0.7.8" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.8.tgz#c050bbeae5f000a1919507f195a1b094e218036e" + integrity sha512-GU12e2c8dmdXb7XUlOgYWZ2o2i+z9/VeACkxTA/zzAe2IjclC5PnVL0lpgjhrqfpDYHzM8B1TF6pqWegMYAzlA== + dependencies: + "@types/long" "^4.0.1" + lodash.camelcase "^4.3.0" + long "^4.0.0" + protobufjs "^7.2.4" + yargs "^17.7.2" + "@headlessui/react@^1.7.11": version "1.7.14" resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.14.tgz#75f19552c535113640fe8a3a40e71474f49e89c9" @@ -3950,6 +4353,13 @@ qrcode "1.5.0" react-remove-scroll "2.5.4" +"@react-native-async-storage/async-storage@^1.18.1": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.19.1.tgz#09d35caaa31823b40fdfeebf95decf8f992a6274" + integrity sha512-5QXuGCtB+HL3VtKL2JN3+6t4qh8VXizK+aGDAv6Dqiq3MLrzgZHb4tjVgtEWMd8CcDtD/JqaAI1b6/EaYGtFIA== + dependencies: + merge-options "^3.0.4" + "@repeaterjs/repeater@3.0.4", "@repeaterjs/repeater@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca" @@ -4624,6 +5034,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.1.tgz#90dad8476f1e42797c49d6f8b69aaf9f876fc69f" integrity sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ== +"@types/node@>=12.12.47": + version "20.4.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" + integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== + "@types/node@>=13.7.0": version "20.2.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.3.tgz#b31eb300610c3835ac008d690de6f87e28f9b878" @@ -6472,6 +6887,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -7771,6 +8195,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +faye-websocket@0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + fb-watchman@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" @@ -7874,6 +8305,38 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +firebase@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.1.0.tgz#07281ac2fe4bcf3886eeddcea8903ad17f1aec67" + integrity sha512-ghcdCe2G9DeGmLOrBgR7XPswuc9BFUfjnU93ABopIisMfbJFzoqpSp4emwNiZt+vVGZV1ifeU3DLfhxlujxhCg== + dependencies: + "@firebase/analytics" "0.10.0" + "@firebase/analytics-compat" "0.2.6" + "@firebase/app" "0.9.15" + "@firebase/app-check" "0.8.0" + "@firebase/app-check-compat" "0.3.7" + "@firebase/app-compat" "0.2.15" + "@firebase/app-types" "0.9.0" + "@firebase/auth" "1.1.0" + "@firebase/auth-compat" "0.4.4" + "@firebase/database" "1.0.1" + "@firebase/database-compat" "1.0.1" + "@firebase/firestore" "4.1.0" + "@firebase/firestore-compat" "0.3.14" + "@firebase/functions" "0.10.0" + "@firebase/functions-compat" "0.3.5" + "@firebase/installations" "0.6.4" + "@firebase/installations-compat" "0.2.4" + "@firebase/messaging" "0.12.4" + "@firebase/messaging-compat" "0.2.4" + "@firebase/performance" "0.6.4" + "@firebase/performance-compat" "0.2.4" + "@firebase/remote-config" "0.4.4" + "@firebase/remote-config-compat" "0.2.4" + "@firebase/storage" "0.11.2" + "@firebase/storage-compat" "0.3.2" + "@firebase/util" "1.9.3" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -8334,6 +8797,11 @@ html-void-elements@^1.0.0: resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + http-proxy-agent@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-6.0.1.tgz#c18aa52daa625a9bd00243f57252fd44e86ff40b" @@ -8391,7 +8859,12 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -idb@^7.0.1: +idb@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/idb/-/idb-7.0.1.tgz#d2875b3a2f205d854ee307f6d196f246fea590a7" + integrity sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg== + +idb@7.1.1, idb@^7.0.1: version "7.1.1" resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== @@ -9476,6 +9949,11 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + longest-streak@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" @@ -10717,7 +11195,7 @@ property-information@^5.0.0: dependencies: xtend "^4.0.0" -protobufjs@^6.10.2: +protobufjs@^6.10.2, protobufjs@^6.11.3: version "6.11.3" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== @@ -10736,6 +11214,24 @@ protobufjs@^6.10.2: "@types/node" ">=13.7.0" long "^4.0.0" +protobufjs@^7.2.4: + version "7.2.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.4.tgz#3fc1ec0cdc89dd91aef9ba6037ba07408485c3ae" + integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== + 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" ">=13.7.0" + long "^5.0.0" + proxy-compare@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.5.1.tgz#17818e33d1653fbac8c2ec31406bce8a2966f600" @@ -11300,7 +11796,7 @@ rxjs@^7.5.5, rxjs@^7.8.1: dependencies: tslib "^2.1.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -12622,6 +13118,20 @@ webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -12954,6 +13464,11 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" @@ -12976,7 +13491,20 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.0.0: +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.0.0, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From 0b0e435e790a1e62b6305286565faf51f6a168c2 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Mon, 31 Jul 2023 16:59:01 +0530 Subject: [PATCH 02/32] added push notification flow --- .env.local.example | 10 ++ .../auth/ProfileModal/ProfileModal.tsx | 7 + .../ProfileModal/contents/LogoutContent.tsx | 67 +++++++- .../notifications/NotificationContent.tsx | 2 +- .../notifications/PushNotificationContent.tsx | 161 ++++++++++++++++++ src/components/auth/ProfileModal/types.ts | 1 + src/pages/api/notifications/commit.ts | 27 +++ src/pages/api/notifications/link-fcm.ts | 43 +++++ src/server/notifications/generated.ts | 124 +++++++++++++- src/server/notifications/index.ts | 80 +++++++++ src/services/api/notifications/mutation.ts | 37 ++++ src/services/firebase/messaging.ts | 4 +- worker/index.ts | 8 + 13 files changed, 563 insertions(+), 8 deletions(-) create mode 100644 src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx create mode 100644 src/pages/api/notifications/commit.ts create mode 100644 src/pages/api/notifications/link-fcm.ts create mode 100644 worker/index.ts diff --git a/.env.local.example b/.env.local.example index c735913a1..645b80094 100644 --- a/.env.local.example +++ b/.env.local.example @@ -12,3 +12,13 @@ NEXT_PUBLIC_MODERATION_URL="https://moderation.subsocial.network/graphql" # recaptcha test keys NEXT_PUBLIC_CAPTCHA_SITE_KEY="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" CAPTCHA_SECRET="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe" + +# Firebase API Keys +NEXT_PUBLIC_NOTIFICATION_APP_ID= +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= +NEXT_PUBLIC_FIREBASE_PROJECT_ID= +NEXT_PUBLIC_FIREBASE_API_KEY= +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= +NEXT_PUBLIC_FIREBASE_MESSAGING_ID= +NEXT_PUBLIC_FIREBASE_APP_ID= +NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID= diff --git a/src/components/auth/ProfileModal/ProfileModal.tsx b/src/components/auth/ProfileModal/ProfileModal.tsx index 6c4244602..e92f63c7c 100644 --- a/src/components/auth/ProfileModal/ProfileModal.tsx +++ b/src/components/auth/ProfileModal/ProfileModal.tsx @@ -10,6 +10,7 @@ import LinkEvmAddressContent from './contents/evm-linking/LinkEvmAddressContent' import UnlinkEvmConfirmationContent from './contents/evm-linking/UnlinkEvmConfirmationContent' import LogoutContent from './contents/LogoutContent' import NotificationContent from './contents/notifications/NotificationContent' +import PushNotificationContent from './contents/notifications/PushNotificationContent' import TelegramNotificationContent from './contents/notifications/TelegramNotificationContent' import PrivateKeyContent from './contents/PrivateKeyContent' import ShareSessionContent from './contents/ShareSessionContent' @@ -29,6 +30,7 @@ const modalContents: { 'evm-address-linked': CommonEvmAddressLinked, notifications: NotificationContent, 'telegram-notifications': TelegramNotificationContent, + 'push-notifications': PushNotificationContent, } export default function ProfileModal({ @@ -112,6 +114,11 @@ export default function ProfileModal({ desc: 'Connect your account to our Telegram bot to receive notifications from Grill.', withBackButton: 'notifications', }, + 'push-notifications': { + title: '🔔 Push Notifications', + desc: 'Enable Push Notifications on your browser to receive direct updates from Grill.', + withBackButton: 'notifications', + }, } const { title, desc, withBackButton, withoutDefaultPadding, withFooter } = diff --git a/src/components/auth/ProfileModal/contents/LogoutContent.tsx b/src/components/auth/ProfileModal/contents/LogoutContent.tsx index 75133a28c..30274e38a 100644 --- a/src/components/auth/ProfileModal/contents/LogoutContent.tsx +++ b/src/components/auth/ProfileModal/contents/LogoutContent.tsx @@ -1,12 +1,49 @@ import Button from '@/components/Button' +import useSignMessage from '@/hooks/useSignMessage' +import { + useCommitSignedMessageWithAction, + useGetFcmLinkingMessage, +} from '@/services/api/notifications/mutation' import { useSendEvent } from '@/stores/analytics' import { useMyAccount } from '@/stores/my-account' +import { LocalStorage } from '@/utils/storage' +import { sortObj } from 'jsonabc' import { ContentProps } from '../types' +const FCM_PUSH_NOTIFICATION_STORAGE_KEY = 'push-notification-fcm-token' + +const fcmTokenStorage = new LocalStorage( + () => FCM_PUSH_NOTIFICATION_STORAGE_KEY +) + function LogoutContent({ setCurrentState }: ContentProps) { + const { address } = useMyAccount() const logout = useMyAccount((state) => state.logout) const sendEvent = useSendEvent() + const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = + useCommitSignedMessageWithAction({ + onSuccess: (data) => { + if (!data) throw new Error('Error generating url') + + // FCM Token Disabled. + fcmTokenStorage.remove() + }, + }) + + const processMessage = useProcessMessage() + const { mutate: getLinkingMessage, isLoading: isGettingLinkingMessage } = + useGetFcmLinkingMessage({ + onSuccess: async (data) => { + const processedData = await processMessage(data) + commitSignedMessage({ + signedMessageWithDetails: processedData, + }) + }, + }) + + const isLoading = isCommitingMessage || isGettingLinkingMessage + const onShowPrivateKeyClick = () => { sendEvent('click no_show_me_my_private_key_button') setCurrentState('private-key') @@ -14,6 +51,12 @@ function LogoutContent({ setCurrentState }: ContentProps) { const onLogoutClick = () => { sendEvent('click yes_log_out_button') logout() + + const fcmToken = fcmTokenStorage.get() + + if (fcmToken && address) { + getLinkingMessage({ address, fcmToken, action: 'link' }) + } } return ( @@ -21,7 +64,12 @@ function LogoutContent({ setCurrentState }: ContentProps) { - @@ -29,3 +77,20 @@ function LogoutContent({ setCurrentState }: ContentProps) { } export default LogoutContent + +function useProcessMessage() { + const signMessage = useSignMessage() + + return async (data: { messageData: any; payloadToSign: string } | null) => { + if (!data) throw new Error('No data') + + const signedPayload = await signMessage(data.payloadToSign) + data.messageData['signature'] = signedPayload + + const signedMessage = encodeURIComponent( + JSON.stringify(sortObj(data.messageData)) + ) + + return signedMessage + } +} diff --git a/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx index 9ae428050..02015e334 100644 --- a/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx @@ -53,7 +53,7 @@ export default function NotificationContent({ setCurrentState }: ContentProps) { icon: BellIcon, onClick: () => { pwa.closeNotification() - enablePushNotification() + setCurrentState('push-notifications') }, }, { diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx new file mode 100644 index 000000000..a771e9e74 --- /dev/null +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -0,0 +1,161 @@ +import Button from '@/components/Button' +import useSignMessage from '@/hooks/useSignMessage' +import { + useCommitSignedMessageWithAction, + useGetFcmLinkingMessage, +} from '@/services/api/notifications/mutation' +import { getMessageToken } from '@/services/firebase/messaging' +import { LocalStorage } from '@/utils/storage' +import { sortObj } from 'jsonabc' +import { useEffect, useState } from 'react' +import { ContentProps } from '../../types' + +const STORAGE_KEY = 'push-notification-fcm-token' +const storage = new LocalStorage(() => STORAGE_KEY) + +type NotificationButtonProps = ContentProps & { + setIsRegisterd: (v: boolean) => void +} + +export default function PushNotificationContent(props: ContentProps) { + const isNotificationNotSupported = !Notification + const permission = Notification.permission + + const [isRegistered, setIsRegistered] = useState(false) + + useEffect(() => { + const storedFcmToken = storage.get() + if (storedFcmToken) { + setIsRegistered(true) + } else { + setIsRegistered(false) + } + }, [isRegistered]) + + if (isNotificationNotSupported) { + return ( + + ) + } + + if (permission === 'granted' && isRegistered) { + // Disable Notifications. + return ( + + ) + } + + return ( + + ) +} + +function DisableNotificationButton({ + address, + setIsRegisterd, +}: NotificationButtonProps) { + const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = + useCommitSignedMessageWithAction({ + onSuccess: (data) => { + if (!data) throw new Error('Error generating url') + + // FCM Token Disabled. + storage.remove() + setIsRegisterd(false) + }, + }) + + const processMessage = useProcessMessage() + const { mutate: getLinkingMessage, isLoading: isGettingLinkingMessage } = + useGetFcmLinkingMessage({ + onSuccess: async (data) => { + const processedData = await processMessage(data) + commitSignedMessage({ + signedMessageWithDetails: processedData, + }) + }, + }) + + const isLoading = isCommitingMessage || isGettingLinkingMessage + + const handleClickEnable = async () => { + if (!address) return + const fcmToken = await getMessageToken() + if (!fcmToken) return + + getLinkingMessage({ address, fcmToken, action: 'unlink' }) + } + + return ( + + ) +} + +function EnableNotificationButton({ + address, + setIsRegisterd, +}: NotificationButtonProps) { + const [fcmToken, setFcmToken] = useState() + + const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = + useCommitSignedMessageWithAction({ + onSuccess: (data) => { + if (!data) throw new Error('Error subscribing for notifications') + + // FCM Token Enabled. + if (fcmToken) { + storage.set(fcmToken) + setIsRegisterd(true) + } + }, + }) + + const processMessage = useProcessMessage() + const { mutate: getLinkingMessage, isLoading: isGettingLinkingMessage } = + useGetFcmLinkingMessage({ + onSuccess: async (data) => { + const processedData = await processMessage(data) + commitSignedMessage({ + signedMessageWithDetails: processedData, + }) + }, + }) + + const isLoading = isCommitingMessage || isGettingLinkingMessage + + const handleClickEnable = async () => { + if (!address) return + const fcmToken = await getMessageToken() + if (!fcmToken) return + + setFcmToken(fcmToken) + getLinkingMessage({ address, fcmToken, action: 'link' }) + } + + return ( + + ) +} + +function useProcessMessage() { + const signMessage = useSignMessage() + + return async (data: { messageData: any; payloadToSign: string } | null) => { + if (!data) throw new Error('No data') + + const signedPayload = await signMessage(data.payloadToSign) + data.messageData['signature'] = signedPayload + + const signedMessage = encodeURIComponent( + JSON.stringify(sortObj(data.messageData)) + ) + + return signedMessage + } +} diff --git a/src/components/auth/ProfileModal/types.ts b/src/components/auth/ProfileModal/types.ts index 60b924a52..73f4692c7 100644 --- a/src/components/auth/ProfileModal/types.ts +++ b/src/components/auth/ProfileModal/types.ts @@ -23,6 +23,7 @@ export type ModalState = | 'evm-address-linked' | 'notifications' | 'telegram-notifications' + | 'push-notifications' export type ContentProps = { address: string diff --git a/src/pages/api/notifications/commit.ts b/src/pages/api/notifications/commit.ts new file mode 100644 index 000000000..40a70bf7f --- /dev/null +++ b/src/pages/api/notifications/commit.ts @@ -0,0 +1,27 @@ +import { ApiResponse, handlerWrapper } from '@/server/common' +import { commitSignedMessageWithAction } from '@/server/notifications' +import { NextApiRequest } from 'next' +import { z } from 'zod' + +const bodySchema = z.object({ + signedMessageWithDetails: z.string(), +}) +export type ApiCommitSignedMessageBody = z.infer + +type ResponseData = {} +export type ApiCommitSignedMessageResponse = ApiResponse + +export default handlerWrapper({ + inputSchema: bodySchema, + dataGetter: (req: NextApiRequest) => req.body, +})({ + allowedMethods: ['POST'], + errorLabel: 'commit-signed-message', + handler: async (data, _, res) => { + await commitSignedMessageWithAction(data.signedMessageWithDetails) + return res.status(200).send({ + success: true, + message: 'OK', + }) + }, +}) diff --git a/src/pages/api/notifications/link-fcm.ts b/src/pages/api/notifications/link-fcm.ts new file mode 100644 index 000000000..0cd4c3d9b --- /dev/null +++ b/src/pages/api/notifications/link-fcm.ts @@ -0,0 +1,43 @@ +import { ApiResponse, handlerWrapper } from '@/server/common' +import { + createLinkingMessageForFcm, + createUnlinkingMessageForFcm, +} from '@/server/notifications' +import { NextApiRequest } from 'next' +import { z } from 'zod' + +const bodySchema = z.object({ + address: z.string(), + fcmToken: z.string(), + action: z.union([z.literal('link'), z.literal('unlink')], { + invalid_type_error: 'Invalid action', + }), +}) +export type ApiFcmNotificationsLinkMessageBody = z.infer + +type ResponseData = { + data: string +} +export type ApiFcmNotificationsLinkMessageResponse = ApiResponse + +export default handlerWrapper({ + inputSchema: bodySchema, + dataGetter: (req: NextApiRequest) => req.body, +})({ + allowedMethods: ['POST'], + errorLabel: 'link-fcm', + handler: async (data, _, res) => { + const createMessageForFcm = + data.action === 'link' + ? createLinkingMessageForFcm + : createUnlinkingMessageForFcm + + const message = await createMessageForFcm(data.address, data.fcmToken) + + return res.status(200).send({ + success: true, + message: 'OK', + data: message, + }) + }, +}) diff --git a/src/server/notifications/generated.ts b/src/server/notifications/generated.ts index 2660c9696..b4e9a5f08 100644 --- a/src/server/notifications/generated.ts +++ b/src/server/notifications/generated.ts @@ -20,11 +20,37 @@ export type AccountsLinkingMessageTemplateGql = { messageTpl: Scalars['String']['output']; }; +export type AddFcmTokenToAddressMessageMessageInput = { + fcmToken: Scalars['String']['input']; + substrateAddress: Scalars['String']['input']; +}; + +export type CommitSignedMessageResponse = { + __typename?: 'CommitSignedMessageResponse'; + data?: Maybe; + message?: Maybe; + success: Scalars['Boolean']['output']; +}; + +export type CommitSignedMessageResponseData = { + __typename?: 'CommitSignedMessageResponseData'; + tmpLinkingIdForTelegram?: Maybe; +}; + export type CreateTemporaryLinkingIdForTelegramResponseDto = { __typename?: 'CreateTemporaryLinkingIdForTelegramResponseDto'; id: Scalars['String']['output']; }; +export type DeleteFcmTokenFromAddressMessageInput = { + fcmToken: Scalars['String']['input']; + substrateAddress: Scalars['String']['input']; +}; + +export type LinkAddressWithTelegramAccountMessageInput = { + substrateAddress: Scalars['String']['input']; +}; + export type LinkedTgAccountsToSubstrateAccountResponseType = { __typename?: 'LinkedTgAccountsToSubstrateAccountResponseType'; telegramAccounts?: Maybe>; @@ -32,15 +58,23 @@ export type LinkedTgAccountsToSubstrateAccountResponseType = { export type Mutation = { __typename?: 'Mutation'; + commitSignedMessageWithAction?: Maybe; createNotificationSettingsToAccount: NotificationSettingsGql; + /** This mutation is deprecated and "commitSignedMessageWithAction" must be used instead. */ createTemporaryLinkingIdForTelegram: CreateTemporaryLinkingIdForTelegramResponseDto; + /** This mutation is deprecated and "commitSignedMessageWithAction" must be used instead. */ unlinkTelegramAccount: UnlinkTelegramAccountResponseDto; updateNotificationSettingsToAccount: NotificationSettingsGql; }; +export type MutationCommitSignedMessageWithActionArgs = { + signedMessage: Scalars['String']['input']; +}; + + export type MutationCreateNotificationSettingsToAccountArgs = { - createNotificationSettingsInput: NotificationSettingsGqlInput; + createNotificationSettingsInput: NotificationSettingsInputGql; }; @@ -55,7 +89,7 @@ export type MutationUnlinkTelegramAccountArgs = { export type MutationUpdateNotificationSettingsToAccountArgs = { - updateNotificationSettingsInput: NotificationSettingsGqlInput; + updateNotificationSettingsInput: NotificationSettingsInputGql; }; export type NotificationSettingsGql = { @@ -66,7 +100,7 @@ export type NotificationSettingsGql = { substrateAccountId: Scalars['String']['output']; }; -export type NotificationSettingsGqlInput = { +export type NotificationSettingsInputGql = { subscriptions: Array; substrateAccountId: Scalars['String']['input']; }; @@ -74,23 +108,44 @@ export type NotificationSettingsGqlInput = { export type NotificationSubscription = { __typename?: 'NotificationSubscription'; eventName: Scalars['String']['output']; + fcm: Scalars['Boolean']['output']; telegramBot: Scalars['Boolean']['output']; }; export type NotificationSubscriptionInputType = { eventName: Scalars['String']['input']; + fcm: Scalars['Boolean']['input']; telegramBot: Scalars['Boolean']['input']; }; export type Query = { __typename?: 'Query'; + addFcmTokenToAddressMessage: SignedMessageWithActionTemplateResponseDto; + deleteFcmTokenFromAddressMessage: SignedMessageWithActionTemplateResponseDto; + linkAddressWithTelegramAccountMessage: SignedMessageWithActionTemplateResponseDto; linkingMessageForTelegramAccount: AccountsLinkingMessageTemplateGql; notificationSettingsByAccountId: NotificationSettingsGql; telegramAccountsLinkedToSubstrateAccount: LinkedTgAccountsToSubstrateAccountResponseType; + unlinkAddressFromTelegramAccountMessage: SignedMessageWithActionTemplateResponseDto; unlinkingMessageForTelegramAccount: AccountsLinkingMessageTemplateGql; }; +export type QueryAddFcmTokenToAddressMessageArgs = { + input: AddFcmTokenToAddressMessageMessageInput; +}; + + +export type QueryDeleteFcmTokenFromAddressMessageArgs = { + input: DeleteFcmTokenFromAddressMessageInput; +}; + + +export type QueryLinkAddressWithTelegramAccountMessageArgs = { + input: LinkAddressWithTelegramAccountMessageInput; +}; + + export type QueryLinkingMessageForTelegramAccountArgs = { substrateAccount: Scalars['String']['input']; }; @@ -106,10 +161,20 @@ export type QueryTelegramAccountsLinkedToSubstrateAccountArgs = { }; +export type QueryUnlinkAddressFromTelegramAccountMessageArgs = { + input: UnlinkAddressWithTelegramAccountMessageInput; +}; + + export type QueryUnlinkingMessageForTelegramAccountArgs = { substrateAccount: Scalars['String']['input']; }; +export type SignedMessageWithActionTemplateResponseDto = { + __typename?: 'SignedMessageWithActionTemplateResponseDto'; + messageTpl: Scalars['String']['output']; +}; + export type TelegramAccountDetails = { __typename?: 'TelegramAccountDetails'; accountId: Scalars['String']['output']; @@ -119,6 +184,10 @@ export type TelegramAccountDetails = { userName: Scalars['String']['output']; }; +export type UnlinkAddressWithTelegramAccountMessageInput = { + substrateAddress: Scalars['String']['input']; +}; + export type UnlinkTelegramAccountResponseDto = { __typename?: 'UnlinkTelegramAccountResponseDto'; message?: Maybe; @@ -160,6 +229,29 @@ export type UnlinkTelegramAccountMutationVariables = Exact<{ export type UnlinkTelegramAccountMutation = { __typename?: 'Mutation', unlinkTelegramAccount: { __typename?: 'UnlinkTelegramAccountResponseDto', message?: string | null, success: boolean } }; +export type GetLinkingMessageForFcmQueryVariables = Exact<{ + address: Scalars['String']['input']; + fcmToken: Scalars['String']['input']; +}>; + + +export type GetLinkingMessageForFcmQuery = { __typename?: 'Query', addFcmTokenToAddressMessage: { __typename?: 'SignedMessageWithActionTemplateResponseDto', messageTpl: string } }; + +export type GetUnlinkingMessageFromFcmQueryVariables = Exact<{ + address: Scalars['String']['input']; + fcmToken: Scalars['String']['input']; +}>; + + +export type GetUnlinkingMessageFromFcmQuery = { __typename?: 'Query', deleteFcmTokenFromAddressMessage: { __typename?: 'SignedMessageWithActionTemplateResponseDto', messageTpl: string } }; + +export type CommitSignedMessageMutationVariables = Exact<{ + signedMessageWithDetails: Scalars['String']['input']; +}>; + + +export type CommitSignedMessageMutation = { __typename?: 'Mutation', commitSignedMessageWithAction?: { __typename?: 'CommitSignedMessageResponse', message?: string | null, success: boolean } | null }; + export const GetTelegramAccountsLinked = gql` query GetTelegramAccountsLinked($address: String!) { @@ -199,5 +291,31 @@ export const UnlinkTelegramAccount = gql` message success } +} + `; +export const GetLinkingMessageForFcm = gql` + query GetLinkingMessageForFcm($address: String!, $fcmToken: String!) { + addFcmTokenToAddressMessage( + input: {substrateAddress: $address, fcmToken: $fcmToken} + ) { + messageTpl + } +} + `; +export const GetUnlinkingMessageFromFcm = gql` + query GetUnlinkingMessageFromFcm($address: String!, $fcmToken: String!) { + deleteFcmTokenFromAddressMessage( + input: {substrateAddress: $address, fcmToken: $fcmToken} + ) { + messageTpl + } +} + `; +export const CommitSignedMessage = gql` + mutation CommitSignedMessage($signedMessageWithDetails: String!) { + commitSignedMessageWithAction(signedMessage: $signedMessageWithDetails) { + message + success + } } `; \ No newline at end of file diff --git a/src/server/notifications/index.ts b/src/server/notifications/index.ts index 318e19dc7..d80a5bdd3 100644 --- a/src/server/notifications/index.ts +++ b/src/server/notifications/index.ts @@ -1,14 +1,20 @@ import { getTelegramNotificationsBotLink } from '@/constants/links' import { gql } from 'graphql-request' import { + CommitSignedMessageMutation, + CommitSignedMessageMutationVariables, CreateTemporaryLinkingIdForTelegramMutation, CreateTemporaryLinkingIdForTelegramMutationVariables, + GetLinkingMessageForFcmQuery, + GetLinkingMessageForFcmQueryVariables, GetLinkingMessageForTelegramQuery, GetLinkingMessageForTelegramQueryVariables, GetTelegramAccountsLinkedQuery, GetTelegramAccountsLinkedQueryVariables, GetUnlinkingMessageForTelegramQuery, GetUnlinkingMessageForTelegramQueryVariables, + GetUnlinkingMessageFromFcmQuery, + GetUnlinkingMessageFromFcmQueryVariables, UnlinkTelegramAccountMutation, UnlinkTelegramAccountMutationVariables, } from './generated' @@ -120,3 +126,77 @@ export async function unlinkTelegramAccount(signedMessageWithDetails: string) { ) } } + +// Linking FCM Token with Account. +const GET_LINKING_MESSAGE_FOR_FCM = gql` + query GetLinkingMessageForFcm($address: String!, $fcmToken: String!) { + addFcmTokenToAddressMessage( + input: { substrateAddress: $address, fcmToken: $fcmToken } + ) { + messageTpl + } + } +` +export async function createLinkingMessageForFcm( + address: string, + fcmToken: string +) { + const data = await notificationsRequest< + GetLinkingMessageForFcmQuery, + GetLinkingMessageForFcmQueryVariables + >({ + document: GET_LINKING_MESSAGE_FOR_FCM, + variables: { address, fcmToken }, + }) + return data.addFcmTokenToAddressMessage.messageTpl +} + +// Unlinking FCM Token with Account. +const GET_UNLINKING_MESSAGE_FOR_FCM = gql` + query GetUnlinkingMessageFromFcm($address: String!, $fcmToken: String!) { + deleteFcmTokenFromAddressMessage( + input: { substrateAddress: $address, fcmToken: $fcmToken } + ) { + messageTpl + } + } +` +export async function createUnlinkingMessageForFcm( + address: string, + fcmToken: string +) { + const data = await notificationsRequest< + GetUnlinkingMessageFromFcmQuery, + GetUnlinkingMessageFromFcmQueryVariables + >({ + document: GET_UNLINKING_MESSAGE_FOR_FCM, + variables: { address, fcmToken }, + }) + return data.deleteFcmTokenFromAddressMessage.messageTpl +} + +const COMMIT_SIGNED_MESSAGE = gql` + mutation CommitSignedMessage($signedMessageWithDetails: String!) { + commitSignedMessageWithAction(signedMessage: $signedMessageWithDetails) { + message + success + } + } +` +export async function commitSignedMessageWithAction( + signedMessageWithDetails: string +) { + const data = await notificationsRequest< + CommitSignedMessageMutation, + CommitSignedMessageMutationVariables + >({ + document: COMMIT_SIGNED_MESSAGE, + variables: { signedMessageWithDetails }, + }) + const success = data.commitSignedMessageWithAction?.success + if (!success) { + throw new Error( + `Unable to commit signed message: ${data.commitSignedMessageWithAction?.message}` + ) + } +} diff --git a/src/services/api/notifications/mutation.ts b/src/services/api/notifications/mutation.ts index be5fd4ab6..65f564047 100644 --- a/src/services/api/notifications/mutation.ts +++ b/src/services/api/notifications/mutation.ts @@ -1,7 +1,15 @@ +import { + ApiCommitSignedMessageBody, + ApiCommitSignedMessageResponse, +} from '@/pages/api/notifications/commit' import { ApiNotificationsLinkUrlBody, ApiNotificationsLinkUrlResponse, } from '@/pages/api/notifications/link' +import { + ApiFcmNotificationsLinkMessageBody, + ApiFcmNotificationsLinkMessageResponse, +} from '@/pages/api/notifications/link-fcm' import { ApiNotificationsLinkMessageBody, ApiNotificationsLinkMessageResponse, @@ -35,3 +43,32 @@ async function linkingAccount(data: ApiNotificationsLinkUrlBody) { return resData.url } export const useLinkingAccount = mutationWrapper(linkingAccount) + +async function getFcmLinkingMessage(data: ApiFcmNotificationsLinkMessageBody) { + if (!data) return null + + const res = await axios.post('/api/notifications/link-fcm', data) + const encodedMessage = (res.data as ApiFcmNotificationsLinkMessageResponse) + .data + const decodedMessage = decodeURIComponent(encodedMessage) + + const parsedMessage = JSON.parse(decodedMessage) + const sortedPayload = sortObj(parsedMessage.payload) + + return { + messageData: parsedMessage, + payloadToSign: JSON.stringify(sortedPayload), + } +} +export const useGetFcmLinkingMessage = mutationWrapper(getFcmLinkingMessage) + +async function commitSignedMessageWithAction(data: ApiCommitSignedMessageBody) { + if (!data) return null + + const res = await axios.post('/api/notifications/commit', data) + const resData = res.data as ApiCommitSignedMessageResponse + return resData +} +export const useCommitSignedMessageWithAction = mutationWrapper( + commitSignedMessageWithAction +) diff --git a/src/services/firebase/messaging.ts b/src/services/firebase/messaging.ts index f6c48c85e..02d91477f 100644 --- a/src/services/firebase/messaging.ts +++ b/src/services/firebase/messaging.ts @@ -2,7 +2,7 @@ import { getFirebaseNotificationAppId } from '@/utils/env/client' import { getMessaging, getToken } from 'firebase/messaging' import firebaseApp from './config' -export const getMessageToken = async (): Promise => { +export const getMessageToken = async (): Promise => { const messaging = getMessaging(firebaseApp) const permission = Notification.permission @@ -12,8 +12,6 @@ export const getMessageToken = async (): Promise => { const token = await getToken(messaging, { vapidKey: getFirebaseNotificationAppId(), }) - - alert(`Copy the FCM token: ${token}`) return token } else if (permission === 'denied') { // The user has denied permission. diff --git a/worker/index.ts b/worker/index.ts new file mode 100644 index 000000000..54fb252d8 --- /dev/null +++ b/worker/index.ts @@ -0,0 +1,8 @@ +import { initializeApp } from 'firebase/app' +import { getMessaging } from 'firebase/messaging' + +const app = initializeApp({ + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_ID, // Firebase Message Sender Config. +}) + +const initMessaging = getMessaging(app) From adbf85180e3cf019a09b3d4a2c7263ef7ae99690 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Mon, 31 Jul 2023 17:03:29 +0530 Subject: [PATCH 03/32] removed unused method --- .../contents/notifications/NotificationContent.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx index 02015e334..abf8674f1 100644 --- a/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx @@ -3,7 +3,6 @@ import MailIcon from '@/assets/icons/mail.svg' import DotBlinkingNotification from '@/components/DotBlinkingNotification' import MenuList from '@/components/MenuList' import useFirstVisitNotification from '@/hooks/useFirstVisitNotification' -import { getMessageToken } from '@/services/firebase/messaging' import { useMyAccount } from '@/stores/my-account' import { cx } from '@/utils/class-names' import { FaDiscord, FaTelegram } from 'react-icons/fa' @@ -15,15 +14,6 @@ export default function NotificationContent({ setCurrentState }: ContentProps) { const pwa = useFirstVisitNotification('pwa-notification') const telegram = useFirstVisitNotification('telegram-notification') - const enablePushNotification = async () => { - const token = await getMessageToken() - - if (token) { - // TODO: Send backend request to store mapping between token & address. - console.log('fcm token', token, 'User Address: ', address) - } - } - return ( Date: Mon, 31 Jul 2023 17:16:19 +0530 Subject: [PATCH 04/32] updated gitignore file --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bef13b90b..39d781a60 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,6 @@ next-env.d.ts .idea /public/sw.js -/public/workbox-*.js \ No newline at end of file +/public/workbox-*.js +/public/worker-*.js +/public/worker-*.txt \ No newline at end of file From 3f00bca4f66a801f5483525e389bc2a180fdcc3f Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Mon, 31 Jul 2023 21:01:08 +0700 Subject: [PATCH 05/32] Update fcm worker registration --- public/firebase-messaging-sw.js | 19 ------------------- .../notifications/PushNotificationContent.tsx | 1 + src/services/firebase/messaging.ts | 2 ++ worker/index.js | 14 ++++++++++++++ worker/index.ts | 8 -------- 5 files changed, 17 insertions(+), 27 deletions(-) delete mode 100644 public/firebase-messaging-sw.js create mode 100644 worker/index.js delete mode 100644 worker/index.ts diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js deleted file mode 100644 index af9fdeef2..000000000 --- a/public/firebase-messaging-sw.js +++ /dev/null @@ -1,19 +0,0 @@ -importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js') -importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js') - -if ('serviceWorker' in navigator) { - navigator.serviceWorker - .register('../firebase-messaging-sw.js') - .then(function (registration) { - console.log('Registration successful, scope is:', registration.scope) - }) - .catch(function (err) { - console.log('Service worker registration failed, error:', err) - }) -} - -firebase.initializeApp({ - messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_ID, // Firebase Message Sender Config. -}) - -const initMessaging = firebase.messaging() diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index a771e9e74..cb67c166d 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -130,6 +130,7 @@ function EnableNotificationButton({ const handleClickEnable = async () => { if (!address) return const fcmToken = await getMessageToken() + console.log('FCM Token', fcmToken) if (!fcmToken) return setFcmToken(fcmToken) diff --git a/src/services/firebase/messaging.ts b/src/services/firebase/messaging.ts index 02d91477f..52e117ed7 100644 --- a/src/services/firebase/messaging.ts +++ b/src/services/firebase/messaging.ts @@ -9,8 +9,10 @@ export const getMessageToken = async (): Promise => { if (permission === 'granted') { // The user has already granted permission. + const registration = await navigator.serviceWorker.getRegistration() const token = await getToken(messaging, { vapidKey: getFirebaseNotificationAppId(), + serviceWorkerRegistration: registration, }) return token } else if (permission === 'denied') { diff --git a/worker/index.js b/worker/index.js new file mode 100644 index 000000000..bd2d44edb --- /dev/null +++ b/worker/index.js @@ -0,0 +1,14 @@ +importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js') +importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js') + +firebase.initializeApp({ + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_ID, + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, + measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, +}) + +const initMessaging = firebase.messaging() diff --git a/worker/index.ts b/worker/index.ts deleted file mode 100644 index 54fb252d8..000000000 --- a/worker/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { initializeApp } from 'firebase/app' -import { getMessaging } from 'firebase/messaging' - -const app = initializeApp({ - messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_ID, // Firebase Message Sender Config. -}) - -const initMessaging = getMessaging(app) From 2afd21f045042fd47c55192d43f57362eefc4665 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Tue, 1 Aug 2023 17:35:30 +0530 Subject: [PATCH 06/32] added notification handling --- src/services/firebase/messaging.ts | 4 +-- worker/index.js | 42 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/services/firebase/messaging.ts b/src/services/firebase/messaging.ts index 52e117ed7..213e929ac 100644 --- a/src/services/firebase/messaging.ts +++ b/src/services/firebase/messaging.ts @@ -20,9 +20,9 @@ export const getMessageToken = async (): Promise => { console.log('Permission denied by the user.') } else { // The user has not yet been asked for permission. - Notification.requestPermission().then(async () => { + return Notification.requestPermission().then(async () => { // The user has granted or denied permission. - await getMessageToken() + return await getMessageToken() }) } } diff --git a/worker/index.js b/worker/index.js index bd2d44edb..820d17d6b 100644 --- a/worker/index.js +++ b/worker/index.js @@ -1,3 +1,45 @@ +// Handling Notification Click event. +// Keep this method above the importScripts to avoid overriding. +self.addEventListener('notificationclick', (event) => { + event.notification.close() + + let urlToOpen = self.location.origin + + try { + const notification = event.notification?.data + + if (notification) { + const data = notification['FCM_MSG']['data'] + const { postId, rootPostId, spaceId } = data + urlToOpen += `/${spaceId}/${rootPostId}/${postId}` + } + } catch (e) { + console.log('Error in loading notification response:', e) + } finally { + event.waitUntil( + // Check if a client (window/tab) is already open and in focus. + clients + .matchAll({ type: 'window', includeUncontrolled: true }) + .then((clientList) => { + for (const client of clientList) { + // If the client with the specified URL is already open, focus it and navigate to the URL. + if ( + client.url.includes(self.location.origin) && + client.url.includes(`/${spaceId}`) && + 'focus' in client + ) { + return client.focus().then(() => client.navigate(urlToOpen)) + } + } + // If no client with the URL is open, open a new window/tab with the PWA and navigate to the URL. + if (clients.openWindow) { + return clients.openWindow(urlToOpen) + } + }) + ) + } +}) + importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js') importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js') From b6bc59753922fb81e76bb770ed0ef086a3723302 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Tue, 1 Aug 2023 19:00:05 +0530 Subject: [PATCH 07/32] fixed notification supported error --- .../contents/notifications/PushNotificationContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index cb67c166d..0d33faef2 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -18,8 +18,7 @@ type NotificationButtonProps = ContentProps & { } export default function PushNotificationContent(props: ContentProps) { - const isNotificationNotSupported = !Notification - const permission = Notification.permission + const isNotificationNotSupported = typeof Notification === 'undefined' const [isRegistered, setIsRegistered] = useState(false) @@ -40,6 +39,7 @@ export default function PushNotificationContent(props: ContentProps) { ) } + const permission = Notification.permission if (permission === 'granted' && isRegistered) { // Disable Notifications. return ( From 2b66bacd91eb6120e85c852f67fb15b3fd47113e Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 1 Aug 2023 23:35:13 +0700 Subject: [PATCH 08/32] Add necessary ci configs to add envs --- ci.env | 8 ++++++++ docker/Dockerfile | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/ci.env b/ci.env index 8e12e90d6..b6b99d031 100644 --- a/ci.env +++ b/ci.env @@ -4,6 +4,14 @@ SERVER_MNEMONIC='$GH_SERVER_MNEMONIC' GH_SERVER_DISCUSSION_CREATOR_MNEMONIC='$GH_SERVER_DISCUSSION_CREATOR_MNEMONIC' NEXT_PUBLIC_SPACE_IDS='$GH_NEXT_PUBLIC_SPACE_IDS' NEXT_PUBLIC_MODERATION_URL='$GH_NEXT_PUBLIC_MODERATION_URL' +NEXT_PUBLIC_NOTIFICATION_APP_ID='$GH_NEXT_PUBLIC_NOTIFICATION_APP_ID' +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN='$GH_NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN' +NEXT_PUBLIC_FIREBASE_PROJECT_ID='$GH_NEXT_PUBLIC_FIREBASE_PROJECT_ID' +NEXT_PUBLIC_FIREBASE_API_KEY='$GH_NEXT_PUBLIC_FIREBASE_API_KEY' +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET='$GH_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET' +NEXT_PUBLIC_FIREBASE_MESSAGING_ID='$GH_NEXT_PUBLIC_FIREBASE_MESSAGING_ID' +NEXT_PUBLIC_FIREBASE_APP_ID='$GH_NEXT_PUBLIC_FIREBASE_APP_ID' +NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID='$GH_NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID' NOTIFICATIONS_URL='$GH_NOTIFICATIONS_URL' NOTIFICATIONS_TOKEN='$GH_NOTIFICATIONS_TOKEN' NEXT_PUBLIC_AMP_ID='$GH_NEXT_PUBLIC_AMP_ID' diff --git a/docker/Dockerfile b/docker/Dockerfile index 727ef23d0..447235187 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,6 +8,14 @@ ARG GH_CRUST_IPFS_AUTH ARG GH_NEXT_PUBLIC_CAPTCHA_SITE_KEY ARG GH_NEXT_PUBLIC_SPACE_IDS ARG GH_NEXT_PUBLIC_MODERATION_URL +ARG GH_NEXT_PUBLIC_NOTIFICATION_APP_ID +ARG GH_NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN +ARG GH_NEXT_PUBLIC_FIREBASE_PROJECT_ID +ARG GH_NEXT_PUBLIC_FIREBASE_API_KEY +ARG GH_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET +ARG GH_NEXT_PUBLIC_FIREBASE_MESSAGING_ID +ARG GH_NEXT_PUBLIC_FIREBASE_APP_ID +ARG GH_NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID ARG GH_NOTIFICATIONS_URL ARG GH_NOTIFICATIONS_TOKEN ARG GH_NEXT_PUBLIC_AMP_ID @@ -20,6 +28,14 @@ ARG GH_SUBSOCIAL_PROMO_SECRET_HEX ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=${GH_NEXT_PUBLIC_CAPTCHA_SITE_KEY} \ NEXT_PUBLIC_SPACE_IDS=${GH_NEXT_PUBLIC_SPACE_IDS} \ NEXT_PUBLIC_MODERATION_URL=${GH_NEXT_PUBLIC_MODERATION_URL} \ + NEXT_PUBLIC_NOTIFICATION_APP_ID=${GH_NEXT_PUBLIC_NOTIFICATION_APP_ID} \ + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=${GH_NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN} \ + NEXT_PUBLIC_FIREBASE_PROJECT_ID=${GH_NEXT_PUBLIC_FIREBASE_PROJECT_ID} \ + NEXT_PUBLIC_FIREBASE_API_KEY=${GH_NEXT_PUBLIC_FIREBASE_API_KEY} \ + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=${GH_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET} \ + NEXT_PUBLIC_FIREBASE_MESSAGING_ID=${GH_NEXT_PUBLIC_FIREBASE_MESSAGING_ID} \ + NEXT_PUBLIC_FIREBASE_APP_ID=${GH_NEXT_PUBLIC_FIREBASE_APP_ID} \ + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=${GH_NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID} \ NOTIFICATIONS_URL=${GH_NOTIFICATIONS_URL} \ NOTIFICATIONS_TOKEN=${GH_NOTIFICATIONS_TOKEN} \ NEXT_PUBLIC_AMP_ID=${GH_NEXT_PUBLIC_AMP_ID} \ @@ -56,6 +72,14 @@ ARG GH_CRUST_IPFS_AUTH ARG GH_NEXT_PUBLIC_CAPTCHA_SITE_KEY ARG GH_NEXT_PUBLIC_SPACE_IDS ARG GH_NEXT_PUBLIC_MODERATION_URL +ARG GH_NEXT_PUBLIC_NOTIFICATION_APP_ID +ARG GH_NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN +ARG GH_NEXT_PUBLIC_FIREBASE_PROJECT_ID +ARG GH_NEXT_PUBLIC_FIREBASE_API_KEY +ARG GH_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET +ARG GH_NEXT_PUBLIC_FIREBASE_MESSAGING_ID +ARG GH_NEXT_PUBLIC_FIREBASE_APP_ID +ARG GH_NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID ARG GH_NOTIFICATIONS_URL ARG GH_NOTIFICATIONS_TOKEN ARG GH_NEXT_PUBLIC_AMP_ID @@ -68,6 +92,14 @@ ARG GH_SUBSOCIAL_PROMO_SECRET_HEX ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=${GH_NEXT_PUBLIC_CAPTCHA_SITE_KEY} \ NEXT_PUBLIC_SPACE_IDS=${GH_NEXT_PUBLIC_SPACE_IDS} \ NEXT_PUBLIC_MODERATION_URL=${GH_NEXT_PUBLIC_MODERATION_URL} \ + NEXT_PUBLIC_NOTIFICATION_APP_ID=${GH_NEXT_PUBLIC_NOTIFICATION_APP_ID} \ + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=${GH_NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN} \ + NEXT_PUBLIC_FIREBASE_PROJECT_ID=${GH_NEXT_PUBLIC_FIREBASE_PROJECT_ID} \ + NEXT_PUBLIC_FIREBASE_API_KEY=${GH_NEXT_PUBLIC_FIREBASE_API_KEY} \ + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=${GH_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET} \ + NEXT_PUBLIC_FIREBASE_MESSAGING_ID=${GH_NEXT_PUBLIC_FIREBASE_MESSAGING_ID} \ + NEXT_PUBLIC_FIREBASE_APP_ID=${GH_NEXT_PUBLIC_FIREBASE_APP_ID} \ + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=${GH_NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID} \ NOTIFICATIONS_URL=${GH_NOTIFICATIONS_URL} \ NOTIFICATIONS_TOKEN=${GH_NOTIFICATIONS_TOKEN} \ NEXT_PUBLIC_AMP_ID=${GH_NEXT_PUBLIC_AMP_ID} \ From 26d0498449a3c6aea85c35c073ab66b7c08c0c08 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Tue, 1 Aug 2023 23:35:35 +0700 Subject: [PATCH 09/32] Add env for staging --- .github/workflows/feature-based.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/feature-based.yaml b/.github/workflows/feature-based.yaml index d42046e6f..918466f95 100644 --- a/.github/workflows/feature-based.yaml +++ b/.github/workflows/feature-based.yaml @@ -63,6 +63,14 @@ jobs: GH_NEXT_PUBLIC_GA_ID=G-FT28TL1W3M GH_IPFS_PIN_URL=https://test-pin.crustcloud.io/psa GH_COVALENT_API_KEY=cqt_rQtDdp9G9DFvqcGjVPDD4FFCyhFQ + GH_NEXT_PUBLIC_NOTIFICATION_APP_ID=BJaKjx7xtiIXtSUT8tR6VDG0WwhFwdqOqp8L5kc6alGBUtndnlV_DUJ6sze_z70VEBOKk4AT_wZTJqE9dC7sbEQ + GH_NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=gl-notifications-dev.firebaseapp.com + GH_NEXT_PUBLIC_FIREBASE_PROJECT_ID=gl-notifications-dev + GH_NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyC7pSekdJQ0PpUDCY4eEkEeDGCazVES6QI + GH_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=gl-notifications-dev.appspot.com + GH_NEXT_PUBLIC_FIREBASE_MESSAGING_ID=581626459774 + GH_NEXT_PUBLIC_FIREBASE_APP_ID=1:581626459774:web:6b7098eb48c0b142f50a2f + GH_NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-ZJBCKJR5F3 tags: | ${{ env.image }} cache-from: type=local,src=/tmp/.buildx-cache From 7e3a14a68eecfcf00f55eed1edf970ea7febddb8 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 18:13:50 +0700 Subject: [PATCH 10/32] Remove unused variable --- .../contents/notifications/NotificationContent.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx index abf8674f1..9a4374bbe 100644 --- a/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/NotificationContent.tsx @@ -3,14 +3,11 @@ import MailIcon from '@/assets/icons/mail.svg' import DotBlinkingNotification from '@/components/DotBlinkingNotification' import MenuList from '@/components/MenuList' import useFirstVisitNotification from '@/hooks/useFirstVisitNotification' -import { useMyAccount } from '@/stores/my-account' import { cx } from '@/utils/class-names' import { FaDiscord, FaTelegram } from 'react-icons/fa' import { ContentProps } from '../../types' export default function NotificationContent({ setCurrentState }: ContentProps) { - const address = useMyAccount((state) => state.address) - const pwa = useFirstVisitNotification('pwa-notification') const telegram = useFirstVisitNotification('telegram-notification') From 6b2e9678eab22093612035046aa632222418f608 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 18:14:04 +0700 Subject: [PATCH 11/32] Re register service worker if its not registered yet --- src/services/firebase/messaging.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/services/firebase/messaging.ts b/src/services/firebase/messaging.ts index 213e929ac..4be40fe7c 100644 --- a/src/services/firebase/messaging.ts +++ b/src/services/firebase/messaging.ts @@ -9,7 +9,11 @@ export const getMessageToken = async (): Promise => { if (permission === 'granted') { // The user has already granted permission. - const registration = await navigator.serviceWorker.getRegistration() + let registration = await navigator.serviceWorker.getRegistration() + if (!registration) { + registration = await navigator.serviceWorker.register('/sw.js') + } + const token = await getToken(messaging, { vapidKey: getFirebaseNotificationAppId(), serviceWorkerRegistration: registration, From 6a16dfa37a0f0f53da686c1598747cc4a4ff397b Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 18:44:26 +0700 Subject: [PATCH 12/32] Add loading state when getting token --- .../contents/notifications/PushNotificationContent.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index 0d33faef2..e83a7158e 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -56,6 +56,8 @@ function DisableNotificationButton({ address, setIsRegisterd, }: NotificationButtonProps) { + const [isGettingToken, setIsGettingToken] = useState(false) + const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = useCommitSignedMessageWithAction({ onSuccess: (data) => { @@ -78,11 +80,14 @@ function DisableNotificationButton({ }, }) - const isLoading = isCommitingMessage || isGettingLinkingMessage + const isLoading = + isCommitingMessage || isGettingLinkingMessage || isGettingToken const handleClickEnable = async () => { if (!address) return + setIsGettingToken(true) const fcmToken = await getMessageToken() + setIsGettingToken(false) if (!fcmToken) return getLinkingMessage({ address, fcmToken, action: 'unlink' }) From 591c81731fa9178aa9ec63f2a85f7cd85ba3bd56 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 19:23:31 +0700 Subject: [PATCH 13/32] Add loading to enable push notif --- .../contents/notifications/PushNotificationContent.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index e83a7158e..ed48feb95 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -83,7 +83,7 @@ function DisableNotificationButton({ const isLoading = isCommitingMessage || isGettingLinkingMessage || isGettingToken - const handleClickEnable = async () => { + const handleClickDisable = async () => { if (!address) return setIsGettingToken(true) const fcmToken = await getMessageToken() @@ -94,7 +94,7 @@ function DisableNotificationButton({ } return ( - ) @@ -104,6 +104,7 @@ function EnableNotificationButton({ address, setIsRegisterd, }: NotificationButtonProps) { + const [isGettingToken, setIsGettingToken] = useState(false) const [fcmToken, setFcmToken] = useState() const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = @@ -134,7 +135,9 @@ function EnableNotificationButton({ const handleClickEnable = async () => { if (!address) return + setIsGettingToken(true) const fcmToken = await getMessageToken() + setIsGettingToken(false) console.log('FCM Token', fcmToken) if (!fcmToken) return From 4dd96d1804761e602f4510170f83c3ec27228df7 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 19:31:50 +0700 Subject: [PATCH 14/32] Add is getting token to isloading state --- .../contents/notifications/PushNotificationContent.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index ed48feb95..24f081648 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -131,7 +131,8 @@ function EnableNotificationButton({ }, }) - const isLoading = isCommitingMessage || isGettingLinkingMessage + const isLoading = + isCommitingMessage || isGettingLinkingMessage || isGettingToken const handleClickEnable = async () => { if (!address) return From 35e57b384387d24a5752d8667d30f9899dca290c Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 20:25:59 +0700 Subject: [PATCH 15/32] Change get registration function --- src/services/firebase/messaging.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/firebase/messaging.ts b/src/services/firebase/messaging.ts index 4be40fe7c..39aefc022 100644 --- a/src/services/firebase/messaging.ts +++ b/src/services/firebase/messaging.ts @@ -9,15 +9,15 @@ export const getMessageToken = async (): Promise => { if (permission === 'granted') { // The user has already granted permission. - let registration = await navigator.serviceWorker.getRegistration() - if (!registration) { - registration = await navigator.serviceWorker.register('/sw.js') - } + const registration = await navigator.serviceWorker.ready + if (!registration) throw new Error('Registration not found') + alert('GETTING TOKEN') const token = await getToken(messaging, { vapidKey: getFirebaseNotificationAppId(), serviceWorkerRegistration: registration, }) + alert(`TOKEN ${token}`) return token } else if (permission === 'denied') { // The user has denied permission. From acfd66a039d59f3aa8f96f9d4357539006b77e94 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 21:40:48 +0700 Subject: [PATCH 16/32] Add prompt new version available --- src/stores/version.tsx | 12 ++++++++++++ src/utils/window.ts | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/stores/version.tsx b/src/stores/version.tsx index effd9e4a6..8157c29b8 100644 --- a/src/stores/version.tsx +++ b/src/stores/version.tsx @@ -1,5 +1,6 @@ import Button from '@/components/Button' import Toast from '@/components/Toast' +import { getWorkbox } from '@/utils/window' import axios from 'axios' import { toast } from 'react-hot-toast' import { IoRefresh } from 'react-icons/io5' @@ -33,6 +34,17 @@ export const useVersion = create()((set, get) => ({ setInterval(() => { versionHandling() }, INTERVAL) + + const wb = getWorkbox() + if (!wb) return + + const promptNewVersionAvailable = () => { + wb.messageSkipWaiting() + wb.addEventListener('controlling', () => { + notifyDifferentVersion() + }) + } + wb.addEventListener('waiting', promptNewVersionAvailable) }, })) diff --git a/src/utils/window.ts b/src/utils/window.ts index 1bb5abee5..e7e7791a3 100644 --- a/src/utils/window.ts +++ b/src/utils/window.ts @@ -47,3 +47,14 @@ export function replaceUrl(url: string) { url ) } + +export function getWorkbox() { + if ( + typeof window !== 'undefined' && + 'serviceWorker' in navigator && + (window as any).workbox !== undefined + ) { + return (window as any).workbox + } + return null +} From 06c1d43598ecee11b06107d4ef2d9bf69eeb4153 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 22:03:28 +0700 Subject: [PATCH 17/32] Exclude precaching js and images --- next.config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/next.config.js b/next.config.js index 1482336fd..34b8c1ad1 100644 --- a/next.config.js +++ b/next.config.js @@ -4,6 +4,8 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ const withPWA = require('next-pwa')({ dest: 'public', disable: process.env.NODE_ENV !== 'production', + buildExcludes: [/chunks\/.*$/, /media\/.*$/], + publicExcludes: ['!splashscreens/**/*', '!screenshots/**/*'], }) /** @type {import('next').NextConfig} */ From f564caeb7af02d4d5069d6d9e61ffa702722a714 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 22:09:38 +0700 Subject: [PATCH 18/32] Add offline page --- src/pages/_offline.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/pages/_offline.tsx diff --git a/src/pages/_offline.tsx b/src/pages/_offline.tsx new file mode 100644 index 000000000..609b8826b --- /dev/null +++ b/src/pages/_offline.tsx @@ -0,0 +1,20 @@ +import Button from '@/components/Button' +import Logo from '@/components/Logo' + +export default function OfflinePage() { + return ( +
+
+ +

You are currently offline 🥲

+

If you are back online, you can try to refresh the page.

+ +
+
+ ) +} From 31ff209699d46147b1fc82746bd723d7458108d3 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 22:12:17 +0700 Subject: [PATCH 19/32] Add gitignore for offline page fallback --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 39d781a60..5fb92c0f7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,6 @@ next-env.d.ts /public/sw.js /public/workbox-*.js /public/worker-*.js -/public/worker-*.txt \ No newline at end of file +/public/worker-*.txt + +/public/fallback-*.js From 061a58ac22c7da32c8907b815e3181e67e837d62 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Wed, 2 Aug 2023 23:16:32 +0700 Subject: [PATCH 20/32] Improve new version sw update --- next.config.js | 1 + src/stores/version.tsx | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/next.config.js b/next.config.js index 34b8c1ad1..c88b15fb4 100644 --- a/next.config.js +++ b/next.config.js @@ -4,6 +4,7 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ const withPWA = require('next-pwa')({ dest: 'public', disable: process.env.NODE_ENV !== 'production', + skipWaiting: false, buildExcludes: [/chunks\/.*$/, /media\/.*$/], publicExcludes: ['!splashscreens/**/*', '!screenshots/**/*'], }) diff --git a/src/stores/version.tsx b/src/stores/version.tsx index 8157c29b8..8231fd0a8 100644 --- a/src/stores/version.tsx +++ b/src/stores/version.tsx @@ -39,9 +39,11 @@ export const useVersion = create()((set, get) => ({ if (!wb) return const promptNewVersionAvailable = () => { - wb.messageSkipWaiting() - wb.addEventListener('controlling', () => { - notifyDifferentVersion() + notifyDifferentVersion(() => { + wb.addEventListener('controlling', () => { + window.location.reload() + }) + wb.messageSkipWaiting() }) } wb.addEventListener('waiting', promptNewVersionAvailable) @@ -64,7 +66,7 @@ async function validateSameVersion(currentVersion: string | undefined) { } } -function notifyDifferentVersion() { +function notifyDifferentVersion(onClick?: () => void) { toast.custom( (t) => ( window.location.reload()} + onClick={onClick ?? (() => window.location.reload())} > From 492d1d41cd60cd5398e37580ffecf59812bebf18 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Fri, 4 Aug 2023 01:55:04 +0530 Subject: [PATCH 21/32] added review changes --- .../ProfileModal/contents/LogoutContent.tsx | 5 +- .../notifications/PushNotificationContent.tsx | 27 ++++----- src/services/firebase/config.ts | 22 +------ src/services/firebase/messaging.ts | 6 +- src/utils/env/client.ts | 57 ++++--------------- 5 files changed, 27 insertions(+), 90 deletions(-) diff --git a/src/components/auth/ProfileModal/contents/LogoutContent.tsx b/src/components/auth/ProfileModal/contents/LogoutContent.tsx index 30274e38a..40171737b 100644 --- a/src/components/auth/ProfileModal/contents/LogoutContent.tsx +++ b/src/components/auth/ProfileModal/contents/LogoutContent.tsx @@ -9,8 +9,7 @@ import { useMyAccount } from '@/stores/my-account' import { LocalStorage } from '@/utils/storage' import { sortObj } from 'jsonabc' import { ContentProps } from '../types' - -const FCM_PUSH_NOTIFICATION_STORAGE_KEY = 'push-notification-fcm-token' +import { FCM_PUSH_NOTIFICATION_STORAGE_KEY } from './notifications/PushNotificationContent' const fcmTokenStorage = new LocalStorage( () => FCM_PUSH_NOTIFICATION_STORAGE_KEY @@ -24,7 +23,7 @@ function LogoutContent({ setCurrentState }: ContentProps) { const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = useCommitSignedMessageWithAction({ onSuccess: (data) => { - if (!data) throw new Error('Error generating url') + if (!data) throw new Error('Error removing token on logout') // FCM Token Disabled. fcmTokenStorage.remove() diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index 24f081648..bb2160762 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -10,12 +10,8 @@ import { sortObj } from 'jsonabc' import { useEffect, useState } from 'react' import { ContentProps } from '../../types' -const STORAGE_KEY = 'push-notification-fcm-token' -const storage = new LocalStorage(() => STORAGE_KEY) - -type NotificationButtonProps = ContentProps & { - setIsRegisterd: (v: boolean) => void -} +export const FCM_PUSH_NOTIFICATION_STORAGE_KEY = 'push-notification-fcm-token' +const storage = new LocalStorage(() => FCM_PUSH_NOTIFICATION_STORAGE_KEY) export default function PushNotificationContent(props: ContentProps) { const isNotificationNotSupported = typeof Notification === 'undefined' @@ -24,11 +20,7 @@ export default function PushNotificationContent(props: ContentProps) { useEffect(() => { const storedFcmToken = storage.get() - if (storedFcmToken) { - setIsRegistered(true) - } else { - setIsRegistered(false) - } + setIsRegistered(!!storedFcmToken) }, [isRegistered]) if (isNotificationNotSupported) { @@ -41,7 +33,6 @@ export default function PushNotificationContent(props: ContentProps) { const permission = Notification.permission if (permission === 'granted' && isRegistered) { - // Disable Notifications. return ( ) @@ -52,20 +43,24 @@ export default function PushNotificationContent(props: ContentProps) { ) } +type NotificationButtonProps = ContentProps & { + setIsRegistered: (v: boolean) => void +} + function DisableNotificationButton({ address, - setIsRegisterd, + setIsRegistered, }: NotificationButtonProps) { const [isGettingToken, setIsGettingToken] = useState(false) const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = useCommitSignedMessageWithAction({ onSuccess: (data) => { - if (!data) throw new Error('Error generating url') + if (!data) throw new Error('Error in disabling notification request') // FCM Token Disabled. storage.remove() - setIsRegisterd(false) + setIsRegistered(false) }, }) @@ -102,7 +97,7 @@ function DisableNotificationButton({ function EnableNotificationButton({ address, - setIsRegisterd, + setIsRegistered, }: NotificationButtonProps) { const [isGettingToken, setIsGettingToken] = useState(false) const [fcmToken, setFcmToken] = useState() diff --git a/src/services/firebase/config.ts b/src/services/firebase/config.ts index 6ab521dd6..7e8be8773 100644 --- a/src/services/firebase/config.ts +++ b/src/services/firebase/config.ts @@ -1,26 +1,8 @@ // firebaseConfig.js -import { - getFirebaseApiKey, - getFirebaseAppId, - getFirebaseAuthDomain, - getFirebaseMeasurementId, - getFirebaseMessagingId, - getFirebaseProjectId, - getFirebaseStorageBucket, -} from '@/utils/env/client' +import { getFirebaseConfig } from '@/utils/env/client' import * as firebase from 'firebase/app' import 'firebase/firestore' // Import any Firebase services you plan to use -const firebaseConfig = { - apiKey: getFirebaseApiKey(), - authDomain: getFirebaseAuthDomain(), - projectId: getFirebaseProjectId(), - storageBucket: getFirebaseStorageBucket(), - messagingSenderId: getFirebaseMessagingId(), - appId: getFirebaseAppId(), - measurementId: getFirebaseMeasurementId(), -} - -const firebaseApp = firebase.initializeApp(firebaseConfig) +const firebaseApp = firebase.initializeApp(getFirebaseConfig()) export default firebaseApp diff --git a/src/services/firebase/messaging.ts b/src/services/firebase/messaging.ts index 39aefc022..d1b3e0cdd 100644 --- a/src/services/firebase/messaging.ts +++ b/src/services/firebase/messaging.ts @@ -24,9 +24,7 @@ export const getMessageToken = async (): Promise => { console.log('Permission denied by the user.') } else { // The user has not yet been asked for permission. - return Notification.requestPermission().then(async () => { - // The user has granted or denied permission. - return await getMessageToken() - }) + await Notification.requestPermission() + return await getMessageToken() } } diff --git a/src/utils/env/client.ts b/src/utils/env/client.ts index 176607c45..388bffb57 100644 --- a/src/utils/env/client.ts +++ b/src/utils/env/client.ts @@ -46,11 +46,16 @@ export function getSquidUrl() { return checkEnv(process.env.NEXT_PUBLIC_SQUID_URL, 'NEXT_PUBLIC_SQUID_URL') } -export function getFirebaseApiKey() { - return checkEnv( - process.env.NEXT_PUBLIC_FIREBASE_API_KEY, - 'NEXT_PUBLIC_FIREBASE_API_KEY' - ) +export function getFirebaseConfig() { + const apiKey = checkEnv(process.env.NEXT_PUBLIC_FIREBASE_API_KEY, 'NEXT_PUBLIC_FIREBASE_API_KEY') + const authDomain = checkEnv(process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, 'NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN') + const projectId = checkEnv(process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, 'NEXT_PUBLIC_FIREBASE_PROJECT_ID') + const storageBucket = checkEnv(process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, 'NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET') + const messagingSenderId = checkEnv(process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_ID,'NEXT_PUBLIC_FIREBASE_MESSAGING_ID') + const appId = checkEnv(process.env.NEXT_PUBLIC_FIREBASE_APP_ID,'NEXT_PUBLIC_FIREBASE_APP_ID') + const measurementId = checkEnv(process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,'NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID') + + return { apiKey, authDomain, projectId, storageBucket, messagingSenderId, appId, measurementId } } export function getFirebaseNotificationAppId() { @@ -59,45 +64,3 @@ export function getFirebaseNotificationAppId() { 'NEXT_PUBLIC_NOTIFICATION_APP_ID' ) } - -export function getFirebaseAuthDomain() { - return checkEnv( - process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, - 'NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN' - ) -} - -export function getFirebaseProjectId() { - return checkEnv( - process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, - 'NEXT_PUBLIC_FIREBASE_PROJECT_ID' - ) -} - -export function getFirebaseStorageBucket() { - return checkEnv( - process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, - 'NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET' - ) -} - -export function getFirebaseMessagingId() { - return checkEnv( - process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_ID, - 'NEXT_PUBLIC_FIREBASE_MESSAGING_ID' - ) -} - -export function getFirebaseAppId() { - return checkEnv( - process.env.NEXT_PUBLIC_FIREBASE_APP_ID, - 'NEXT_PUBLIC_FIREBASE_APP_ID' - ) -} - -export function getFirebaseMeasurementId() { - return checkEnv( - process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, - 'NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID' - ) -} From b14626086f6f5dbe8226eac917a96df9ffcb7d91 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Fri, 4 Aug 2023 02:02:50 +0530 Subject: [PATCH 22/32] fix ci failing issue --- .../contents/notifications/PushNotificationContent.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index bb2160762..b81e4f91b 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -34,12 +34,12 @@ export default function PushNotificationContent(props: ContentProps) { const permission = Notification.permission if (permission === 'granted' && isRegistered) { return ( - + ) } return ( - + ) } @@ -110,7 +110,7 @@ function EnableNotificationButton({ // FCM Token Enabled. if (fcmToken) { storage.set(fcmToken) - setIsRegisterd(true) + setIsRegistered(true) } }, }) From 22b927f53b938d43cd416f4c44b96cbb8adf7866 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Fri, 4 Aug 2023 12:14:52 +0530 Subject: [PATCH 23/32] remove unwanted alerts --- src/services/firebase/messaging.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/firebase/messaging.ts b/src/services/firebase/messaging.ts index d1b3e0cdd..5e59b9e18 100644 --- a/src/services/firebase/messaging.ts +++ b/src/services/firebase/messaging.ts @@ -12,12 +12,11 @@ export const getMessageToken = async (): Promise => { const registration = await navigator.serviceWorker.ready if (!registration) throw new Error('Registration not found') - alert('GETTING TOKEN') const token = await getToken(messaging, { vapidKey: getFirebaseNotificationAppId(), serviceWorkerRegistration: registration, }) - alert(`TOKEN ${token}`) + return token } else if (permission === 'denied') { // The user has denied permission. From 4f37614ccfae748da80d191b2673f622297296d1 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Fri, 4 Aug 2023 19:02:45 +0700 Subject: [PATCH 24/32] Remove workbox notification prompt --- src/stores/version.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/stores/version.tsx b/src/stores/version.tsx index 8231fd0a8..013b6daba 100644 --- a/src/stores/version.tsx +++ b/src/stores/version.tsx @@ -1,6 +1,5 @@ import Button from '@/components/Button' import Toast from '@/components/Toast' -import { getWorkbox } from '@/utils/window' import axios from 'axios' import { toast } from 'react-hot-toast' import { IoRefresh } from 'react-icons/io5' @@ -34,19 +33,6 @@ export const useVersion = create()((set, get) => ({ setInterval(() => { versionHandling() }, INTERVAL) - - const wb = getWorkbox() - if (!wb) return - - const promptNewVersionAvailable = () => { - notifyDifferentVersion(() => { - wb.addEventListener('controlling', () => { - window.location.reload() - }) - wb.messageSkipWaiting() - }) - } - wb.addEventListener('waiting', promptNewVersionAvailable) }, })) From 5c69874e01ca101229b86f5cf3a4af1a57e2e634 Mon Sep 17 00:00:00 2001 From: Mell Date: Tue, 8 Aug 2023 19:47:22 +0300 Subject: [PATCH 25/32] Add prod firebase keys --- .github/workflows/build-deploy.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index 32141cd89..71c0a0308 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -62,6 +62,13 @@ jobs: GH_NEXT_PUBLIC_CAPTCHA_SITE_KEY=6LcnGU0lAAAAANPvuR2I3gZswHpX1GJnpJG2ocVb GH_SERVER_DISCUSSION_CREATOR_MNEMONIC=${{ secrets.SERVER_DISCUSSION_CREATOR_MNEMONIC }} GH_COVALENT_API_KEY=${{ secrets.COVALENT_API_KEY }} + GH_NEXT_PUBLIC_NOTIFICATION_APP_ID=BECyoVsDLEgsOj9MvhoetL3YGYZVCE5RzhADmBugpp0hu7QBV_xG8veiT_qAFxF9S8qXKhPvaPiD5oMrdWrFNB0 + GH_NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=grill-web-push.firebaseapp.com + GH_NEXT_PUBLIC_FIREBASE_PROJECT_ID=grill-web-push + GH_NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyCdu_lvl18590HFoAXTysuKStaJJkaA4h4 + GH_NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=grill-web-push.appspot.com + GH_NEXT_PUBLIC_FIREBASE_MESSAGING_ID=762898090466 + GH_NEXT_PUBLIC_FIREBASE_APP_ID=1:762898090466:web:2a09d26c3bab706b95d1bb tags: | ${{ env.image }} dappforce/subsocial-web-app:grillchat-master-latest From 1f2a0f72f6e0f84dd56d65cdb9ffbf373d8cf869 Mon Sep 17 00:00:00 2001 From: Tushar Ojha Date: Wed, 9 Aug 2023 11:45:57 +0530 Subject: [PATCH 26/32] added app badge definition --- worker/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/worker/index.js b/worker/index.js index 820d17d6b..718916591 100644 --- a/worker/index.js +++ b/worker/index.js @@ -16,6 +16,9 @@ self.addEventListener('notificationclick', (event) => { } catch (e) { console.log('Error in loading notification response:', e) } finally { + // Update / clear the value for App Badge on notification click. + event.waitUntil(navigator.clearAppBadge()) + event.waitUntil( // Check if a client (window/tab) is already open and in focus. clients @@ -40,6 +43,12 @@ self.addEventListener('notificationclick', (event) => { } }) +self.addEventListener('push', (event) => { + // Fetch the value for setting app badge from GraphQL. + // And pass it like this: navigator.setAppBadge(value). + event.waitUntil(navigator.setAppBadge()) +}) + importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-app.js') importScripts('https://www.gstatic.com/firebasejs/3.5.0/firebase-messaging.js') From 1ff1bae5252014f29f6e4a40414fb7325b0d8dc7 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 10 Aug 2023 22:35:59 +0700 Subject: [PATCH 27/32] Fix wrong ci env --- ci.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci.env b/ci.env index f434cb092..98717678e 100644 --- a/ci.env +++ b/ci.env @@ -1,7 +1,7 @@ NEXT_PUBLIC_CAPTCHA_SITE_KEY='$GH_NEXT_PUBLIC_CAPTCHA_SITE_KEY' CAPTCHA_SECRET='$GH_CAPTCHA_SECRET' SERVER_MNEMONIC='$GH_SERVER_MNEMONIC' -GH_SERVER_DISCUSSION_CREATOR_MNEMONIC='$GH_SERVER_DISCUSSION_CREATOR_MNEMONIC' +SERVER_DISCUSSION_CREATOR_MNEMONIC='$GH_SERVER_DISCUSSION_CREATOR_MNEMONIC' NEXT_PUBLIC_SPACE_IDS='$GH_NEXT_PUBLIC_SPACE_IDS' MODERATION_URL='$GH_MODERATION_URL' MODERATION_TOKEN='$GH_MODERATION_TOKEN' From 339f8bde26c49b45c9bc3ccf66aaba366967b498 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 10 Aug 2023 22:44:00 +0700 Subject: [PATCH 28/32] Refactor fcm mutations --- .../ProfileModal/contents/LogoutContent.tsx | 51 +-------- .../notifications/PushNotificationContent.tsx | 100 +++++------------- .../TelegramNotificationContent.tsx | 6 +- src/services/api/notifications/mutation.ts | 36 +++---- 4 files changed, 45 insertions(+), 148 deletions(-) diff --git a/src/components/auth/ProfileModal/contents/LogoutContent.tsx b/src/components/auth/ProfileModal/contents/LogoutContent.tsx index 40171737b..2242dcc7e 100644 --- a/src/components/auth/ProfileModal/contents/LogoutContent.tsx +++ b/src/components/auth/ProfileModal/contents/LogoutContent.tsx @@ -1,13 +1,8 @@ import Button from '@/components/Button' -import useSignMessage from '@/hooks/useSignMessage' -import { - useCommitSignedMessageWithAction, - useGetFcmLinkingMessage, -} from '@/services/api/notifications/mutation' +import { useLinkFcm } from '@/services/api/notifications/mutation' import { useSendEvent } from '@/stores/analytics' import { useMyAccount } from '@/stores/my-account' import { LocalStorage } from '@/utils/storage' -import { sortObj } from 'jsonabc' import { ContentProps } from '../types' import { FCM_PUSH_NOTIFICATION_STORAGE_KEY } from './notifications/PushNotificationContent' @@ -20,28 +15,9 @@ function LogoutContent({ setCurrentState }: ContentProps) { const logout = useMyAccount((state) => state.logout) const sendEvent = useSendEvent() - const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = - useCommitSignedMessageWithAction({ - onSuccess: (data) => { - if (!data) throw new Error('Error removing token on logout') - - // FCM Token Disabled. - fcmTokenStorage.remove() - }, - }) - - const processMessage = useProcessMessage() - const { mutate: getLinkingMessage, isLoading: isGettingLinkingMessage } = - useGetFcmLinkingMessage({ - onSuccess: async (data) => { - const processedData = await processMessage(data) - commitSignedMessage({ - signedMessageWithDetails: processedData, - }) - }, - }) - - const isLoading = isCommitingMessage || isGettingLinkingMessage + const { mutate: linkFcm, isLoading } = useLinkFcm({ + onSuccess: () => fcmTokenStorage.remove(), + }) const onShowPrivateKeyClick = () => { sendEvent('click no_show_me_my_private_key_button') @@ -54,7 +30,7 @@ function LogoutContent({ setCurrentState }: ContentProps) { const fcmToken = fcmTokenStorage.get() if (fcmToken && address) { - getLinkingMessage({ address, fcmToken, action: 'link' }) + linkFcm({ address, fcmToken, action: 'unlink' }) } } @@ -76,20 +52,3 @@ function LogoutContent({ setCurrentState }: ContentProps) { } export default LogoutContent - -function useProcessMessage() { - const signMessage = useSignMessage() - - return async (data: { messageData: any; payloadToSign: string } | null) => { - if (!data) throw new Error('No data') - - const signedPayload = await signMessage(data.payloadToSign) - data.messageData['signature'] = signedPayload - - const signedMessage = encodeURIComponent( - JSON.stringify(sortObj(data.messageData)) - ) - - return signedMessage - } -} diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index b81e4f91b..1cae0df69 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -1,12 +1,7 @@ import Button from '@/components/Button' -import useSignMessage from '@/hooks/useSignMessage' -import { - useCommitSignedMessageWithAction, - useGetFcmLinkingMessage, -} from '@/services/api/notifications/mutation' +import { useLinkFcm } from '@/services/api/notifications/mutation' import { getMessageToken } from '@/services/firebase/messaging' import { LocalStorage } from '@/utils/storage' -import { sortObj } from 'jsonabc' import { useEffect, useState } from 'react' import { ContentProps } from '../../types' @@ -53,30 +48,17 @@ function DisableNotificationButton({ }: NotificationButtonProps) { const [isGettingToken, setIsGettingToken] = useState(false) - const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = - useCommitSignedMessageWithAction({ - onSuccess: (data) => { - if (!data) throw new Error('Error in disabling notification request') - - // FCM Token Disabled. - storage.remove() - setIsRegistered(false) - }, - }) - - const processMessage = useProcessMessage() - const { mutate: getLinkingMessage, isLoading: isGettingLinkingMessage } = - useGetFcmLinkingMessage({ - onSuccess: async (data) => { - const processedData = await processMessage(data) - commitSignedMessage({ - signedMessageWithDetails: processedData, - }) - }, - }) - - const isLoading = - isCommitingMessage || isGettingLinkingMessage || isGettingToken + const { mutate: unlinkFcm, isLoading: isUnlinking } = useLinkFcm({ + onSuccess: (data) => { + if (!data) throw new Error('Error in disabling notification request') + + // FCM Token Disabled. + storage.remove() + setIsRegistered(false) + }, + }) + + const isLoading = isUnlinking || isGettingToken const handleClickDisable = async () => { if (!address) return @@ -85,7 +67,7 @@ function DisableNotificationButton({ setIsGettingToken(false) if (!fcmToken) return - getLinkingMessage({ address, fcmToken, action: 'unlink' }) + unlinkFcm({ address, fcmToken, action: 'unlink' }) } return ( @@ -102,32 +84,17 @@ function EnableNotificationButton({ const [isGettingToken, setIsGettingToken] = useState(false) const [fcmToken, setFcmToken] = useState() - const { mutate: commitSignedMessage, isLoading: isCommitingMessage } = - useCommitSignedMessageWithAction({ - onSuccess: (data) => { - if (!data) throw new Error('Error subscribing for notifications') - - // FCM Token Enabled. - if (fcmToken) { - storage.set(fcmToken) - setIsRegistered(true) - } - }, - }) - - const processMessage = useProcessMessage() - const { mutate: getLinkingMessage, isLoading: isGettingLinkingMessage } = - useGetFcmLinkingMessage({ - onSuccess: async (data) => { - const processedData = await processMessage(data) - commitSignedMessage({ - signedMessageWithDetails: processedData, - }) - }, - }) - - const isLoading = - isCommitingMessage || isGettingLinkingMessage || isGettingToken + const { mutate: linkFcm, isLoading: isLinking } = useLinkFcm({ + onSuccess: (data) => { + // FCM Token Enabled. + if (fcmToken) { + storage.set(fcmToken) + setIsRegistered(true) + } + }, + }) + + const isLoading = isLinking || isGettingToken const handleClickEnable = async () => { if (!address) return @@ -138,7 +105,7 @@ function EnableNotificationButton({ if (!fcmToken) return setFcmToken(fcmToken) - getLinkingMessage({ address, fcmToken, action: 'link' }) + linkFcm({ address, fcmToken, action: 'link' }) } return ( @@ -147,20 +114,3 @@ function EnableNotificationButton({ ) } - -function useProcessMessage() { - const signMessage = useSignMessage() - - return async (data: { messageData: any; payloadToSign: string } | null) => { - if (!data) throw new Error('No data') - - const signedPayload = await signMessage(data.payloadToSign) - data.messageData['signature'] = signedPayload - - const signedMessage = encodeURIComponent( - JSON.stringify(sortObj(data.messageData)) - ) - - return signedMessage - } -} diff --git a/src/components/auth/ProfileModal/contents/notifications/TelegramNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/TelegramNotificationContent.tsx index f097be120..240ab36ce 100644 --- a/src/components/auth/ProfileModal/contents/notifications/TelegramNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/TelegramNotificationContent.tsx @@ -4,7 +4,7 @@ import LinkText from '@/components/LinkText' import Notice from '@/components/Notice' import { useIntegratedSkeleton } from '@/components/SkeletonFallback' import Toast from '@/components/Toast' -import { useLinkingAccount } from '@/services/api/notifications/mutation' +import { useLinkTelegramAccount } from '@/services/api/notifications/mutation' import { getLinkedTelegramAccountsQuery } from '@/services/api/notifications/query' import { getIsInIos } from '@/utils/window' import { useQueryClient } from '@tanstack/react-query' @@ -85,7 +85,7 @@ function DisconnectButton({ address, afterDisconnect, }: ContentProps & { afterDisconnect?: () => void }) { - const { mutate: getLinkingMessage, isLoading } = useLinkingAccount({ + const { mutate: getLinkingMessage, isLoading } = useLinkTelegramAccount({ onSuccess: () => { afterDisconnect?.() }, @@ -116,7 +116,7 @@ function ConnectTelegramButton({ address }: ContentProps) { }) const [openedTelegramBotLink, setOpenedTelegramBotLink] = useState(false) - const { mutate: getLinkingMessage, isLoading } = useLinkingAccount({ + const { mutate: getLinkingMessage, isLoading } = useLinkTelegramAccount({ onSuccess: async (url) => { if (!url) throw new Error('Error generating url') if (!getIsInIos()) { diff --git a/src/services/api/notifications/mutation.ts b/src/services/api/notifications/mutation.ts index 957267961..58af40e92 100644 --- a/src/services/api/notifications/mutation.ts +++ b/src/services/api/notifications/mutation.ts @@ -17,11 +17,10 @@ import { import { queryClient } from '@/services/provider' import mutationWrapper from '@/subsocial-query/base' import axios, { AxiosResponse } from 'axios' -import { sortObj } from 'jsonabc' import { processMessageTpl } from '../utils' import { getLinkedTelegramAccountsQuery } from './query' -async function linkingAccount(data: ApiNotificationsLinkMessageBody) { +async function linkTelegramAccount(data: ApiNotificationsLinkMessageBody) { if (!data) return null const res = await axios.post('/api/notifications/link-message', data) @@ -39,7 +38,7 @@ async function linkingAccount(data: ApiNotificationsLinkMessageBody) { const resData = linkRes.data return resData.url } -export const useLinkingAccount = mutationWrapper(linkingAccount, { +export const useLinkTelegramAccount = mutationWrapper(linkTelegramAccount, { onSuccess: (_, variables) => { if (variables.action === 'unlink') { getLinkedTelegramAccountsQuery.invalidate(queryClient, { @@ -49,31 +48,20 @@ export const useLinkingAccount = mutationWrapper(linkingAccount, { }, }) -async function getFcmLinkingMessage(data: ApiFcmNotificationsLinkMessageBody) { +async function linkFcm(data: ApiFcmNotificationsLinkMessageBody) { if (!data) return null const res = await axios.post('/api/notifications/link-fcm', data) const encodedMessage = (res.data as ApiFcmNotificationsLinkMessageResponse) .data - const decodedMessage = decodeURIComponent(encodedMessage) - - const parsedMessage = JSON.parse(decodedMessage) - const sortedPayload = sortObj(parsedMessage.payload) - - return { - messageData: parsedMessage, - payloadToSign: JSON.stringify(sortedPayload), - } -} -export const useGetFcmLinkingMessage = mutationWrapper(getFcmLinkingMessage) - -async function commitSignedMessageWithAction(data: ApiCommitSignedMessageBody) { - if (!data) return null + const signedMessage = await processMessageTpl(encodedMessage) - const res = await axios.post('/api/notifications/commit', data) - const resData = res.data as ApiCommitSignedMessageResponse - return resData + const linkRes = await axios.post< + any, + AxiosResponse, + ApiCommitSignedMessageBody + >('/api/notifications/commit', { signedMessageWithDetails: signedMessage }) + const resData = linkRes.data + return resData.message } -export const useCommitSignedMessageWithAction = mutationWrapper( - commitSignedMessageWithAction -) +export const useLinkFcm = mutationWrapper(linkFcm) From cb755251f814b0e9ae149c9f7d61bce6829a7d05 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 10 Aug 2023 22:51:39 +0700 Subject: [PATCH 29/32] Make server discussion not required --- .env.example | 1 - .env.local.example | 11 ----------- src/utils/env/server.ts | 3 +-- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/.env.example b/.env.example index 0554b6f64..b3c1ab548 100644 --- a/.env.example +++ b/.env.example @@ -38,7 +38,6 @@ NEXT_PUBLIC_COMMUNITY_HUB_ID= - # Firebase API Keys NEXT_PUBLIC_NOTIFICATION_APP_ID= NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= diff --git a/.env.local.example b/.env.local.example index 28ef61e0d..6c003c2b9 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,5 +1,4 @@ SERVER_MNEMONIC="bounce clutch hotel number lab imitate grass loop jewel cousin network paddle" -SERVER_DISCUSSION_CREATOR_MNEMONIC="" # ipfs test values CRUST_IPFS_AUTH="eyJkYXRhIjp7ImRvbWFpbiI6eyJjaGFpbklkIjoiNSIsIm5hbWUiOiJDbG91ZDMuY2MiLCJ2ZXJpZnlpbmdDb250cmFjdCI6IjB4Q2NDQ2NjY2NDQ0NDY0NDQ0NDQ2NDY0NjY0NjQ0NDY0NjY2NjY2NjQyIsInZlcnNpb24iOiIxIn0sIm1lc3NhZ2UiOnsiZGVzY3JpcHRpb24iOiJTaWduIGZvciBXMyBCdWNrZXQgQWNjZXNzIEF1dGhlbnRpY2F0aW9uIiwic2lnbmluZ0FkZHJlc3MiOiIweDAwNDFjNzA1ZTEwNmVlOWRFMjI3Q2ExMzlBZDRBOTlEQjY0NENCM2EiLCJ0b2tlbkFkZHJlc3MiOiIweDM5ODY2Mzg0MjY4MDMzMkExQWJBM0IwM2JkNmRCNDdhRTk4NDk5NEMiLCJ0b2tlbklkIjoiMzAwMDAzNyIsImVmZmVjdGl2ZVRpbWVzdGFtcCI6MTY3ODUzNzYxNywiZXhwaXJhdGlvblRpbWVzdGFtcCI6MH0sInByaW1hcnlUeXBlIjoiVzNCdWNrZXQiLCJ0eXBlcyI6eyJXM0J1Y2tldCI6W3sibmFtZSI6ImRlc2NyaXB0aW9uIiwidHlwZSI6InN0cmluZyJ9LHsibmFtZSI6InNpZ25pbmdBZGRyZXNzIiwidHlwZSI6ImFkZHJlc3MifSx7Im5hbWUiOiJ0b2tlbkFkZHJlc3MiLCJ0eXBlIjoiYWRkcmVzcyJ9LHsibmFtZSI6InRva2VuSWQiLCJ0eXBlIjoic3RyaW5nIn0seyJuYW1lIjoiZWZmZWN0aXZlVGltZXN0YW1wIiwidHlwZSI6InVpbnQyNTYifSx7Im5hbWUiOiJleHBpcmF0aW9uVGltZXN0YW1wIiwidHlwZSI6InVpbnQyNTYifV19fSwic2lnbmF0dXJlIjoiMHhhMTUxODkyZGNhMTZlMTJkZmViYTFjY2FkMjNmZjM5MGVmODQ1MzdhMGI2MWI4YWQ1NDY1ZGI1ZjI1NjZkMWJmMjc3MGE1OTQyMzFmNzFmYzA5YWVlZGJhNzJkZjM5Y2Y4NDIxMTQ3YzJkNWQyNTFkNDZjNGQyMDFlOGM1MmUzZTFjIn0=" @@ -12,13 +11,3 @@ MODERATION_URL="https://moderation.subsocial.network/graphql" # recaptcha test keys NEXT_PUBLIC_CAPTCHA_SITE_KEY="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" CAPTCHA_SECRET="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe" - -# Firebase API Keys -NEXT_PUBLIC_NOTIFICATION_APP_ID= -NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= -NEXT_PUBLIC_FIREBASE_PROJECT_ID= -NEXT_PUBLIC_FIREBASE_API_KEY= -NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= -NEXT_PUBLIC_FIREBASE_MESSAGING_ID= -NEXT_PUBLIC_FIREBASE_APP_ID= -NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID= diff --git a/src/utils/env/server.ts b/src/utils/env/server.ts index d29a8f791..cc0e7aa0f 100644 --- a/src/utils/env/server.ts +++ b/src/utils/env/server.ts @@ -7,8 +7,7 @@ export function getServerMnemonic() { export function getDiscussionCreatorMnemonic() { return checkEnv( process.env.SERVER_DISCUSSION_CREATOR_MNEMONIC, - 'SERVER_DISCUSSION_CREATOR_MNEMONIC', - true + 'SERVER_DISCUSSION_CREATOR_MNEMONIC' ) } From 36b8763251db68e1e96476042ff9d2de61b360f4 Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 10 Aug 2023 22:58:54 +0700 Subject: [PATCH 30/32] Remove skip waiting config --- next.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/next.config.js b/next.config.js index c88b15fb4..34b8c1ad1 100644 --- a/next.config.js +++ b/next.config.js @@ -4,7 +4,6 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ const withPWA = require('next-pwa')({ dest: 'public', disable: process.env.NODE_ENV !== 'production', - skipWaiting: false, buildExcludes: [/chunks\/.*$/, /media\/.*$/], publicExcludes: ['!splashscreens/**/*', '!screenshots/**/*'], }) From 681a5ace7ee6b52d34db9e89c3bdd8e483ac87fb Mon Sep 17 00:00:00 2001 From: teodorus-nathaniel Date: Thu, 10 Aug 2023 22:59:50 +0700 Subject: [PATCH 31/32] Export storage instead of storage key for fcm token --- .../auth/ProfileModal/contents/LogoutContent.tsx | 11 +++-------- .../notifications/PushNotificationContent.tsx | 12 +++++++----- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/components/auth/ProfileModal/contents/LogoutContent.tsx b/src/components/auth/ProfileModal/contents/LogoutContent.tsx index 2242dcc7e..05a150e50 100644 --- a/src/components/auth/ProfileModal/contents/LogoutContent.tsx +++ b/src/components/auth/ProfileModal/contents/LogoutContent.tsx @@ -2,13 +2,8 @@ import Button from '@/components/Button' import { useLinkFcm } from '@/services/api/notifications/mutation' import { useSendEvent } from '@/stores/analytics' import { useMyAccount } from '@/stores/my-account' -import { LocalStorage } from '@/utils/storage' import { ContentProps } from '../types' -import { FCM_PUSH_NOTIFICATION_STORAGE_KEY } from './notifications/PushNotificationContent' - -const fcmTokenStorage = new LocalStorage( - () => FCM_PUSH_NOTIFICATION_STORAGE_KEY -) +import { fcmPushNotificationStorage } from './notifications/PushNotificationContent' function LogoutContent({ setCurrentState }: ContentProps) { const { address } = useMyAccount() @@ -16,7 +11,7 @@ function LogoutContent({ setCurrentState }: ContentProps) { const sendEvent = useSendEvent() const { mutate: linkFcm, isLoading } = useLinkFcm({ - onSuccess: () => fcmTokenStorage.remove(), + onSuccess: () => fcmPushNotificationStorage.remove(), }) const onShowPrivateKeyClick = () => { @@ -27,7 +22,7 @@ function LogoutContent({ setCurrentState }: ContentProps) { sendEvent('click yes_log_out_button') logout() - const fcmToken = fcmTokenStorage.get() + const fcmToken = fcmPushNotificationStorage.get() if (fcmToken && address) { linkFcm({ address, fcmToken, action: 'unlink' }) diff --git a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx index 1cae0df69..f32694099 100644 --- a/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx +++ b/src/components/auth/ProfileModal/contents/notifications/PushNotificationContent.tsx @@ -5,8 +5,10 @@ import { LocalStorage } from '@/utils/storage' import { useEffect, useState } from 'react' import { ContentProps } from '../../types' -export const FCM_PUSH_NOTIFICATION_STORAGE_KEY = 'push-notification-fcm-token' -const storage = new LocalStorage(() => FCM_PUSH_NOTIFICATION_STORAGE_KEY) +const FCM_PUSH_NOTIFICATION_STORAGE_KEY = 'push-notification-fcm-token' +export const fcmPushNotificationStorage = new LocalStorage( + () => FCM_PUSH_NOTIFICATION_STORAGE_KEY +) export default function PushNotificationContent(props: ContentProps) { const isNotificationNotSupported = typeof Notification === 'undefined' @@ -14,7 +16,7 @@ export default function PushNotificationContent(props: ContentProps) { const [isRegistered, setIsRegistered] = useState(false) useEffect(() => { - const storedFcmToken = storage.get() + const storedFcmToken = fcmPushNotificationStorage.get() setIsRegistered(!!storedFcmToken) }, [isRegistered]) @@ -53,7 +55,7 @@ function DisableNotificationButton({ if (!data) throw new Error('Error in disabling notification request') // FCM Token Disabled. - storage.remove() + fcmPushNotificationStorage.remove() setIsRegistered(false) }, }) @@ -88,7 +90,7 @@ function EnableNotificationButton({ onSuccess: (data) => { // FCM Token Enabled. if (fcmToken) { - storage.set(fcmToken) + fcmPushNotificationStorage.set(fcmToken) setIsRegistered(true) } }, From 9d6795cdb75756c76ad8ff26d7b5bf251db641f8 Mon Sep 17 00:00:00 2001 From: TeodorusNathaniel Date: Tue, 15 Aug 2023 23:35:03 +0700 Subject: [PATCH 32/32] Add max length for message body --- src/constants/chat.ts | 8 ++++++++ src/services/subsocial/commentIds/mutation.ts | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/src/constants/chat.ts b/src/constants/chat.ts index 8e93ad85c..0b8d4164f 100644 --- a/src/constants/chat.ts +++ b/src/constants/chat.ts @@ -26,3 +26,11 @@ export function getWhitelistedAddressesInChatId(chatId: string) { | undefined return addresses } + +const DEFAULT_MAX_MESSAGE_LENGTH = 10_000 +const CUSTOM_CHAT_MAX_LENGTH: Record = { + // example: '1': 1_000 +} +export function getMaxMessageLength(chatId: string) { + return CUSTOM_CHAT_MAX_LENGTH[chatId] ?? DEFAULT_MAX_MESSAGE_LENGTH +} diff --git a/src/services/subsocial/commentIds/mutation.ts b/src/services/subsocial/commentIds/mutation.ts index 6ade595bc..ec40b3c39 100644 --- a/src/services/subsocial/commentIds/mutation.ts +++ b/src/services/subsocial/commentIds/mutation.ts @@ -1,3 +1,4 @@ +import { getMaxMessageLength } from '@/constants/chat' import useWaitHasEnergy from '@/hooks/useWaitHasEnergy' import { useSaveFile } from '@/services/api/mutation' import { MutationConfig } from '@/subsocial-query' @@ -28,6 +29,12 @@ export function useSendMessage(config?: MutationConfig) { return useSubsocialMutation( getWallet, async (params, { substrateApi }) => { + const maxLength = getMaxMessageLength(params.chatId) + if (params.message && params.message.length > maxLength) + throw new Error( + 'Your message is too long, please split it up to multiple messages' + ) + console.log('waiting energy...') await waitHasEnergy() const { cid, success } = await saveFile(generateMessageContent(params))