From 50af7ca26e069bb6c9b213e7147bb483c3fb14d5 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Tue, 4 Feb 2025 18:43:56 +0000 Subject: [PATCH 01/22] feat: add dedicated hook for delegation --- apps/mobile/src/hooks/useDelegateKey.ts | 117 ++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 apps/mobile/src/hooks/useDelegateKey.ts diff --git a/apps/mobile/src/hooks/useDelegateKey.ts b/apps/mobile/src/hooks/useDelegateKey.ts new file mode 100644 index 0000000000..1524241c84 --- /dev/null +++ b/apps/mobile/src/hooks/useDelegateKey.ts @@ -0,0 +1,117 @@ +import { useState, useCallback } from 'react' +import { ethers } from 'ethers' +import DeviceCrypto from 'react-native-device-crypto' +import * as Keychain from 'react-native-keychain' + +import Logger from '../utils/logger' +import { Address } from '@/src/types/address' +import GatewayService from '../services/gateway/GatewayService' +import { useSign } from './useSign' +import { asymmetricKey, keychainGenericPassword } from '@/src/store/constants' + +export function useDelegateKey() { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { getPrivateKey, createMnemonicAccount } = useSign() + + const createDelegate = useCallback(async (ownerAddress: Address) => { + setLoading(true) + setError(null) + + try { + if (!ownerAddress) { + throw Logger.info('OwnerKeyNotFoundForDelegate') + } + + // Step 0: Get users private key from keychain. TODO: need to pass ownerAddress to getPrivateKey + const privateKey = await getPrivateKey() + + if (!privateKey) { + throw Logger.error('useDelegateKey: Something went wrong', error) + } + + // Step 1: Generate delegate key + const delegateMnemonic = ethers.Wallet.createRandom().mnemonic?.phrase + + if (!delegateMnemonic) { + throw Logger.error('useDelegateKey: Something went wrong', error) + } + + const delegatedAccount = await createMnemonicAccount(delegateMnemonic) + + if (!delegatedAccount) { + throw Logger.error('useDelegateKey: Something went wrong', error) + } + + // Step 2: Generate message to sign + const time = Math.floor(Date.now() / 3600000).toString() + const messageToSign = delegatedAccount.address + time + const hashToSign = ethers.hashMessage(messageToSign) + + // Step 3: Sign message + const signature = await signMessage({ + signer: delegatedAccount, + message: hashToSign, + }) + + // Step 4: Send to backend + await createOnBackEnd(ownerAddress, delegatedAccount, signature) + + // Step 5: Store delegate key in keychain + const encryptedDelegateKey = await DeviceCrypto.encrypt(asymmetricKey, delegatedAccount.privateKey, { + biometryTitle: 'Authenticate', + biometrySubTitle: 'Saving delegated key', + biometryDescription: 'Please authenticate yourself', + }) + + await Keychain.setGenericPassword( + keychainGenericPassword, + JSON.stringify({ + encryptedPassword: encryptedDelegateKey.encryptedText, + iv: encryptedDelegateKey.iv, + }), + ) + } catch (err) { + throw Logger.error('useDelegateKey: Something went wrong', err) + } finally { + setLoading(false) + } + }, []) + + const deleteDelegate = useCallback(async () => { + setLoading(true) + setError(null) + }, []) + + const signMessage = useCallback(async ({ signer, message }: { signer: ethers.HDNodeWallet; message: string }) => { + const signature = await signer.signMessage(message) + return signature + }, []) + + const createOnBackEnd = async (ownerAddress: Address, delegatedAccount: ethers.HDNodeWallet, signature: string) => { + console.log({ createOnBackEnd: delegatedAccount, signature }) + // TODO 1: call backend endpoints to create delegate into database + // TODO 2: check if we need to loop through all chains and create delegate for each chain + // Chain.all.forEach(async (chain) => { + try { + await GatewayService.createDelegate({ + delegatorAddress: ownerAddress, + delegatedAddress: delegatedAccount.address, + signature, + description: 'iOS Device Delegate', + chainId: 'chain.id', //TODO: need to pass chainId + }) + } catch (err) { + throw Logger.error('CreateDelegateFailed', err) + } + } + + // const deleteOnBackEnd = async (delegateAddress: Address, signature: string) => {} + + return { + loading, + error, + createDelegate, + deleteDelegate, + } +} From 67d6a07306a5e993c443818996010156e513aa81 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Tue, 4 Feb 2025 18:44:26 +0000 Subject: [PATCH 02/22] chore: move constants to global --- apps/mobile/src/store/constants.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/mobile/src/store/constants.ts b/apps/mobile/src/store/constants.ts index aa705b94de..a5048a8121 100644 --- a/apps/mobile/src/store/constants.ts +++ b/apps/mobile/src/store/constants.ts @@ -343,3 +343,6 @@ export enum PressActionId { } export const LAUNCH_ACTIVITY = 'global.safe.mobileapp.ui.MainActivity' + +export const asymmetricKey = 'safe' +export const keychainGenericPassword = 'safeuser' From d75da8ee8eccb6be50a2fe65b4280a6040714f1e Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Tue, 4 Feb 2025 18:45:00 +0000 Subject: [PATCH 03/22] fix: typo --- apps/mobile/src/hooks/useSign/useSign.test.ts | 4 ++-- apps/mobile/src/hooks/useSign/useSign.ts | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/mobile/src/hooks/useSign/useSign.test.ts b/apps/mobile/src/hooks/useSign/useSign.test.ts index 342f5bea81..1a3d6f3b03 100644 --- a/apps/mobile/src/hooks/useSign/useSign.test.ts +++ b/apps/mobile/src/hooks/useSign/useSign.test.ts @@ -24,7 +24,7 @@ describe('useSign', () => { }) expect(spy).toHaveBeenCalledWith( keychainGenericPassword, - JSON.stringify({ encryptyedPassword: 'encryptedText', iv: `${privateKey}000` }), + JSON.stringify({ encryptedPassword: 'encryptedText', iv: `${privateKey}000` }), ) }) @@ -45,7 +45,7 @@ describe('useSign', () => { expect(spy).toHaveBeenCalledWith( 'safeuser', - JSON.stringify({ encryptyedPassword: 'encryptedText', iv: `${privateKey}000` }), + JSON.stringify({ encryptedPassword: 'encryptedText', iv: `${privateKey}000` }), ) expect(returnedKey).toBe(privateKey) }) diff --git a/apps/mobile/src/hooks/useSign/useSign.ts b/apps/mobile/src/hooks/useSign/useSign.ts index a6030bf6d9..ad7d01b89b 100644 --- a/apps/mobile/src/hooks/useSign/useSign.ts +++ b/apps/mobile/src/hooks/useSign/useSign.ts @@ -2,9 +2,7 @@ import DeviceCrypto from 'react-native-device-crypto' import * as Keychain from 'react-native-keychain' import DeviceInfo from 'react-native-device-info' import { Wallet } from 'ethers' - -export const asymmetricKey = 'safe' -export const keychainGenericPassword = 'safeuser' +import { asymmetricKey, keychainGenericPassword } from '@/src/store/constants' export function useSign() { // TODO: move it to a global context or reduce @@ -17,7 +15,7 @@ export function useSign() { invalidateOnNewBiometry: true, }) - const encryptyedPrivateKey = await DeviceCrypto.encrypt(asymmetricKey, privateKey, { + const encryptedPrivateKey = await DeviceCrypto.encrypt(asymmetricKey, privateKey, { biometryTitle: 'Authenticate', biometrySubTitle: 'Saving key', biometryDescription: 'Please authenticate yourself', @@ -26,8 +24,8 @@ export function useSign() { await Keychain.setGenericPassword( keychainGenericPassword, JSON.stringify({ - encryptyedPassword: encryptyedPrivateKey.encryptedText, - iv: encryptyedPrivateKey.iv, + encryptedPassword: encryptedPrivateKey.encryptedText, + iv: encryptedPrivateKey.iv, }), ) } catch (err) { @@ -43,8 +41,8 @@ export function useSign() { throw 'user password not found' } - const { encryptyedPassword, iv } = JSON.parse(user.password) - const decryptedKey = await DeviceCrypto.decrypt(asymmetricKey, encryptyedPassword, iv, { + const { encryptedPassword, iv } = JSON.parse(user.password) + const decryptedKey = await DeviceCrypto.decrypt(asymmetricKey, encryptedPassword, iv, { biometryTitle: 'Authenticate', biometrySubTitle: 'Signing', biometryDescription: 'Authenticate yourself to sign the text', From 07e4b56e20495adef5f031d2d5fdf2022a5dd49d Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Wed, 5 Feb 2025 16:37:31 +0000 Subject: [PATCH 04/22] feat: create delegate hook --- apps/mobile/src/hooks/useDelegateKey.ts | 155 ++++++++++++++---------- 1 file changed, 92 insertions(+), 63 deletions(-) diff --git a/apps/mobile/src/hooks/useDelegateKey.ts b/apps/mobile/src/hooks/useDelegateKey.ts index 1524241c84..384dd60486 100644 --- a/apps/mobile/src/hooks/useDelegateKey.ts +++ b/apps/mobile/src/hooks/useDelegateKey.ts @@ -1,76 +1,74 @@ import { useState, useCallback } from 'react' import { ethers } from 'ethers' -import DeviceCrypto from 'react-native-device-crypto' -import * as Keychain from 'react-native-keychain' +import { useAuthGetNonceV1Query, useAuthVerifyV1Mutation } from '@safe-global/store/gateway/AUTO_GENERATED/auth' +import { + useDelegatesGetDelegatesV2Query, + useDelegatesPostDelegateV2Mutation, +} from '@safe-global/store/gateway/AUTO_GENERATED/delegates' -import Logger from '../utils/logger' +import Logger from '@/src/utils/logger' import { Address } from '@/src/types/address' -import GatewayService from '../services/gateway/GatewayService' import { useSign } from './useSign' -import { asymmetricKey, keychainGenericPassword } from '@/src/store/constants' +import { selectActiveSafe } from '@/src/store/activeSafeSlice' +import { useAppSelector } from '@/src/store/hooks' + +const ERROR_MSG = 'useDelegateKey: Something went wrong' export function useDelegateKey() { + // Local states const [loading, setLoading] = useState(false) const [error, setError] = useState(null) - const { getPrivateKey, createMnemonicAccount } = useSign() + + // Redux states + const activeSafe = useAppSelector(selectActiveSafe) + + // Hook calls + const { getPrivateKey } = useSign() + + // Step 0 - Get the nonce to be included in the message to be sent to the backend + const { data } = useAuthGetNonceV1Query() const createDelegate = useCallback(async (ownerAddress: Address) => { setLoading(true) setError(null) try { - if (!ownerAddress) { - throw Logger.info('OwnerKeyNotFoundForDelegate') + if (!ownerAddress || !activeSafe) { + throw Logger.info(ERROR_MSG) } - - // Step 0: Get users private key from keychain. TODO: need to pass ownerAddress to getPrivateKey + // Step 1 - Get the private key of the owner/signer const privateKey = await getPrivateKey() if (!privateKey) { - throw Logger.error('useDelegateKey: Something went wrong', error) + throw Logger.error(ERROR_MSG, error) } + // Step 2 - Create a new random delegate private key + const delegatePrivateKey = ethers.Wallet.createRandom() - // Step 1: Generate delegate key - const delegateMnemonic = ethers.Wallet.createRandom().mnemonic?.phrase - - if (!delegateMnemonic) { - throw Logger.error('useDelegateKey: Something went wrong', error) + if (!delegatePrivateKey) { + throw Logger.error(ERROR_MSG, error) } - const delegatedAccount = await createMnemonicAccount(delegateMnemonic) - - if (!delegatedAccount) { - throw Logger.error('useDelegateKey: Something went wrong', error) + // Step 3 - Create a message following the SIWE standard + const siweMessage = { + address: ownerAddress, + chainId: Number(activeSafe.chainId), + domain: 'global.safe.mobileapp', + statement: 'Sign in with Ethereum to the app.', + nonce: data?.nonce, + uri: 'rnsiwe://', //TODO: Update this + version: '1', + issuedAt: new Date().toISOString(), } - // Step 2: Generate message to sign - const time = Math.floor(Date.now() / 3600000).toString() - const messageToSign = delegatedAccount.address + time - const hashToSign = ethers.hashMessage(messageToSign) - - // Step 3: Sign message - const signature = await signMessage({ - signer: delegatedAccount, - message: hashToSign, - }) - - // Step 4: Send to backend - await createOnBackEnd(ownerAddress, delegatedAccount, signature) - - // Step 5: Store delegate key in keychain - const encryptedDelegateKey = await DeviceCrypto.encrypt(asymmetricKey, delegatedAccount.privateKey, { - biometryTitle: 'Authenticate', - biometrySubTitle: 'Saving delegated key', - biometryDescription: 'Please authenticate yourself', + // Step 4 - Triggers the backend to create the delegate + await createOnBackEnd({ + safeAddress: activeSafe.address, + signer: ownerAddress, + delegatedAccount: delegatePrivateKey, + message: siweMessage, + chainId: activeSafe.chainId, }) - - await Keychain.setGenericPassword( - keychainGenericPassword, - JSON.stringify({ - encryptedPassword: encryptedDelegateKey.encryptedText, - iv: encryptedDelegateKey.iv, - }), - ) } catch (err) { throw Logger.error('useDelegateKey: Something went wrong', err) } finally { @@ -83,24 +81,55 @@ export function useDelegateKey() { setError(null) }, []) - const signMessage = useCallback(async ({ signer, message }: { signer: ethers.HDNodeWallet; message: string }) => { - const signature = await signer.signMessage(message) - return signature - }, []) - - const createOnBackEnd = async (ownerAddress: Address, delegatedAccount: ethers.HDNodeWallet, signature: string) => { - console.log({ createOnBackEnd: delegatedAccount, signature }) - // TODO 1: call backend endpoints to create delegate into database - // TODO 2: check if we need to loop through all chains and create delegate for each chain - // Chain.all.forEach(async (chain) => { + const createOnBackEnd = async ({ + safeAddress, + signer, + delegatedAccount, + message, + chainId, + }: { + safeAddress: Address + signer: Address + delegatedAccount: ethers.HDNodeWallet + message: object + chainId: string + }) => { + const [authVerifyV1] = useAuthVerifyV1Mutation() + + // Step 5 - calls /v1/auth/verify to verify the signature try { - await GatewayService.createDelegate({ - delegatorAddress: ownerAddress, - delegatedAddress: delegatedAccount.address, - signature, - description: 'iOS Device Delegate', - chainId: 'chain.id', //TODO: need to pass chainId + const response = await authVerifyV1({ + siweDto: { + message: message.toString(), + signature: signer, + }, + }) + + console.log({ response }) + + // Step 6 - calls /v2/delegates + const [delegatesPostDelegateV2] = useDelegatesPostDelegateV2Mutation() + const { data: delegateData, error: delegateError } = await delegatesPostDelegateV2({ + chainId, + createDelegateDto: { + safe: safeAddress, + delegate: delegatedAccount.address, + delegator: signer, + signature: signer, + label: 'Delegate', + }, }) + + console.log({ delegateData, delegateError }) + + const { data, error, isFetching } = useDelegatesGetDelegatesV2Query({ + safe: safeAddress, + delegate: signer, + chainId, + }) + + console.log({ data, error, isFetching }) + // Step 7 - calls /v2/register/notifications } catch (err) { throw Logger.error('CreateDelegateFailed', err) } From 178d4d0e694f1b33776e59576ea047ab065ac5fa Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Wed, 5 Feb 2025 16:55:03 +0000 Subject: [PATCH 05/22] refactor: sending signature --- apps/mobile/src/hooks/useDelegateKey.ts | 8 ++++-- apps/mobile/src/hooks/useSiwe.ts | 36 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 apps/mobile/src/hooks/useSiwe.ts diff --git a/apps/mobile/src/hooks/useDelegateKey.ts b/apps/mobile/src/hooks/useDelegateKey.ts index 384dd60486..4bd11b936a 100644 --- a/apps/mobile/src/hooks/useDelegateKey.ts +++ b/apps/mobile/src/hooks/useDelegateKey.ts @@ -11,6 +11,7 @@ import { Address } from '@/src/types/address' import { useSign } from './useSign' import { selectActiveSafe } from '@/src/store/activeSafeSlice' import { useAppSelector } from '@/src/store/hooks' +import { useSiwe } from './useSiwe' const ERROR_MSG = 'useDelegateKey: Something went wrong' @@ -95,13 +96,16 @@ export function useDelegateKey() { chainId: string }) => { const [authVerifyV1] = useAuthVerifyV1Mutation() + const { signMessage } = useSiwe() + + const signature = await signMessage({ signer: delegatedAccount, message: message.toString() }) // Step 5 - calls /v1/auth/verify to verify the signature try { const response = await authVerifyV1({ siweDto: { message: message.toString(), - signature: signer, + signature, }, }) @@ -115,7 +119,7 @@ export function useDelegateKey() { safe: safeAddress, delegate: delegatedAccount.address, delegator: signer, - signature: signer, + signature, label: 'Delegate', }, }) diff --git a/apps/mobile/src/hooks/useSiwe.ts b/apps/mobile/src/hooks/useSiwe.ts new file mode 100644 index 0000000000..30f6a049ce --- /dev/null +++ b/apps/mobile/src/hooks/useSiwe.ts @@ -0,0 +1,36 @@ +import { ethers } from 'ethers' +import { useCallback } from 'react' +import { SiweMessage } from 'siwe' + +interface SiweMessageProps { + address: string + chainId: number + nonce: string + statement: string +} + +export function useSiwe() { + const createSiweMessage = useCallback(({ address, chainId, nonce, statement }: SiweMessageProps) => { + const message = new SiweMessage({ + address, + chainId, + domain: 'global.safe.mobileapp', + statement, + nonce, + uri: 'rnsiwe://', //TODO: Update this + version: '1', + issuedAt: new Date().toISOString(), + }) + return message.prepareMessage() + }, []) + + const signMessage = useCallback(async ({ signer, message }: { signer: ethers.HDNodeWallet; message: string }) => { + const signature = await signer.signMessage(message) + return signature + }, []) + + return { + createSiweMessage, + signMessage, + } +} From 34dc5e711b0932616fdc117198d1bd3f0de6431c Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Fri, 7 Feb 2025 16:41:42 +0000 Subject: [PATCH 06/22] chore: add siwe lib and utils --- apps/mobile/package.json | 3 ++ yarn.lock | 64 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/apps/mobile/package.json b/apps/mobile/package.json index f0b470e765..9735be749a 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -89,6 +89,7 @@ "react-native-device-info": "^14.0.1", "react-native-draggable-flatlist": "^4.0.1", "react-native-gesture-handler": "~2.20.2", + "react-native-get-random-values": "^1.11.0", "react-native-keychain": "^9.2.2", "react-native-mmkv": "^3.1.0", "react-native-pager-view": "^6.5.1", @@ -100,6 +101,7 @@ "react-redux": "^9.1.2", "redux": "^5.0.1", "redux-persist": "^6.0.0", + "siwe": "^3.0.0", "tamagui": "^1.117.1", "timezone-mock": "^1.3.6", "tsconfig-paths-webpack-plugin": "^4.2.0" @@ -132,6 +134,7 @@ "@types/lodash": "^4.17.13", "@types/node": "^22.9.1", "@types/react": "~18.3.12", + "@types/react-native-get-random-values": "^1", "babel-loader": "^8.4.1", "eslint": "^9.19.0", "eslint-config-prettier": "^9.1.0", diff --git a/yarn.lock b/yarn.lock index 2f46228fcc..672534f031 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6929,6 +6929,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:^1.1.2": + version: 1.7.1 + resolution: "@noble/hashes@npm:1.7.1" + checksum: 10/ca3120da0c3e7881d6a481e9667465cc9ebbee1329124fb0de442e56d63fef9870f8cc96f264ebdb18096e0e36cebc0e6e979a872d545deb0a6fed9353f17e05 + languageName: node + linkType: hard + "@noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.2": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" @@ -8211,6 +8218,7 @@ __metadata: "@types/lodash": "npm:^4.17.13" "@types/node": "npm:^22.9.1" "@types/react": "npm:~18.3.12" + "@types/react-native-get-random-values": "npm:^1" babel-loader: "npm:^8.4.1" babel-plugin-react-native-web: "npm:^0.19.13" blo: "npm:^1.2.0" @@ -8249,6 +8257,7 @@ __metadata: react-native-device-info: "npm:^14.0.1" react-native-draggable-flatlist: "npm:^4.0.1" react-native-gesture-handler: "npm:~2.20.2" + react-native-get-random-values: "npm:^1.11.0" react-native-keychain: "npm:^9.2.2" react-native-mmkv: "npm:^3.1.0" react-native-pager-view: "npm:^6.5.1" @@ -8262,6 +8271,7 @@ __metadata: redux: "npm:^5.0.1" redux-devtools-expo-dev-plugin: "npm:^1.0.0" redux-persist: "npm:^6.0.0" + siwe: "npm:^3.0.0" storybook: "npm:^8.4.6" tamagui: "npm:^1.117.1" timezone-mock: "npm:^1.3.6" @@ -9382,6 +9392,16 @@ __metadata: languageName: node linkType: hard +"@spruceid/siwe-parser@npm:^3.0.0": + version: 3.0.0 + resolution: "@spruceid/siwe-parser@npm:3.0.0" + dependencies: + "@noble/hashes": "npm:^1.1.2" + apg-js: "npm:^4.4.0" + checksum: 10/19239d4a18a953812e4a5cd25c51a9869b67643b00cf9b6f36091b0d7b8be09934d1ba41c7df60f6e05307ba5cbc7c4812c69b5d3de64a132ea08b80a35cb87f + languageName: node + linkType: hard + "@stablelib/aead@npm:^1.0.1": version: 1.0.1 resolution: "@stablelib/aead@npm:1.0.1" @@ -13158,6 +13178,13 @@ __metadata: languageName: node linkType: hard +"@types/react-native-get-random-values@npm:^1": + version: 1.8.2 + resolution: "@types/react-native-get-random-values@npm:1.8.2" + checksum: 10/08f3f82efbb5b6d9acd8f7f55a2dac9f228886323ac3018a1bab46cd1b45f24809d194fd2a3fe02a9ec4196606325e5cfffde0b0057ae785208b658fdc83c821 + languageName: node + linkType: hard + "@types/react-transition-group@npm:^4.4.11, @types/react-transition-group@npm:^4.4.12": version: 4.4.12 resolution: "@types/react-transition-group@npm:4.4.12" @@ -14767,6 +14794,13 @@ __metadata: languageName: node linkType: hard +"apg-js@npm:^4.4.0": + version: 4.4.0 + resolution: "apg-js@npm:4.4.0" + checksum: 10/425f19096026742f5f156f26542b68f55602aa60f0c4ae2d72a0a888cf15fe9622223191202262dd8979d76a6125de9d8fd164d56c95fb113f49099f405eb08c + languageName: node + linkType: hard + "application-config-path@npm:^0.1.0": version: 0.1.1 resolution: "application-config-path@npm:0.1.1" @@ -20399,6 +20433,13 @@ __metadata: languageName: node linkType: hard +"fast-base64-decode@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-base64-decode@npm:1.0.0" + checksum: 10/4c59eb1775a7f132333f296c5082476fdcc8f58d023c42ed6d378d2e2da4c328c7a71562f271181a725dd17cdaa8f2805346cc330cdbad3b8e4b9751508bd0a3 + languageName: node + linkType: hard + "fast-deep-equal@npm:^2.0.1": version: 2.0.1 resolution: "fast-deep-equal@npm:2.0.1" @@ -28864,6 +28905,17 @@ __metadata: languageName: node linkType: hard +"react-native-get-random-values@npm:^1.11.0": + version: 1.11.0 + resolution: "react-native-get-random-values@npm:1.11.0" + dependencies: + fast-base64-decode: "npm:^1.0.0" + peerDependencies: + react-native: ">=0.56" + checksum: 10/eb04833ce2b66309d737f1447ab01ad32aca00a8d1cc4de7b4e751c23f4d9f8059a2643bb16e0b6f3e6b074a9e57e769bb2bf2afc50a912c5661d80a6ce4de34 + languageName: node + linkType: hard + "react-native-helmet-async@npm:2.0.4": version: 2.0.4 resolution: "react-native-helmet-async@npm:2.0.4" @@ -30967,6 +31019,18 @@ __metadata: languageName: node linkType: hard +"siwe@npm:^3.0.0": + version: 3.0.0 + resolution: "siwe@npm:3.0.0" + dependencies: + "@spruceid/siwe-parser": "npm:^3.0.0" + "@stablelib/random": "npm:^1.0.1" + peerDependencies: + ethers: ^5.6.8 || ^6.0.8 + checksum: 10/9be89fe6163be6508c4a65594002a2841cc2ea5cbf3bdb25ca26a214ad08a7a7ad21915b1fe6dd23bdd2f02c61992e1b7676b24d70e1ae85e5cb0e832470c7cc + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" From ef8b296c019e2722b1375175145e67e932fca858 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Fri, 7 Feb 2025 16:44:54 +0000 Subject: [PATCH 07/22] fix: add cockie auth on RTK --- packages/store/src/gateway/cgwClient.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/store/src/gateway/cgwClient.ts b/packages/store/src/gateway/cgwClient.ts index 9e42e7f6ba..a66af21419 100644 --- a/packages/store/src/gateway/cgwClient.ts +++ b/packages/store/src/gateway/cgwClient.ts @@ -11,6 +11,12 @@ export const getBaseUrl = () => { } export const rawBaseQuery = fetchBaseQuery({ baseUrl: '/', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Set-Cookie': 'HttpOnly;Secure;SameSite=None', + }, }) export const dynamicBaseQuery: BaseQueryFn = async ( From df34acc65327cdfb1707e9417474f801b0ccff79 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Fri, 7 Feb 2025 16:45:08 +0000 Subject: [PATCH 08/22] chore: add debug logger --- .../src/services/notifications/FCMService.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/mobile/src/services/notifications/FCMService.ts b/apps/mobile/src/services/notifications/FCMService.ts index 00bcf4ddc6..cec2b44e38 100644 --- a/apps/mobile/src/services/notifications/FCMService.ts +++ b/apps/mobile/src/services/notifications/FCMService.ts @@ -20,6 +20,9 @@ class FCMService { async saveFCMToken(): Promise { try { const fcmToken = await messaging().getToken() + + Logger.info('FCMService :: fcmToken', fcmToken) + if (fcmToken) { store.dispatch(savePushToken(fcmToken)) } @@ -52,16 +55,17 @@ class FCMService { } async registerAppWithFCM(): Promise { - if (!messaging().registerDeviceForRemoteMessages) { - await messaging() - .registerDeviceForRemoteMessages() - .then((status: unknown) => { - Logger.info('registerDeviceForRemoteMessages status', status) - }) - .catch((error) => { - Logger.error('registerAppWithFCM: Something went wrong', error) - }) - } + // if (!messaging().registerDeviceForRemoteMessages) { + console.log('registerAppWithFCM :: CALLED') + await messaging() + .registerDeviceForRemoteMessages() + .then((status: unknown) => { + Logger.info('registerDeviceForRemoteMessages status', status) + }) + .catch((error) => { + Logger.error('registerAppWithFCM: Something went wrong', error) + }) + // } } } export default new FCMService() From 497cce79f610e10763b6a877a6b3d5de96e9e098 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Fri, 7 Feb 2025 16:45:30 +0000 Subject: [PATCH 09/22] feat: add siwe hook --- apps/mobile/src/hooks/useSiwe.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/mobile/src/hooks/useSiwe.ts b/apps/mobile/src/hooks/useSiwe.ts index 30f6a049ce..33c8432ecd 100644 --- a/apps/mobile/src/hooks/useSiwe.ts +++ b/apps/mobile/src/hooks/useSiwe.ts @@ -1,4 +1,4 @@ -import { ethers } from 'ethers' +import { HDNodeWallet, Wallet } from 'ethers' import { useCallback } from 'react' import { SiweMessage } from 'siwe' @@ -17,14 +17,14 @@ export function useSiwe() { domain: 'global.safe.mobileapp', statement, nonce, - uri: 'rnsiwe://', //TODO: Update this + uri: 'https://safe.global', version: '1', issuedAt: new Date().toISOString(), }) return message.prepareMessage() }, []) - const signMessage = useCallback(async ({ signer, message }: { signer: ethers.HDNodeWallet; message: string }) => { + const signMessage = useCallback(async ({ signer, message }: { signer: HDNodeWallet | Wallet; message: string }) => { const signature = await signer.signMessage(message) return signature }, []) From 81d76fa88c30f167c138ab68ece0ae200ae98ce3 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Fri, 7 Feb 2025 16:45:47 +0000 Subject: [PATCH 10/22] feat: add useDelegate hook --- apps/mobile/src/hooks/useDelegateKey.ts | 238 +++++++++++++----------- 1 file changed, 132 insertions(+), 106 deletions(-) diff --git a/apps/mobile/src/hooks/useDelegateKey.ts b/apps/mobile/src/hooks/useDelegateKey.ts index 4bd11b936a..e89453a08e 100644 --- a/apps/mobile/src/hooks/useDelegateKey.ts +++ b/apps/mobile/src/hooks/useDelegateKey.ts @@ -1,145 +1,171 @@ import { useState, useCallback } from 'react' -import { ethers } from 'ethers' +import DeviceInfo from 'react-native-device-info' +import 'react-native-get-random-values' +import { HDNodeWallet, Wallet } from 'ethers' import { useAuthGetNonceV1Query, useAuthVerifyV1Mutation } from '@safe-global/store/gateway/AUTO_GENERATED/auth' import { - useDelegatesGetDelegatesV2Query, - useDelegatesPostDelegateV2Mutation, -} from '@safe-global/store/gateway/AUTO_GENERATED/delegates' + useNotificationsDeleteSubscriptionV2Mutation, + useNotificationsUpsertSubscriptionsV2Mutation, +} from '@safe-global/store/gateway/AUTO_GENERATED/notifications' import Logger from '@/src/utils/logger' import { Address } from '@/src/types/address' -import { useSign } from './useSign' import { selectActiveSafe } from '@/src/store/activeSafeSlice' import { useAppSelector } from '@/src/store/hooks' import { useSiwe } from './useSiwe' +import { useSign } from './useSign' +import { isAndroid } from '../config/constants' +import { selectFCMToken } from '../store/notificationsSlice' const ERROR_MSG = 'useDelegateKey: Something went wrong' export function useDelegateKey() { // Local states const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) + const [error, setError] = useState(null) + + // Queries + const [authVerifyV1] = useAuthVerifyV1Mutation() + const [notificationsUpsertSubscriptionsV2] = useNotificationsUpsertSubscriptionsV2Mutation() + const [notificationsDeleteSubscriptionsV2] = useNotificationsDeleteSubscriptionV2Mutation() + // Custom hooks + const { signMessage } = useSiwe() + const { getPrivateKey } = useSign() // Redux states const activeSafe = useAppSelector(selectActiveSafe) - - // Hook calls - const { getPrivateKey } = useSign() + const fcmToken = useAppSelector(selectFCMToken) // Step 0 - Get the nonce to be included in the message to be sent to the backend const { data } = useAuthGetNonceV1Query() - const createDelegate = useCallback(async (ownerAddress: Address) => { + const createDelegate = useCallback( + async (ownerAddress: Address) => { + setLoading(true) + setError(null) + const nonce = data?.nonce + // Step 1 - Try to get the owner's private key + const ownerPrivateKey = await getPrivateKey() + + try { + if (!ownerAddress || !activeSafe || !nonce || !fcmToken) { + throw Logger.info(ERROR_MSG) + } + + // Step 2 - Create a new random (delegated) private key in case the owner's private key is not available + //TODO: Double check if we have a wallet stored already avoiding to create a new one + const signerAccount = ownerPrivateKey ? new Wallet(ownerPrivateKey) : Wallet.createRandom() + + if (!signerAccount) { + throw Logger.error(ERROR_MSG, error) + } + + // Step 3 - Create a message following the SIWE standard + const siweMessage = `SafeWallet wants you to sign in with your Ethereum account: +${signerAccount.address} + +Sign in with Ethereum to the app. + +URI: https://safe.global +Version: 1 +Chain ID: ${activeSafe.chainId} +Nonce: ${nonce} +Issued At: ${new Date().toISOString()}` + + // Step 4 - Triggers the backend to create the delegate + await createOnBackEnd({ + safeAddress: activeSafe.address, + signer: signerAccount, + message: siweMessage, + chainId: activeSafe.chainId, + fcmToken, + }) + } catch (err) { + Logger.error('useDelegateKey: Something went wrong', err) + setError(err) + return + } finally { + setLoading(false) + } + }, + [data, activeSafe, fcmToken], + ) + + const deleteDelegate = useCallback(async () => { setLoading(true) setError(null) - try { - if (!ownerAddress || !activeSafe) { - throw Logger.info(ERROR_MSG) - } - // Step 1 - Get the private key of the owner/signer - const privateKey = await getPrivateKey() - - if (!privateKey) { - throw Logger.error(ERROR_MSG, error) - } - // Step 2 - Create a new random delegate private key - const delegatePrivateKey = ethers.Wallet.createRandom() - - if (!delegatePrivateKey) { - throw Logger.error(ERROR_MSG, error) - } - - // Step 3 - Create a message following the SIWE standard - const siweMessage = { - address: ownerAddress, - chainId: Number(activeSafe.chainId), - domain: 'global.safe.mobileapp', - statement: 'Sign in with Ethereum to the app.', - nonce: data?.nonce, - uri: 'rnsiwe://', //TODO: Update this - version: '1', - issuedAt: new Date().toISOString(), - } - - // Step 4 - Triggers the backend to create the delegate - await createOnBackEnd({ - safeAddress: activeSafe.address, - signer: ownerAddress, - delegatedAccount: delegatePrivateKey, - message: siweMessage, - chainId: activeSafe.chainId, - }) + await deleteOnBackEnd() } catch (err) { - throw Logger.error('useDelegateKey: Something went wrong', err) + Logger.error('useDelegateKey: Something went wrong', err) + setError(err) + return } finally { setLoading(false) } }, []) - const deleteDelegate = useCallback(async () => { - setLoading(true) - setError(null) - }, []) + const createOnBackEnd = useCallback( + async ({ + safeAddress, + signer, + message, + chainId, + fcmToken, + }: { + safeAddress: Address + signer: HDNodeWallet | Wallet + message: string + chainId: string + fcmToken: string + }) => { + const signature = await signMessage({ signer, message }) + try { + await authVerifyV1({ + siweDto: { + message, + signature, + }, + }) + + const deviceUuid = await DeviceInfo.getUniqueId() + + await notificationsUpsertSubscriptionsV2({ + upsertSubscriptionsDto: { + cloudMessagingToken: fcmToken, + safes: [ + { + chainId, + address: safeAddress, + notificationTypes: ['MESSAGE_CONFIRMATION_REQUEST', 'CONFIRMATION_REQUEST'], + }, + ], + deviceType: isAndroid ? 'ANDROID' : 'IOS', + deviceUuid, + }, + }) + } catch (err) { + Logger.error('CreateDelegateFailed', err) + setError(err) + return + } + }, + [], + ) - const createOnBackEnd = async ({ - safeAddress, - signer, - delegatedAccount, - message, - chainId, - }: { - safeAddress: Address - signer: Address - delegatedAccount: ethers.HDNodeWallet - message: object - chainId: string - }) => { - const [authVerifyV1] = useAuthVerifyV1Mutation() - const { signMessage } = useSiwe() - - const signature = await signMessage({ signer: delegatedAccount, message: message.toString() }) - - // Step 5 - calls /v1/auth/verify to verify the signature + const deleteOnBackEnd = useCallback(async () => { try { - const response = await authVerifyV1({ - siweDto: { - message: message.toString(), - signature, - }, - }) - - console.log({ response }) - - // Step 6 - calls /v2/delegates - const [delegatesPostDelegateV2] = useDelegatesPostDelegateV2Mutation() - const { data: delegateData, error: delegateError } = await delegatesPostDelegateV2({ - chainId, - createDelegateDto: { - safe: safeAddress, - delegate: delegatedAccount.address, - delegator: signer, - signature, - label: 'Delegate', - }, - }) - - console.log({ delegateData, delegateError }) - - const { data, error, isFetching } = useDelegatesGetDelegatesV2Query({ - safe: safeAddress, - delegate: signer, - chainId, + await notificationsDeleteSubscriptionsV2({ + deviceUuid: await DeviceInfo.getUniqueId(), + chainId: activeSafe.chainId, + safeAddress: activeSafe.address, }) - - console.log({ data, error, isFetching }) - // Step 7 - calls /v2/register/notifications } catch (err) { - throw Logger.error('CreateDelegateFailed', err) + Logger.error('DeleteDelegateFailed', err) + setError(err) + return } - } - - // const deleteOnBackEnd = async (delegateAddress: Address, signature: string) => {} + }, []) return { loading, From 61668970201e9a96f121048b57bcd4f8ce04ca6c Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:25:18 +0000 Subject: [PATCH 11/22] chore: add support to random numbers generation --- apps/mobile/babel.config.js | 12 ++++++++++++ apps/mobile/shim.js | 26 ++++++++++++++++++++++++++ apps/mobile/src/app/_layout.tsx | 5 ++--- 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 apps/mobile/shim.js diff --git a/apps/mobile/babel.config.js b/apps/mobile/babel.config.js index e1e3637afd..25a3946a60 100644 --- a/apps/mobile/babel.config.js +++ b/apps/mobile/babel.config.js @@ -2,5 +2,17 @@ module.exports = function (api) { api.cache(true) return { presets: ['babel-preset-expo'], + plugins: [ + [ + 'module-resolver', + { + alias: { + crypto: 'react-native-quick-crypto', + stream: 'stream-browserify', + buffer: '@craftzdog/react-native-buffer', + }, + }, + ], + ], } } diff --git a/apps/mobile/shim.js b/apps/mobile/shim.js new file mode 100644 index 0000000000..a2b790664c --- /dev/null +++ b/apps/mobile/shim.js @@ -0,0 +1,26 @@ +import { install } from 'react-native-quick-crypto' +install() + +import { ethers } from 'ethers' + +import crypto from 'react-native-quick-crypto' + +ethers.randomBytes.register((length) => { + return new Uint8Array(crypto.randomBytes(length)) +}) + +ethers.computeHmac.register((algo, key, data) => { + return crypto.createHmac(algo, key).update(data).digest() +}) + +ethers.pbkdf2.register((passwd, salt, iter, keylen, algo) => { + return crypto.pbkdf2Sync(passwd, salt, iter, keylen, algo) +}) + +ethers.sha256.register((data) => { + return crypto.createHash('sha256').update(data).digest() +}) + +ethers.sha512.register((data) => { + return crypto.createHash('sha512').update(data).digest() +}) diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx index c2339e7d4f..18c0655ed1 100644 --- a/apps/mobile/src/app/_layout.tsx +++ b/apps/mobile/src/app/_layout.tsx @@ -1,3 +1,5 @@ +import '../../shim' + import { Stack } from 'expo-router' import 'react-native-reanimated' import { SafeThemeProvider } from '@/src/theme/provider/safeTheme' @@ -13,12 +15,9 @@ import { NotificationsProvider } from '@/src/context/NotificationsContext' import { SafeToastProvider } from '@/src/theme/provider/toastProvider' import { configureReanimatedLogger, ReanimatedLogLevel } from 'react-native-reanimated' import { OnboardingHeader } from '@/src/features/Onboarding/components/OnboardingHeader' -import { install } from 'react-native-quick-crypto' import { getDefaultScreenOptions } from '@/src/navigation/hooks/utils' import { NavigationGuardHOC } from '@/src/navigation/NavigationGuardHOC' -install() - configureReanimatedLogger({ level: ReanimatedLogLevel.warn, strict: false, From dcece0ec16e1eeddbf2dd9ff0a142da53ab8acf9 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:26:03 +0000 Subject: [PATCH 12/22] chore: adds sign w ethereum hook --- apps/mobile/src/hooks/useSiwe.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/mobile/src/hooks/useSiwe.ts b/apps/mobile/src/hooks/useSiwe.ts index 33c8432ecd..e085030e00 100644 --- a/apps/mobile/src/hooks/useSiwe.ts +++ b/apps/mobile/src/hooks/useSiwe.ts @@ -1,6 +1,6 @@ -import { HDNodeWallet, Wallet } from 'ethers' import { useCallback } from 'react' import { SiweMessage } from 'siwe' +import { HDNodeWallet, Wallet } from 'ethers' interface SiweMessageProps { address: string @@ -24,7 +24,7 @@ export function useSiwe() { return message.prepareMessage() }, []) - const signMessage = useCallback(async ({ signer, message }: { signer: HDNodeWallet | Wallet; message: string }) => { + const signMessage = useCallback(async ({ signer, message }: { signer: Wallet | HDNodeWallet; message: string }) => { const signature = await signer.signMessage(message) return signature }, []) From 925989321cecd3c04851c27799f8cbe2ca5536bb Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:26:28 +0000 Subject: [PATCH 13/22] chore: adds specific function to return signer --- apps/mobile/src/utils/notifications/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/mobile/src/utils/notifications/index.ts b/apps/mobile/src/utils/notifications/index.ts index 49bcd2f68b..7cd8c11b41 100644 --- a/apps/mobile/src/utils/notifications/index.ts +++ b/apps/mobile/src/utils/notifications/index.ts @@ -1,4 +1,5 @@ import { AndroidChannel, AndroidImportance } from '@notifee/react-native' +import { HDNodeWallet, Wallet } from 'ethers' export enum ChannelId { DEFAULT_NOTIFICATION_CHANNEL_ID = 'DEFAULT_NOTIFICATION_CHANNEL_ID', @@ -36,3 +37,12 @@ export function withTimeout(promise: Promise, ms: number): Promise { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms)) return Promise.race([promise, timeout]) } + +export function getSigner( + safeOwnerPK: string | undefined, + randomDelegatedAccount: HDNodeWallet, +): Wallet | HDNodeWallet { + const signerAccount = safeOwnerPK ? new Wallet(safeOwnerPK) : randomDelegatedAccount + + return signerAccount +} From bdb6c849d2ccf2769385f5f3ce38349d713a683b Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:27:54 +0000 Subject: [PATCH 14/22] chore: removes console.log --- apps/mobile/src/hooks/useNotifications.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/mobile/src/hooks/useNotifications.ts b/apps/mobile/src/hooks/useNotifications.ts index 0ea029e9b8..a7fd3f251b 100644 --- a/apps/mobile/src/hooks/useNotifications.ts +++ b/apps/mobile/src/hooks/useNotifications.ts @@ -26,7 +26,6 @@ const useNotifications = (): NotificationsProps => { const fcmToken = useAppSelector(selectFCMToken) const remoteMessages = useAppSelector(selectRemoteMessages) const promptAttempts = useAppSelector(selectPromptAttempts) - const enableNotifications = useCallback(() => { const checkNotifications = async () => { const isDeviceNotificationEnabled = await NotificationsService.isDeviceNotificationEnabled() @@ -34,7 +33,6 @@ const useNotifications = (): NotificationsProps => { dispatch(updatePromptAttempts(1)) const { permission } = await NotificationsService.getAllPermissions() - if (permission !== 'authorized') { return } From 6a7264ef1c2a70d5ecbe36116be7dd13c15a5653 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:28:40 +0000 Subject: [PATCH 15/22] chore: adds hook to hold interaction w/ gateway --- apps/mobile/src/hooks/useGTW.ts | 114 ++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 apps/mobile/src/hooks/useGTW.ts diff --git a/apps/mobile/src/hooks/useGTW.ts b/apps/mobile/src/hooks/useGTW.ts new file mode 100644 index 0000000000..5e3c2a516b --- /dev/null +++ b/apps/mobile/src/hooks/useGTW.ts @@ -0,0 +1,114 @@ +import { useCallback } from 'react' +import DeviceInfo from 'react-native-device-info' + +import { useAuthVerifyV1Mutation } from '@safe-global/store/gateway/AUTO_GENERATED/auth' +import { useDelegatesPostDelegateV2Mutation } from '@safe-global/store/gateway/AUTO_GENERATED/delegates' +import { + useNotificationsUpsertSubscriptionsV2Mutation, + useNotificationsDeleteSubscriptionV2Mutation, + NotificationType, +} from '@safe-global/store/gateway/AUTO_GENERATED/notifications' + +import { isAndroid } from '../config/constants' +import { DELEGATED_ACCOUNT_TYPE } from './useDelegateKey' +import { Address, SafeInfo } from '../types/address' +import { useSiwe } from './useSiwe' +import Logger from '@/src/utils/logger' +import { HDNodeWallet, Wallet } from 'ethers' + +export function useGTW() { + // Queries + const [authVerifyV1] = useAuthVerifyV1Mutation() + const [notificationsUpsertSubscriptionsV2] = useNotificationsUpsertSubscriptionsV2Mutation() + const [notificationsDeleteSubscriptionsV2] = useNotificationsDeleteSubscriptionV2Mutation() + const [delegatesPostDelegateV2] = useDelegatesPostDelegateV2Mutation() + const { signMessage } = useSiwe() + const REGULAR_NOTIFICATIONS = ['MESSAGE_CONFIRMATION_REQUEST', 'CONFIRMATION_REQUEST'] + const OWNER_NOTIFICATIONS = [...REGULAR_NOTIFICATIONS, 'INCOMING_ETHER', 'INCOMING_TOKEN', 'CONFIRMATION_REQUEST'] + + const createDelegatedKeyOnBackEnd = useCallback( + async ({ + safeAddress, + signer, + message, + chainId, + fcmToken, + delegatedAccount, + delegatedAccountType, + }: { + safeAddress: Address + signer: Wallet | HDNodeWallet + message: string + chainId: string + fcmToken: string + delegatedAccount: Wallet | HDNodeWallet + delegatedAccountType?: DELEGATED_ACCOUNT_TYPE + }) => { + try { + const signature = await signMessage({ signer, message }) + const deviceUuid = await DeviceInfo.getUniqueId() + + if (delegatedAccountType === DELEGATED_ACCOUNT_TYPE.REGULAR) { + await authVerifyV1({ + siweDto: { + message, + signature, + }, + }) + } else { + delegatesPostDelegateV2({ + chainId, + createDelegateDto: { + safe: safeAddress, + delegator: signer.address, + delegate: delegatedAccount.address, + signature, + label: DELEGATED_ACCOUNT_TYPE.OWNER, + }, + }) + } + + const NOTIFICATIONS_GRANTED = + delegatedAccountType === DELEGATED_ACCOUNT_TYPE.REGULAR ? REGULAR_NOTIFICATIONS : OWNER_NOTIFICATIONS + + await notificationsUpsertSubscriptionsV2({ + upsertSubscriptionsDto: { + cloudMessagingToken: fcmToken, + safes: [ + { + chainId, + address: safeAddress, + notificationTypes: NOTIFICATIONS_GRANTED as NotificationType[], + }, + ], + deviceType: isAndroid ? 'ANDROID' : 'IOS', + deviceUuid, + }, + }) + } catch (err) { + Logger.error('CreateDelegateFailed', err) + return + } + }, + [], + ) + + const deleteDelegatedKeyOnBackEnd = useCallback(async (activeSafe: SafeInfo | null) => { + try { + if (!activeSafe) { + throw new Error('DeleteDelegateFailed :: No active safe') + } + + await notificationsDeleteSubscriptionsV2({ + deviceUuid: await DeviceInfo.getUniqueId(), + chainId: activeSafe.chainId, + safeAddress: activeSafe.address, + }) + } catch (err) { + Logger.error('DeleteDelegateFailed', err) + return + } + }, []) + + return { createDelegatedKeyOnBackEnd, deleteDelegatedKeyOnBackEnd } +} From 815898671bf2eb16d5957badd10ada06a23cb9ed Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:29:17 +0000 Subject: [PATCH 16/22] chore: adds redux structure for delegators --- apps/mobile/src/store/delegatedSlice.ts | 42 +++++++++++++++++++++++++ apps/mobile/src/store/index.ts | 2 ++ 2 files changed, 44 insertions(+) create mode 100644 apps/mobile/src/store/delegatedSlice.ts diff --git a/apps/mobile/src/store/delegatedSlice.ts b/apps/mobile/src/store/delegatedSlice.ts new file mode 100644 index 0000000000..343c7f1e75 --- /dev/null +++ b/apps/mobile/src/store/delegatedSlice.ts @@ -0,0 +1,42 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' + +import { RootState } from '.' +import { Address, SafeInfo } from '../types/address' + +export interface SafesSliceItem { + safes: SafeInfo[] +} + +export type DelegatedSafesSlice = Record + +interface DelegatedSafesState { + delegatedSafes: DelegatedSafesSlice +} + +const initialState: DelegatedSafesState = { + delegatedSafes: {}, +} + +const delegatedSlice = createSlice({ + name: 'delegated', + initialState, + reducers: { + addDelegatedAddress: (state, action: PayloadAction<{ delegatedAddress: Address; safes: SafeInfo[] }>) => { + const { delegatedAddress, safes } = action.payload + state.delegatedSafes[delegatedAddress] = { safes } + }, + updateDelegatedAddress: (state, action: PayloadAction<{ delegatedAddress: Address; safes: SafeInfo[] }>) => { + const { delegatedAddress, safes } = action.payload + + state.delegatedSafes[delegatedAddress] = { ...safes, safes } + + return state + }, + }, +}) + +export const { addDelegatedAddress, updateDelegatedAddress } = delegatedSlice.actions + +export const selectDelegatedAddresses = (state: RootState) => state.delegatedAddresses + +export default delegatedSlice.reducer diff --git a/apps/mobile/src/store/index.ts b/apps/mobile/src/store/index.ts index 3798df032f..f0625f5b08 100644 --- a/apps/mobile/src/store/index.ts +++ b/apps/mobile/src/store/index.ts @@ -4,6 +4,7 @@ import { reduxStorage } from './storage' import txHistory from './txHistorySlice' import activeSafe from './activeSafeSlice' import signers from './signersSlice' +import delegatedAddresses from './delegatedSlice' import myAccounts from './myAccountsSlice' import notifications from './notificationsSlice' import settings from './settingsSlice' @@ -26,6 +27,7 @@ export const rootReducer = combineReducers({ notifications, myAccounts, signers, + delegatedAddresses, settings, [cgwClient.reducerPath]: cgwClient.reducer, }) From fd1ca3f223976f358c65c60874cff80edb949183 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:29:47 +0000 Subject: [PATCH 17/22] chore: adds timeout wrapper --- .../src/services/notifications/FCMService.ts | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/mobile/src/services/notifications/FCMService.ts b/apps/mobile/src/services/notifications/FCMService.ts index cec2b44e38..316a6fdc19 100644 --- a/apps/mobile/src/services/notifications/FCMService.ts +++ b/apps/mobile/src/services/notifications/FCMService.ts @@ -1,7 +1,9 @@ +//@ts-ignore +globalThis.RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS = true import messaging, { FirebaseMessagingTypes } from '@react-native-firebase/messaging' import Logger from '@/src/utils/logger' import NotificationsService from './NotificationService' -import { ChannelId } from '@/src/utils/notifications' +import { ChannelId, withTimeout } from '@/src/utils/notifications' import { store } from '@/src/store' import { savePushToken } from '@/src/store/notificationsSlice' @@ -11,6 +13,7 @@ class FCMService { async getFCMToken(): Promise { const { fcmToken } = store.getState().notifications const token = fcmToken || undefined + if (!token) { Logger.info('getFCMToken: No FCM token found') } @@ -19,8 +22,7 @@ class FCMService { async saveFCMToken(): Promise { try { - const fcmToken = await messaging().getToken() - + const fcmToken = await withTimeout(messaging().getToken(), 5000) Logger.info('FCMService :: fcmToken', fcmToken) if (fcmToken) { @@ -55,17 +57,16 @@ class FCMService { } async registerAppWithFCM(): Promise { - // if (!messaging().registerDeviceForRemoteMessages) { - console.log('registerAppWithFCM :: CALLED') - await messaging() - .registerDeviceForRemoteMessages() - .then((status: unknown) => { - Logger.info('registerDeviceForRemoteMessages status', status) - }) - .catch((error) => { - Logger.error('registerAppWithFCM: Something went wrong', error) - }) - // } + if (!messaging().registerDeviceForRemoteMessages) { + await messaging() + .registerDeviceForRemoteMessages() + .then((status: unknown) => { + Logger.info('registerDeviceForRemoteMessages status', status) + }) + .catch((error) => { + Logger.error('registerAppWithFCM: Something went wrong', error) + }) + } } } export default new FCMService() From f5d09abee957bc59e875858acf052a53cf78f8d7 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:30:57 +0000 Subject: [PATCH 18/22] chore: adds userId --- apps/mobile/src/hooks/useSign/useSign.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/mobile/src/hooks/useSign/useSign.ts b/apps/mobile/src/hooks/useSign/useSign.ts index 5596648283..e64217cc8a 100644 --- a/apps/mobile/src/hooks/useSign/useSign.ts +++ b/apps/mobile/src/hooks/useSign/useSign.ts @@ -16,7 +16,7 @@ export function useSign() { invalidateOnNewBiometry: true, }) - const encryptyedPrivateKey = await DeviceCrypto.encrypt(userId, privateKey, { + const encryptedPrivateKey = await DeviceCrypto.encrypt(userId, privateKey, { biometryTitle: 'Authenticate', biometrySubTitle: 'Saving key', biometryDescription: 'Please authenticate yourself', @@ -57,8 +57,8 @@ export function useSign() { throw 'user password not found' } - const { encryptyedPassword, iv } = JSON.parse(user.password) - const decryptedKey = await DeviceCrypto.decrypt(userId, encryptyedPassword, iv, { + const { encryptedPassword, iv } = JSON.parse(user.password) + const decryptedKey = await DeviceCrypto.decrypt(userId, encryptedPassword, iv, { biometryTitle: 'Authenticate', biometrySubTitle: 'Signing', biometryDescription: 'Authenticate yourself to sign the text', From 7bf5d31a3f786d0f8152ef3236a4a2b5fb9a56da Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Thu, 13 Feb 2025 16:33:10 +0000 Subject: [PATCH 19/22] refactor: useDelegator hook --- apps/mobile/index.js | 7 +- apps/mobile/src/hooks/useDelegateKey.ts | 227 ++++++++++-------------- 2 files changed, 100 insertions(+), 134 deletions(-) diff --git a/apps/mobile/index.js b/apps/mobile/index.js index 1d6e981ef6..17d055ee4b 100644 --- a/apps/mobile/index.js +++ b/apps/mobile/index.js @@ -1,8 +1,9 @@ -import { registerRootComponent } from 'expo'; +import './shim' +import { registerRootComponent } from 'expo' -import App from './App'; +import App from './App' // registerRootComponent calls AppRegistry.registerComponent('main', () => App); // It also ensures that whether you load the app in Expo Go or in a native build, // the environment is set up appropriately -registerRootComponent(App); +registerRootComponent(App) diff --git a/apps/mobile/src/hooks/useDelegateKey.ts b/apps/mobile/src/hooks/useDelegateKey.ts index e89453a08e..1650aa48df 100644 --- a/apps/mobile/src/hooks/useDelegateKey.ts +++ b/apps/mobile/src/hooks/useDelegateKey.ts @@ -1,101 +1,113 @@ import { useState, useCallback } from 'react' -import DeviceInfo from 'react-native-device-info' -import 'react-native-get-random-values' -import { HDNodeWallet, Wallet } from 'ethers' -import { useAuthGetNonceV1Query, useAuthVerifyV1Mutation } from '@safe-global/store/gateway/AUTO_GENERATED/auth' -import { - useNotificationsDeleteSubscriptionV2Mutation, - useNotificationsUpsertSubscriptionsV2Mutation, -} from '@safe-global/store/gateway/AUTO_GENERATED/notifications' +import { Wallet } from 'ethers' + +import { AuthNonce } from '@safe-global/store/gateway/AUTO_GENERATED/auth' +import { AddressInfo } from '@safe-global/store/gateway/AUTO_GENERATED/safes' import Logger from '@/src/utils/logger' import { Address } from '@/src/types/address' import { selectActiveSafe } from '@/src/store/activeSafeSlice' -import { useAppSelector } from '@/src/store/hooks' -import { useSiwe } from './useSiwe' +import { useAppDispatch, useAppSelector } from '@/src/store/hooks' import { useSign } from './useSign' -import { isAndroid } from '../config/constants' +import { useGTW } from './useGTW' + import { selectFCMToken } from '../store/notificationsSlice' +// import { selectSigners } from '../store/signersSlice' +import { addDelegatedAddress } from '../store/delegatedSlice' +import { useSiwe } from './useSiwe' +import { getSigner } from '../utils/notifications' const ERROR_MSG = 'useDelegateKey: Something went wrong' -export function useDelegateKey() { +export enum DELEGATED_ACCOUNT_TYPE { + REGULAR = 'REGULAR', + OWNER = 'OWNER', +} + +export function useDelegateKey(safeOwner?: AddressInfo) { // Local states const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + const [delegatedAccountType, setDelegatedAccountType] = useState() - // Queries - const [authVerifyV1] = useAuthVerifyV1Mutation() - const [notificationsUpsertSubscriptionsV2] = useNotificationsUpsertSubscriptionsV2Mutation() - const [notificationsDeleteSubscriptionsV2] = useNotificationsDeleteSubscriptionV2Mutation() // Custom hooks - const { signMessage } = useSiwe() - const { getPrivateKey } = useSign() - - // Redux states + const { getPrivateKey, storePrivateKey } = useSign() + const { createDelegatedKeyOnBackEnd, deleteDelegatedKeyOnBackEnd } = useGTW() + const { createSiweMessage } = useSiwe() + // Redux + const dispatch = useAppDispatch() const activeSafe = useAppSelector(selectActiveSafe) + + // const appSigners = useAppSelector(selectSigners) const fcmToken = useAppSelector(selectFCMToken) - // Step 0 - Get the nonce to be included in the message to be sent to the backend - const { data } = useAuthGetNonceV1Query() - - const createDelegate = useCallback( - async (ownerAddress: Address) => { - setLoading(true) - setError(null) - const nonce = data?.nonce - // Step 1 - Try to get the owner's private key - const ownerPrivateKey = await getPrivateKey() - - try { - if (!ownerAddress || !activeSafe || !nonce || !fcmToken) { - throw Logger.info(ERROR_MSG) - } - - // Step 2 - Create a new random (delegated) private key in case the owner's private key is not available - //TODO: Double check if we have a wallet stored already avoiding to create a new one - const signerAccount = ownerPrivateKey ? new Wallet(ownerPrivateKey) : Wallet.createRandom() - - if (!signerAccount) { - throw Logger.error(ERROR_MSG, error) - } - - // Step 3 - Create a message following the SIWE standard - const siweMessage = `SafeWallet wants you to sign in with your Ethereum account: -${signerAccount.address} - -Sign in with Ethereum to the app. - -URI: https://safe.global -Version: 1 -Chain ID: ${activeSafe.chainId} -Nonce: ${nonce} -Issued At: ${new Date().toISOString()}` - - // Step 4 - Triggers the backend to create the delegate - await createOnBackEnd({ - safeAddress: activeSafe.address, - signer: signerAccount, - message: siweMessage, - chainId: activeSafe.chainId, - fcmToken, - }) - } catch (err) { - Logger.error('useDelegateKey: Something went wrong', err) - setError(err) - return - } finally { - setLoading(false) - } - }, - [data, activeSafe, fcmToken], - ) + /** + * case 2: Starting w/ PK + * [x] - Select active safe (useAppSelector(selectActiveSafe)) + * [] - fetch signers from Redux slice --> activeSafe filter if its owner are present in one of the signers + * [x] - fetch PK from keychain using previous address as userId + * [x] - create a new delegate PK (customRandom) + * [x] - create a SiWE message, sign and call authVerifyV1 with owner's account + * [x] - authorize the delegator PK through /v1/{chains}/{chainId}/delegates call. + * [x] - this will enable full access (outgoing/income/queue) notifications + * [x] - create a new redux structure for store the delegatedSigner object + */ + // selectSafeInfo filtered by activeSafe owners - const deleteDelegate = useCallback(async () => { + // Step 0 - Get the nonce to be included in the message to be sent to the backend + const createDelegate = useCallback(async (data: AuthNonce | undefined) => { setLoading(true) setError(null) + + const nonce = data?.nonce + + if (!activeSafe || !fcmToken || !nonce) { + throw Logger.info(ERROR_MSG) + } + try { - await deleteOnBackEnd() + // Step 1 - Try to get the safe owner's private key from keychain + const safeOwnerPK = safeOwner && (await getPrivateKey(safeOwner.value)) + + const delegatedAccType = safeOwnerPK ? DELEGATED_ACCOUNT_TYPE.OWNER : DELEGATED_ACCOUNT_TYPE.REGULAR + setDelegatedAccountType(delegatedAccType) + + // Step 2 - Create a new random (delegated) private key + const randomDelegatedAccount = Wallet.createRandom() + + if (!randomDelegatedAccount) { + throw Logger.error(ERROR_MSG, error) + } + + // Step 2.1 - Store the delegated account in the redux store + dispatch( + addDelegatedAddress({ delegatedAddress: randomDelegatedAccount.address as Address, safes: [activeSafe] }), + ) + + // Step 2.2 - Store it in the keychain + storePrivateKey(randomDelegatedAccount.address, randomDelegatedAccount.privateKey) + + // Step 2.3 - Define the signer account + const signerAccount = getSigner(safeOwnerPK, randomDelegatedAccount) + + // Step 3 - Create a message following the SIWE standard + const siweMessage = createSiweMessage({ + address: signerAccount.address, + chainId: Number(activeSafe.chainId), + nonce, + statement: 'SafeWallet wants you to sign in with your Ethereum account', + }) + + // Step 4 - Triggers the backend to create the delegate + await createDelegatedKeyOnBackEnd({ + safeAddress: activeSafe.address, + signer: signerAccount, + message: siweMessage, + chainId: activeSafe.chainId, + fcmToken, + delegatedAccount: randomDelegatedAccount, + delegatedAccountType: delegatedAccType, + }) } catch (err) { Logger.error('useDelegateKey: Something went wrong', err) setError(err) @@ -105,65 +117,17 @@ Issued At: ${new Date().toISOString()}` } }, []) - const createOnBackEnd = useCallback( - async ({ - safeAddress, - signer, - message, - chainId, - fcmToken, - }: { - safeAddress: Address - signer: HDNodeWallet | Wallet - message: string - chainId: string - fcmToken: string - }) => { - const signature = await signMessage({ signer, message }) - try { - await authVerifyV1({ - siweDto: { - message, - signature, - }, - }) - - const deviceUuid = await DeviceInfo.getUniqueId() - - await notificationsUpsertSubscriptionsV2({ - upsertSubscriptionsDto: { - cloudMessagingToken: fcmToken, - safes: [ - { - chainId, - address: safeAddress, - notificationTypes: ['MESSAGE_CONFIRMATION_REQUEST', 'CONFIRMATION_REQUEST'], - }, - ], - deviceType: isAndroid ? 'ANDROID' : 'IOS', - deviceUuid, - }, - }) - } catch (err) { - Logger.error('CreateDelegateFailed', err) - setError(err) - return - } - }, - [], - ) - - const deleteOnBackEnd = useCallback(async () => { + const deleteDelegate = useCallback(async () => { + setLoading(true) + setError(null) try { - await notificationsDeleteSubscriptionsV2({ - deviceUuid: await DeviceInfo.getUniqueId(), - chainId: activeSafe.chainId, - safeAddress: activeSafe.address, - }) + await deleteDelegatedKeyOnBackEnd(activeSafe) } catch (err) { - Logger.error('DeleteDelegateFailed', err) + Logger.error('useDelegateKey: Something went wrong', err) setError(err) return + } finally { + setLoading(false) } }, []) @@ -172,5 +136,6 @@ Issued At: ${new Date().toISOString()}` error, createDelegate, deleteDelegate, + delegatedAccountType, } } From 8109e9b9beb2b89fdaa71bab3821ee1b02f2e7de Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Tue, 18 Feb 2025 16:46:31 +0000 Subject: [PATCH 20/22] refactor: fixes key generation and granted rights --- apps/mobile/package.json | 14 ++++++----- apps/mobile/src/app/notifications-opt-in.tsx | 14 +++++++++-- apps/mobile/src/config/constants.ts | 2 +- .../Assets/components/Navbar/Navbar.tsx | 1 + .../Notifications/Notifications.container.tsx | 7 +++++- apps/mobile/src/hooks/useDelegateKey.ts | 14 ----------- apps/mobile/src/hooks/useGTW.ts | 9 +++++++- apps/mobile/src/hooks/useNotifications.ts | 9 ++++---- .../src/services/notifications/FCMService.ts | 23 +++++++++---------- .../notifications/NotificationService.ts | 16 ++++++++++--- 10 files changed, 65 insertions(+), 44 deletions(-) diff --git a/apps/mobile/package.json b/apps/mobile/package.json index c9f7fbc674..4eb69b86a7 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -8,7 +8,7 @@ } }, "scripts": { - "start": "expo start", + "start": "expo start --dev-client", "start:android": "expo run:android", "start:ios": "expo run:ios", "storybook:metro": "STORYBOOK_ENABLED='true' expo start", @@ -44,8 +44,8 @@ "@notifee/react-native": "^9.1.8", "@react-native-clipboard/clipboard": "^1.15.0", "@react-native-community/blur": "^4.4.1", - "@react-native-firebase/app": "^21.7.1", - "@react-native-firebase/messaging": "^21.7.1", + "@react-native-firebase/app": "^21.8.0", + "@react-native-firebase/messaging": "^21.8.0", "@react-native-menu/menu": "^1.1.6", "@react-native/babel-preset": "^0.76.2", "@react-navigation/material-top-tabs": "^7.1.0", @@ -70,6 +70,7 @@ "expo-blur": "~14.0.1", "expo-build-properties": "^0.13.2", "expo-constants": "~17.0.5", + "expo-crypto": "~14.0.2", "expo-dev-client": "~5.0.5", "expo-font": "~13.0.3", "expo-image": "~2.0.3", @@ -84,18 +85,18 @@ "moti": "^0.29.0", "react": "18.3.1", "react-dom": "^18.3.1", - "react-native": "0.76.3", + "react-native": "0.76.7", "react-native-collapsible-tab-view": "^8.0.0", "react-native-device-crypto": "^0.1.7", "react-native-device-info": "^14.0.1", "react-native-draggable-flatlist": "^4.0.1", "react-native-gesture-handler": "~2.20.2", - "react-native-get-random-values": "^1.11.0", + "react-native-get-random-values": "~1.11.0", "react-native-keychain": "^9.2.2", "react-native-mmkv": "^3.1.0", "react-native-pager-view": "^6.5.1", "react-native-progress": "^5.0.1", - "react-native-quick-crypto": "^0.7.11", + "react-native-quick-crypto": "^0.7.12", "react-native-reanimated": "~3.16.7", "react-native-safe-area-context": "~5.1.0", "react-native-screens": "~4.5.0", @@ -139,6 +140,7 @@ "@types/react": "~18.3.12", "@types/react-native-get-random-values": "^1", "babel-loader": "^8.4.1", + "babel-plugin-module-resolver": "^5.0.2", "eslint": "^9.19.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", diff --git a/apps/mobile/src/app/notifications-opt-in.tsx b/apps/mobile/src/app/notifications-opt-in.tsx index 2705831eb8..174f460ae5 100644 --- a/apps/mobile/src/app/notifications-opt-in.tsx +++ b/apps/mobile/src/app/notifications-opt-in.tsx @@ -1,13 +1,23 @@ -import React from 'react' +import React, { useCallback } from 'react' import { useColorScheme } from 'react-native' import { OptIn } from '@/src/components/OptIn' import useNotifications from '@/src/hooks/useNotifications' import { router, useFocusEffect } from 'expo-router' +import { useDelegateKey } from '../hooks/useDelegateKey' +import { useAuthGetNonceV1Query } from '@safe-global/store/gateway/AUTO_GENERATED/auth' function NotificationsOptIn() { const { enableNotifications, isAppNotificationEnabled } = useNotifications() + const { data } = useAuthGetNonceV1Query() + const { createDelegate } = useDelegateKey() + const colorScheme = useColorScheme() + const toggleNotificationsOn = useCallback(async () => { + enableNotifications() + await createDelegate(data) + }, [data]) + useFocusEffect(() => { if (isAppNotificationEnabled) { router.replace('/(tabs)') @@ -27,7 +37,7 @@ function NotificationsOptIn() { image={image} isVisible ctaButton={{ - onPress: enableNotifications, + onPress: toggleNotificationsOn, label: 'Enable notifications', }} secondaryButton={{ diff --git a/apps/mobile/src/config/constants.ts b/apps/mobile/src/config/constants.ts index a9a1b075a3..dd1b187ea1 100644 --- a/apps/mobile/src/config/constants.ts +++ b/apps/mobile/src/config/constants.ts @@ -12,7 +12,7 @@ export const POLLING_INTERVAL = 15_000 export const GATEWAY_URL_PRODUCTION = process.env.NEXT_PUBLIC_GATEWAY_URL_PRODUCTION || 'https://safe-client.safe.global' export const GATEWAY_URL_STAGING = process.env.NEXT_PUBLIC_GATEWAY_URL_STAGING || 'https://safe-client.staging.5afe.dev' -export const GATEWAY_URL = isProduction ? GATEWAY_URL_PRODUCTION : GATEWAY_URL_STAGING +export const GATEWAY_URL = 'https://safe-client.staging.5afe.dev' /** * The version of the onboarding flow. diff --git a/apps/mobile/src/features/Assets/components/Navbar/Navbar.tsx b/apps/mobile/src/features/Assets/components/Navbar/Navbar.tsx index e10e24766d..82ac43017d 100644 --- a/apps/mobile/src/features/Assets/components/Navbar/Navbar.tsx +++ b/apps/mobile/src/features/Assets/components/Navbar/Navbar.tsx @@ -22,6 +22,7 @@ export const Navbar = () => { const router = useRouter() const activeSafe = useDefinedActiveSafe() const isAppNotificationEnabled = useAppSelector(selectAppNotificationStatus) + const handleNotificationAccess = () => { if (!isAppNotificationEnabled) { router.navigate('/notifications-opt-in') diff --git a/apps/mobile/src/features/Notifications/Notifications.container.tsx b/apps/mobile/src/features/Notifications/Notifications.container.tsx index 2a794f90f5..1f0f874ead 100644 --- a/apps/mobile/src/features/Notifications/Notifications.container.tsx +++ b/apps/mobile/src/features/Notifications/Notifications.container.tsx @@ -5,13 +5,18 @@ import { View, Text } from 'tamagui' import { useAppSelector, useAppDispatch } from '@/src/store/hooks' import { SafeListItem } from '@/src/components/SafeListItem' import { selectAppNotificationStatus, toggleAppNotifications } from '@/src/store/notificationsSlice' +import { useDelegateKey } from '@/src/hooks/useDelegateKey' export const NotificationsContainer = () => { const dispatch = useAppDispatch() + const { deleteDelegate } = useDelegateKey() const isAppNotificationEnabled = useAppSelector(selectAppNotificationStatus) - const handleToggleAppNotifications = useCallback(() => { + const handleToggleAppNotifications = useCallback(async () => { dispatch(toggleAppNotifications(!isAppNotificationEnabled)) + if (!isAppNotificationEnabled) { + await deleteDelegate() + } }, [isAppNotificationEnabled]) return ( diff --git a/apps/mobile/src/hooks/useDelegateKey.ts b/apps/mobile/src/hooks/useDelegateKey.ts index 1650aa48df..134960c884 100644 --- a/apps/mobile/src/hooks/useDelegateKey.ts +++ b/apps/mobile/src/hooks/useDelegateKey.ts @@ -41,26 +41,12 @@ export function useDelegateKey(safeOwner?: AddressInfo) { // const appSigners = useAppSelector(selectSigners) const fcmToken = useAppSelector(selectFCMToken) - /** - * case 2: Starting w/ PK - * [x] - Select active safe (useAppSelector(selectActiveSafe)) - * [] - fetch signers from Redux slice --> activeSafe filter if its owner are present in one of the signers - * [x] - fetch PK from keychain using previous address as userId - * [x] - create a new delegate PK (customRandom) - * [x] - create a SiWE message, sign and call authVerifyV1 with owner's account - * [x] - authorize the delegator PK through /v1/{chains}/{chainId}/delegates call. - * [x] - this will enable full access (outgoing/income/queue) notifications - * [x] - create a new redux structure for store the delegatedSigner object - */ - // selectSafeInfo filtered by activeSafe owners - // Step 0 - Get the nonce to be included in the message to be sent to the backend const createDelegate = useCallback(async (data: AuthNonce | undefined) => { setLoading(true) setError(null) const nonce = data?.nonce - if (!activeSafe || !fcmToken || !nonce) { throw Logger.info(ERROR_MSG) } diff --git a/apps/mobile/src/hooks/useGTW.ts b/apps/mobile/src/hooks/useGTW.ts index 5e3c2a516b..eb31c8055a 100644 --- a/apps/mobile/src/hooks/useGTW.ts +++ b/apps/mobile/src/hooks/useGTW.ts @@ -23,8 +23,15 @@ export function useGTW() { const [notificationsDeleteSubscriptionsV2] = useNotificationsDeleteSubscriptionV2Mutation() const [delegatesPostDelegateV2] = useDelegatesPostDelegateV2Mutation() const { signMessage } = useSiwe() + const REGULAR_NOTIFICATIONS = ['MESSAGE_CONFIRMATION_REQUEST', 'CONFIRMATION_REQUEST'] - const OWNER_NOTIFICATIONS = [...REGULAR_NOTIFICATIONS, 'INCOMING_ETHER', 'INCOMING_TOKEN', 'CONFIRMATION_REQUEST'] + const OWNER_NOTIFICATIONS = [ + ...REGULAR_NOTIFICATIONS, + 'INCOMING_ETHER', + 'INCOMING_TOKEN', + 'MODULE_TRANSACTION', + 'EXECUTED_MULTISIG_TRANSACTION', + ] const createDelegatedKeyOnBackEnd = useCallback( async ({ diff --git a/apps/mobile/src/hooks/useNotifications.ts b/apps/mobile/src/hooks/useNotifications.ts index a7fd3f251b..535d0ef52e 100644 --- a/apps/mobile/src/hooks/useNotifications.ts +++ b/apps/mobile/src/hooks/useNotifications.ts @@ -29,13 +29,14 @@ const useNotifications = (): NotificationsProps => { const enableNotifications = useCallback(() => { const checkNotifications = async () => { const isDeviceNotificationEnabled = await NotificationsService.isDeviceNotificationEnabled() + if (!isDeviceNotificationEnabled) { dispatch(updatePromptAttempts(1)) + } - const { permission } = await NotificationsService.getAllPermissions() - if (permission !== 'authorized') { - return - } + const { permission } = await NotificationsService.getAllPermissions() + if (permission !== 'authorized') { + return } try { diff --git a/apps/mobile/src/services/notifications/FCMService.ts b/apps/mobile/src/services/notifications/FCMService.ts index 316a6fdc19..65ae4f09d7 100644 --- a/apps/mobile/src/services/notifications/FCMService.ts +++ b/apps/mobile/src/services/notifications/FCMService.ts @@ -22,9 +22,10 @@ class FCMService { async saveFCMToken(): Promise { try { - const fcmToken = await withTimeout(messaging().getToken(), 5000) + // Register the app with FCM forcefully to get the token since it has not been reliably saved otherwise + await messaging().registerDeviceForRemoteMessages() + const fcmToken = await withTimeout(messaging().getToken(), 10000) Logger.info('FCMService :: fcmToken', fcmToken) - if (fcmToken) { store.dispatch(savePushToken(fcmToken)) } @@ -57,16 +58,14 @@ class FCMService { } async registerAppWithFCM(): Promise { - if (!messaging().registerDeviceForRemoteMessages) { - await messaging() - .registerDeviceForRemoteMessages() - .then((status: unknown) => { - Logger.info('registerDeviceForRemoteMessages status', status) - }) - .catch((error) => { - Logger.error('registerAppWithFCM: Something went wrong', error) - }) - } + await messaging() + .registerDeviceForRemoteMessages() + .then((status: unknown) => { + Logger.info('registerDeviceForRemoteMessages status', status) + }) + .catch((error) => { + Logger.error('registerAppWithFCM: Something went wrong', error) + }) } } export default new FCMService() diff --git a/apps/mobile/src/services/notifications/NotificationService.ts b/apps/mobile/src/services/notifications/NotificationService.ts index 317ab6c5b8..e6885a1434 100644 --- a/apps/mobile/src/services/notifications/NotificationService.ts +++ b/apps/mobile/src/services/notifications/NotificationService.ts @@ -65,10 +65,18 @@ class NotificationsService { * 4 - If permission has not being granted already or blocked notifications are found, open device's settings * so that user can enable DEVICE notifications **/ - if ((permission !== 'authorized' || blockedNotifications.size !== 0) && shouldOpenSettings) { + + if (shouldOpenSettings) { await this.requestPushNotificationsPermission() permission = await withTimeout(this.checkCurrentPermissions(), 5000) + } else { + store.dispatch(toggleDeviceNotifications(true)) + store.dispatch(toggleAppNotifications(true)) + store.dispatch(updatePromptAttempts(0)) + store.dispatch(updateLastTimePromptAttempted(0)) + permission = (await notifee.requestPermission()).authorizationStatus as unknown as string } + return { permission, blockedNotifications } } catch (error) { Logger.error('Error occurred while fetching permissions:', error) @@ -110,8 +118,10 @@ class NotificationsService { store.dispatch(updatePromptAttempts(0)) store.dispatch(updateLastTimePromptAttempted(0)) - await notifee.requestPermission() - this.openSystemSettings() + const permissions = await notifee.getNotificationSettings() + if (permissions.authorizationStatus === AuthorizationStatus.DENIED) { + this.openSystemSettings() + } resolve(true) }, }, From 18690deb63fd35e1f81d5d5de5a2a201cd93fe99 Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Wed, 19 Feb 2025 15:40:31 +0000 Subject: [PATCH 21/22] chore: addresses various fixes based on first round of review --- apps/mobile/babel.config.js | 12 --- apps/mobile/package.json | 5 +- apps/mobile/src/app/_layout.tsx | 1 - apps/mobile/src/app/notifications-opt-in.tsx | 44 +++++++++-- apps/mobile/src/config/constants.ts | 2 +- .../Notifications/Notifications.container.tsx | 49 +++++++++--- apps/mobile/src/hooks/useDelegateKey.ts | 9 +-- apps/mobile/src/hooks/useGTW.ts | 19 +++-- apps/mobile/src/hooks/useNotifications.ts | 74 ++++++++++++------ .../notifications/NotificationService.ts | 78 ++++++++++--------- apps/mobile/src/store/constants.ts | 8 +- apps/mobile/src/store/delegatedSlice.ts | 12 +-- 12 files changed, 190 insertions(+), 123 deletions(-) diff --git a/apps/mobile/babel.config.js b/apps/mobile/babel.config.js index 25a3946a60..e1e3637afd 100644 --- a/apps/mobile/babel.config.js +++ b/apps/mobile/babel.config.js @@ -2,17 +2,5 @@ module.exports = function (api) { api.cache(true) return { presets: ['babel-preset-expo'], - plugins: [ - [ - 'module-resolver', - { - alias: { - crypto: 'react-native-quick-crypto', - stream: 'stream-browserify', - buffer: '@craftzdog/react-native-buffer', - }, - }, - ], - ], } } diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 4eb69b86a7..dde21086b8 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -8,7 +8,7 @@ } }, "scripts": { - "start": "expo start --dev-client", + "start": "expo start", "start:android": "expo run:android", "start:ios": "expo run:ios", "storybook:metro": "STORYBOOK_ENABLED='true' expo start", @@ -91,7 +91,6 @@ "react-native-device-info": "^14.0.1", "react-native-draggable-flatlist": "^4.0.1", "react-native-gesture-handler": "~2.20.2", - "react-native-get-random-values": "~1.11.0", "react-native-keychain": "^9.2.2", "react-native-mmkv": "^3.1.0", "react-native-pager-view": "^6.5.1", @@ -138,9 +137,7 @@ "@types/lodash": "^4.17.13", "@types/node": "^22.9.1", "@types/react": "~18.3.12", - "@types/react-native-get-random-values": "^1", "babel-loader": "^8.4.1", - "babel-plugin-module-resolver": "^5.0.2", "eslint": "^9.19.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx index 18c0655ed1..20ba3274b0 100644 --- a/apps/mobile/src/app/_layout.tsx +++ b/apps/mobile/src/app/_layout.tsx @@ -1,5 +1,4 @@ import '../../shim' - import { Stack } from 'expo-router' import 'react-native-reanimated' import { SafeThemeProvider } from '@/src/theme/provider/safeTheme' diff --git a/apps/mobile/src/app/notifications-opt-in.tsx b/apps/mobile/src/app/notifications-opt-in.tsx index 174f460ae5..099b065d2a 100644 --- a/apps/mobile/src/app/notifications-opt-in.tsx +++ b/apps/mobile/src/app/notifications-opt-in.tsx @@ -1,28 +1,58 @@ -import React, { useCallback } from 'react' -import { useColorScheme } from 'react-native' +import React, { useCallback, useEffect, useRef } from 'react' +import { useColorScheme, AppState } from 'react-native' import { OptIn } from '@/src/components/OptIn' import useNotifications from '@/src/hooks/useNotifications' -import { router, useFocusEffect } from 'expo-router' +import NotificationsService from '@/src/services/notifications/NotificationService' +import { router } from 'expo-router' import { useDelegateKey } from '../hooks/useDelegateKey' import { useAuthGetNonceV1Query } from '@safe-global/store/gateway/AUTO_GENERATED/auth' +import Logger from '@/src/utils/logger' function NotificationsOptIn() { const { enableNotifications, isAppNotificationEnabled } = useNotifications() + const appState = useRef(AppState.currentState) const { data } = useAuthGetNonceV1Query() const { createDelegate } = useDelegateKey() const colorScheme = useColorScheme() const toggleNotificationsOn = useCallback(async () => { - enableNotifications() - await createDelegate(data) + try { + const deviceNotificationStatus = await NotificationsService.isDeviceNotificationEnabled() + if (deviceNotificationStatus) { + enableNotifications() + await createDelegate(data) + } else { + await NotificationsService.getAllPermissions(true) + } + } catch (error) { + Logger.error('Error enabling push notifications', error) + } }, [data]) - useFocusEffect(() => { + useEffect(() => { + const subscription = AppState.addEventListener('change', async (nextAppState) => { + if (appState.current.match(/inactive|background/) && nextAppState === 'active') { + const deviceNotificationStatus = await NotificationsService.isDeviceNotificationEnabled() + if (deviceNotificationStatus && !isAppNotificationEnabled) { + enableNotifications() + await createDelegate(data) + } + } + + appState.current = nextAppState + }) + + return () => { + subscription.remove() + } + }, []) + + useEffect(() => { if (isAppNotificationEnabled) { router.replace('/(tabs)') } - }) + }, [isAppNotificationEnabled]) const image = colorScheme === 'dark' diff --git a/apps/mobile/src/config/constants.ts b/apps/mobile/src/config/constants.ts index dd1b187ea1..a9a1b075a3 100644 --- a/apps/mobile/src/config/constants.ts +++ b/apps/mobile/src/config/constants.ts @@ -12,7 +12,7 @@ export const POLLING_INTERVAL = 15_000 export const GATEWAY_URL_PRODUCTION = process.env.NEXT_PUBLIC_GATEWAY_URL_PRODUCTION || 'https://safe-client.safe.global' export const GATEWAY_URL_STAGING = process.env.NEXT_PUBLIC_GATEWAY_URL_STAGING || 'https://safe-client.staging.5afe.dev' -export const GATEWAY_URL = 'https://safe-client.staging.5afe.dev' +export const GATEWAY_URL = isProduction ? GATEWAY_URL_PRODUCTION : GATEWAY_URL_STAGING /** * The version of the onboarding flow. diff --git a/apps/mobile/src/features/Notifications/Notifications.container.tsx b/apps/mobile/src/features/Notifications/Notifications.container.tsx index 1f0f874ead..2a2ce80f57 100644 --- a/apps/mobile/src/features/Notifications/Notifications.container.tsx +++ b/apps/mobile/src/features/Notifications/Notifications.container.tsx @@ -1,21 +1,52 @@ -import React, { useCallback } from 'react' -import { Switch } from 'react-native' +import React, { useCallback, useEffect, useRef } from 'react' +import { AppState, Switch } from 'react-native' import { View, Text } from 'tamagui' - -import { useAppSelector, useAppDispatch } from '@/src/store/hooks' +import { useAppDispatch } from '@/src/store/hooks' import { SafeListItem } from '@/src/components/SafeListItem' -import { selectAppNotificationStatus, toggleAppNotifications } from '@/src/store/notificationsSlice' +import NotificationsService from '@/src/services/notifications/NotificationService' +import { toggleAppNotifications } from '@/src/store/notificationsSlice' import { useDelegateKey } from '@/src/hooks/useDelegateKey' +import useNotifications from '@/src/hooks/useNotifications' +import { useAuthGetNonceV1Query } from '@safe-global/store/gateway/AUTO_GENERATED/auth' export const NotificationsContainer = () => { const dispatch = useAppDispatch() - const { deleteDelegate } = useDelegateKey() - const isAppNotificationEnabled = useAppSelector(selectAppNotificationStatus) + const { enableNotifications, isAppNotificationEnabled } = useNotifications() + const { data } = useAuthGetNonceV1Query() + const { createDelegate, deleteDelegate, error } = useDelegateKey() + const appState = useRef(AppState.currentState) const handleToggleAppNotifications = useCallback(async () => { - dispatch(toggleAppNotifications(!isAppNotificationEnabled)) - if (!isAppNotificationEnabled) { + const deviceNotificationStatus = await NotificationsService.isDeviceNotificationEnabled() + + if (!deviceNotificationStatus && !isAppNotificationEnabled) { + await NotificationsService.requestPushNotificationsPermission() + } else if (deviceNotificationStatus && !isAppNotificationEnabled) { + enableNotifications() + await createDelegate(data) + } else { await deleteDelegate() + if (!error) { + dispatch(toggleAppNotifications(!isAppNotificationEnabled)) + } + } + }, [isAppNotificationEnabled]) + + useEffect(() => { + const subscription = AppState.addEventListener('change', async (nextAppState) => { + if (appState.current.match(/inactive|background/) && nextAppState === 'active') { + const deviceNotificationStatus = await NotificationsService.isDeviceNotificationEnabled() + if (deviceNotificationStatus && !isAppNotificationEnabled) { + enableNotifications() + await createDelegate(data) + } + } + + appState.current = nextAppState + }) + + return () => { + subscription.remove() } }, [isAppNotificationEnabled]) diff --git a/apps/mobile/src/hooks/useDelegateKey.ts b/apps/mobile/src/hooks/useDelegateKey.ts index 134960c884..7ee6aa1f16 100644 --- a/apps/mobile/src/hooks/useDelegateKey.ts +++ b/apps/mobile/src/hooks/useDelegateKey.ts @@ -12,17 +12,10 @@ import { useSign } from './useSign' import { useGTW } from './useGTW' import { selectFCMToken } from '../store/notificationsSlice' -// import { selectSigners } from '../store/signersSlice' import { addDelegatedAddress } from '../store/delegatedSlice' import { useSiwe } from './useSiwe' import { getSigner } from '../utils/notifications' - -const ERROR_MSG = 'useDelegateKey: Something went wrong' - -export enum DELEGATED_ACCOUNT_TYPE { - REGULAR = 'REGULAR', - OWNER = 'OWNER', -} +import { DELEGATED_ACCOUNT_TYPE, ERROR_MSG } from '../store/constants' export function useDelegateKey(safeOwner?: AddressInfo) { // Local states diff --git a/apps/mobile/src/hooks/useGTW.ts b/apps/mobile/src/hooks/useGTW.ts index eb31c8055a..4774fc9d5b 100644 --- a/apps/mobile/src/hooks/useGTW.ts +++ b/apps/mobile/src/hooks/useGTW.ts @@ -10,12 +10,20 @@ import { } from '@safe-global/store/gateway/AUTO_GENERATED/notifications' import { isAndroid } from '../config/constants' -import { DELEGATED_ACCOUNT_TYPE } from './useDelegateKey' import { Address, SafeInfo } from '../types/address' import { useSiwe } from './useSiwe' import Logger from '@/src/utils/logger' import { HDNodeWallet, Wallet } from 'ethers' +import { DELEGATED_ACCOUNT_TYPE } from '../store/constants' +const REGULAR_NOTIFICATIONS = ['MESSAGE_CONFIRMATION_REQUEST', 'CONFIRMATION_REQUEST'] +const OWNER_NOTIFICATIONS = [ + ...REGULAR_NOTIFICATIONS, + 'INCOMING_ETHER', + 'INCOMING_TOKEN', + 'MODULE_TRANSACTION', + 'EXECUTED_MULTISIG_TRANSACTION', +] export function useGTW() { // Queries const [authVerifyV1] = useAuthVerifyV1Mutation() @@ -24,15 +32,6 @@ export function useGTW() { const [delegatesPostDelegateV2] = useDelegatesPostDelegateV2Mutation() const { signMessage } = useSiwe() - const REGULAR_NOTIFICATIONS = ['MESSAGE_CONFIRMATION_REQUEST', 'CONFIRMATION_REQUEST'] - const OWNER_NOTIFICATIONS = [ - ...REGULAR_NOTIFICATIONS, - 'INCOMING_ETHER', - 'INCOMING_TOKEN', - 'MODULE_TRANSACTION', - 'EXECUTED_MULTISIG_TRANSACTION', - ] - const createDelegatedKeyOnBackEnd = useCallback( async ({ safeAddress, diff --git a/apps/mobile/src/hooks/useNotifications.ts b/apps/mobile/src/hooks/useNotifications.ts index 535d0ef52e..d537ace40c 100644 --- a/apps/mobile/src/hooks/useNotifications.ts +++ b/apps/mobile/src/hooks/useNotifications.ts @@ -3,9 +3,13 @@ import FCMService from '@/src/services/notifications/FCMService' import { useAppSelector, useAppDispatch } from '@/src/store/hooks' import { selectAppNotificationStatus, + selectDeviceNotificationStatus, selectFCMToken, selectPromptAttempts, selectRemoteMessages, + toggleAppNotifications, + toggleDeviceNotifications, + updateLastTimePromptAttempted, updatePromptAttempts, } from '@/src/store/notificationsSlice' import NotificationsService from '@/src/services/notifications/NotificationService' @@ -14,50 +18,74 @@ import Logger from '@/src/utils/logger' interface NotificationsProps { isAppNotificationEnabled: boolean + deviceNotificationStatus: boolean fcmToken: string | null remoteMessages: FirebaseMessagingTypes.RemoteMessage[] enableNotifications: () => void + checkNotificationsPermission: () => Promise promptAttempts: number } const useNotifications = (): NotificationsProps => { const dispatch = useAppDispatch() const isAppNotificationEnabled = useAppSelector(selectAppNotificationStatus) + const deviceNotificationStatus = useAppSelector(selectDeviceNotificationStatus) const fcmToken = useAppSelector(selectFCMToken) const remoteMessages = useAppSelector(selectRemoteMessages) const promptAttempts = useAppSelector(selectPromptAttempts) - const enableNotifications = useCallback(() => { - const checkNotifications = async () => { - const isDeviceNotificationEnabled = await NotificationsService.isDeviceNotificationEnabled() - if (!isDeviceNotificationEnabled) { - dispatch(updatePromptAttempts(1)) - } + const checkNotificationsPermission = useCallback(async () => { + const isDeviceNotificationEnabled = await NotificationsService.isDeviceNotificationEnabled() - const { permission } = await NotificationsService.getAllPermissions() - if (permission !== 'authorized') { - return - } + let allPermissions + if (!isDeviceNotificationEnabled) { + dispatch(toggleDeviceNotifications(false)) + dispatch(updatePromptAttempts(1)) + allPermissions = await NotificationsService.getAllPermissions(true) + } else { + dispatch(toggleDeviceNotifications(true)) + allPermissions = await NotificationsService.getAllPermissions(false) + } - try { - // Firebase Cloud Messaging - await FCMService.registerAppWithFCM() - await FCMService.saveFCMToken() - FCMService.listenForMessagesBackground() - } catch (error) { - Logger.error('FCM Registration or Token Save failed', error) - return - } + const { permission } = allPermissions + + if (permission === 'authorized') { + dispatch(toggleDeviceNotifications(true)) + } + + return permission + }, []) + + const enableNotifications = useCallback(async () => { + try { + // Firebase Cloud Messaging + await FCMService.registerAppWithFCM() + await FCMService.saveFCMToken() + FCMService.listenForMessagesBackground() + + // Redux store updates + dispatch(toggleAppNotifications(true)) + dispatch(updatePromptAttempts(0)) + dispatch(updateLastTimePromptAttempted(0)) return () => { FCMService.listenForMessagesForeground()() } + } catch (error) { + Logger.error('FCM Registration or Token Save failed', error) + return } + }, []) - checkNotifications() - }, [isAppNotificationEnabled]) - - return { enableNotifications, promptAttempts, isAppNotificationEnabled, fcmToken, remoteMessages } + return { + enableNotifications, + checkNotificationsPermission, + promptAttempts, + isAppNotificationEnabled, + deviceNotificationStatus, + fcmToken, + remoteMessages, + } } export default useNotifications diff --git a/apps/mobile/src/services/notifications/NotificationService.ts b/apps/mobile/src/services/notifications/NotificationService.ts index e6885a1434..e81e344f7f 100644 --- a/apps/mobile/src/services/notifications/NotificationService.ts +++ b/apps/mobile/src/services/notifications/NotificationService.ts @@ -16,7 +16,6 @@ import { ChannelId, notificationChannels, withTimeout } from '@/src/utils/notifi import Logger from '@/src/utils/logger' import { FirebaseMessagingTypes } from '@react-native-firebase/messaging' -import { router } from 'expo-router' interface AlertButton { text: string @@ -50,6 +49,17 @@ class NotificationsService { } } + enableNotifications() { + try { + store.dispatch(toggleDeviceNotifications(true)) + store.dispatch(toggleAppNotifications(true)) + store.dispatch(updatePromptAttempts(0)) + store.dispatch(updateLastTimePromptAttempted(0)) + } catch (error) { + Logger.error('Error checking if a user has push notifications permission', error) + } + } + async getAllPermissions(shouldOpenSettings = true) { try { const promises: Promise[] = notificationChannels.map((channel: AndroidChannel) => @@ -57,31 +67,31 @@ class NotificationsService { ) // 1 - Creates android's notifications channel await Promise.allSettled(promises) - // 2 - Verifies granted permission from device - let permission = await withTimeout(this.checkCurrentPermissions(), 5000) - // 3 - Verifies blocked notifications + + // 2 - Verifies blocked notifications const blockedNotifications = await withTimeout(this.getBlockedNotifications(), 5000) + /** - * 4 - If permission has not being granted already or blocked notifications are found, open device's settings + * 3 - If permission has not being granted already or blocked notifications are found, open device's settings * so that user can enable DEVICE notifications **/ - if (shouldOpenSettings) { - await this.requestPushNotificationsPermission() - permission = await withTimeout(this.checkCurrentPermissions(), 5000) - } else { - store.dispatch(toggleDeviceNotifications(true)) - store.dispatch(toggleAppNotifications(true)) - store.dispatch(updatePromptAttempts(0)) - store.dispatch(updateLastTimePromptAttempted(0)) - permission = (await notifee.requestPermission()).authorizationStatus as unknown as string + this.openDeviceSettings() } - return { permission, blockedNotifications } - } catch (error) { - Logger.error('Error occurred while fetching permissions:', error) + // 4 - Check if the user has enabled device notifications + const permission = await withTimeout(this.checkCurrentPermissions(), 5000) - return { permission: 'denied', blockedNotifications: new Set() } + return { + permission, + blockedNotifications, + } + } catch (error) { + Logger.error('Error checking if a user has push notifications permission', error) + return { + permission: 'denied', + blockedNotifications: new Map(), + } } } @@ -95,6 +105,18 @@ class NotificationsService { return isAuthorized } + async openDeviceSettings() { + try { + if (Platform.OS === 'ios') { + Linking.openSettings() + } else { + notifee.openNotificationSettings() + } + } catch (error) { + Logger.error('Error checking if a user has push notifications permission', error) + } + } + defaultButtons = (resolve: (value: boolean) => void): AlertButton[] => [ { text: 'Maybe later', @@ -105,23 +127,13 @@ class NotificationsService { */ store.dispatch(updatePromptAttempts(1)) store.dispatch(updateLastTimePromptAttempted(Date.now())) - router.navigate('/(tabs)') - resolve(false) }, }, { text: 'Turn on', onPress: async () => { - store.dispatch(toggleDeviceNotifications(true)) - store.dispatch(toggleAppNotifications(true)) - store.dispatch(updatePromptAttempts(0)) - store.dispatch(updateLastTimePromptAttempted(0)) - - const permissions = await notifee.getNotificationSettings() - if (permissions.authorizationStatus === AuthorizationStatus.DENIED) { - this.openSystemSettings() - } + await this.openDeviceSettings() resolve(true) }, }, @@ -149,14 +161,6 @@ class NotificationsService { } } - openSystemSettings() { - if (Platform.OS === 'ios') { - Linking.openSettings() - } else { - notifee.openNotificationSettings() - } - } - async checkCurrentPermissions() { const settings = await notifee.getNotificationSettings() return settings.authorizationStatus === AuthorizationStatus.AUTHORIZED || diff --git a/apps/mobile/src/store/constants.ts b/apps/mobile/src/store/constants.ts index a5048a8121..f1f62366da 100644 --- a/apps/mobile/src/store/constants.ts +++ b/apps/mobile/src/store/constants.ts @@ -344,5 +344,9 @@ export enum PressActionId { export const LAUNCH_ACTIVITY = 'global.safe.mobileapp.ui.MainActivity' -export const asymmetricKey = 'safe' -export const keychainGenericPassword = 'safeuser' +export const ERROR_MSG = 'useDelegateKey: Something went wrong' + +export enum DELEGATED_ACCOUNT_TYPE { + REGULAR = 'REGULAR', + OWNER = 'OWNER', +} diff --git a/apps/mobile/src/store/delegatedSlice.ts b/apps/mobile/src/store/delegatedSlice.ts index 343c7f1e75..2130c3300d 100644 --- a/apps/mobile/src/store/delegatedSlice.ts +++ b/apps/mobile/src/store/delegatedSlice.ts @@ -9,13 +9,7 @@ export interface SafesSliceItem { export type DelegatedSafesSlice = Record -interface DelegatedSafesState { - delegatedSafes: DelegatedSafesSlice -} - -const initialState: DelegatedSafesState = { - delegatedSafes: {}, -} +const initialState: DelegatedSafesSlice = {} const delegatedSlice = createSlice({ name: 'delegated', @@ -23,12 +17,12 @@ const delegatedSlice = createSlice({ reducers: { addDelegatedAddress: (state, action: PayloadAction<{ delegatedAddress: Address; safes: SafeInfo[] }>) => { const { delegatedAddress, safes } = action.payload - state.delegatedSafes[delegatedAddress] = { safes } + state[delegatedAddress] = { safes } }, updateDelegatedAddress: (state, action: PayloadAction<{ delegatedAddress: Address; safes: SafeInfo[] }>) => { const { delegatedAddress, safes } = action.payload - state.delegatedSafes[delegatedAddress] = { ...safes, safes } + state[delegatedAddress] = { ...safes, safes } return state }, From c8ae5be062225da1c836f0328ad81797fb51dedd Mon Sep 17 00:00:00 2001 From: Jonathansoufer Date: Wed, 19 Feb 2025 15:51:20 +0000 Subject: [PATCH 22/22] chore: update yarn.lock --- yarn.lock | 1028 ++++++++++++++++++++++++++--------------------------- 1 file changed, 514 insertions(+), 514 deletions(-) diff --git a/yarn.lock b/yarn.lock index 667d41b935..16db83a154 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4127,21 +4127,6 @@ __metadata: languageName: node linkType: hard -"@firebase/analytics-compat@npm:0.2.14": - version: 0.2.14 - resolution: "@firebase/analytics-compat@npm:0.2.14" - dependencies: - "@firebase/analytics": "npm:0.10.8" - "@firebase/analytics-types": "npm:0.8.2" - "@firebase/component": "npm:0.6.9" - "@firebase/util": "npm:1.10.0" - tslib: "npm:^2.1.0" - peerDependencies: - "@firebase/app-compat": 0.x - checksum: 10/0e368159d24223076b488b27308c11e5ef50456aff49fc58e1f66616228021c61e60c3299f63ce52ddc2f7099d803e9048bc28cd952cf5c302917002c03c85ee - languageName: node - linkType: hard - "@firebase/analytics-compat@npm:0.2.16": version: 0.2.16 resolution: "@firebase/analytics-compat@npm:0.2.16" @@ -4157,10 +4142,18 @@ __metadata: languageName: node linkType: hard -"@firebase/analytics-types@npm:0.8.2": - version: 0.8.2 - resolution: "@firebase/analytics-types@npm:0.8.2" - checksum: 10/297fb7becbc51950c7de5809fed896c391d1e87b4d8bb4bf88f4e8760b2e32f903a7dd8e92de4424b49c4e2ecb60a44d49e2f9c68ac3f7ffe3a0194f78910392 +"@firebase/analytics-compat@npm:0.2.17": + version: 0.2.17 + resolution: "@firebase/analytics-compat@npm:0.2.17" + dependencies: + "@firebase/analytics": "npm:0.10.11" + "@firebase/analytics-types": "npm:0.8.3" + "@firebase/component": "npm:0.6.12" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/3b048b41e0405a3975050f5d55afa923263ba3768d7b1b635d70892504cac03bd0bcf353b44819959dc6de7c04f1df818e34cec705c8ce18cf5c0866abe277b9 languageName: node linkType: hard @@ -4186,34 +4179,18 @@ __metadata: languageName: node linkType: hard -"@firebase/analytics@npm:0.10.8": - version: 0.10.8 - resolution: "@firebase/analytics@npm:0.10.8" +"@firebase/analytics@npm:0.10.11": + version: 0.10.11 + resolution: "@firebase/analytics@npm:0.10.11" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/installations": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/installations": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/152ddaf68146f02baa7060d34426c25ec13890a53942ffa2db09faa148bef35f59ee9810e6fb8f561fb3d115b71d1fb9fb111d2a0f0199aa510220782557c765 - languageName: node - linkType: hard - -"@firebase/app-check-compat@npm:0.3.15": - version: 0.3.15 - resolution: "@firebase/app-check-compat@npm:0.3.15" - dependencies: - "@firebase/app-check": "npm:0.8.8" - "@firebase/app-check-types": "npm:0.5.2" - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" - tslib: "npm:^2.1.0" - peerDependencies: - "@firebase/app-compat": 0.x - checksum: 10/ae541d324d5f91dbb7b479855d3380c4fe73e365013b80973a54620405093e6fd2f8e418549155b3a527530472a19b6edf6df1481a708f823eba42e376105b28 + checksum: 10/804083f61ffc57dabeb7a1b49e16f86969d1b2a37fafc23633c90324768ab849e52324b6a10928d789e038ec2f5d93248717f18d5f0d2a4b916850b86051c214 languageName: node linkType: hard @@ -4233,10 +4210,19 @@ __metadata: languageName: node linkType: hard -"@firebase/app-check-interop-types@npm:0.3.2": - version: 0.3.2 - resolution: "@firebase/app-check-interop-types@npm:0.3.2" - checksum: 10/3effe656a4762c541838f4bde91b4498e51d48389046b930dc3dbb012e54b6ab0727f7c68a3e94198f633d57833346fc337a0847b6b03d2407030e1489d466fe +"@firebase/app-check-compat@npm:0.3.18": + version: 0.3.18 + resolution: "@firebase/app-check-compat@npm:0.3.18" + dependencies: + "@firebase/app-check": "npm:0.8.11" + "@firebase/app-check-types": "npm:0.5.3" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/24b103fc309fa66d9830614c69bdf62810ecf0b77ad4fc9f318e05361a686cc3a684d84bddbd6afddc6a641739ead93ab1e8c28a75ed915750602b371aeb9b32 languageName: node linkType: hard @@ -4247,13 +4233,6 @@ __metadata: languageName: node linkType: hard -"@firebase/app-check-types@npm:0.5.2": - version: 0.5.2 - resolution: "@firebase/app-check-types@npm:0.5.2" - checksum: 10/2b33a7adfb7b6ebf5423940bf0af5909df69bf2d6184e12e989f6c76062077be16c31193795349862b4f8aab6b3059806b732a92995cae30fd77419f19a86c6e - languageName: node - linkType: hard - "@firebase/app-check-types@npm:0.5.3": version: 0.5.3 resolution: "@firebase/app-check-types@npm:0.5.3" @@ -4275,30 +4254,17 @@ __metadata: languageName: node linkType: hard -"@firebase/app-check@npm:0.8.8": - version: 0.8.8 - resolution: "@firebase/app-check@npm:0.8.8" +"@firebase/app-check@npm:0.8.11": + version: 0.8.11 + resolution: "@firebase/app-check@npm:0.8.11" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/a3676f2143c8e438d7e8ac11bb163af30880f6ce6acc5cc54cfcc214b8efd5dabce14c040626f8a64a3967db144b99834f1108c2076a0eae8a6baf864b5a3d77 - languageName: node - linkType: hard - -"@firebase/app-compat@npm:0.2.41": - version: 0.2.41 - resolution: "@firebase/app-compat@npm:0.2.41" - dependencies: - "@firebase/app": "npm:0.10.11" - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" - tslib: "npm:^2.1.0" - checksum: 10/67e4b0572a3c24c4acc13e2c3b55a4fc778d286bae10f1df684a142c9790b4f131519fe84087341884bd67b04b822c3f7092b9748dfa3b52086b6f82ca8a1001 + checksum: 10/e3f6a3940037c17a2faaf97a700d33b2c7821e07460e0a854d9f542acdcb589514bb4699df3adba1fb1d17ee75261006939b8ef60ec44bbe6c8c827b0797aa77 languageName: node linkType: hard @@ -4315,10 +4281,16 @@ __metadata: languageName: node linkType: hard -"@firebase/app-types@npm:0.9.2": - version: 0.9.2 - resolution: "@firebase/app-types@npm:0.9.2" - checksum: 10/566b3714a4d7e8180514258e4b1549bf5b28ae0383b4ff53d3532a45e114048afdd27c1fef8688d871dd9e5ad5307e749776e23f094122655ac6b0fb550eb11a +"@firebase/app-compat@npm:0.2.50": + version: 0.2.50 + resolution: "@firebase/app-compat@npm:0.2.50" + dependencies: + "@firebase/app": "npm:0.11.1" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" + checksum: 10/a46ce03e55d7378939192801ec0e72459ed1ee0cb4c2ccab4a896aec1160b18e07227905466d163dc73d4514eb60eb481492c1ea030563b805af2e6f4f8aa34b languageName: node linkType: hard @@ -4329,19 +4301,6 @@ __metadata: languageName: node linkType: hard -"@firebase/app@npm:0.10.11": - version: 0.10.11 - resolution: "@firebase/app@npm:0.10.11" - dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" - idb: "npm:7.1.1" - tslib: "npm:^2.1.0" - checksum: 10/529d9e59b39e96cd97a8402e1cee508dbbb962aa1805345dc902ecbfe61709bb46ab3b821cd3b50b3d2e3e9f898515eb91cded030492e376550a97518cbcdb70 - languageName: node - linkType: hard - "@firebase/app@npm:0.10.17": version: 0.10.17 resolution: "@firebase/app@npm:0.10.17" @@ -4355,19 +4314,16 @@ __metadata: languageName: node linkType: hard -"@firebase/auth-compat@npm:0.5.14": - version: 0.5.14 - resolution: "@firebase/auth-compat@npm:0.5.14" +"@firebase/app@npm:0.11.1": + version: 0.11.1 + resolution: "@firebase/app@npm:0.11.1" dependencies: - "@firebase/auth": "npm:1.7.9" - "@firebase/auth-types": "npm:0.12.2" - "@firebase/component": "npm:0.6.9" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" + idb: "npm:7.1.1" tslib: "npm:^2.1.0" - undici: "npm:6.19.7" - peerDependencies: - "@firebase/app-compat": 0.x - checksum: 10/85d5259e7b04b14b5d02dc1fb19b015d742c594c14138f33f13146ed9f6caa7ed9d19d65bb99aaca57e70ffd2a491e520d8638eadefbd00f839d37ef972cbbda + checksum: 10/bbb3046d94dc7b0de005ec6b02b1452cf6dc4c81fababdc2347353f52d64b5fbc0aa88cdc25c83200f56b2fd4df0524a9de8954c5f38ad368aeae26e344188d5 languageName: node linkType: hard @@ -4386,10 +4342,18 @@ __metadata: languageName: node linkType: hard -"@firebase/auth-interop-types@npm:0.2.3": - version: 0.2.3 - resolution: "@firebase/auth-interop-types@npm:0.2.3" - checksum: 10/e55b8ded6bd1a5e6a2845c9c7ed520bb9a8a76e4ddf90249bf685986ac7b1fb079be2fa4edcb6a3aa81d1d56870a470eadcd5a8f20b797dccd803d72ed4c80aa +"@firebase/auth-compat@npm:0.5.18": + version: 0.5.18 + resolution: "@firebase/auth-compat@npm:0.5.18" + dependencies: + "@firebase/auth": "npm:1.9.0" + "@firebase/auth-types": "npm:0.13.0" + "@firebase/component": "npm:0.6.12" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/6a3fca333bc1ae2e9c44728aae8a8318cb370d76a744169d8a8c0d1c82f54a5db6f7d80a699a256d5f2e78732bb1bc1bc874f58a2c8382b3c61b9cce1b3b00a1 languageName: node linkType: hard @@ -4400,52 +4364,51 @@ __metadata: languageName: node linkType: hard -"@firebase/auth-types@npm:0.12.2": - version: 0.12.2 - resolution: "@firebase/auth-types@npm:0.12.2" +"@firebase/auth-types@npm:0.12.3": + version: 0.12.3 + resolution: "@firebase/auth-types@npm:0.12.3" peerDependencies: "@firebase/app-types": 0.x "@firebase/util": 1.x - checksum: 10/f55449381de8e2a24ffaf19f12b5c4a093c8323034253ea7a5f7afc946327d20b09f32a483c12960862a1c4814645ea80bc4343f0a9f22db5dc048ca82773132 + checksum: 10/5eda88380e9b33a6c91b0f8dd6a581895c2770aa5b46b1928a006a74d35c6a310bfe737141ff013764a4e02815efa530f1576d674f09f905fbe3b14050dc7fce languageName: node linkType: hard -"@firebase/auth-types@npm:0.12.3": - version: 0.12.3 - resolution: "@firebase/auth-types@npm:0.12.3" +"@firebase/auth-types@npm:0.13.0": + version: 0.13.0 + resolution: "@firebase/auth-types@npm:0.13.0" peerDependencies: "@firebase/app-types": 0.x "@firebase/util": 1.x - checksum: 10/5eda88380e9b33a6c91b0f8dd6a581895c2770aa5b46b1928a006a74d35c6a310bfe737141ff013764a4e02815efa530f1576d674f09f905fbe3b14050dc7fce + checksum: 10/57d8e4b80e58d3a9e453b4676a29e3b0e548ca9f4c2b465137007bb5753e3bde2f6537f0be9779df17859ebc4e6b1b59c88215cdd59a32106391cf117072372d languageName: node linkType: hard -"@firebase/auth@npm:1.7.9": - version: 1.7.9 - resolution: "@firebase/auth@npm:1.7.9" +"@firebase/auth@npm:1.8.1": + version: 1.8.1 + resolution: "@firebase/auth@npm:1.8.1" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.11" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.2" tslib: "npm:^2.1.0" - undici: "npm:6.19.7" peerDependencies: "@firebase/app": 0.x "@react-native-async-storage/async-storage": ^1.18.1 peerDependenciesMeta: "@react-native-async-storage/async-storage": optional: true - checksum: 10/010013ec339c9ef7b4d9278c6cacfd8e2eb3282f27a3e4e89c42a5968955976a26277421f34fda3e9400409a22a61f632bcc03e713b3f39d71e4777bc003165d + checksum: 10/9201278960f5bdbd8c83406a9cd525a0a0c4535ba531bbb3601acf9a9508d0fe73284e689231d01f4053c8b1adcfba2c0a9f6c3e0fc3d8c6b67756e415c6c49a languageName: node linkType: hard -"@firebase/auth@npm:1.8.1": - version: 1.8.1 - resolution: "@firebase/auth@npm:1.8.1" +"@firebase/auth@npm:1.9.0": + version: 1.9.0 + resolution: "@firebase/auth@npm:1.9.0" dependencies: - "@firebase/component": "npm:0.6.11" + "@firebase/component": "npm:0.6.12" "@firebase/logger": "npm:0.4.4" - "@firebase/util": "npm:1.10.2" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x @@ -4453,7 +4416,7 @@ __metadata: peerDependenciesMeta: "@react-native-async-storage/async-storage": optional: true - checksum: 10/9201278960f5bdbd8c83406a9cd525a0a0c4535ba531bbb3601acf9a9508d0fe73284e689231d01f4053c8b1adcfba2c0a9f6c3e0fc3d8c6b67756e415c6c49a + checksum: 10/868cbe90ca414393f913e27c09b9d0c33a50fe0dad9bc3ce5e4961fcd6548ffd38d6de6e1c93ca310f34539b8d1284921784048f20b939df8f1ffd800c2986ae languageName: node linkType: hard @@ -4467,13 +4430,13 @@ __metadata: languageName: node linkType: hard -"@firebase/component@npm:0.6.9": - version: 0.6.9 - resolution: "@firebase/component@npm:0.6.9" +"@firebase/component@npm:0.6.12": + version: 0.6.12 + resolution: "@firebase/component@npm:0.6.12" dependencies: - "@firebase/util": "npm:1.10.0" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" - checksum: 10/76c865d640e4b24a0e50876ecdc0e1199df38af562131a937b5a4bac924d61b6933339afb7906881dca509f38f3b0c511cd6b5008e061424c61b20876de9531e + checksum: 10/4dfd201d3709ef5eed477e13d399611a78a186ca8911846e24361f9848c3b4eecc14c295a8f78ec40c88816329fde0ba6cc30dce9a444fa43a619b7ea744f0dc languageName: node linkType: hard @@ -4492,17 +4455,18 @@ __metadata: languageName: node linkType: hard -"@firebase/database-compat@npm:1.0.8": - version: 1.0.8 - resolution: "@firebase/database-compat@npm:1.0.8" +"@firebase/data-connect@npm:0.3.0": + version: 0.3.0 + resolution: "@firebase/data-connect@npm:0.3.0" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/database": "npm:1.0.8" - "@firebase/database-types": "npm:1.0.5" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" + "@firebase/auth-interop-types": "npm:0.2.4" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" - checksum: 10/28389efcc87da77b822cb27c31707824fe98e7b0a3bf9cbf2b0c0fccd9edd72e2681a9467b76b120281464dbfc814852ebca63d99a385a9cb68fb55c7b334105 + peerDependencies: + "@firebase/app": 0.x + checksum: 10/13edd416331103f331fda28e26be83b17989b29ccdd1c9b953eb1d376eb5b9760c5e4b9576d024f200ba9d75676074a4b19c7b41a0e5539d561709a79e28ed72 languageName: node linkType: hard @@ -4520,13 +4484,17 @@ __metadata: languageName: node linkType: hard -"@firebase/database-types@npm:1.0.5": - version: 1.0.5 - resolution: "@firebase/database-types@npm:1.0.5" +"@firebase/database-compat@npm:2.0.3": + version: 2.0.3 + resolution: "@firebase/database-compat@npm:2.0.3" dependencies: - "@firebase/app-types": "npm:0.9.2" - "@firebase/util": "npm:1.10.0" - checksum: 10/bdf667da0369dce8623987fc01cad8db09cfe1895130f69ab581d34a0ee043ca6113c32457629147ae1441a934d985ede9d7cbe104ac346de6d0c21629903a8b + "@firebase/component": "npm:0.6.12" + "@firebase/database": "npm:1.0.12" + "@firebase/database-types": "npm:1.0.8" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" + checksum: 10/5f91dbdc9ef12994a57c49ac17242fbfa072566c9e149698fb84b1f482ca41032bfce344a27c223e31826a1fcec1f24ddd1de2571c86fd6edd4b44a23742b289 languageName: node linkType: hard @@ -4540,6 +4508,16 @@ __metadata: languageName: node linkType: hard +"@firebase/database-types@npm:1.0.8": + version: 1.0.8 + resolution: "@firebase/database-types@npm:1.0.8" + dependencies: + "@firebase/app-types": "npm:0.9.3" + "@firebase/util": "npm:1.10.3" + checksum: 10/1b5483de082ff8d7551b21f087ba2f237bcd38ca9e3f48b1377b96213718e0a206437fe31a4e055c1b90d05a1f38f89fe1c92d50d907ca06c8727c73fc521c40 + languageName: node + linkType: hard + "@firebase/database@npm:1.0.10": version: 1.0.10 resolution: "@firebase/database@npm:1.0.10" @@ -4555,33 +4533,18 @@ __metadata: languageName: node linkType: hard -"@firebase/database@npm:1.0.8": - version: 1.0.8 - resolution: "@firebase/database@npm:1.0.8" +"@firebase/database@npm:1.0.12": + version: 1.0.12 + resolution: "@firebase/database@npm:1.0.12" dependencies: - "@firebase/app-check-interop-types": "npm:0.3.2" - "@firebase/auth-interop-types": "npm:0.2.3" - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" + "@firebase/app-check-interop-types": "npm:0.3.3" + "@firebase/auth-interop-types": "npm:0.2.4" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" faye-websocket: "npm:0.11.4" tslib: "npm:^2.1.0" - checksum: 10/adb199a6ad7866b418e8b319cc505e108bfc8200b5406f21857706df0849d4e5982a1b0e44e07001821edebef73c4dfffc7f96fb77a2cff10bb9ac26f17d40c3 - languageName: node - linkType: hard - -"@firebase/firestore-compat@npm:0.3.37": - version: 0.3.37 - resolution: "@firebase/firestore-compat@npm:0.3.37" - dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/firestore": "npm:4.7.2" - "@firebase/firestore-types": "npm:3.0.2" - "@firebase/util": "npm:1.10.0" - tslib: "npm:^2.1.0" - peerDependencies: - "@firebase/app-compat": 0.x - checksum: 10/c152ba401f0d786699b25e1101d77351b7a6503f1a1f774efa7fecacc66aec58aca58a7b54e3f8587fcb45ffa3772d5e123ae79ddd90d0a87f2042ac34880d8a + checksum: 10/82ef169190b5dab5760a2802b9f88d59435792892a3727c17b52615803d2f8b62072605d270bc097513af7cd7c24b387bf777d0cccbbf4303c13c4f5bef6d610 languageName: node linkType: hard @@ -4600,13 +4563,18 @@ __metadata: languageName: node linkType: hard -"@firebase/firestore-types@npm:3.0.2": - version: 3.0.2 - resolution: "@firebase/firestore-types@npm:3.0.2" +"@firebase/firestore-compat@npm:0.3.43": + version: 0.3.43 + resolution: "@firebase/firestore-compat@npm:0.3.43" + dependencies: + "@firebase/component": "npm:0.6.12" + "@firebase/firestore": "npm:4.7.8" + "@firebase/firestore-types": "npm:3.0.3" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" peerDependencies: - "@firebase/app-types": 0.x - "@firebase/util": 1.x - checksum: 10/81e91f836a026ecb70937407ca8699add7abb5b050d8815620cde97c3eec3f78f7dfbb366225758909f0df31d9f21e98a84ba62701bd27ee38b2609898c11acd + "@firebase/app-compat": 0.x + checksum: 10/9d542220b01603d8616604a7541fae4237ea3bf7b6734d60006ca457b7ef8727f9c8ef02c1fcf909771a28265add2f6ad3ce9e2d41a4da7fa617e252178eebc1 languageName: node linkType: hard @@ -4620,24 +4588,6 @@ __metadata: languageName: node linkType: hard -"@firebase/firestore@npm:4.7.2": - version: 4.7.2 - resolution: "@firebase/firestore@npm:4.7.2" - dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" - "@firebase/webchannel-wrapper": "npm:1.0.1" - "@grpc/grpc-js": "npm:~1.9.0" - "@grpc/proto-loader": "npm:^0.7.8" - tslib: "npm:^2.1.0" - undici: "npm:6.19.7" - peerDependencies: - "@firebase/app": 0.x - checksum: 10/066a125760bc2163bbc9c6fcde8b3f67da97791f8ce6f5ffa8ff3c40567aff97b2fe02020c3403857f104f051e4d6452aee60fe75ed5e408e467c611c397b4bb - languageName: node - linkType: hard - "@firebase/firestore@npm:4.7.5": version: 4.7.5 resolution: "@firebase/firestore@npm:4.7.5" @@ -4655,18 +4605,20 @@ __metadata: languageName: node linkType: hard -"@firebase/functions-compat@npm:0.3.14": - version: 0.3.14 - resolution: "@firebase/functions-compat@npm:0.3.14" +"@firebase/firestore@npm:4.7.8": + version: 4.7.8 + resolution: "@firebase/firestore@npm:4.7.8" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/functions": "npm:0.11.8" - "@firebase/functions-types": "npm:0.6.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" + "@firebase/webchannel-wrapper": "npm:1.0.3" + "@grpc/grpc-js": "npm:~1.9.0" + "@grpc/proto-loader": "npm:^0.7.8" tslib: "npm:^2.1.0" peerDependencies: - "@firebase/app-compat": 0.x - checksum: 10/a8d6cbcdc646d78adecfcdc1f8fa14a5d9af2394dd69cac00c6826106b923e01d246c67fb7e09025ca7cfb876f8d5df97240cc056c64ccee8899ca5f17178a6c + "@firebase/app": 0.x + checksum: 10/2965b1fa8e2798c6a3fbbf0f0cd40375410dd0ececd623b7efd4446262d4b62d370dd89a159596ea970a055506fd24f0bb636e6381da0efe52d1a5ecbcef8514 languageName: node linkType: hard @@ -4685,10 +4637,18 @@ __metadata: languageName: node linkType: hard -"@firebase/functions-types@npm:0.6.2": - version: 0.6.2 - resolution: "@firebase/functions-types@npm:0.6.2" - checksum: 10/5b8733f9d4bd85a617d35dd10ce296d9ec0490494e584697c4eda8098ff1e865607d7880b84194e86c35d438bbcd714977c111180502d0d1b6b2da1cde1b37ca +"@firebase/functions-compat@npm:0.3.19": + version: 0.3.19 + resolution: "@firebase/functions-compat@npm:0.3.19" + dependencies: + "@firebase/component": "npm:0.6.12" + "@firebase/functions": "npm:0.12.2" + "@firebase/functions-types": "npm:0.6.3" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/a3918d32e4a6d9fac48dc7d1b8c0a0b4324caee0b2e5841c17983b0ea597d3367a855fe9d6f30ede232ee6fa6371ec85114cc9cfb240ca61304acb5f6ad779ff languageName: node linkType: hard @@ -4699,36 +4659,35 @@ __metadata: languageName: node linkType: hard -"@firebase/functions@npm:0.11.8": - version: 0.11.8 - resolution: "@firebase/functions@npm:0.11.8" +"@firebase/functions@npm:0.12.0": + version: 0.12.0 + resolution: "@firebase/functions@npm:0.12.0" dependencies: - "@firebase/app-check-interop-types": "npm:0.3.2" - "@firebase/auth-interop-types": "npm:0.2.3" - "@firebase/component": "npm:0.6.9" - "@firebase/messaging-interop-types": "npm:0.2.2" - "@firebase/util": "npm:1.10.0" + "@firebase/app-check-interop-types": "npm:0.3.3" + "@firebase/auth-interop-types": "npm:0.2.4" + "@firebase/component": "npm:0.6.11" + "@firebase/messaging-interop-types": "npm:0.2.3" + "@firebase/util": "npm:1.10.2" tslib: "npm:^2.1.0" - undici: "npm:6.19.7" peerDependencies: "@firebase/app": 0.x - checksum: 10/44f3e42df189f3f3cb3c366b38e93a0ffdfaa1a7b3f6dba624bcd9a7cda3d3271df66f2769b7cbe7e1e5ff01bf6ab3bef6c1e1e15c6646e34514d1e2ebb60555 + checksum: 10/1d9c2a17d3c4be52ca2678a1ab36be5e9bab8a4a40660996312e22cdcde0c4d25f9830c4f00a8896d378f23cd78907d824805293d3a9a14f8f81557c3903cacd languageName: node linkType: hard -"@firebase/functions@npm:0.12.0": - version: 0.12.0 - resolution: "@firebase/functions@npm:0.12.0" +"@firebase/functions@npm:0.12.2": + version: 0.12.2 + resolution: "@firebase/functions@npm:0.12.2" dependencies: "@firebase/app-check-interop-types": "npm:0.3.3" "@firebase/auth-interop-types": "npm:0.2.4" - "@firebase/component": "npm:0.6.11" + "@firebase/component": "npm:0.6.12" "@firebase/messaging-interop-types": "npm:0.2.3" - "@firebase/util": "npm:1.10.2" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/1d9c2a17d3c4be52ca2678a1ab36be5e9bab8a4a40660996312e22cdcde0c4d25f9830c4f00a8896d378f23cd78907d824805293d3a9a14f8f81557c3903cacd + checksum: 10/82c664dc1ea3a4f68b9bebd52f54b4c223224a5daa9a166a2d82514fc0e436fd06a1c506c1fee4782a1a693442bab68fb4bb7f000ab1861ce456a0ff4df442d9 languageName: node linkType: hard @@ -4747,27 +4706,18 @@ __metadata: languageName: node linkType: hard -"@firebase/installations-compat@npm:0.2.9": - version: 0.2.9 - resolution: "@firebase/installations-compat@npm:0.2.9" +"@firebase/installations-compat@npm:0.2.12": + version: 0.2.12 + resolution: "@firebase/installations-compat@npm:0.2.12" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/installations": "npm:0.6.9" - "@firebase/installations-types": "npm:0.5.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/installations": "npm:0.6.12" + "@firebase/installations-types": "npm:0.5.3" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/919e1a4f4b63f5fe757a3c9cefb4a36cbab92deb4a6e15f249c94d6e80d1c6d37e5e384a460af8c17fc88e3091594bf43d036c88b704516c279b5ab8401977e1 - languageName: node - linkType: hard - -"@firebase/installations-types@npm:0.5.2": - version: 0.5.2 - resolution: "@firebase/installations-types@npm:0.5.2" - peerDependencies: - "@firebase/app-types": 0.x - checksum: 10/2e795280c299d644b8c8e3fdfa5c6f20cb367dd3b7df32317211f84393fa169b33dee0cbed28de407f3b22dc8f1fb2f7a11ae5a373f8082cc570ef61ef6b91ba + checksum: 10/ffd5e08e65e7067c06a4eb5601a09b017fce006b38108c10c412df8144e79bd08b4347998740425f312288b5a0839818e634486875857df5518c05a737c46ad8 languageName: node linkType: hard @@ -4794,26 +4744,17 @@ __metadata: languageName: node linkType: hard -"@firebase/installations@npm:0.6.9": - version: 0.6.9 - resolution: "@firebase/installations@npm:0.6.9" +"@firebase/installations@npm:0.6.12": + version: 0.6.12 + resolution: "@firebase/installations@npm:0.6.12" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/util": "npm:1.10.3" idb: "npm:7.1.1" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/349c8b7e877b002fb29f274f4d239fbca4c2c266ccb66ecfb5f1762f973a7fe1be99cc3346184d1230e6e35feb2b6f9e8b7169479fa0018b53e4a83837848619 - languageName: node - linkType: hard - -"@firebase/logger@npm:0.4.2": - version: 0.4.2 - resolution: "@firebase/logger@npm:0.4.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10/961b4605220c0a56c5f3ccf4e6049e44c27303c1ca998c6fa1d19de785c76d93e3b1a3da455e9f40655711345d8d779912366e4f369d93eda8d08c407cc5b140 + checksum: 10/093295de087b4c9287d06243eb19814e25674047aafa4f5db9a222d8e64283d0362f37edf8cfbe882a80eac1d2d9fc52b821fbb01151ac925f023765251dd1de languageName: node linkType: hard @@ -4826,20 +4767,6 @@ __metadata: languageName: node linkType: hard -"@firebase/messaging-compat@npm:0.2.11": - version: 0.2.11 - resolution: "@firebase/messaging-compat@npm:0.2.11" - dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/messaging": "npm:0.12.11" - "@firebase/util": "npm:1.10.0" - tslib: "npm:^2.1.0" - peerDependencies: - "@firebase/app-compat": 0.x - checksum: 10/8ace6d65adcf891b272875b7b3f43978a15644b23f7ee796346f02eb50007c20c99719f4991772911005697613bf122167ca150d8245d0fccb2b959472b4a625 - languageName: node - linkType: hard - "@firebase/messaging-compat@npm:0.2.15": version: 0.2.15 resolution: "@firebase/messaging-compat@npm:0.2.15" @@ -4854,10 +4781,17 @@ __metadata: languageName: node linkType: hard -"@firebase/messaging-interop-types@npm:0.2.2": - version: 0.2.2 - resolution: "@firebase/messaging-interop-types@npm:0.2.2" - checksum: 10/547f8ebf2c5a8dcbc484991b97d76bd3dc3eb4bd9d4e6ea2ffc652097c7065d92dc68d389ddb19fba41e0ce3b5f4cd757ed22f96b4744801149b0f8dbf323af7 +"@firebase/messaging-compat@npm:0.2.16": + version: 0.2.16 + resolution: "@firebase/messaging-compat@npm:0.2.16" + dependencies: + "@firebase/component": "npm:0.6.12" + "@firebase/messaging": "npm:0.12.16" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/1887599e3f7d7db5a70f923118eda769130aa134c6a6ba0a9f599c541d78b2e00b9548fc51c12f430c60a6e902221fe951a4beeddd674f1c042ffa32d1593dc9 languageName: node linkType: hard @@ -4868,35 +4802,35 @@ __metadata: languageName: node linkType: hard -"@firebase/messaging@npm:0.12.11": - version: 0.12.11 - resolution: "@firebase/messaging@npm:0.12.11" +"@firebase/messaging@npm:0.12.15": + version: 0.12.15 + resolution: "@firebase/messaging@npm:0.12.15" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/installations": "npm:0.6.9" - "@firebase/messaging-interop-types": "npm:0.2.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.11" + "@firebase/installations": "npm:0.6.11" + "@firebase/messaging-interop-types": "npm:0.2.3" + "@firebase/util": "npm:1.10.2" idb: "npm:7.1.1" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/1de21d56c74996e99151a902e0f1ff0825d47ebff044104483a838ff5cb4883433b2f541616b033255e4fd2780b29f71982d8832edf4987c101df97ed508828a + checksum: 10/29daf4f8d970b3893b234c4a38dff22233ac8d541c940de737803e5ff5a1da84271b96f5ab142fd8c7ae0afb95df2d4939c294dccc99ac7ebf9343827097fe0a languageName: node linkType: hard -"@firebase/messaging@npm:0.12.15": - version: 0.12.15 - resolution: "@firebase/messaging@npm:0.12.15" +"@firebase/messaging@npm:0.12.16": + version: 0.12.16 + resolution: "@firebase/messaging@npm:0.12.16" dependencies: - "@firebase/component": "npm:0.6.11" - "@firebase/installations": "npm:0.6.11" + "@firebase/component": "npm:0.6.12" + "@firebase/installations": "npm:0.6.12" "@firebase/messaging-interop-types": "npm:0.2.3" - "@firebase/util": "npm:1.10.2" + "@firebase/util": "npm:1.10.3" idb: "npm:7.1.1" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/29daf4f8d970b3893b234c4a38dff22233ac8d541c940de737803e5ff5a1da84271b96f5ab142fd8c7ae0afb95df2d4939c294dccc99ac7ebf9343827097fe0a + checksum: 10/e237f35c4b179a521a6a37255fa719784ec73f30b76d179c059f21bf1e7ee6f907299c137a7b55496134dc5c3578d365c62b2e44988323edd3d96e5468f016d6 languageName: node linkType: hard @@ -4916,26 +4850,19 @@ __metadata: languageName: node linkType: hard -"@firebase/performance-compat@npm:0.2.9": - version: 0.2.9 - resolution: "@firebase/performance-compat@npm:0.2.9" +"@firebase/performance-compat@npm:0.2.13": + version: 0.2.13 + resolution: "@firebase/performance-compat@npm:0.2.13" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/performance": "npm:0.6.9" - "@firebase/performance-types": "npm:0.2.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/performance": "npm:0.7.0" + "@firebase/performance-types": "npm:0.2.3" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/bc4e8b0208c9bc603518e1388713ec80658ee109c6af80d429479447ccb85e8e831269383233c379ed66bf37469d13f5c234074d0c0c9e7e69e909be5fdeca4f - languageName: node - linkType: hard - -"@firebase/performance-types@npm:0.2.2": - version: 0.2.2 - resolution: "@firebase/performance-types@npm:0.2.2" - checksum: 10/d25ae06cb75ab6b44ffacf7affadc1f651881f283e58381c444eb63b62dfb74c33c544ab89843518ec1d15367ba7c4343b4d6b22de1f1df35126a1283baa578d + checksum: 10/d87da72d45134fc3708f0faf26b9cd8b74e609f67ebea906a5fc3a23f78c65f1559409be5f6c6c06dbae6cd12eb2b8ff6b685c4a02effa76e37d2b0e913847ac languageName: node linkType: hard @@ -4961,18 +4888,19 @@ __metadata: languageName: node linkType: hard -"@firebase/performance@npm:0.6.9": - version: 0.6.9 - resolution: "@firebase/performance@npm:0.6.9" +"@firebase/performance@npm:0.7.0": + version: 0.7.0 + resolution: "@firebase/performance@npm:0.7.0" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/installations": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/installations": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" + web-vitals: "npm:^4.2.4" peerDependencies: "@firebase/app": 0.x - checksum: 10/d682d0b1e342ed3eda1a5ddab39c8ddac33afc9edb2c7335a2f9a28eb8c268b975bbf450a3bad5443138edebaf2aa731dca0b774bcf3211a6dc215b35d86d849 + checksum: 10/5428160baccd7c8d8248e1a78afcd75882842f5fbe8aecccfc3dc6b3cab0698b45a3628566c54a137a09c094ae610a5a84da9f74801ecab548eb10a494e72f76 languageName: node linkType: hard @@ -4992,26 +4920,19 @@ __metadata: languageName: node linkType: hard -"@firebase/remote-config-compat@npm:0.2.9": - version: 0.2.9 - resolution: "@firebase/remote-config-compat@npm:0.2.9" +"@firebase/remote-config-compat@npm:0.2.12": + version: 0.2.12 + resolution: "@firebase/remote-config-compat@npm:0.2.12" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/remote-config": "npm:0.4.9" - "@firebase/remote-config-types": "npm:0.3.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/remote-config": "npm:0.5.0" + "@firebase/remote-config-types": "npm:0.4.0" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app-compat": 0.x - checksum: 10/a6db7509512d8d22b7ddf1127c741715e379e04e5b3246372bb0302d7c84afb421a94550adebecddcce5def115d61729a9580940dce6e65f8d77f9af30f69fe1 - languageName: node - linkType: hard - -"@firebase/remote-config-types@npm:0.3.2": - version: 0.3.2 - resolution: "@firebase/remote-config-types@npm:0.3.2" - checksum: 10/6c91599c653825708aba9fe9e4562997f108c3e4f3eaf5d188f31c859a6ad013414aa7a213b6b021b68049dd0dd57158546dbc9fb64384652274ef7f57ce7d7d + checksum: 10/931c4739c2b11b2719076630f09f5aa18f9edf8e89cf35c9b9a3a8cc5afc497c86e68cca165e1416afcb0b8040ed04363c676d31118fdcf4bf3823ef9172785c languageName: node linkType: hard @@ -5022,6 +4943,13 @@ __metadata: languageName: node linkType: hard +"@firebase/remote-config-types@npm:0.4.0": + version: 0.4.0 + resolution: "@firebase/remote-config-types@npm:0.4.0" + checksum: 10/67de8c448412974bdbdc10b6bca90d957fa81f967553ff9a4aee316d374f9ebb3a24fa2541af639c1a1ece79070fab0ab64c925bcf6bb807e212cba3297e5ddf + languageName: node + linkType: hard + "@firebase/remote-config@npm:0.4.11": version: 0.4.11 resolution: "@firebase/remote-config@npm:0.4.11" @@ -5037,33 +4965,18 @@ __metadata: languageName: node linkType: hard -"@firebase/remote-config@npm:0.4.9": - version: 0.4.9 - resolution: "@firebase/remote-config@npm:0.4.9" +"@firebase/remote-config@npm:0.5.0": + version: 0.5.0 + resolution: "@firebase/remote-config@npm:0.5.0" dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/installations": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" + "@firebase/component": "npm:0.6.12" + "@firebase/installations": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" peerDependencies: "@firebase/app": 0.x - checksum: 10/f14189f38c8cf75db16bf8b85dd004486b1dd8242f62d697c716fa85cd32928aed549ccea8c632a528870a424fc7f04f1132a14b3b099276cd7696c78e644b28 - languageName: node - linkType: hard - -"@firebase/storage-compat@npm:0.3.12": - version: 0.3.12 - resolution: "@firebase/storage-compat@npm:0.3.12" - dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/storage": "npm:0.13.2" - "@firebase/storage-types": "npm:0.8.2" - "@firebase/util": "npm:1.10.0" - tslib: "npm:^2.1.0" - peerDependencies: - "@firebase/app-compat": 0.x - checksum: 10/4eea49a57f1d7537da697e5ff8b4e035ff1af69e416e7eab14485753c39c25eaa5a71bd2bafba0985ac6a7ce803f98f2f2f83c613c78c8f74bce286e3259b8ec + checksum: 10/58a6fad255d3975700e65d4d19ec3360703f920bcbd3bd2ff21f239367af7405bfec5fddf3f800fb405dd4e4456f73cdf0c5cbf624a9512d77293f7cf14b64d8 languageName: node linkType: hard @@ -5082,13 +4995,18 @@ __metadata: languageName: node linkType: hard -"@firebase/storage-types@npm:0.8.2": - version: 0.8.2 - resolution: "@firebase/storage-types@npm:0.8.2" +"@firebase/storage-compat@npm:0.3.16": + version: 0.3.16 + resolution: "@firebase/storage-compat@npm:0.3.16" + dependencies: + "@firebase/component": "npm:0.6.12" + "@firebase/storage": "npm:0.13.6" + "@firebase/storage-types": "npm:0.8.3" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" peerDependencies: - "@firebase/app-types": 0.x - "@firebase/util": 1.x - checksum: 10/e00716932370d2004dc9f7ef6d7e3aff72305b91569fa6ec15e8bc2ec784b03a150391e8be2c063234edbbfda7796da915d48e26ce2f1f7c5d3343acd39afd99 + "@firebase/app-compat": 0.x + checksum: 10/485aebbaf56875783eb02be29b6398e700d935a9cd6c7bbc92b27b230ad3021f75c75cdef64a7f3f90fa679207916362b0b202b2c10ad3631d256f8acd0bab8f languageName: node linkType: hard @@ -5102,20 +5020,6 @@ __metadata: languageName: node linkType: hard -"@firebase/storage@npm:0.13.2": - version: 0.13.2 - resolution: "@firebase/storage@npm:0.13.2" - dependencies: - "@firebase/component": "npm:0.6.9" - "@firebase/util": "npm:1.10.0" - tslib: "npm:^2.1.0" - undici: "npm:6.19.7" - peerDependencies: - "@firebase/app": 0.x - checksum: 10/d887f80cf95ef5daa80ffb2e6d564d25abb8a3e84099bee9730c95082597a12028bbf73bfe66fca2df3cdf04eaadea8e9d74ec0a826f946bc8f002293a9983ea - languageName: node - linkType: hard - "@firebase/storage@npm:0.13.4": version: 0.13.4 resolution: "@firebase/storage@npm:0.13.4" @@ -5129,12 +5033,16 @@ __metadata: languageName: node linkType: hard -"@firebase/util@npm:1.10.0": - version: 1.10.0 - resolution: "@firebase/util@npm:1.10.0" +"@firebase/storage@npm:0.13.6": + version: 0.13.6 + resolution: "@firebase/storage@npm:0.13.6" dependencies: + "@firebase/component": "npm:0.6.12" + "@firebase/util": "npm:1.10.3" tslib: "npm:^2.1.0" - checksum: 10/eb161f1c6294ff097f3c40236820e9e6e29cd6582e5e1254148157143272493580535ee2cb9e7c6055d3909b3ef39d8b64086895b071c665827acb66742b63eb + peerDependencies: + "@firebase/app": 0.x + checksum: 10/f82707d28a993cc92717f39cd39d0b77fe5be20a8f95ccf357c9a6e38b037b6d9210b08b9bad0eb64aac11dd7f49c76e167ba68439a237ec776996e59b3555a2 languageName: node linkType: hard @@ -5147,19 +5055,12 @@ __metadata: languageName: node linkType: hard -"@firebase/vertexai-preview@npm:0.0.4": - version: 0.0.4 - resolution: "@firebase/vertexai-preview@npm:0.0.4" +"@firebase/util@npm:1.10.3": + version: 1.10.3 + resolution: "@firebase/util@npm:1.10.3" dependencies: - "@firebase/app-check-interop-types": "npm:0.3.2" - "@firebase/component": "npm:0.6.9" - "@firebase/logger": "npm:0.4.2" - "@firebase/util": "npm:1.10.0" tslib: "npm:^2.1.0" - peerDependencies: - "@firebase/app": 0.x - "@firebase/app-types": 0.x - checksum: 10/8ec48d81f48aebdcc63b65d802c67bf36880f256e5c2f5f3b152dc91c8c0e924053fba2fac5218716612f8ee720b25d0822337a214f16f5b7e51ce0247dfc4e5 + checksum: 10/8e5e1664a09798348abfa0cd138157943f8ee9c6e3804e6cb1dcff004b351a03f14f4b2711338133bb89f7f824546664af2c2aa98e229becbc9294cdddeecc99 languageName: node linkType: hard @@ -5179,10 +5080,19 @@ __metadata: languageName: node linkType: hard -"@firebase/webchannel-wrapper@npm:1.0.1": - version: 1.0.1 - resolution: "@firebase/webchannel-wrapper@npm:1.0.1" - checksum: 10/22fc7e1e6dd36ab7c13f3a6c1ff51f4d405304424dc323cb146109e7a3ab3b592e2ddb29f53197ee5719a8448cdedb98d9e86a080f9365e389f8429b1c6555c2 +"@firebase/vertexai@npm:1.0.4": + version: 1.0.4 + resolution: "@firebase/vertexai@npm:1.0.4" + dependencies: + "@firebase/app-check-interop-types": "npm:0.3.3" + "@firebase/component": "npm:0.6.12" + "@firebase/logger": "npm:0.4.4" + "@firebase/util": "npm:1.10.3" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app": 0.x + "@firebase/app-types": 0.x + checksum: 10/13eb8ac4a94c6166ed138c7bab28dbab687341b094dfa0ca1d3c19d10002554a5eb4214efacb927480fb6691ee8d055bbf5ded79ef3441ee99422246fc347410 languageName: node linkType: hard @@ -7475,11 +7385,11 @@ __metadata: languageName: node linkType: hard -"@react-native-firebase/app@npm:^21.7.1": - version: 21.7.1 - resolution: "@react-native-firebase/app@npm:21.7.1" +"@react-native-firebase/app@npm:^21.8.0": + version: 21.10.1 + resolution: "@react-native-firebase/app@npm:21.10.1" dependencies: - firebase: "npm:10.13.2" + firebase: "npm:11.3.1" peerDependencies: expo: ">=47.0.0" react: "*" @@ -7487,20 +7397,20 @@ __metadata: peerDependenciesMeta: expo: optional: true - checksum: 10/33e35907cb564f6e4537eba963b202070c596c34a84027cccc343567d96f714f444cfa3e56a3f2c276c0aa9eef274a5b223f4308e70b8f0fc628db4362c24e3e + checksum: 10/3d1f1e578497b1bb3a9c101698d0ba40ed97e6bcde906e2bcf59286946420bc7b681fb5f7650adc4f8485c276ea9b4200930ddbd33e88854cf343db0c521e699 languageName: node linkType: hard -"@react-native-firebase/messaging@npm:^21.7.1": - version: 21.7.1 - resolution: "@react-native-firebase/messaging@npm:21.7.1" +"@react-native-firebase/messaging@npm:^21.8.0": + version: 21.10.1 + resolution: "@react-native-firebase/messaging@npm:21.10.1" peerDependencies: - "@react-native-firebase/app": 21.7.1 + "@react-native-firebase/app": 21.10.1 expo: ">=47.0.0" peerDependenciesMeta: expo: optional: true - checksum: 10/035a7e07fb2ff4d7f5b60eb7ba17c55b72f4c0438649085059e724e14bbf3d7a433701e7e22ea92450ca72c14b5b1d71ecdee2d49b6a5eae7cca4e3411b6138f + checksum: 10/5a6d3dcc9f2ad593b6819ba4d0d6a70550d944daecd8e2e8fe0a2a87692a2e43ad849ff01882ebaaba633392cdd1a12123fc69608e1b9e18c7e4661c18faa2df languageName: node linkType: hard @@ -7514,10 +7424,10 @@ __metadata: languageName: node linkType: hard -"@react-native/assets-registry@npm:0.76.3": - version: 0.76.3 - resolution: "@react-native/assets-registry@npm:0.76.3" - checksum: 10/e56bf32d5900933474ff77b5441a285d6494fa8762eefcb3d3d1ffac85bade6464437142eb156f9c7214bff1a4107ff2054fe96d6e33f74b9b26001868706678 +"@react-native/assets-registry@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/assets-registry@npm:0.76.7" + checksum: 10/9e8c49fb919a8a79ffff03bf5026b2dbf7eee10a435a09d55faef938734bfc71971fdce50577eaa5dd30017d8d786a23b0c1d6291be935c1a3df8a479af836ff languageName: node linkType: hard @@ -7530,6 +7440,15 @@ __metadata: languageName: node linkType: hard +"@react-native/babel-plugin-codegen@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/babel-plugin-codegen@npm:0.76.7" + dependencies: + "@react-native/codegen": "npm:0.76.7" + checksum: 10/32ee00092d2bc486b544070072a2add5a92dd10a814ffa9d5d1fa4c03b47c19dd83048c598f7f8e2274ebde4974f9afd67d6f4331518790344500c4fd70f56a7 + languageName: node + linkType: hard + "@react-native/babel-preset@npm:0.76.3, @react-native/babel-preset@npm:^0.76.2": version: 0.76.3 resolution: "@react-native/babel-preset@npm:0.76.3" @@ -7585,6 +7504,61 @@ __metadata: languageName: node linkType: hard +"@react-native/babel-preset@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/babel-preset@npm:0.76.7" + dependencies: + "@babel/core": "npm:^7.25.2" + "@babel/plugin-proposal-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" + "@babel/plugin-syntax-export-default-from": "npm:^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" + "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" + "@babel/plugin-transform-block-scoping": "npm:^7.25.0" + "@babel/plugin-transform-class-properties": "npm:^7.25.4" + "@babel/plugin-transform-classes": "npm:^7.25.4" + "@babel/plugin-transform-computed-properties": "npm:^7.24.7" + "@babel/plugin-transform-destructuring": "npm:^7.24.8" + "@babel/plugin-transform-flow-strip-types": "npm:^7.25.2" + "@babel/plugin-transform-for-of": "npm:^7.24.7" + "@babel/plugin-transform-function-name": "npm:^7.25.1" + "@babel/plugin-transform-literals": "npm:^7.25.2" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" + "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" + "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" + "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" + "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" + "@babel/plugin-transform-parameters": "npm:^7.24.7" + "@babel/plugin-transform-private-methods": "npm:^7.24.7" + "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" + "@babel/plugin-transform-react-display-name": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx": "npm:^7.25.2" + "@babel/plugin-transform-react-jsx-self": "npm:^7.24.7" + "@babel/plugin-transform-react-jsx-source": "npm:^7.24.7" + "@babel/plugin-transform-regenerator": "npm:^7.24.7" + "@babel/plugin-transform-runtime": "npm:^7.24.7" + "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" + "@babel/plugin-transform-spread": "npm:^7.24.7" + "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" + "@babel/plugin-transform-typescript": "npm:^7.25.2" + "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" + "@babel/template": "npm:^7.25.0" + "@react-native/babel-plugin-codegen": "npm:0.76.7" + babel-plugin-syntax-hermes-parser: "npm:^0.25.1" + babel-plugin-transform-flow-enums: "npm:^0.0.2" + react-refresh: "npm:^0.14.0" + peerDependencies: + "@babel/core": "*" + checksum: 10/ac60c54e1390fe34c696b3749225444e07a3112b71bc0d93cc735aa88fcc0a3a570289bbb6392f8fb441c67d38c046f3728008ca28c99a70da26cacba7915787 + languageName: node + linkType: hard + "@react-native/codegen@npm:0.76.3": version: 0.76.3 resolution: "@react-native/codegen@npm:0.76.3" @@ -7603,12 +7577,30 @@ __metadata: languageName: node linkType: hard -"@react-native/community-cli-plugin@npm:0.76.3": - version: 0.76.3 - resolution: "@react-native/community-cli-plugin@npm:0.76.3" +"@react-native/codegen@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/codegen@npm:0.76.7" dependencies: - "@react-native/dev-middleware": "npm:0.76.3" - "@react-native/metro-babel-transformer": "npm:0.76.3" + "@babel/parser": "npm:^7.25.3" + glob: "npm:^7.1.1" + hermes-parser: "npm:0.23.1" + invariant: "npm:^2.2.4" + jscodeshift: "npm:^0.14.0" + mkdirp: "npm:^0.5.1" + nullthrows: "npm:^1.1.1" + yargs: "npm:^17.6.2" + peerDependencies: + "@babel/preset-env": ^7.1.6 + checksum: 10/51d1ba3ca399d98afdd118c748397b418bfab607008965f772872b090f4bfe93633e51ad10ab2451e1767a4be1589d4a0037feed78628cfc8ac18ce5eeaa70b4 + languageName: node + linkType: hard + +"@react-native/community-cli-plugin@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/community-cli-plugin@npm:0.76.7" + dependencies: + "@react-native/dev-middleware": "npm:0.76.7" + "@react-native/metro-babel-transformer": "npm:0.76.7" chalk: "npm:^4.0.0" execa: "npm:^5.1.1" invariant: "npm:^2.2.4" @@ -7623,7 +7615,7 @@ __metadata: peerDependenciesMeta: "@react-native-community/cli-server-api": optional: true - checksum: 10/c415f26bdebb9c32230423879ca206a34343a644c307e4efc07d09a677b1394ef945b1b65d615c84913227cd03bf205315728c8cd9188e5cb51c5cb3b12b589d + checksum: 10/14f4132482ab8aac0cbb38ad1d8a663ae25a3a9b270e23b2f71ae674a8e6464c5ee678b9b82d89ec3dc9469d0f51e93fc3f0b732dab20b6d4c7d0932d32cc1c4 languageName: node linkType: hard @@ -7634,6 +7626,13 @@ __metadata: languageName: node linkType: hard +"@react-native/debugger-frontend@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/debugger-frontend@npm:0.76.7" + checksum: 10/8638336794cd18b95aefd70bd2cba52eeb529667400c42f18b421342862f7ddf5650d716d1311387689b6c7092b963ad7b80793a05bcd346ff273f7c652d6054 + languageName: node + linkType: hard + "@react-native/dev-middleware@npm:0.76.3": version: 0.76.3 resolution: "@react-native/dev-middleware@npm:0.76.3" @@ -7653,31 +7652,51 @@ __metadata: languageName: node linkType: hard -"@react-native/gradle-plugin@npm:0.76.3": - version: 0.76.3 - resolution: "@react-native/gradle-plugin@npm:0.76.3" - checksum: 10/331263f289418b5416f298328d8ef7b1cf7cc4fcb13707d941f666414c4628d59b2fba988ec4dc38b3a9dbcd2e937053e2259aec7dbdcac890017914ea24516d +"@react-native/dev-middleware@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/dev-middleware@npm:0.76.7" + dependencies: + "@isaacs/ttlcache": "npm:^1.4.1" + "@react-native/debugger-frontend": "npm:0.76.7" + chrome-launcher: "npm:^0.15.2" + chromium-edge-launcher: "npm:^0.2.0" + connect: "npm:^3.6.5" + debug: "npm:^2.2.0" + invariant: "npm:^2.2.4" + nullthrows: "npm:^1.1.1" + open: "npm:^7.0.3" + selfsigned: "npm:^2.4.1" + serve-static: "npm:^1.13.1" + ws: "npm:^6.2.3" + checksum: 10/dabb6c85af8bb17d0f4c1934baf459f0164d62d6268b72ba5a752c8168b68a3864f19f0892288eb1ac85aa30e88a69378c2534decdca25617e8b70b190933e9c languageName: node linkType: hard -"@react-native/js-polyfills@npm:0.76.3": - version: 0.76.3 - resolution: "@react-native/js-polyfills@npm:0.76.3" - checksum: 10/6bf86f6003a26fcee796a5e6642eea0c8b8f49016d5fb8c39a5c13397b7c3c26cc0e3c96c9dc40ac8983148a252904ec6fa2201fcbe6c47819bde891d2db0a77 +"@react-native/gradle-plugin@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/gradle-plugin@npm:0.76.7" + checksum: 10/3d0cbe77f7e61412a573a43d3bfd6a58de24cef06e44222b976be7360781f691d6ab35a98d9388da21f01d399832a81129e5eca7d421a0f5979eae51ca721584 languageName: node linkType: hard -"@react-native/metro-babel-transformer@npm:0.76.3": - version: 0.76.3 - resolution: "@react-native/metro-babel-transformer@npm:0.76.3" +"@react-native/js-polyfills@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/js-polyfills@npm:0.76.7" + checksum: 10/0c6318c087d0d8cc6e57e4de4d2f3d414659a56cdc3e8f8e21a26799042e2a5a9cd80febace7534f77ea0dd321ce673edf6017f4648b758c60d3ed19c37f1877 + languageName: node + linkType: hard + +"@react-native/metro-babel-transformer@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/metro-babel-transformer@npm:0.76.7" dependencies: "@babel/core": "npm:^7.25.2" - "@react-native/babel-preset": "npm:0.76.3" + "@react-native/babel-preset": "npm:0.76.7" hermes-parser: "npm:0.23.1" nullthrows: "npm:^1.1.1" peerDependencies: "@babel/core": "*" - checksum: 10/00cc1092042a530665ceadae6315441de8efb41448f2011fdfa7f586c8787346ec90c68f740980b56b1271cea2436497d82b78f5f1551b8df83eab032d05b251 + checksum: 10/7950a8e5503f034b6a0f591744386095b91f8b32537b161d10e767b10c6696eb6c9b9fed0357158a3c610aef71804381b562ad92be839f3bb779c3d4294cbbf5 languageName: node linkType: hard @@ -7702,6 +7721,13 @@ __metadata: languageName: node linkType: hard +"@react-native/normalize-colors@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/normalize-colors@npm:0.76.7" + checksum: 10/d2b3a6deea551beec532ad3faa4b6622bfcaa123212f53cf664f7e24304280f52eb000932c5c08dafb00d992ba342ba386bc00393b6b7b7021325f25f4ea3234 + languageName: node + linkType: hard + "@react-native/normalize-colors@npm:^0.74.1": version: 0.74.88 resolution: "@react-native/normalize-colors@npm:0.74.88" @@ -7709,9 +7735,9 @@ __metadata: languageName: node linkType: hard -"@react-native/virtualized-lists@npm:0.76.3": - version: 0.76.3 - resolution: "@react-native/virtualized-lists@npm:0.76.3" +"@react-native/virtualized-lists@npm:0.76.7": + version: 0.76.7 + resolution: "@react-native/virtualized-lists@npm:0.76.7" dependencies: invariant: "npm:^2.2.4" nullthrows: "npm:^1.1.1" @@ -7722,7 +7748,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/e3a43d669ff00379f93a03c8de4fddc88c0eafd30b1da9927c9613208e8b04bb1a41d1c955c29a2a7a0390ec318bc14aca6364541c9e29e0f803cedc8c0d2542 + checksum: 10/de535649bc12add81e8eb80f699d39d0027ae90bb5cfc259c4eb8e315b2ee7f5b59ccdb3b596731bc9875b100cba010d673067fa133c10d687279eba511a1b5b languageName: node linkType: hard @@ -8204,8 +8230,8 @@ __metadata: "@react-native-community/blur": "npm:^4.4.1" "@react-native-community/datetimepicker": "npm:8.2.0" "@react-native-community/slider": "npm:4.5.5" - "@react-native-firebase/app": "npm:^21.7.1" - "@react-native-firebase/messaging": "npm:^21.7.1" + "@react-native-firebase/app": "npm:^21.8.0" + "@react-native-firebase/messaging": "npm:^21.8.0" "@react-native-menu/menu": "npm:^1.1.6" "@react-native/babel-preset": "npm:^0.76.2" "@react-navigation/material-top-tabs": "npm:^7.1.0" @@ -8239,7 +8265,6 @@ __metadata: "@types/lodash": "npm:^4.17.13" "@types/node": "npm:^22.13.1" "@types/react": "npm:~18.3.12" - "@types/react-native-get-random-values": "npm:^1" babel-loader: "npm:^8.4.1" babel-plugin-react-native-web: "npm:^0.19.13" blo: "npm:^1.2.0" @@ -8273,18 +8298,17 @@ __metadata: moti: "npm:^0.29.0" react: "npm:18.3.1" react-dom: "npm:^18.3.1" - react-native: "npm:0.76.3" + react-native: "npm:0.76.7" react-native-collapsible-tab-view: "npm:^8.0.0" react-native-device-crypto: "npm:^0.1.7" react-native-device-info: "npm:^14.0.1" react-native-draggable-flatlist: "npm:^4.0.1" react-native-gesture-handler: "npm:~2.20.2" - react-native-get-random-values: "npm:^1.11.0" react-native-keychain: "npm:^9.2.2" react-native-mmkv: "npm:^3.1.0" react-native-pager-view: "npm:^6.5.1" react-native-progress: "npm:^5.0.1" - react-native-quick-crypto: "npm:^0.7.11" + react-native-quick-crypto: "npm:^0.7.12" react-native-reanimated: "npm:~3.16.7" react-native-safe-area-context: "npm:~5.1.0" react-native-screens: "npm:~4.5.0" @@ -13201,13 +13225,6 @@ __metadata: languageName: node linkType: hard -"@types/react-native-get-random-values@npm:^1": - version: 1.8.2 - resolution: "@types/react-native-get-random-values@npm:1.8.2" - checksum: 10/08f3f82efbb5b6d9acd8f7f55a2dac9f228886323ac3018a1bab46cd1b45f24809d194fd2a3fe02a9ec4196606325e5cfffde0b0057ae785208b658fdc83c821 - languageName: node - linkType: hard - "@types/react-transition-group@npm:^4.4.11, @types/react-transition-group@npm:^4.4.12": version: 4.4.12 resolution: "@types/react-transition-group@npm:4.4.12" @@ -20447,13 +20464,6 @@ __metadata: languageName: node linkType: hard -"fast-base64-decode@npm:^1.0.0": - version: 1.0.0 - resolution: "fast-base64-decode@npm:1.0.0" - checksum: 10/4c59eb1775a7f132333f296c5082476fdcc8f58d023c42ed6d378d2e2da4c328c7a71562f271181a725dd17cdaa8f2805346cc330cdbad3b8e4b9751508bd0a3 - languageName: node - linkType: hard - "fast-deep-equal@npm:^2.0.1": version: 2.0.1 resolution: "fast-deep-equal@npm:2.0.1" @@ -20815,38 +20825,39 @@ __metadata: languageName: node linkType: hard -"firebase@npm:10.13.2": - version: 10.13.2 - resolution: "firebase@npm:10.13.2" - dependencies: - "@firebase/analytics": "npm:0.10.8" - "@firebase/analytics-compat": "npm:0.2.14" - "@firebase/app": "npm:0.10.11" - "@firebase/app-check": "npm:0.8.8" - "@firebase/app-check-compat": "npm:0.3.15" - "@firebase/app-compat": "npm:0.2.41" - "@firebase/app-types": "npm:0.9.2" - "@firebase/auth": "npm:1.7.9" - "@firebase/auth-compat": "npm:0.5.14" - "@firebase/database": "npm:1.0.8" - "@firebase/database-compat": "npm:1.0.8" - "@firebase/firestore": "npm:4.7.2" - "@firebase/firestore-compat": "npm:0.3.37" - "@firebase/functions": "npm:0.11.8" - "@firebase/functions-compat": "npm:0.3.14" - "@firebase/installations": "npm:0.6.9" - "@firebase/installations-compat": "npm:0.2.9" - "@firebase/messaging": "npm:0.12.11" - "@firebase/messaging-compat": "npm:0.2.11" - "@firebase/performance": "npm:0.6.9" - "@firebase/performance-compat": "npm:0.2.9" - "@firebase/remote-config": "npm:0.4.9" - "@firebase/remote-config-compat": "npm:0.2.9" - "@firebase/storage": "npm:0.13.2" - "@firebase/storage-compat": "npm:0.3.12" - "@firebase/util": "npm:1.10.0" - "@firebase/vertexai-preview": "npm:0.0.4" - checksum: 10/c91a047b34f3e2a0b0f563a4b9b4aca4887c0052f82819384acc482c1523c83c108d47eb8a96aa2adce94e07d0f9eeabbd7fd4d2b4fde1e2706fb90a6aea2db1 +"firebase@npm:11.3.1": + version: 11.3.1 + resolution: "firebase@npm:11.3.1" + dependencies: + "@firebase/analytics": "npm:0.10.11" + "@firebase/analytics-compat": "npm:0.2.17" + "@firebase/app": "npm:0.11.1" + "@firebase/app-check": "npm:0.8.11" + "@firebase/app-check-compat": "npm:0.3.18" + "@firebase/app-compat": "npm:0.2.50" + "@firebase/app-types": "npm:0.9.3" + "@firebase/auth": "npm:1.9.0" + "@firebase/auth-compat": "npm:0.5.18" + "@firebase/data-connect": "npm:0.3.0" + "@firebase/database": "npm:1.0.12" + "@firebase/database-compat": "npm:2.0.3" + "@firebase/firestore": "npm:4.7.8" + "@firebase/firestore-compat": "npm:0.3.43" + "@firebase/functions": "npm:0.12.2" + "@firebase/functions-compat": "npm:0.3.19" + "@firebase/installations": "npm:0.6.12" + "@firebase/installations-compat": "npm:0.2.12" + "@firebase/messaging": "npm:0.12.16" + "@firebase/messaging-compat": "npm:0.2.16" + "@firebase/performance": "npm:0.7.0" + "@firebase/performance-compat": "npm:0.2.13" + "@firebase/remote-config": "npm:0.5.0" + "@firebase/remote-config-compat": "npm:0.2.12" + "@firebase/storage": "npm:0.13.6" + "@firebase/storage-compat": "npm:0.3.16" + "@firebase/util": "npm:1.10.3" + "@firebase/vertexai": "npm:1.0.4" + checksum: 10/281d2c3f1ea7b4de758879b0f80b8b737ef2908e519adf950c3a680162c107201c2b1ee2a86309c215add0b84043f27df76990fa86e52d4f3652c85cc4b30178 languageName: node linkType: hard @@ -28970,17 +28981,6 @@ __metadata: languageName: node linkType: hard -"react-native-get-random-values@npm:^1.11.0": - version: 1.11.0 - resolution: "react-native-get-random-values@npm:1.11.0" - dependencies: - fast-base64-decode: "npm:^1.0.0" - peerDependencies: - react-native: ">=0.56" - checksum: 10/eb04833ce2b66309d737f1447ab01ad32aca00a8d1cc4de7b4e751c23f4d9f8059a2643bb16e0b6f3e6b074a9e57e769bb2bf2afc50a912c5661d80a6ce4de34 - languageName: node - linkType: hard - "react-native-helmet-async@npm:2.0.4": version: 2.0.4 resolution: "react-native-helmet-async@npm:2.0.4" @@ -29075,16 +29075,16 @@ __metadata: languageName: node linkType: hard -"react-native-quick-crypto@npm:^0.7.11": - version: 0.7.11 - resolution: "react-native-quick-crypto@npm:0.7.11" +"react-native-quick-crypto@npm:^0.7.12": + version: 0.7.12 + resolution: "react-native-quick-crypto@npm:0.7.12" dependencies: "@craftzdog/react-native-buffer": "npm:^6.0.5" events: "npm:^3.3.0" readable-stream: "npm:^4.5.2" string_decoder: "npm:^1.3.0" util: "npm:^0.12.5" - checksum: 10/a14ece6ef32d80f5e19a559f332512dc5e850fbe0f93e09de091d1dec9bd22ee2f20a23d3a52f8441f01f3c58c161c3e4cfa2febbc3a2b994804c8e21aa856b7 + checksum: 10/ce52d64b91cce9198628610858aade1ca6098b273c87feb5355212979b8346b066ee5a8cc9ea43f5859426c45c7bc67d80c9402bd3512e17cd467771232dd96c languageName: node linkType: hard @@ -29218,18 +29218,18 @@ __metadata: languageName: node linkType: hard -"react-native@npm:0.76.3": - version: 0.76.3 - resolution: "react-native@npm:0.76.3" +"react-native@npm:0.76.7": + version: 0.76.7 + resolution: "react-native@npm:0.76.7" dependencies: "@jest/create-cache-key-function": "npm:^29.6.3" - "@react-native/assets-registry": "npm:0.76.3" - "@react-native/codegen": "npm:0.76.3" - "@react-native/community-cli-plugin": "npm:0.76.3" - "@react-native/gradle-plugin": "npm:0.76.3" - "@react-native/js-polyfills": "npm:0.76.3" - "@react-native/normalize-colors": "npm:0.76.3" - "@react-native/virtualized-lists": "npm:0.76.3" + "@react-native/assets-registry": "npm:0.76.7" + "@react-native/codegen": "npm:0.76.7" + "@react-native/community-cli-plugin": "npm:0.76.7" + "@react-native/gradle-plugin": "npm:0.76.7" + "@react-native/js-polyfills": "npm:0.76.7" + "@react-native/normalize-colors": "npm:0.76.7" + "@react-native/virtualized-lists": "npm:0.76.7" abort-controller: "npm:^3.0.0" anser: "npm:^1.4.9" ansi-regex: "npm:^5.0.0" @@ -29268,7 +29268,7 @@ __metadata: optional: true bin: react-native: cli.js - checksum: 10/973f0310ebb56eae4025de7eebf100c83b8e82cd23dd7c51767f004bdff0ed7eac3b6ddf19630376edaaefd3108953b4e3be728b59b3825b67426e02b7fb724b + checksum: 10/989a67e229cac5013876061a59cb961bab0d1018e84a7cc5077e081792fee0d0544b091ddbffc1524bc43d30ab33471d70aeccef99d01b52931a1228fd4297bd languageName: node linkType: hard @@ -33485,13 +33485,6 @@ __metadata: languageName: node linkType: hard -"undici@npm:6.19.7": - version: 6.19.7 - resolution: "undici@npm:6.19.7" - checksum: 10/77fb8b0377388f6dba8244b015841318d621031211b4f3c2273d809304b77ec44adeba4b89dfd6708bdc044190e18f068e5b231882ef15d057d4624e46f544e3 - languageName: node - linkType: hard - "undici@npm:^6.11.1, undici@npm:^6.18.2": version: 6.21.1 resolution: "undici@npm:6.21.1" @@ -34273,6 +34266,13 @@ __metadata: languageName: node linkType: hard +"web-vitals@npm:^4.2.4": + version: 4.2.4 + resolution: "web-vitals@npm:4.2.4" + checksum: 10/68cd1c2625a04b26e7eab67110623396afc6c9ef8c3a76f4e780aefe5b7d4ca1691737a0b99119e1d1ca9a463c4d468c0f0090b1875b6d784589d3a4a8503313 + languageName: node + linkType: hard + "webauthn-p256@npm:0.0.10": version: 0.0.10 resolution: "webauthn-p256@npm:0.0.10"