@metamask-previews/profile-sync-controller 28.3.0-preview-ec4680201 → 28.3.0-preview-40d5fb1e2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/authentication/AuthenticationController-method-action-types.cjs.map +1 -1
- package/dist/controllers/authentication/AuthenticationController-method-action-types.d.cts.map +1 -1
- package/dist/controllers/authentication/AuthenticationController-method-action-types.d.mts.map +1 -1
- package/dist/controllers/authentication/AuthenticationController-method-action-types.mjs.map +1 -1
- package/dist/controllers/authentication/AuthenticationController.cjs +9 -9
- package/dist/controllers/authentication/AuthenticationController.cjs.map +1 -1
- package/dist/controllers/authentication/AuthenticationController.d.cts.map +1 -1
- package/dist/controllers/authentication/AuthenticationController.d.mts.map +1 -1
- package/dist/controllers/authentication/AuthenticationController.mjs.map +1 -1
- package/dist/controllers/authentication/index.cjs +3 -3
- package/dist/controllers/authentication/index.cjs.map +1 -1
- package/dist/controllers/authentication/index.d.cts.map +1 -1
- package/dist/controllers/authentication/index.d.mts.map +1 -1
- package/dist/controllers/authentication/index.mjs.map +1 -1
- package/dist/controllers/authentication/mocks/index.cjs.map +1 -1
- package/dist/controllers/authentication/mocks/index.d.cts.map +1 -1
- package/dist/controllers/authentication/mocks/index.d.mts.map +1 -1
- package/dist/controllers/authentication/mocks/index.mjs.map +1 -1
- package/dist/controllers/authentication/mocks/mockResponses.cjs +12 -12
- package/dist/controllers/authentication/mocks/mockResponses.cjs.map +1 -1
- package/dist/controllers/authentication/mocks/mockResponses.mjs.map +1 -1
- package/dist/controllers/index.cjs.map +1 -1
- package/dist/controllers/index.d.cts.map +1 -1
- package/dist/controllers/index.d.mts.map +1 -1
- package/dist/controllers/index.mjs.map +1 -1
- package/dist/controllers/user-storage/UserStorageController-method-action-types.cjs.map +1 -1
- package/dist/controllers/user-storage/UserStorageController-method-action-types.d.cts.map +1 -1
- package/dist/controllers/user-storage/UserStorageController-method-action-types.d.mts.map +1 -1
- package/dist/controllers/user-storage/UserStorageController-method-action-types.mjs.map +1 -1
- package/dist/controllers/user-storage/UserStorageController.cjs +15 -15
- package/dist/controllers/user-storage/UserStorageController.cjs.map +1 -1
- package/dist/controllers/user-storage/UserStorageController.d.cts.map +1 -1
- package/dist/controllers/user-storage/UserStorageController.d.mts.map +1 -1
- package/dist/controllers/user-storage/UserStorageController.mjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/controller-integration.cjs +23 -23
- package/dist/controllers/user-storage/contact-syncing/controller-integration.cjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/controller-integration.d.cts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/controller-integration.d.mts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/controller-integration.mjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.cjs +9 -9
- package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.cjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.d.cts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.d.mts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.mjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/sync-utils.cjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/sync-utils.d.cts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/sync-utils.d.mts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/sync-utils.mjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/types.cjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/types.d.cts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/types.d.mts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/types.mjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/utils.cjs +2 -2
- package/dist/controllers/user-storage/contact-syncing/utils.cjs.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/utils.d.cts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/utils.d.mts.map +1 -1
- package/dist/controllers/user-storage/contact-syncing/utils.mjs.map +1 -1
- package/dist/controllers/user-storage/index.cjs +3 -3
- package/dist/controllers/user-storage/index.cjs.map +1 -1
- package/dist/controllers/user-storage/index.d.cts.map +1 -1
- package/dist/controllers/user-storage/index.d.mts.map +1 -1
- package/dist/controllers/user-storage/index.mjs.map +1 -1
- package/dist/controllers/user-storage/mocks/index.cjs.map +1 -1
- package/dist/controllers/user-storage/mocks/index.d.cts.map +1 -1
- package/dist/controllers/user-storage/mocks/index.d.mts.map +1 -1
- package/dist/controllers/user-storage/mocks/index.mjs.map +1 -1
- package/dist/controllers/user-storage/mocks/mockResponses.cjs +15 -15
- package/dist/controllers/user-storage/mocks/mockResponses.cjs.map +1 -1
- package/dist/controllers/user-storage/mocks/mockResponses.d.cts.map +1 -1
- package/dist/controllers/user-storage/mocks/mockResponses.d.mts.map +1 -1
- package/dist/controllers/user-storage/mocks/mockResponses.mjs.map +1 -1
- package/dist/controllers/user-storage/mocks/mockStorage.cjs +3 -3
- package/dist/controllers/user-storage/mocks/mockStorage.cjs.map +1 -1
- package/dist/controllers/user-storage/mocks/mockStorage.d.cts.map +1 -1
- package/dist/controllers/user-storage/mocks/mockStorage.d.mts.map +1 -1
- package/dist/controllers/user-storage/mocks/mockStorage.mjs.map +1 -1
- package/dist/controllers/user-storage/types.cjs.map +1 -1
- package/dist/controllers/user-storage/types.d.cts.map +1 -1
- package/dist/controllers/user-storage/types.d.mts.map +1 -1
- package/dist/controllers/user-storage/types.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/flow-siwe.cjs +11 -11
- package/dist/sdk/authentication-jwt-bearer/flow-siwe.cjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/flow-siwe.d.cts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/flow-siwe.d.mts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/flow-siwe.mjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/flow-srp.cjs +22 -22
- package/dist/sdk/authentication-jwt-bearer/flow-srp.cjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/flow-srp.d.cts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/flow-srp.d.mts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/flow-srp.mjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/services.cjs +37 -37
- package/dist/sdk/authentication-jwt-bearer/services.cjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/services.d.cts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/services.d.mts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/services.mjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/types.cjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/types.d.cts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/types.d.mts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/types.mjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/utils/identifier.cjs +2 -2
- package/dist/sdk/authentication-jwt-bearer/utils/identifier.cjs.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/utils/identifier.d.cts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/utils/identifier.d.mts.map +1 -1
- package/dist/sdk/authentication-jwt-bearer/utils/identifier.mjs.map +1 -1
- package/dist/sdk/authentication.cjs +21 -21
- package/dist/sdk/authentication.cjs.map +1 -1
- package/dist/sdk/authentication.d.cts.map +1 -1
- package/dist/sdk/authentication.d.mts.map +1 -1
- package/dist/sdk/authentication.mjs.map +1 -1
- package/dist/sdk/errors.cjs +3 -3
- package/dist/sdk/errors.cjs.map +1 -1
- package/dist/sdk/errors.mjs.map +1 -1
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.d.cts.map +1 -1
- package/dist/sdk/index.d.mts.map +1 -1
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/sdk/mocks/auth.cjs +12 -12
- package/dist/sdk/mocks/auth.cjs.map +1 -1
- package/dist/sdk/mocks/auth.d.cts.map +1 -1
- package/dist/sdk/mocks/auth.d.mts.map +1 -1
- package/dist/sdk/mocks/auth.mjs.map +1 -1
- package/dist/sdk/mocks/userstorage.cjs +8 -8
- package/dist/sdk/mocks/userstorage.cjs.map +1 -1
- package/dist/sdk/mocks/userstorage.mjs.map +1 -1
- package/dist/sdk/user-storage.cjs +32 -32
- package/dist/sdk/user-storage.cjs.map +1 -1
- package/dist/sdk/user-storage.d.cts.map +1 -1
- package/dist/sdk/user-storage.d.mts.map +1 -1
- package/dist/sdk/user-storage.mjs +1 -1
- package/dist/sdk/user-storage.mjs.map +1 -1
- package/dist/sdk/utils/validate-login-response.cjs.map +1 -1
- package/dist/sdk/utils/validate-login-response.d.cts.map +1 -1
- package/dist/sdk/utils/validate-login-response.d.mts.map +1 -1
- package/dist/sdk/utils/validate-login-response.mjs.map +1 -1
- package/dist/shared/encryption/cache.cjs +6 -6
- package/dist/shared/encryption/cache.cjs.map +1 -1
- package/dist/shared/encryption/cache.mjs.map +1 -1
- package/dist/shared/encryption/encryption.cjs +26 -26
- package/dist/shared/encryption/encryption.cjs.map +1 -1
- package/dist/shared/encryption/encryption.d.cts.map +1 -1
- package/dist/shared/encryption/encryption.d.mts.map +1 -1
- package/dist/shared/encryption/encryption.mjs.map +1 -1
- package/dist/shared/encryption/index.cjs +3 -3
- package/dist/shared/encryption/index.cjs.map +1 -1
- package/dist/shared/encryption/index.d.cts.map +1 -1
- package/dist/shared/encryption/index.d.mts.map +1 -1
- package/dist/shared/encryption/index.mjs.map +1 -1
- package/dist/shared/storage-schema.cjs +2 -2
- package/dist/shared/storage-schema.cjs.map +1 -1
- package/dist/shared/storage-schema.mjs.map +1 -1
- package/dist/shared/types/services.cjs.map +1 -1
- package/dist/shared/types/services.d.cts.map +1 -1
- package/dist/shared/types/services.d.mts.map +1 -1
- package/dist/shared/types/services.mjs.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserStorageController.mjs","sourceRoot":"","sources":["../../../src/controllers/user-storage/UserStorageController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AddressBookControllerContactUpdatedEvent,\n AddressBookControllerContactDeletedEvent,\n AddressBookControllerActions,\n AddressBookControllerListAction,\n AddressBookControllerSetAction,\n AddressBookControllerDeleteAction,\n} from '@metamask/address-book-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type {\n TraceCallback,\n TraceContext,\n TraceRequest,\n} from '@metamask/controller-utils';\nimport { KeyringTypes } from '@metamask/keyring-controller';\nimport type {\n KeyringControllerGetStateAction,\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type { SnapControllerHandleRequestAction } from '@metamask/snaps-controllers';\n\nimport type {\n UserStorageGenericFeatureKey,\n UserStorageGenericPathWithFeatureAndKey,\n UserStorageGenericPathWithFeatureOnly,\n} from '../../sdk/index.js';\nimport { Env, UserStorage } from '../../sdk/index.js';\nimport type { NativeScrypt } from '../../shared/types/encryption.js';\nimport { EventQueue } from '../../shared/utils/event-queue.js';\nimport { createSnapSignMessageRequest } from '../authentication/auth-snap-requests.js';\nimport type {\n AuthenticationControllerGetBearerTokenAction,\n AuthenticationControllerGetSessionProfileAction,\n AuthenticationControllerIsSignedInAction,\n AuthenticationControllerPerformSignInAction,\n} from '../authentication/AuthenticationController-method-action-types.js';\nimport { BACKUPANDSYNC_FEATURES } from './constants.js';\nimport { syncContactsWithUserStorage } from './contact-syncing/controller-integration.js';\nimport { setupContactSyncingSubscriptions } from './contact-syncing/setup-subscriptions.js';\nimport type { UserStorageControllerMethodActions } from './UserStorageController-method-action-types.js';\n\nconst controllerName = 'UserStorageController';\n\n// State\nexport type UserStorageControllerState = {\n /**\n * Condition used by UI and to determine if we can use some of the User Storage methods.\n */\n isBackupAndSyncEnabled: boolean;\n /**\n * Loading state for the backup and sync update\n */\n isBackupAndSyncUpdateLoading: boolean;\n /**\n * Condition used by UI to determine if account syncing is enabled.\n */\n isAccountSyncingEnabled: boolean;\n /**\n * Condition used by UI to determine if contact syncing is enabled.\n */\n isContactSyncingEnabled: boolean;\n /**\n * Condition used by UI to determine if contact syncing is in progress.\n */\n isContactSyncingInProgress: boolean;\n};\n\nexport const defaultState: UserStorageControllerState = {\n isBackupAndSyncEnabled: true,\n isBackupAndSyncUpdateLoading: false,\n isAccountSyncingEnabled: true,\n isContactSyncingEnabled: true,\n isContactSyncingInProgress: false,\n};\n\nconst metadata: StateMetadata<UserStorageControllerState> = {\n isBackupAndSyncEnabled: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n isBackupAndSyncUpdateLoading: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n isAccountSyncingEnabled: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n isContactSyncingEnabled: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n isContactSyncingInProgress: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n};\n\ntype ControllerConfig = {\n env: Env;\n contactSyncing?: {\n /**\n * Callback that fires when contact sync updates a contact.\n * This is used for analytics.\n */\n onContactUpdated?: (profileId: string) => void;\n\n /**\n * Callback that fires when contact sync deletes a contact.\n * This is used for analytics.\n */\n onContactDeleted?: (profileId: string) => void;\n\n /**\n * Callback that fires when an erroneous situation happens during contact sync.\n * This is used for analytics.\n */\n onContactSyncErroneousSituation?: (\n profileId: string,\n situationMessage: string,\n sentryContext?: Record<string, unknown>,\n ) => void;\n };\n};\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'performGetStorage',\n 'performGetStorageAllFeatureEntries',\n 'performSetStorage',\n 'performBatchSetStorage',\n 'performDeleteStorage',\n 'performBatchDeleteStorage',\n 'getStorageKey',\n 'performDeleteStorageAllFeatureEntries',\n 'listEntropySources',\n 'setIsBackupAndSyncFeatureEnabled',\n 'setIsContactSyncingInProgress',\n 'syncContactsWithUserStorage',\n] as const;\n\nexport type UserStorageControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n UserStorageControllerState\n>;\nexport type Actions =\n | UserStorageControllerGetStateAction\n | UserStorageControllerMethodActions;\n\nexport type AllowedActions =\n // Keyring Requests\n | KeyringControllerGetStateAction\n // Snap Requests\n | SnapControllerHandleRequestAction\n // Auth Requests\n | AuthenticationControllerGetBearerTokenAction\n | AuthenticationControllerGetSessionProfileAction\n | AuthenticationControllerPerformSignInAction\n | AuthenticationControllerIsSignedInAction\n // Contact Syncing\n | AddressBookControllerListAction\n | AddressBookControllerSetAction\n | AddressBookControllerDeleteAction\n | AddressBookControllerActions;\n\n// Messenger events\nexport type UserStorageControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n UserStorageControllerState\n>;\n\nexport type Events = UserStorageControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | KeyringControllerLockEvent\n | KeyringControllerUnlockEvent\n // Address Book Events\n | AddressBookControllerContactUpdatedEvent\n | AddressBookControllerContactDeletedEvent;\n\n// Messenger\nexport type UserStorageControllerMessenger = Messenger<\n typeof controllerName,\n Actions | AllowedActions,\n Events | AllowedEvents\n>;\n\n/**\n * Reusable controller that allows any team to store synchronized data for a given user.\n * These can be settings shared cross MetaMask clients, or data we want to persist when uninstalling/reinstalling.\n *\n * NOTE:\n * - data stored on UserStorage is FULLY encrypted, with the only keys stored/managed on the client.\n * - No one can access this data unless they are have the SRP and are able to run the signing snap.\n */\nexport class UserStorageController extends BaseController<\n typeof controllerName,\n UserStorageControllerState,\n UserStorageControllerMessenger\n> {\n readonly #userStorage: UserStorage;\n\n readonly #auth = {\n getProfileId: async (entropySourceId?: string) => {\n const sessionProfile = await this.messenger.call(\n 'AuthenticationController:getSessionProfile',\n entropySourceId,\n );\n return sessionProfile?.profileId;\n },\n isSignedIn: () => {\n return this.messenger.call('AuthenticationController:isSignedIn');\n },\n signIn: async () => {\n return await this.messenger.call(\n 'AuthenticationController:performSignIn',\n );\n },\n };\n\n readonly #config: ControllerConfig = {\n env: Env.PRD,\n };\n\n readonly #trace: TraceCallback;\n\n #isUnlocked = false;\n\n // Both caches are keyed by `${entropySourceId}:${message}` (the primary SRP\n // resolves to its HD keyring metadata ID) so two SRPs that transiently\n // resolve to the same `profileId` can never share a cached storage key /\n // signature and leak data across each other's user storage.\n #storageKeyCache: Record<string, string> = {};\n\n #snapSignMessageCache: Record<string, string> = {};\n\n readonly #keyringController = {\n setupLockedStateSubscriptions: () => {\n const { isUnlocked } = this.messenger.call('KeyringController:getState');\n this.#isUnlocked = isUnlocked;\n\n this.messenger.subscribe('KeyringController:unlock', () => {\n this.#isUnlocked = true;\n });\n\n this.messenger.subscribe('KeyringController:lock', () => {\n this.#isUnlocked = false;\n });\n },\n };\n\n readonly #nativeScryptCrypto: NativeScrypt | undefined = undefined;\n\n eventQueue = new EventQueue();\n\n constructor({\n messenger,\n state,\n config,\n nativeScryptCrypto,\n trace,\n }: {\n messenger: UserStorageControllerMessenger;\n state?: UserStorageControllerState;\n config?: Partial<ControllerConfig>;\n nativeScryptCrypto?: NativeScrypt;\n trace?: TraceCallback;\n }) {\n super({\n messenger,\n metadata,\n name: controllerName,\n state: { ...defaultState, ...state },\n });\n\n this.#config = {\n ...this.#config,\n ...config,\n };\n this.#trace =\n trace ??\n (async <ReturnType>(\n _request: TraceRequest,\n fn?: (context?: TraceContext) => ReturnType,\n ): Promise<ReturnType> => {\n if (!fn) {\n return undefined as ReturnType;\n }\n return await Promise.resolve(fn());\n });\n\n this.#userStorage = new UserStorage(\n {\n env: this.#config.env,\n auth: {\n getAccessToken: (entropySourceId?: string) =>\n this.messenger.call(\n 'AuthenticationController:getBearerToken',\n entropySourceId,\n ),\n getUserProfile: async (entropySourceId?: string) => {\n return await this.messenger.call(\n 'AuthenticationController:getSessionProfile',\n entropySourceId,\n );\n },\n signMessage: (message: string, entropySourceId?: string) =>\n this.#snapSignMessage(\n message as `metamask:${string}`,\n entropySourceId,\n ),\n },\n },\n {\n storage: {\n getStorageKey: async (message, entropySourceId) => {\n // No derived key can exist while locked (the KeyringController\n // clears its keyrings on lock, so the scope is unresolvable).\n if (!this.#isUnlocked) {\n return null;\n }\n return (\n this.#storageKeyCache[\n this.#scopedCacheKey(message, entropySourceId)\n ] ?? null\n );\n },\n // Only ever reached after a successful signature, i.e. while unlocked.\n setStorageKey: async (message, key, entropySourceId) => {\n this.#storageKeyCache[\n this.#scopedCacheKey(message, entropySourceId)\n ] = key;\n },\n },\n },\n );\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#keyringController.setupLockedStateSubscriptions();\n this.#nativeScryptCrypto = nativeScryptCrypto;\n\n // Contact Syncing\n setupContactSyncingSubscriptions({\n getUserStorageControllerInstance: () => this,\n getMessenger: () => this.messenger,\n trace: this.#trace,\n });\n }\n\n /**\n * Allows retrieval of stored data. Data stored is string formatted.\n * Developers can extend the entry path and entry name through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns the decrypted string contents found from user storage (or null if not found)\n */\n public async performGetStorage(\n path: UserStorageGenericPathWithFeatureAndKey,\n entropySourceId?: string,\n ): Promise<string | null> {\n return await this.#userStorage.getItem(path, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows retrieval of all stored data for a specific feature. Data stored is formatted as an array of strings.\n * Developers can extend the entry path through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}` that matches schema\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns the array of decrypted string contents found from user storage (or null if not found)\n */\n public async performGetStorageAllFeatureEntries(\n path: UserStorageGenericPathWithFeatureOnly,\n entropySourceId?: string,\n ): Promise<string[] | null> {\n return await this.#userStorage.getAllFeatureItems(path, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows storage of user data. Data stored must be string formatted.\n * Developers can extend the entry path and entry name through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param value - The string data you want to store.\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to store data.\n */\n public async performSetStorage(\n path: UserStorageGenericPathWithFeatureAndKey,\n value: string,\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.setItem(path, value, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows storage of multiple user data entries for one specific feature. Data stored must be string formatted.\n * Developers can extend the entry path through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}` that matches schema\n * @param values - data to store, in the form of an array of `[entryKey, entryValue]` pairs\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to store data.\n */\n public async performBatchSetStorage(\n path: UserStorageGenericPathWithFeatureOnly,\n values: [UserStorageGenericFeatureKey, string][],\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.batchSetItems(path, values, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows deletion of user data. Developers can extend the entry path and entry name through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to delete data.\n */\n public async performDeleteStorage(\n path: UserStorageGenericPathWithFeatureAndKey,\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.deleteItem(path, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows deletion of all user data entries for a specific feature.\n * Developers can extend the entry path through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}` that matches schema\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to delete data.\n */\n public async performDeleteStorageAllFeatureEntries(\n path: UserStorageGenericPathWithFeatureOnly,\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.deleteAllFeatureItems(path, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows delete of multiple user data entries for one specific feature. Data deleted must be string formatted.\n * Developers can extend the entry path through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}` that matches schema\n * @param values - data to store, in the form of an array of entryKey[]\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to store data.\n */\n public async performBatchDeleteStorage(\n path: UserStorageGenericPathWithFeatureOnly,\n values: UserStorageGenericFeatureKey[],\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.batchDeleteItems(path, values, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Retrieves the storage key, for internal use only!\n *\n * @returns the storage key\n */\n public async getStorageKey(): Promise<string> {\n return await this.#userStorage.getStorageKey();\n }\n\n /**\n * Flushes the storage key cache.\n * CAUTION: This is only public for testing purposes.\n * It should not be used in production code.\n */\n public flushStorageKeyCache(): void {\n this.#storageKeyCache = {};\n }\n\n /**\n * Lists all the available HD keyring metadata IDs.\n * These IDs can be used in a multi-SRP context to segregate data specific to different SRPs.\n *\n * @returns A promise that resolves to an array of HD keyring metadata IDs.\n */\n async listEntropySources(): Promise<string[]> {\n if (!this.#isUnlocked) {\n throw new Error(\n 'listEntropySources - unable to list entropy sources, wallet is locked',\n );\n }\n\n return this.#getHdKeyringEntropySourceIds();\n }\n\n /**\n * Reads the HD keyring entropy source IDs (metadata IDs) from the\n * KeyringController, primary first. Returns an empty array when none are\n * available (e.g. the wallet is locked, where `keyrings` is cleared).\n *\n * @returns The HD keyring metadata IDs, primary first.\n */\n #getHdKeyringEntropySourceIds(): string[] {\n const { keyrings } = this.messenger.call('KeyringController:getState');\n return (keyrings ?? [])\n .filter((keyring) => keyring.type === KeyringTypes.hd.toString())\n .map((keyring) => keyring.metadata.id);\n }\n\n /**\n * Resolves the primary SRP's entropy source ID (the first HD keyring's\n * metadata ID), used to scope the primary's cache entries. The ID is randomly\n * regenerated whenever the vault is recreated (e.g. on restore), so a new\n * primary can never inherit a previous vault's cached key.\n *\n * @returns The primary HD keyring metadata ID.\n * @throws If no HD keyring is available; callers must only resolve the scope\n * while the wallet is unlocked.\n */\n #getPrimaryEntropySourceId(): string {\n const [primaryEntropySourceId] = this.#getHdKeyringEntropySourceIds();\n if (!primaryEntropySourceId) {\n throw new Error('#getPrimaryEntropySourceId - no HD keyring available');\n }\n return primaryEntropySourceId;\n }\n\n /**\n * Builds a cache key scoped to a specific entropy source, so each SRP's\n * signature/storage key derivation stays isolated even when two SRPs\n * transiently resolve to the same `profileId` (see `#storageKeyCache`).\n *\n * When `entropySourceId` is omitted (primary SRP), it is resolved to the\n * primary HD keyring's metadata ID rather than a stable literal. Because that\n * ID is randomly regenerated whenever the vault is recreated (e.g. on\n * restore), the cached entry is naturally invalidated across vaults — a\n * different SRP can never inherit the previous primary's cached key.\n *\n * @param message - The tagged message used for signing.\n * @param entropySourceId - The entropy source ID. Omit for the primary SRP.\n * @returns The scoped cache key.\n */\n #scopedCacheKey(\n message: `metamask:${string}`,\n entropySourceId?: string,\n ): string {\n return `${entropySourceId ?? this.#getPrimaryEntropySourceId()}:${message}`;\n }\n\n /**\n * Signs a specific message using an underlying auth snap.\n *\n * @param message - A specific tagged message to sign.\n * @param entropySourceId - The entropy source ID used to derive the key,\n * when multiple sources are available (Multi-SRP).\n * @returns A Signature created by the snap.\n */\n async #snapSignMessage(\n message: `metamask:${string}`,\n entropySourceId?: string,\n ): Promise<string> {\n if (!this.#isUnlocked) {\n throw new Error(\n '#snapSignMessage - unable to call snap, wallet is locked',\n );\n }\n\n const cacheKey = this.#scopedCacheKey(message, entropySourceId);\n if (this.#snapSignMessageCache[cacheKey]) {\n return this.#snapSignMessageCache[cacheKey];\n }\n\n const result = (await this.messenger.call(\n 'SnapController:handleRequest',\n createSnapSignMessageRequest(message, entropySourceId),\n )) as string;\n\n this.#snapSignMessageCache[cacheKey] = result;\n\n return result;\n }\n\n public async setIsBackupAndSyncFeatureEnabled(\n feature: keyof typeof BACKUPANDSYNC_FEATURES,\n enabled: boolean,\n ): Promise<void> {\n try {\n this.#setIsBackupAndSyncUpdateLoading(true);\n\n if (enabled) {\n // If any of the features are enabled, we need to ensure the user is signed in\n const isSignedIn = this.#auth.isSignedIn();\n if (!isSignedIn) {\n await this.#auth.signIn();\n }\n }\n\n this.update((state) => {\n if (feature === BACKUPANDSYNC_FEATURES.main) {\n state.isBackupAndSyncEnabled = enabled;\n }\n\n if (feature === BACKUPANDSYNC_FEATURES.accountSyncing) {\n state.isAccountSyncingEnabled = enabled;\n }\n\n if (feature === BACKUPANDSYNC_FEATURES.contactSyncing) {\n state.isContactSyncingEnabled = enabled;\n }\n });\n } catch (e) {\n // istanbul ignore next\n const errorMessage = e instanceof Error ? e.message : JSON.stringify(e);\n // istanbul ignore next\n throw new Error(\n `${controllerName} - failed to ${enabled ? 'enable' : 'disable'} ${feature} - ${errorMessage}`,\n );\n } finally {\n this.#setIsBackupAndSyncUpdateLoading(false);\n }\n }\n\n #setIsBackupAndSyncUpdateLoading(\n isBackupAndSyncUpdateLoading: boolean,\n ): void {\n this.update((state) => {\n state.isBackupAndSyncUpdateLoading = isBackupAndSyncUpdateLoading;\n });\n }\n\n /**\n * Sets the isContactSyncingInProgress flag to prevent infinite loops during contact synchronization\n *\n * @param isContactSyncingInProgress - Whether contact syncing is in progress\n */\n async setIsContactSyncingInProgress(\n isContactSyncingInProgress: boolean,\n ): Promise<void> {\n this.update((state) => {\n state.isContactSyncingInProgress = isContactSyncingInProgress;\n });\n }\n\n /**\n * Syncs the address book list with the user storage address book list.\n * This method is used to make sure that the address book list is up-to-date with the user storage address book list and vice-versa.\n * It will add new contacts to the address book list, update/merge conflicting contacts and re-upload the results in some cases to the user storage.\n */\n async syncContactsWithUserStorage(): Promise<void> {\n const profileId = await this.#auth.getProfileId();\n\n const config = {\n onContactUpdated: () => {\n this.#config?.contactSyncing?.onContactUpdated?.(profileId);\n },\n onContactDeleted: () => {\n this.#config?.contactSyncing?.onContactDeleted?.(profileId);\n },\n onContactSyncErroneousSituation: (\n errorMessage: string,\n sentryContext?: Record<string, unknown>,\n ) => {\n this.#config?.contactSyncing?.onContactSyncErroneousSituation?.(\n profileId,\n errorMessage,\n sentryContext,\n );\n },\n };\n\n await syncContactsWithUserStorage(config, {\n getMessenger: () => this.messenger,\n getUserStorageControllerInstance: () => this,\n trace: this.#trace,\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"UserStorageController.mjs","sourceRoot":"","sources":["../../../src/controllers/user-storage/UserStorageController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AddressBookControllerContactUpdatedEvent,\n AddressBookControllerContactDeletedEvent,\n AddressBookControllerActions,\n AddressBookControllerListAction,\n AddressBookControllerSetAction,\n AddressBookControllerDeleteAction,\n} from '@metamask/address-book-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type {\n TraceCallback,\n TraceContext,\n TraceRequest,\n} from '@metamask/controller-utils';\nimport { KeyringTypes } from '@metamask/keyring-controller';\nimport type {\n KeyringControllerGetStateAction,\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type { SnapControllerHandleRequestAction } from '@metamask/snaps-controllers';\n\nimport type {\n UserStorageGenericFeatureKey,\n UserStorageGenericPathWithFeatureAndKey,\n UserStorageGenericPathWithFeatureOnly,\n} from '../../sdk';\nimport { Env, UserStorage } from '../../sdk';\nimport type { NativeScrypt } from '../../shared/types/encryption';\nimport { EventQueue } from '../../shared/utils/event-queue';\nimport { createSnapSignMessageRequest } from '../authentication/auth-snap-requests';\nimport type {\n AuthenticationControllerGetBearerTokenAction,\n AuthenticationControllerGetSessionProfileAction,\n AuthenticationControllerIsSignedInAction,\n AuthenticationControllerPerformSignInAction,\n} from '../authentication/AuthenticationController-method-action-types';\nimport { BACKUPANDSYNC_FEATURES } from './constants';\nimport { syncContactsWithUserStorage } from './contact-syncing/controller-integration';\nimport { setupContactSyncingSubscriptions } from './contact-syncing/setup-subscriptions';\nimport type { UserStorageControllerMethodActions } from './UserStorageController-method-action-types';\n\nconst controllerName = 'UserStorageController';\n\n// State\nexport type UserStorageControllerState = {\n /**\n * Condition used by UI and to determine if we can use some of the User Storage methods.\n */\n isBackupAndSyncEnabled: boolean;\n /**\n * Loading state for the backup and sync update\n */\n isBackupAndSyncUpdateLoading: boolean;\n /**\n * Condition used by UI to determine if account syncing is enabled.\n */\n isAccountSyncingEnabled: boolean;\n /**\n * Condition used by UI to determine if contact syncing is enabled.\n */\n isContactSyncingEnabled: boolean;\n /**\n * Condition used by UI to determine if contact syncing is in progress.\n */\n isContactSyncingInProgress: boolean;\n};\n\nexport const defaultState: UserStorageControllerState = {\n isBackupAndSyncEnabled: true,\n isBackupAndSyncUpdateLoading: false,\n isAccountSyncingEnabled: true,\n isContactSyncingEnabled: true,\n isContactSyncingInProgress: false,\n};\n\nconst metadata: StateMetadata<UserStorageControllerState> = {\n isBackupAndSyncEnabled: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n isBackupAndSyncUpdateLoading: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n isAccountSyncingEnabled: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n isContactSyncingEnabled: {\n includeInStateLogs: true,\n persist: true,\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n isContactSyncingInProgress: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n};\n\ntype ControllerConfig = {\n env: Env;\n contactSyncing?: {\n /**\n * Callback that fires when contact sync updates a contact.\n * This is used for analytics.\n */\n onContactUpdated?: (profileId: string) => void;\n\n /**\n * Callback that fires when contact sync deletes a contact.\n * This is used for analytics.\n */\n onContactDeleted?: (profileId: string) => void;\n\n /**\n * Callback that fires when an erroneous situation happens during contact sync.\n * This is used for analytics.\n */\n onContactSyncErroneousSituation?: (\n profileId: string,\n situationMessage: string,\n sentryContext?: Record<string, unknown>,\n ) => void;\n };\n};\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'performGetStorage',\n 'performGetStorageAllFeatureEntries',\n 'performSetStorage',\n 'performBatchSetStorage',\n 'performDeleteStorage',\n 'performBatchDeleteStorage',\n 'getStorageKey',\n 'performDeleteStorageAllFeatureEntries',\n 'listEntropySources',\n 'setIsBackupAndSyncFeatureEnabled',\n 'setIsContactSyncingInProgress',\n 'syncContactsWithUserStorage',\n] as const;\n\nexport type UserStorageControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n UserStorageControllerState\n>;\nexport type Actions =\n | UserStorageControllerGetStateAction\n | UserStorageControllerMethodActions;\n\nexport type AllowedActions =\n // Keyring Requests\n | KeyringControllerGetStateAction\n // Snap Requests\n | SnapControllerHandleRequestAction\n // Auth Requests\n | AuthenticationControllerGetBearerTokenAction\n | AuthenticationControllerGetSessionProfileAction\n | AuthenticationControllerPerformSignInAction\n | AuthenticationControllerIsSignedInAction\n // Contact Syncing\n | AddressBookControllerListAction\n | AddressBookControllerSetAction\n | AddressBookControllerDeleteAction\n | AddressBookControllerActions;\n\n// Messenger events\nexport type UserStorageControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n UserStorageControllerState\n>;\n\nexport type Events = UserStorageControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | KeyringControllerLockEvent\n | KeyringControllerUnlockEvent\n // Address Book Events\n | AddressBookControllerContactUpdatedEvent\n | AddressBookControllerContactDeletedEvent;\n\n// Messenger\nexport type UserStorageControllerMessenger = Messenger<\n typeof controllerName,\n Actions | AllowedActions,\n Events | AllowedEvents\n>;\n\n/**\n * Reusable controller that allows any team to store synchronized data for a given user.\n * These can be settings shared cross MetaMask clients, or data we want to persist when uninstalling/reinstalling.\n *\n * NOTE:\n * - data stored on UserStorage is FULLY encrypted, with the only keys stored/managed on the client.\n * - No one can access this data unless they are have the SRP and are able to run the signing snap.\n */\nexport class UserStorageController extends BaseController<\n typeof controllerName,\n UserStorageControllerState,\n UserStorageControllerMessenger\n> {\n readonly #userStorage: UserStorage;\n\n readonly #auth = {\n getProfileId: async (entropySourceId?: string) => {\n const sessionProfile = await this.messenger.call(\n 'AuthenticationController:getSessionProfile',\n entropySourceId,\n );\n return sessionProfile?.profileId;\n },\n isSignedIn: () => {\n return this.messenger.call('AuthenticationController:isSignedIn');\n },\n signIn: async () => {\n return await this.messenger.call(\n 'AuthenticationController:performSignIn',\n );\n },\n };\n\n readonly #config: ControllerConfig = {\n env: Env.PRD,\n };\n\n readonly #trace: TraceCallback;\n\n #isUnlocked = false;\n\n // Both caches are keyed by `${entropySourceId}:${message}` (the primary SRP\n // resolves to its HD keyring metadata ID) so two SRPs that transiently\n // resolve to the same `profileId` can never share a cached storage key /\n // signature and leak data across each other's user storage.\n #storageKeyCache: Record<string, string> = {};\n\n #snapSignMessageCache: Record<string, string> = {};\n\n readonly #keyringController = {\n setupLockedStateSubscriptions: () => {\n const { isUnlocked } = this.messenger.call('KeyringController:getState');\n this.#isUnlocked = isUnlocked;\n\n this.messenger.subscribe('KeyringController:unlock', () => {\n this.#isUnlocked = true;\n });\n\n this.messenger.subscribe('KeyringController:lock', () => {\n this.#isUnlocked = false;\n });\n },\n };\n\n readonly #nativeScryptCrypto: NativeScrypt | undefined = undefined;\n\n eventQueue = new EventQueue();\n\n constructor({\n messenger,\n state,\n config,\n nativeScryptCrypto,\n trace,\n }: {\n messenger: UserStorageControllerMessenger;\n state?: UserStorageControllerState;\n config?: Partial<ControllerConfig>;\n nativeScryptCrypto?: NativeScrypt;\n trace?: TraceCallback;\n }) {\n super({\n messenger,\n metadata,\n name: controllerName,\n state: { ...defaultState, ...state },\n });\n\n this.#config = {\n ...this.#config,\n ...config,\n };\n this.#trace =\n trace ??\n (async <ReturnType>(\n _request: TraceRequest,\n fn?: (context?: TraceContext) => ReturnType,\n ): Promise<ReturnType> => {\n if (!fn) {\n return undefined as ReturnType;\n }\n return await Promise.resolve(fn());\n });\n\n this.#userStorage = new UserStorage(\n {\n env: this.#config.env,\n auth: {\n getAccessToken: (entropySourceId?: string) =>\n this.messenger.call(\n 'AuthenticationController:getBearerToken',\n entropySourceId,\n ),\n getUserProfile: async (entropySourceId?: string) => {\n return await this.messenger.call(\n 'AuthenticationController:getSessionProfile',\n entropySourceId,\n );\n },\n signMessage: (message: string, entropySourceId?: string) =>\n this.#snapSignMessage(\n message as `metamask:${string}`,\n entropySourceId,\n ),\n },\n },\n {\n storage: {\n getStorageKey: async (message, entropySourceId) => {\n // No derived key can exist while locked (the KeyringController\n // clears its keyrings on lock, so the scope is unresolvable).\n if (!this.#isUnlocked) {\n return null;\n }\n return (\n this.#storageKeyCache[\n this.#scopedCacheKey(message, entropySourceId)\n ] ?? null\n );\n },\n // Only ever reached after a successful signature, i.e. while unlocked.\n setStorageKey: async (message, key, entropySourceId) => {\n this.#storageKeyCache[\n this.#scopedCacheKey(message, entropySourceId)\n ] = key;\n },\n },\n },\n );\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#keyringController.setupLockedStateSubscriptions();\n this.#nativeScryptCrypto = nativeScryptCrypto;\n\n // Contact Syncing\n setupContactSyncingSubscriptions({\n getUserStorageControllerInstance: () => this,\n getMessenger: () => this.messenger,\n trace: this.#trace,\n });\n }\n\n /**\n * Allows retrieval of stored data. Data stored is string formatted.\n * Developers can extend the entry path and entry name through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns the decrypted string contents found from user storage (or null if not found)\n */\n public async performGetStorage(\n path: UserStorageGenericPathWithFeatureAndKey,\n entropySourceId?: string,\n ): Promise<string | null> {\n return await this.#userStorage.getItem(path, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows retrieval of all stored data for a specific feature. Data stored is formatted as an array of strings.\n * Developers can extend the entry path through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}` that matches schema\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns the array of decrypted string contents found from user storage (or null if not found)\n */\n public async performGetStorageAllFeatureEntries(\n path: UserStorageGenericPathWithFeatureOnly,\n entropySourceId?: string,\n ): Promise<string[] | null> {\n return await this.#userStorage.getAllFeatureItems(path, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows storage of user data. Data stored must be string formatted.\n * Developers can extend the entry path and entry name through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param value - The string data you want to store.\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to store data.\n */\n public async performSetStorage(\n path: UserStorageGenericPathWithFeatureAndKey,\n value: string,\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.setItem(path, value, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows storage of multiple user data entries for one specific feature. Data stored must be string formatted.\n * Developers can extend the entry path through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}` that matches schema\n * @param values - data to store, in the form of an array of `[entryKey, entryValue]` pairs\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to store data.\n */\n public async performBatchSetStorage(\n path: UserStorageGenericPathWithFeatureOnly,\n values: [UserStorageGenericFeatureKey, string][],\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.batchSetItems(path, values, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows deletion of user data. Developers can extend the entry path and entry name through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}.${key}` that matches schema\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to delete data.\n */\n public async performDeleteStorage(\n path: UserStorageGenericPathWithFeatureAndKey,\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.deleteItem(path, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows deletion of all user data entries for a specific feature.\n * Developers can extend the entry path through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}` that matches schema\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to delete data.\n */\n public async performDeleteStorageAllFeatureEntries(\n path: UserStorageGenericPathWithFeatureOnly,\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.deleteAllFeatureItems(path, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Allows delete of multiple user data entries for one specific feature. Data deleted must be string formatted.\n * Developers can extend the entry path through the `schema.ts` file.\n *\n * @param path - string in the form of `${feature}` that matches schema\n * @param values - data to store, in the form of an array of entryKey[]\n * @param entropySourceId - The entropy source ID used to generate the encryption key.\n * @returns nothing. NOTE that an error is thrown if fails to store data.\n */\n public async performBatchDeleteStorage(\n path: UserStorageGenericPathWithFeatureOnly,\n values: UserStorageGenericFeatureKey[],\n entropySourceId?: string,\n ): Promise<void> {\n return await this.#userStorage.batchDeleteItems(path, values, {\n nativeScryptCrypto: this.#nativeScryptCrypto,\n entropySourceId,\n });\n }\n\n /**\n * Retrieves the storage key, for internal use only!\n *\n * @returns the storage key\n */\n public async getStorageKey(): Promise<string> {\n return await this.#userStorage.getStorageKey();\n }\n\n /**\n * Flushes the storage key cache.\n * CAUTION: This is only public for testing purposes.\n * It should not be used in production code.\n */\n public flushStorageKeyCache(): void {\n this.#storageKeyCache = {};\n }\n\n /**\n * Lists all the available HD keyring metadata IDs.\n * These IDs can be used in a multi-SRP context to segregate data specific to different SRPs.\n *\n * @returns A promise that resolves to an array of HD keyring metadata IDs.\n */\n async listEntropySources(): Promise<string[]> {\n if (!this.#isUnlocked) {\n throw new Error(\n 'listEntropySources - unable to list entropy sources, wallet is locked',\n );\n }\n\n return this.#getHdKeyringEntropySourceIds();\n }\n\n /**\n * Reads the HD keyring entropy source IDs (metadata IDs) from the\n * KeyringController, primary first. Returns an empty array when none are\n * available (e.g. the wallet is locked, where `keyrings` is cleared).\n *\n * @returns The HD keyring metadata IDs, primary first.\n */\n #getHdKeyringEntropySourceIds(): string[] {\n const { keyrings } = this.messenger.call('KeyringController:getState');\n return (keyrings ?? [])\n .filter((keyring) => keyring.type === KeyringTypes.hd.toString())\n .map((keyring) => keyring.metadata.id);\n }\n\n /**\n * Resolves the primary SRP's entropy source ID (the first HD keyring's\n * metadata ID), used to scope the primary's cache entries. The ID is randomly\n * regenerated whenever the vault is recreated (e.g. on restore), so a new\n * primary can never inherit a previous vault's cached key.\n *\n * @returns The primary HD keyring metadata ID.\n * @throws If no HD keyring is available; callers must only resolve the scope\n * while the wallet is unlocked.\n */\n #getPrimaryEntropySourceId(): string {\n const [primaryEntropySourceId] = this.#getHdKeyringEntropySourceIds();\n if (!primaryEntropySourceId) {\n throw new Error('#getPrimaryEntropySourceId - no HD keyring available');\n }\n return primaryEntropySourceId;\n }\n\n /**\n * Builds a cache key scoped to a specific entropy source, so each SRP's\n * signature/storage key derivation stays isolated even when two SRPs\n * transiently resolve to the same `profileId` (see `#storageKeyCache`).\n *\n * When `entropySourceId` is omitted (primary SRP), it is resolved to the\n * primary HD keyring's metadata ID rather than a stable literal. Because that\n * ID is randomly regenerated whenever the vault is recreated (e.g. on\n * restore), the cached entry is naturally invalidated across vaults — a\n * different SRP can never inherit the previous primary's cached key.\n *\n * @param message - The tagged message used for signing.\n * @param entropySourceId - The entropy source ID. Omit for the primary SRP.\n * @returns The scoped cache key.\n */\n #scopedCacheKey(\n message: `metamask:${string}`,\n entropySourceId?: string,\n ): string {\n return `${entropySourceId ?? this.#getPrimaryEntropySourceId()}:${message}`;\n }\n\n /**\n * Signs a specific message using an underlying auth snap.\n *\n * @param message - A specific tagged message to sign.\n * @param entropySourceId - The entropy source ID used to derive the key,\n * when multiple sources are available (Multi-SRP).\n * @returns A Signature created by the snap.\n */\n async #snapSignMessage(\n message: `metamask:${string}`,\n entropySourceId?: string,\n ): Promise<string> {\n if (!this.#isUnlocked) {\n throw new Error(\n '#snapSignMessage - unable to call snap, wallet is locked',\n );\n }\n\n const cacheKey = this.#scopedCacheKey(message, entropySourceId);\n if (this.#snapSignMessageCache[cacheKey]) {\n return this.#snapSignMessageCache[cacheKey];\n }\n\n const result = (await this.messenger.call(\n 'SnapController:handleRequest',\n createSnapSignMessageRequest(message, entropySourceId),\n )) as string;\n\n this.#snapSignMessageCache[cacheKey] = result;\n\n return result;\n }\n\n public async setIsBackupAndSyncFeatureEnabled(\n feature: keyof typeof BACKUPANDSYNC_FEATURES,\n enabled: boolean,\n ): Promise<void> {\n try {\n this.#setIsBackupAndSyncUpdateLoading(true);\n\n if (enabled) {\n // If any of the features are enabled, we need to ensure the user is signed in\n const isSignedIn = this.#auth.isSignedIn();\n if (!isSignedIn) {\n await this.#auth.signIn();\n }\n }\n\n this.update((state) => {\n if (feature === BACKUPANDSYNC_FEATURES.main) {\n state.isBackupAndSyncEnabled = enabled;\n }\n\n if (feature === BACKUPANDSYNC_FEATURES.accountSyncing) {\n state.isAccountSyncingEnabled = enabled;\n }\n\n if (feature === BACKUPANDSYNC_FEATURES.contactSyncing) {\n state.isContactSyncingEnabled = enabled;\n }\n });\n } catch (e) {\n // istanbul ignore next\n const errorMessage = e instanceof Error ? e.message : JSON.stringify(e);\n // istanbul ignore next\n throw new Error(\n `${controllerName} - failed to ${enabled ? 'enable' : 'disable'} ${feature} - ${errorMessage}`,\n );\n } finally {\n this.#setIsBackupAndSyncUpdateLoading(false);\n }\n }\n\n #setIsBackupAndSyncUpdateLoading(\n isBackupAndSyncUpdateLoading: boolean,\n ): void {\n this.update((state) => {\n state.isBackupAndSyncUpdateLoading = isBackupAndSyncUpdateLoading;\n });\n }\n\n /**\n * Sets the isContactSyncingInProgress flag to prevent infinite loops during contact synchronization\n *\n * @param isContactSyncingInProgress - Whether contact syncing is in progress\n */\n async setIsContactSyncingInProgress(\n isContactSyncingInProgress: boolean,\n ): Promise<void> {\n this.update((state) => {\n state.isContactSyncingInProgress = isContactSyncingInProgress;\n });\n }\n\n /**\n * Syncs the address book list with the user storage address book list.\n * This method is used to make sure that the address book list is up-to-date with the user storage address book list and vice-versa.\n * It will add new contacts to the address book list, update/merge conflicting contacts and re-upload the results in some cases to the user storage.\n */\n async syncContactsWithUserStorage(): Promise<void> {\n const profileId = await this.#auth.getProfileId();\n\n const config = {\n onContactUpdated: () => {\n this.#config?.contactSyncing?.onContactUpdated?.(profileId);\n },\n onContactDeleted: () => {\n this.#config?.contactSyncing?.onContactDeleted?.(profileId);\n },\n onContactSyncErroneousSituation: (\n errorMessage: string,\n sentryContext?: Record<string, unknown>,\n ) => {\n this.#config?.contactSyncing?.onContactSyncErroneousSituation?.(\n profileId,\n errorMessage,\n sentryContext,\n );\n },\n };\n\n await syncContactsWithUserStorage(config, {\n getMessenger: () => this.messenger,\n getUserStorageControllerInstance: () => this,\n trace: this.#trace,\n });\n }\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.deleteContactInRemoteStorage = exports.updateContactInRemoteStorage = exports.syncContactsWithUserStorage = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
4
|
+
const storage_schema_1 = require("../../../shared/storage-schema.cjs");
|
|
5
|
+
const constants_1 = require("../constants.cjs");
|
|
6
|
+
const sync_utils_1 = require("./sync-utils.cjs");
|
|
7
|
+
const utils_1 = require("./utils.cjs");
|
|
8
|
+
const utils_2 = require("./utils.cjs");
|
|
9
9
|
/**
|
|
10
10
|
* Creates a unique key for a contact based on chainId and address
|
|
11
11
|
*
|
|
@@ -42,7 +42,7 @@ async function syncContactsWithUserStorage(config, options) {
|
|
|
42
42
|
const { getMessenger, getUserStorageControllerInstance, trace } = options;
|
|
43
43
|
const { onContactSyncErroneousSituation, onContactUpdated, onContactDeleted, } = config;
|
|
44
44
|
// Cannot perform sync, conditions not met
|
|
45
|
-
if (!(0,
|
|
45
|
+
if (!(0, sync_utils_1.canPerformContactSyncing)(options)) {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
// NOTE: Pre-sync operations (canPerformContactSyncing, AddressBookController:list, getRemoteContacts)
|
|
@@ -51,7 +51,7 @@ async function syncContactsWithUserStorage(config, options) {
|
|
|
51
51
|
// Get all local contacts from AddressBookController (exclude chain "*" contacts)
|
|
52
52
|
const localVisibleContacts = getMessenger()
|
|
53
53
|
.call('AddressBookController:list')
|
|
54
|
-
.filter((contact) => !(0,
|
|
54
|
+
.filter((contact) => !(0, utils_2.isContactBridgedFromAccounts)(contact))
|
|
55
55
|
.filter((contact) => contact.address && contact.chainId && contact.name?.trim()) || [];
|
|
56
56
|
// Get remote contacts from user storage API
|
|
57
57
|
const remoteContacts = await getRemoteContacts(options);
|
|
@@ -175,7 +175,7 @@ async function syncContactsWithUserStorage(config, options) {
|
|
|
175
175
|
const initialLocalContacts = localVisibleContacts;
|
|
176
176
|
const initialValidRemoteContacts = validRemoteContacts;
|
|
177
177
|
await trace({
|
|
178
|
-
name:
|
|
178
|
+
name: constants_1.TraceName.ContactSyncFull,
|
|
179
179
|
data: {
|
|
180
180
|
localContactCount: initialLocalContacts.length,
|
|
181
181
|
remoteContactCount: initialValidRemoteContacts.length,
|
|
@@ -204,14 +204,14 @@ exports.syncContactsWithUserStorage = syncContactsWithUserStorage;
|
|
|
204
204
|
async function getRemoteContacts(options) {
|
|
205
205
|
const { getUserStorageControllerInstance } = options;
|
|
206
206
|
try {
|
|
207
|
-
const remoteContactsJsonArray = await getUserStorageControllerInstance().performGetStorageAllFeatureEntries(
|
|
207
|
+
const remoteContactsJsonArray = await getUserStorageControllerInstance().performGetStorageAllFeatureEntries(storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook);
|
|
208
208
|
if (!remoteContactsJsonArray || remoteContactsJsonArray.length === 0) {
|
|
209
209
|
return null;
|
|
210
210
|
}
|
|
211
211
|
// Parse each JSON entry and convert from UserStorageContactEntry to AddressBookEntry
|
|
212
212
|
const remoteStorageEntries = remoteContactsJsonArray.map((contactJson) => {
|
|
213
213
|
const entry = JSON.parse(contactJson);
|
|
214
|
-
return (0,
|
|
214
|
+
return (0, utils_1.mapUserStorageEntryToAddressBookEntry)(entry);
|
|
215
215
|
});
|
|
216
216
|
return remoteStorageEntries;
|
|
217
217
|
}
|
|
@@ -235,14 +235,14 @@ async function saveContactsToUserStorage(contacts, options) {
|
|
|
235
235
|
// Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs
|
|
236
236
|
const storageEntries = contacts.map((contact) => {
|
|
237
237
|
const key = createContactKey(contact);
|
|
238
|
-
const storageEntry = (0,
|
|
238
|
+
const storageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(contact);
|
|
239
239
|
return [key, JSON.stringify(storageEntry)];
|
|
240
240
|
});
|
|
241
|
-
await getUserStorageControllerInstance().performBatchSetStorage(
|
|
241
|
+
await getUserStorageControllerInstance().performBatchSetStorage(storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook, storageEntries);
|
|
242
242
|
};
|
|
243
243
|
return trace
|
|
244
244
|
? await trace({
|
|
245
|
-
name:
|
|
245
|
+
name: constants_1.TraceName.ContactSyncSaveBatch,
|
|
246
246
|
data: {
|
|
247
247
|
contactCount: contacts.length,
|
|
248
248
|
// Performance scaling indicators
|
|
@@ -264,7 +264,7 @@ async function saveContactsToUserStorage(contacts, options) {
|
|
|
264
264
|
async function updateContactInRemoteStorage(contact, options) {
|
|
265
265
|
const { trace } = options;
|
|
266
266
|
const updateContact = async () => {
|
|
267
|
-
if (!(0,
|
|
267
|
+
if (!(0, sync_utils_1.canPerformContactSyncing)(options) ||
|
|
268
268
|
!contact.address ||
|
|
269
269
|
!contact.chainId ||
|
|
270
270
|
!contact.name?.trim()) {
|
|
@@ -277,13 +277,13 @@ async function updateContactInRemoteStorage(contact, options) {
|
|
|
277
277
|
lastUpdatedAt: contact.lastUpdatedAt || Date.now(),
|
|
278
278
|
};
|
|
279
279
|
const key = createContactKey(contact);
|
|
280
|
-
const storageEntry = (0,
|
|
280
|
+
const storageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(updatedEntry);
|
|
281
281
|
// Save individual contact to remote storage
|
|
282
|
-
await getUserStorageControllerInstance().performSetStorage(`${
|
|
282
|
+
await getUserStorageControllerInstance().performSetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`, JSON.stringify(storageEntry));
|
|
283
283
|
};
|
|
284
284
|
if (trace) {
|
|
285
285
|
return await trace({
|
|
286
|
-
name:
|
|
286
|
+
name: constants_1.TraceName.ContactSyncUpdateRemote,
|
|
287
287
|
data: {
|
|
288
288
|
chainId: contact.chainId,
|
|
289
289
|
// Performance indicators
|
|
@@ -307,7 +307,7 @@ exports.updateContactInRemoteStorage = updateContactInRemoteStorage;
|
|
|
307
307
|
async function deleteContactInRemoteStorage(contact, options) {
|
|
308
308
|
const { trace } = options;
|
|
309
309
|
const deleteContact = async () => {
|
|
310
|
-
if (!(0,
|
|
310
|
+
if (!(0, sync_utils_1.canPerformContactSyncing)(options) ||
|
|
311
311
|
!contact.address ||
|
|
312
312
|
!contact.chainId ||
|
|
313
313
|
!contact.name?.trim()) {
|
|
@@ -317,20 +317,20 @@ async function deleteContactInRemoteStorage(contact, options) {
|
|
|
317
317
|
const key = createContactKey(contact);
|
|
318
318
|
try {
|
|
319
319
|
// Try to get the existing contact first
|
|
320
|
-
const existingContactJson = await getUserStorageControllerInstance().performGetStorage(`${
|
|
320
|
+
const existingContactJson = await getUserStorageControllerInstance().performGetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`);
|
|
321
321
|
if (existingContactJson) {
|
|
322
322
|
// Mark the existing contact as deleted
|
|
323
323
|
const existingStorageEntry = JSON.parse(existingContactJson);
|
|
324
|
-
const existingContact = (0,
|
|
324
|
+
const existingContact = (0, utils_1.mapUserStorageEntryToAddressBookEntry)(existingStorageEntry);
|
|
325
325
|
const now = Date.now();
|
|
326
326
|
const deletedContact = {
|
|
327
327
|
...existingContact,
|
|
328
328
|
deletedAt: now,
|
|
329
329
|
lastUpdatedAt: now,
|
|
330
330
|
};
|
|
331
|
-
const deletedStorageEntry = (0,
|
|
331
|
+
const deletedStorageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(deletedContact);
|
|
332
332
|
// Save the deleted contact back to storage
|
|
333
|
-
await getUserStorageControllerInstance().performSetStorage(`${
|
|
333
|
+
await getUserStorageControllerInstance().performSetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`, JSON.stringify(deletedStorageEntry));
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
catch {
|
|
@@ -339,7 +339,7 @@ async function deleteContactInRemoteStorage(contact, options) {
|
|
|
339
339
|
}
|
|
340
340
|
};
|
|
341
341
|
return trace
|
|
342
|
-
? await trace({ name:
|
|
342
|
+
? await trace({ name: constants_1.TraceName.ContactSyncDeleteRemote }, deleteContact)
|
|
343
343
|
: await deleteContact();
|
|
344
344
|
}
|
|
345
345
|
exports.deleteContactInRemoteStorage = deleteContactInRemoteStorage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-integration.cjs","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AddressBookEntry } from '@metamask/address-book-controller';\n\nimport { USER_STORAGE_FEATURE_NAMES } from '../../../shared/storage-schema.js';\nimport { TraceName } from '../constants.js';\nimport { canPerformContactSyncing } from './sync-utils.js';\nimport type { ContactSyncingOptions } from './types.js';\nimport type { UserStorageContactEntry } from './types.js';\nimport type { SyncAddressBookEntry } from './utils.js';\nimport {\n mapAddressBookEntryToUserStorageEntry,\n mapUserStorageEntryToAddressBookEntry,\n} from './utils.js';\nimport { isContactBridgedFromAccounts } from './utils.js';\n\nexport type SyncContactsWithUserStorageConfig = {\n onContactSyncErroneousSituation?: (\n errorMessage: string,\n sentryContext?: Record<string, unknown>,\n ) => void;\n onContactUpdated?: () => void;\n onContactDeleted?: () => void;\n};\n\n/**\n * Creates a unique key for a contact based on chainId and address\n *\n * @param contact - The contact to create a key for\n * @returns A unique string key\n */\nfunction createContactKey(contact: AddressBookEntry): string {\n if (!contact.address) {\n throw new Error('Contact address is required to create storage key');\n }\n return `${contact.chainId}_${contact.address.toLowerCase()}`;\n}\n\n/**\n * Syncs contacts between local storage and user storage (remote).\n *\n * Handles the following syncing scenarios:\n * 1. First Sync: When local contacts exist but there are no remote contacts, uploads all local contacts.\n * 2. New Device Sync: Downloads remote contacts that don't exist locally (empty local address book).\n * 3. Simple Merge: Ensures both sides (local & remote) have all contacts.\n * 4. Contact Naming Conflicts: When same contact has different names, uses most recent by timestamp.\n * 5. Local Updates: When a contact was updated locally, syncs changes to remote if local is newer.\n * 6. Remote Updates: When a contact was updated remotely, applies changes locally if remote is newer.\n * 7. Local Deletions: Handled by real-time event handlers (deleteContactInRemoteStorage) to prevent false positives.\n * 8. Remote Deletions: When a contact was deleted remotely, applies deletion locally.\n * 9. Concurrent Updates: Resolves conflicts using timestamps to determine the winner.\n * 10. Restore After Delete: If a contact is modified after being deleted, restores it.\n * 11. ChainId Differences: Treats same address on different chains as separate contacts.\n *\n * @param config - Parameters used for syncing callbacks\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when contact synchronization is complete\n */\nexport async function syncContactsWithUserStorage(\n config: SyncContactsWithUserStorageConfig,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getMessenger, getUserStorageControllerInstance, trace } = options;\n const {\n onContactSyncErroneousSituation,\n onContactUpdated,\n onContactDeleted,\n } = config;\n\n // Cannot perform sync, conditions not met\n if (!canPerformContactSyncing(options)) {\n return;\n }\n\n // NOTE: Pre-sync operations (canPerformContactSyncing, AddressBookController:list, getRemoteContacts)\n // are intentionally outside try-catch to let errors bubble up to Sentry for better debugging.\n // Only \"erroneous situation\" errors during sync logic itself should be caught.\n\n // Get all local contacts from AddressBookController (exclude chain \"*\" contacts)\n const localVisibleContacts =\n getMessenger()\n .call('AddressBookController:list')\n .filter((contact) => !isContactBridgedFromAccounts(contact))\n .filter(\n (contact) => contact.address && contact.chainId && contact.name?.trim(),\n ) || [];\n\n // Get remote contacts from user storage API\n const remoteContacts = await getRemoteContacts(options);\n\n // Filter remote contacts to exclude invalid ones (or empty array if no remote contacts)\n const validRemoteContacts =\n remoteContacts?.filter(\n (contact) => contact.address && contact.chainId && contact.name?.trim(),\n ) || [];\n\n const performSync = async () => {\n try {\n // Activate sync semaphore to prevent event loops\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n true,\n );\n\n // Prepare maps for efficient lookup\n const localContactsMap = new Map<string, AddressBookEntry>();\n const remoteContactsMap = new Map<string, SyncAddressBookEntry>();\n\n localVisibleContacts.forEach((contact) => {\n const key = createContactKey(contact);\n localContactsMap.set(key, contact);\n });\n\n validRemoteContacts.forEach((contact) => {\n const key = createContactKey(contact);\n remoteContactsMap.set(key, contact);\n });\n\n // Lists to track contacts that need to be synced\n const contactsToAddOrUpdateLocally: SyncAddressBookEntry[] = [];\n const contactsToDeleteLocally: SyncAddressBookEntry[] = [];\n const contactsToUpdateRemotely: AddressBookEntry[] = [];\n\n // SCENARIO 2 & 6: Process remote contacts - handle new device sync and remote updates\n for (const remoteContact of validRemoteContacts) {\n const key = createContactKey(remoteContact);\n const localContact = localContactsMap.get(key);\n\n // Handle remote contact based on its status and local existence\n if (remoteContact.deletedAt) {\n // SCENARIO 8: Remote deletion - should be applied locally if contact exists locally\n if (localContact) {\n contactsToDeleteLocally.push(remoteContact);\n }\n } else if (!localContact) {\n // SCENARIO 2: New contact from remote - import to local\n contactsToAddOrUpdateLocally.push(remoteContact);\n } else {\n // SCENARIO 4 & 6: Contact exists on both sides - check for conflicts\n const hasContentDifference =\n localContact.name !== remoteContact.name ||\n localContact.memo !== remoteContact.memo;\n\n if (hasContentDifference) {\n // Check timestamps to determine which version to keep\n const localTimestamp = localContact.lastUpdatedAt || 0;\n const remoteTimestamp = remoteContact.lastUpdatedAt || 0;\n\n if (localTimestamp >= remoteTimestamp) {\n // Local is newer (or same age) - use local version\n contactsToUpdateRemotely.push(localContact);\n } else {\n // Remote is newer - use remote version\n contactsToAddOrUpdateLocally.push(remoteContact);\n }\n }\n\n // Else: content is identical, no action needed\n }\n }\n\n // SCENARIO 1, 3 & 5: Process local contacts not in remote - handles first sync and new local contacts\n for (const localContact of localVisibleContacts) {\n const key = createContactKey(localContact);\n const remoteContact = remoteContactsMap.get(key);\n\n if (!remoteContact) {\n // New local contact or first sync - add to remote\n contactsToUpdateRemotely.push(localContact);\n }\n }\n\n // Apply local deletions\n // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n // for debugging. Previous versions silently suppressed these errors which made\n // troubleshooting contact sync issues difficult.\n for (const contact of contactsToDeleteLocally) {\n getMessenger().call(\n 'AddressBookController:delete',\n contact.chainId,\n contact.address,\n );\n\n if (onContactDeleted) {\n onContactDeleted();\n }\n }\n\n // Apply local additions/updates\n // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n // for debugging. Previous versions silently suppressed these errors which made\n // troubleshooting contact sync issues difficult.\n for (const contact of contactsToAddOrUpdateLocally) {\n if (!contact.deletedAt) {\n getMessenger().call(\n 'AddressBookController:set',\n contact.address,\n contact.name || '',\n contact.chainId,\n contact.memo || '',\n contact.addressType,\n );\n\n if (onContactUpdated) {\n onContactUpdated();\n }\n }\n }\n\n // Apply changes to remote storage\n if (contactsToUpdateRemotely.length > 0) {\n const updatedRemoteContacts: Record<string, SyncAddressBookEntry> = {};\n for (const localContact of contactsToUpdateRemotely) {\n const key = createContactKey(localContact);\n updatedRemoteContacts[key] = {\n ...remoteContactsMap.get(key), // Start with an existing remote contact if it exists\n ...localContact, // override with local changes\n lastUpdatedAt: Date.now(), // mark as updated\n };\n }\n // Save updated contacts to remote storage\n await saveContactsToUserStorage(\n Object.values(updatedRemoteContacts),\n options,\n );\n }\n } catch (error) {\n if (onContactSyncErroneousSituation) {\n onContactSyncErroneousSituation('Error synchronizing contacts', {\n error,\n });\n\n // Re-throw the error to be handled by the caller\n throw error;\n }\n } finally {\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n false,\n );\n }\n };\n\n if (trace) {\n // Gather pre-sync metrics for performance analysis\n const initialLocalContacts = localVisibleContacts;\n const initialValidRemoteContacts = validRemoteContacts;\n\n await trace(\n {\n name: TraceName.ContactSyncFull,\n data: {\n localContactCount: initialLocalContacts.length,\n remoteContactCount: initialValidRemoteContacts.length,\n isFirstSync:\n initialValidRemoteContacts.length === 0 &&\n initialLocalContacts.length > 0,\n isNewDeviceSync:\n initialLocalContacts.length === 0 &&\n initialValidRemoteContacts.length > 0,\n isRegularSync:\n initialLocalContacts.length > 0 &&\n initialValidRemoteContacts.length > 0,\n hasDataToSync:\n initialLocalContacts.length > 0 ||\n initialValidRemoteContacts.length > 0,\n expectedWorkload:\n initialLocalContacts.length + initialValidRemoteContacts.length,\n },\n },\n performSync,\n );\n\n return;\n }\n\n await performSync();\n}\n\n/**\n * Retrieves remote contacts from user storage API\n *\n * @param options - Parameters used for retrieving remote contacts\n * @returns Array of contacts from remote storage, or null if none found\n */\nasync function getRemoteContacts(\n options: ContactSyncingOptions,\n): Promise<SyncAddressBookEntry[] | null> {\n const { getUserStorageControllerInstance } = options;\n\n try {\n const remoteContactsJsonArray =\n await getUserStorageControllerInstance().performGetStorageAllFeatureEntries(\n USER_STORAGE_FEATURE_NAMES.addressBook,\n );\n\n if (!remoteContactsJsonArray || remoteContactsJsonArray.length === 0) {\n return null;\n }\n\n // Parse each JSON entry and convert from UserStorageContactEntry to AddressBookEntry\n const remoteStorageEntries = remoteContactsJsonArray.map((contactJson) => {\n const entry = JSON.parse(contactJson) as UserStorageContactEntry;\n return mapUserStorageEntryToAddressBookEntry(entry);\n });\n\n return remoteStorageEntries;\n } catch {\n return null;\n }\n}\n\n/**\n * Saves local contacts to user storage\n *\n * @param contacts - The contacts to save to user storage\n * @param options - Parameters used for saving contacts\n * @returns Promise that resolves when contacts are saved\n */\nasync function saveContactsToUserStorage(\n contacts: AddressBookEntry[],\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getUserStorageControllerInstance, trace } = options;\n\n const saveContacts = async () => {\n if (!contacts || contacts.length === 0) {\n return;\n }\n\n // Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs\n const storageEntries: [string, string][] = contacts.map((contact) => {\n const key = createContactKey(contact);\n const storageEntry = mapAddressBookEntryToUserStorageEntry(contact);\n return [key, JSON.stringify(storageEntry)];\n });\n\n await getUserStorageControllerInstance().performBatchSetStorage(\n USER_STORAGE_FEATURE_NAMES.addressBook,\n storageEntries,\n );\n };\n\n return trace\n ? await trace(\n {\n name: TraceName.ContactSyncSaveBatch,\n data: {\n contactCount: contacts.length,\n // Performance scaling indicators\n hasBatchOperations: contacts.length > 1,\n chainCount: new Set(contacts.map((c) => c.chainId)).size,\n hasMemosCount: contacts.filter((c) => c.memo?.length).length,\n },\n },\n saveContacts,\n )\n : await saveContacts();\n}\n\n/**\n * Updates a single contact in remote storage without performing a full sync\n * This is used when a contact is updated locally to efficiently push changes to remote\n *\n * @param contact - The contact that was updated locally\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is updated\n */\nexport async function updateContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { trace } = options;\n\n const updateContact = async () => {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId ||\n !contact.name?.trim()\n ) {\n return;\n }\n\n const { getUserStorageControllerInstance } = options;\n\n // Create an updated entry with timestamp\n const updatedEntry = {\n ...contact,\n lastUpdatedAt: contact.lastUpdatedAt || Date.now(),\n } as SyncAddressBookEntry;\n\n const key = createContactKey(contact);\n const storageEntry = mapAddressBookEntryToUserStorageEntry(updatedEntry);\n\n // Save individual contact to remote storage\n await getUserStorageControllerInstance().performSetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n JSON.stringify(storageEntry),\n );\n };\n\n if (trace) {\n return await trace(\n {\n name: TraceName.ContactSyncUpdateRemote,\n data: {\n chainId: contact.chainId,\n // Performance indicators\n hasTimestamp: Boolean(contact.lastUpdatedAt),\n hasMemo: Boolean(contact.memo?.length),\n isUpdate: Boolean(contact.lastUpdatedAt), // vs new contact\n },\n },\n updateContact,\n );\n }\n\n return await updateContact();\n}\n\n/**\n * Marks a single contact as deleted in remote storage without performing a full sync\n * This is used when a contact is deleted locally to efficiently push the deletion to remote\n *\n * @param contact - The contact that was deleted locally (contains at least address and chainId)\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is marked as deleted\n */\nexport async function deleteContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { trace } = options;\n const deleteContact = async () => {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId ||\n !contact.name?.trim()\n ) {\n return;\n }\n\n const { getUserStorageControllerInstance } = options;\n const key = createContactKey(contact);\n\n try {\n // Try to get the existing contact first\n const existingContactJson =\n await getUserStorageControllerInstance().performGetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n );\n\n if (existingContactJson) {\n // Mark the existing contact as deleted\n const existingStorageEntry = JSON.parse(\n existingContactJson,\n ) as UserStorageContactEntry;\n const existingContact =\n mapUserStorageEntryToAddressBookEntry(existingStorageEntry);\n\n const now = Date.now();\n const deletedContact = {\n ...existingContact,\n deletedAt: now,\n lastUpdatedAt: now,\n } as SyncAddressBookEntry;\n\n const deletedStorageEntry =\n mapAddressBookEntryToUserStorageEntry(deletedContact);\n\n // Save the deleted contact back to storage\n await getUserStorageControllerInstance().performSetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n JSON.stringify(deletedStorageEntry),\n );\n }\n } catch {\n // If contact doesn't exist in remote storage, no need to mark as deleted\n console.warn('Contact not found in remote storage for deletion:', key);\n }\n };\n\n return trace\n ? await trace({ name: TraceName.ContactSyncDeleteRemote }, deleteContact)\n : await deleteContact();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"controller-integration.cjs","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AddressBookEntry } from '@metamask/address-book-controller';\n\nimport { USER_STORAGE_FEATURE_NAMES } from '../../../shared/storage-schema';\nimport { TraceName } from '../constants';\nimport { canPerformContactSyncing } from './sync-utils';\nimport type { ContactSyncingOptions } from './types';\nimport type { UserStorageContactEntry } from './types';\nimport type { SyncAddressBookEntry } from './utils';\nimport {\n mapAddressBookEntryToUserStorageEntry,\n mapUserStorageEntryToAddressBookEntry,\n} from './utils';\nimport { isContactBridgedFromAccounts } from './utils';\n\nexport type SyncContactsWithUserStorageConfig = {\n onContactSyncErroneousSituation?: (\n errorMessage: string,\n sentryContext?: Record<string, unknown>,\n ) => void;\n onContactUpdated?: () => void;\n onContactDeleted?: () => void;\n};\n\n/**\n * Creates a unique key for a contact based on chainId and address\n *\n * @param contact - The contact to create a key for\n * @returns A unique string key\n */\nfunction createContactKey(contact: AddressBookEntry): string {\n if (!contact.address) {\n throw new Error('Contact address is required to create storage key');\n }\n return `${contact.chainId}_${contact.address.toLowerCase()}`;\n}\n\n/**\n * Syncs contacts between local storage and user storage (remote).\n *\n * Handles the following syncing scenarios:\n * 1. First Sync: When local contacts exist but there are no remote contacts, uploads all local contacts.\n * 2. New Device Sync: Downloads remote contacts that don't exist locally (empty local address book).\n * 3. Simple Merge: Ensures both sides (local & remote) have all contacts.\n * 4. Contact Naming Conflicts: When same contact has different names, uses most recent by timestamp.\n * 5. Local Updates: When a contact was updated locally, syncs changes to remote if local is newer.\n * 6. Remote Updates: When a contact was updated remotely, applies changes locally if remote is newer.\n * 7. Local Deletions: Handled by real-time event handlers (deleteContactInRemoteStorage) to prevent false positives.\n * 8. Remote Deletions: When a contact was deleted remotely, applies deletion locally.\n * 9. Concurrent Updates: Resolves conflicts using timestamps to determine the winner.\n * 10. Restore After Delete: If a contact is modified after being deleted, restores it.\n * 11. ChainId Differences: Treats same address on different chains as separate contacts.\n *\n * @param config - Parameters used for syncing callbacks\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when contact synchronization is complete\n */\nexport async function syncContactsWithUserStorage(\n config: SyncContactsWithUserStorageConfig,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getMessenger, getUserStorageControllerInstance, trace } = options;\n const {\n onContactSyncErroneousSituation,\n onContactUpdated,\n onContactDeleted,\n } = config;\n\n // Cannot perform sync, conditions not met\n if (!canPerformContactSyncing(options)) {\n return;\n }\n\n // NOTE: Pre-sync operations (canPerformContactSyncing, AddressBookController:list, getRemoteContacts)\n // are intentionally outside try-catch to let errors bubble up to Sentry for better debugging.\n // Only \"erroneous situation\" errors during sync logic itself should be caught.\n\n // Get all local contacts from AddressBookController (exclude chain \"*\" contacts)\n const localVisibleContacts =\n getMessenger()\n .call('AddressBookController:list')\n .filter((contact) => !isContactBridgedFromAccounts(contact))\n .filter(\n (contact) => contact.address && contact.chainId && contact.name?.trim(),\n ) || [];\n\n // Get remote contacts from user storage API\n const remoteContacts = await getRemoteContacts(options);\n\n // Filter remote contacts to exclude invalid ones (or empty array if no remote contacts)\n const validRemoteContacts =\n remoteContacts?.filter(\n (contact) => contact.address && contact.chainId && contact.name?.trim(),\n ) || [];\n\n const performSync = async () => {\n try {\n // Activate sync semaphore to prevent event loops\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n true,\n );\n\n // Prepare maps for efficient lookup\n const localContactsMap = new Map<string, AddressBookEntry>();\n const remoteContactsMap = new Map<string, SyncAddressBookEntry>();\n\n localVisibleContacts.forEach((contact) => {\n const key = createContactKey(contact);\n localContactsMap.set(key, contact);\n });\n\n validRemoteContacts.forEach((contact) => {\n const key = createContactKey(contact);\n remoteContactsMap.set(key, contact);\n });\n\n // Lists to track contacts that need to be synced\n const contactsToAddOrUpdateLocally: SyncAddressBookEntry[] = [];\n const contactsToDeleteLocally: SyncAddressBookEntry[] = [];\n const contactsToUpdateRemotely: AddressBookEntry[] = [];\n\n // SCENARIO 2 & 6: Process remote contacts - handle new device sync and remote updates\n for (const remoteContact of validRemoteContacts) {\n const key = createContactKey(remoteContact);\n const localContact = localContactsMap.get(key);\n\n // Handle remote contact based on its status and local existence\n if (remoteContact.deletedAt) {\n // SCENARIO 8: Remote deletion - should be applied locally if contact exists locally\n if (localContact) {\n contactsToDeleteLocally.push(remoteContact);\n }\n } else if (!localContact) {\n // SCENARIO 2: New contact from remote - import to local\n contactsToAddOrUpdateLocally.push(remoteContact);\n } else {\n // SCENARIO 4 & 6: Contact exists on both sides - check for conflicts\n const hasContentDifference =\n localContact.name !== remoteContact.name ||\n localContact.memo !== remoteContact.memo;\n\n if (hasContentDifference) {\n // Check timestamps to determine which version to keep\n const localTimestamp = localContact.lastUpdatedAt || 0;\n const remoteTimestamp = remoteContact.lastUpdatedAt || 0;\n\n if (localTimestamp >= remoteTimestamp) {\n // Local is newer (or same age) - use local version\n contactsToUpdateRemotely.push(localContact);\n } else {\n // Remote is newer - use remote version\n contactsToAddOrUpdateLocally.push(remoteContact);\n }\n }\n\n // Else: content is identical, no action needed\n }\n }\n\n // SCENARIO 1, 3 & 5: Process local contacts not in remote - handles first sync and new local contacts\n for (const localContact of localVisibleContacts) {\n const key = createContactKey(localContact);\n const remoteContact = remoteContactsMap.get(key);\n\n if (!remoteContact) {\n // New local contact or first sync - add to remote\n contactsToUpdateRemotely.push(localContact);\n }\n }\n\n // Apply local deletions\n // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n // for debugging. Previous versions silently suppressed these errors which made\n // troubleshooting contact sync issues difficult.\n for (const contact of contactsToDeleteLocally) {\n getMessenger().call(\n 'AddressBookController:delete',\n contact.chainId,\n contact.address,\n );\n\n if (onContactDeleted) {\n onContactDeleted();\n }\n }\n\n // Apply local additions/updates\n // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n // for debugging. Previous versions silently suppressed these errors which made\n // troubleshooting contact sync issues difficult.\n for (const contact of contactsToAddOrUpdateLocally) {\n if (!contact.deletedAt) {\n getMessenger().call(\n 'AddressBookController:set',\n contact.address,\n contact.name || '',\n contact.chainId,\n contact.memo || '',\n contact.addressType,\n );\n\n if (onContactUpdated) {\n onContactUpdated();\n }\n }\n }\n\n // Apply changes to remote storage\n if (contactsToUpdateRemotely.length > 0) {\n const updatedRemoteContacts: Record<string, SyncAddressBookEntry> = {};\n for (const localContact of contactsToUpdateRemotely) {\n const key = createContactKey(localContact);\n updatedRemoteContacts[key] = {\n ...remoteContactsMap.get(key), // Start with an existing remote contact if it exists\n ...localContact, // override with local changes\n lastUpdatedAt: Date.now(), // mark as updated\n };\n }\n // Save updated contacts to remote storage\n await saveContactsToUserStorage(\n Object.values(updatedRemoteContacts),\n options,\n );\n }\n } catch (error) {\n if (onContactSyncErroneousSituation) {\n onContactSyncErroneousSituation('Error synchronizing contacts', {\n error,\n });\n\n // Re-throw the error to be handled by the caller\n throw error;\n }\n } finally {\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n false,\n );\n }\n };\n\n if (trace) {\n // Gather pre-sync metrics for performance analysis\n const initialLocalContacts = localVisibleContacts;\n const initialValidRemoteContacts = validRemoteContacts;\n\n await trace(\n {\n name: TraceName.ContactSyncFull,\n data: {\n localContactCount: initialLocalContacts.length,\n remoteContactCount: initialValidRemoteContacts.length,\n isFirstSync:\n initialValidRemoteContacts.length === 0 &&\n initialLocalContacts.length > 0,\n isNewDeviceSync:\n initialLocalContacts.length === 0 &&\n initialValidRemoteContacts.length > 0,\n isRegularSync:\n initialLocalContacts.length > 0 &&\n initialValidRemoteContacts.length > 0,\n hasDataToSync:\n initialLocalContacts.length > 0 ||\n initialValidRemoteContacts.length > 0,\n expectedWorkload:\n initialLocalContacts.length + initialValidRemoteContacts.length,\n },\n },\n performSync,\n );\n\n return;\n }\n\n await performSync();\n}\n\n/**\n * Retrieves remote contacts from user storage API\n *\n * @param options - Parameters used for retrieving remote contacts\n * @returns Array of contacts from remote storage, or null if none found\n */\nasync function getRemoteContacts(\n options: ContactSyncingOptions,\n): Promise<SyncAddressBookEntry[] | null> {\n const { getUserStorageControllerInstance } = options;\n\n try {\n const remoteContactsJsonArray =\n await getUserStorageControllerInstance().performGetStorageAllFeatureEntries(\n USER_STORAGE_FEATURE_NAMES.addressBook,\n );\n\n if (!remoteContactsJsonArray || remoteContactsJsonArray.length === 0) {\n return null;\n }\n\n // Parse each JSON entry and convert from UserStorageContactEntry to AddressBookEntry\n const remoteStorageEntries = remoteContactsJsonArray.map((contactJson) => {\n const entry = JSON.parse(contactJson) as UserStorageContactEntry;\n return mapUserStorageEntryToAddressBookEntry(entry);\n });\n\n return remoteStorageEntries;\n } catch {\n return null;\n }\n}\n\n/**\n * Saves local contacts to user storage\n *\n * @param contacts - The contacts to save to user storage\n * @param options - Parameters used for saving contacts\n * @returns Promise that resolves when contacts are saved\n */\nasync function saveContactsToUserStorage(\n contacts: AddressBookEntry[],\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getUserStorageControllerInstance, trace } = options;\n\n const saveContacts = async () => {\n if (!contacts || contacts.length === 0) {\n return;\n }\n\n // Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs\n const storageEntries: [string, string][] = contacts.map((contact) => {\n const key = createContactKey(contact);\n const storageEntry = mapAddressBookEntryToUserStorageEntry(contact);\n return [key, JSON.stringify(storageEntry)];\n });\n\n await getUserStorageControllerInstance().performBatchSetStorage(\n USER_STORAGE_FEATURE_NAMES.addressBook,\n storageEntries,\n );\n };\n\n return trace\n ? await trace(\n {\n name: TraceName.ContactSyncSaveBatch,\n data: {\n contactCount: contacts.length,\n // Performance scaling indicators\n hasBatchOperations: contacts.length > 1,\n chainCount: new Set(contacts.map((c) => c.chainId)).size,\n hasMemosCount: contacts.filter((c) => c.memo?.length).length,\n },\n },\n saveContacts,\n )\n : await saveContacts();\n}\n\n/**\n * Updates a single contact in remote storage without performing a full sync\n * This is used when a contact is updated locally to efficiently push changes to remote\n *\n * @param contact - The contact that was updated locally\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is updated\n */\nexport async function updateContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { trace } = options;\n\n const updateContact = async () => {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId ||\n !contact.name?.trim()\n ) {\n return;\n }\n\n const { getUserStorageControllerInstance } = options;\n\n // Create an updated entry with timestamp\n const updatedEntry = {\n ...contact,\n lastUpdatedAt: contact.lastUpdatedAt || Date.now(),\n } as SyncAddressBookEntry;\n\n const key = createContactKey(contact);\n const storageEntry = mapAddressBookEntryToUserStorageEntry(updatedEntry);\n\n // Save individual contact to remote storage\n await getUserStorageControllerInstance().performSetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n JSON.stringify(storageEntry),\n );\n };\n\n if (trace) {\n return await trace(\n {\n name: TraceName.ContactSyncUpdateRemote,\n data: {\n chainId: contact.chainId,\n // Performance indicators\n hasTimestamp: Boolean(contact.lastUpdatedAt),\n hasMemo: Boolean(contact.memo?.length),\n isUpdate: Boolean(contact.lastUpdatedAt), // vs new contact\n },\n },\n updateContact,\n );\n }\n\n return await updateContact();\n}\n\n/**\n * Marks a single contact as deleted in remote storage without performing a full sync\n * This is used when a contact is deleted locally to efficiently push the deletion to remote\n *\n * @param contact - The contact that was deleted locally (contains at least address and chainId)\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is marked as deleted\n */\nexport async function deleteContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { trace } = options;\n const deleteContact = async () => {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId ||\n !contact.name?.trim()\n ) {\n return;\n }\n\n const { getUserStorageControllerInstance } = options;\n const key = createContactKey(contact);\n\n try {\n // Try to get the existing contact first\n const existingContactJson =\n await getUserStorageControllerInstance().performGetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n );\n\n if (existingContactJson) {\n // Mark the existing contact as deleted\n const existingStorageEntry = JSON.parse(\n existingContactJson,\n ) as UserStorageContactEntry;\n const existingContact =\n mapUserStorageEntryToAddressBookEntry(existingStorageEntry);\n\n const now = Date.now();\n const deletedContact = {\n ...existingContact,\n deletedAt: now,\n lastUpdatedAt: now,\n } as SyncAddressBookEntry;\n\n const deletedStorageEntry =\n mapAddressBookEntryToUserStorageEntry(deletedContact);\n\n // Save the deleted contact back to storage\n await getUserStorageControllerInstance().performSetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n JSON.stringify(deletedStorageEntry),\n );\n }\n } catch {\n // If contact doesn't exist in remote storage, no need to mark as deleted\n console.warn('Contact not found in remote storage for deletion:', key);\n }\n };\n\n return trace\n ? await trace({ name: TraceName.ContactSyncDeleteRemote }, deleteContact)\n : await deleteContact();\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-integration.d.cts","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,0CAA0C;AAK1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"controller-integration.d.cts","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,0CAA0C;AAK1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAgB;AASrD,MAAM,MAAM,iCAAiC,GAAG;IAC9C,+BAA+B,CAAC,EAAE,CAChC,YAAY,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACpC,IAAI,CAAC;IACV,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B,CAAC;AAeF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,iCAAiC,EACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAsNf;AAmFD;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgDf;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAuDf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-integration.d.mts","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,0CAA0C;AAK1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"controller-integration.d.mts","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,0CAA0C;AAK1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAgB;AASrD,MAAM,MAAM,iCAAiC,GAAG;IAC9C,+BAA+B,CAAC,EAAE,CAChC,YAAY,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACpC,IAAI,CAAC;IACV,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B,CAAC;AAeF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,iCAAiC,EACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAsNf;AAmFD;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgDf;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAuDf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-integration.mjs","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AddressBookEntry } from '@metamask/address-book-controller';\n\nimport { USER_STORAGE_FEATURE_NAMES } from '../../../shared/storage-schema.js';\nimport { TraceName } from '../constants.js';\nimport { canPerformContactSyncing } from './sync-utils.js';\nimport type { ContactSyncingOptions } from './types.js';\nimport type { UserStorageContactEntry } from './types.js';\nimport type { SyncAddressBookEntry } from './utils.js';\nimport {\n mapAddressBookEntryToUserStorageEntry,\n mapUserStorageEntryToAddressBookEntry,\n} from './utils.js';\nimport { isContactBridgedFromAccounts } from './utils.js';\n\nexport type SyncContactsWithUserStorageConfig = {\n onContactSyncErroneousSituation?: (\n errorMessage: string,\n sentryContext?: Record<string, unknown>,\n ) => void;\n onContactUpdated?: () => void;\n onContactDeleted?: () => void;\n};\n\n/**\n * Creates a unique key for a contact based on chainId and address\n *\n * @param contact - The contact to create a key for\n * @returns A unique string key\n */\nfunction createContactKey(contact: AddressBookEntry): string {\n if (!contact.address) {\n throw new Error('Contact address is required to create storage key');\n }\n return `${contact.chainId}_${contact.address.toLowerCase()}`;\n}\n\n/**\n * Syncs contacts between local storage and user storage (remote).\n *\n * Handles the following syncing scenarios:\n * 1. First Sync: When local contacts exist but there are no remote contacts, uploads all local contacts.\n * 2. New Device Sync: Downloads remote contacts that don't exist locally (empty local address book).\n * 3. Simple Merge: Ensures both sides (local & remote) have all contacts.\n * 4. Contact Naming Conflicts: When same contact has different names, uses most recent by timestamp.\n * 5. Local Updates: When a contact was updated locally, syncs changes to remote if local is newer.\n * 6. Remote Updates: When a contact was updated remotely, applies changes locally if remote is newer.\n * 7. Local Deletions: Handled by real-time event handlers (deleteContactInRemoteStorage) to prevent false positives.\n * 8. Remote Deletions: When a contact was deleted remotely, applies deletion locally.\n * 9. Concurrent Updates: Resolves conflicts using timestamps to determine the winner.\n * 10. Restore After Delete: If a contact is modified after being deleted, restores it.\n * 11. ChainId Differences: Treats same address on different chains as separate contacts.\n *\n * @param config - Parameters used for syncing callbacks\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when contact synchronization is complete\n */\nexport async function syncContactsWithUserStorage(\n config: SyncContactsWithUserStorageConfig,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getMessenger, getUserStorageControllerInstance, trace } = options;\n const {\n onContactSyncErroneousSituation,\n onContactUpdated,\n onContactDeleted,\n } = config;\n\n // Cannot perform sync, conditions not met\n if (!canPerformContactSyncing(options)) {\n return;\n }\n\n // NOTE: Pre-sync operations (canPerformContactSyncing, AddressBookController:list, getRemoteContacts)\n // are intentionally outside try-catch to let errors bubble up to Sentry for better debugging.\n // Only \"erroneous situation\" errors during sync logic itself should be caught.\n\n // Get all local contacts from AddressBookController (exclude chain \"*\" contacts)\n const localVisibleContacts =\n getMessenger()\n .call('AddressBookController:list')\n .filter((contact) => !isContactBridgedFromAccounts(contact))\n .filter(\n (contact) => contact.address && contact.chainId && contact.name?.trim(),\n ) || [];\n\n // Get remote contacts from user storage API\n const remoteContacts = await getRemoteContacts(options);\n\n // Filter remote contacts to exclude invalid ones (or empty array if no remote contacts)\n const validRemoteContacts =\n remoteContacts?.filter(\n (contact) => contact.address && contact.chainId && contact.name?.trim(),\n ) || [];\n\n const performSync = async () => {\n try {\n // Activate sync semaphore to prevent event loops\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n true,\n );\n\n // Prepare maps for efficient lookup\n const localContactsMap = new Map<string, AddressBookEntry>();\n const remoteContactsMap = new Map<string, SyncAddressBookEntry>();\n\n localVisibleContacts.forEach((contact) => {\n const key = createContactKey(contact);\n localContactsMap.set(key, contact);\n });\n\n validRemoteContacts.forEach((contact) => {\n const key = createContactKey(contact);\n remoteContactsMap.set(key, contact);\n });\n\n // Lists to track contacts that need to be synced\n const contactsToAddOrUpdateLocally: SyncAddressBookEntry[] = [];\n const contactsToDeleteLocally: SyncAddressBookEntry[] = [];\n const contactsToUpdateRemotely: AddressBookEntry[] = [];\n\n // SCENARIO 2 & 6: Process remote contacts - handle new device sync and remote updates\n for (const remoteContact of validRemoteContacts) {\n const key = createContactKey(remoteContact);\n const localContact = localContactsMap.get(key);\n\n // Handle remote contact based on its status and local existence\n if (remoteContact.deletedAt) {\n // SCENARIO 8: Remote deletion - should be applied locally if contact exists locally\n if (localContact) {\n contactsToDeleteLocally.push(remoteContact);\n }\n } else if (!localContact) {\n // SCENARIO 2: New contact from remote - import to local\n contactsToAddOrUpdateLocally.push(remoteContact);\n } else {\n // SCENARIO 4 & 6: Contact exists on both sides - check for conflicts\n const hasContentDifference =\n localContact.name !== remoteContact.name ||\n localContact.memo !== remoteContact.memo;\n\n if (hasContentDifference) {\n // Check timestamps to determine which version to keep\n const localTimestamp = localContact.lastUpdatedAt || 0;\n const remoteTimestamp = remoteContact.lastUpdatedAt || 0;\n\n if (localTimestamp >= remoteTimestamp) {\n // Local is newer (or same age) - use local version\n contactsToUpdateRemotely.push(localContact);\n } else {\n // Remote is newer - use remote version\n contactsToAddOrUpdateLocally.push(remoteContact);\n }\n }\n\n // Else: content is identical, no action needed\n }\n }\n\n // SCENARIO 1, 3 & 5: Process local contacts not in remote - handles first sync and new local contacts\n for (const localContact of localVisibleContacts) {\n const key = createContactKey(localContact);\n const remoteContact = remoteContactsMap.get(key);\n\n if (!remoteContact) {\n // New local contact or first sync - add to remote\n contactsToUpdateRemotely.push(localContact);\n }\n }\n\n // Apply local deletions\n // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n // for debugging. Previous versions silently suppressed these errors which made\n // troubleshooting contact sync issues difficult.\n for (const contact of contactsToDeleteLocally) {\n getMessenger().call(\n 'AddressBookController:delete',\n contact.chainId,\n contact.address,\n );\n\n if (onContactDeleted) {\n onContactDeleted();\n }\n }\n\n // Apply local additions/updates\n // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n // for debugging. Previous versions silently suppressed these errors which made\n // troubleshooting contact sync issues difficult.\n for (const contact of contactsToAddOrUpdateLocally) {\n if (!contact.deletedAt) {\n getMessenger().call(\n 'AddressBookController:set',\n contact.address,\n contact.name || '',\n contact.chainId,\n contact.memo || '',\n contact.addressType,\n );\n\n if (onContactUpdated) {\n onContactUpdated();\n }\n }\n }\n\n // Apply changes to remote storage\n if (contactsToUpdateRemotely.length > 0) {\n const updatedRemoteContacts: Record<string, SyncAddressBookEntry> = {};\n for (const localContact of contactsToUpdateRemotely) {\n const key = createContactKey(localContact);\n updatedRemoteContacts[key] = {\n ...remoteContactsMap.get(key), // Start with an existing remote contact if it exists\n ...localContact, // override with local changes\n lastUpdatedAt: Date.now(), // mark as updated\n };\n }\n // Save updated contacts to remote storage\n await saveContactsToUserStorage(\n Object.values(updatedRemoteContacts),\n options,\n );\n }\n } catch (error) {\n if (onContactSyncErroneousSituation) {\n onContactSyncErroneousSituation('Error synchronizing contacts', {\n error,\n });\n\n // Re-throw the error to be handled by the caller\n throw error;\n }\n } finally {\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n false,\n );\n }\n };\n\n if (trace) {\n // Gather pre-sync metrics for performance analysis\n const initialLocalContacts = localVisibleContacts;\n const initialValidRemoteContacts = validRemoteContacts;\n\n await trace(\n {\n name: TraceName.ContactSyncFull,\n data: {\n localContactCount: initialLocalContacts.length,\n remoteContactCount: initialValidRemoteContacts.length,\n isFirstSync:\n initialValidRemoteContacts.length === 0 &&\n initialLocalContacts.length > 0,\n isNewDeviceSync:\n initialLocalContacts.length === 0 &&\n initialValidRemoteContacts.length > 0,\n isRegularSync:\n initialLocalContacts.length > 0 &&\n initialValidRemoteContacts.length > 0,\n hasDataToSync:\n initialLocalContacts.length > 0 ||\n initialValidRemoteContacts.length > 0,\n expectedWorkload:\n initialLocalContacts.length + initialValidRemoteContacts.length,\n },\n },\n performSync,\n );\n\n return;\n }\n\n await performSync();\n}\n\n/**\n * Retrieves remote contacts from user storage API\n *\n * @param options - Parameters used for retrieving remote contacts\n * @returns Array of contacts from remote storage, or null if none found\n */\nasync function getRemoteContacts(\n options: ContactSyncingOptions,\n): Promise<SyncAddressBookEntry[] | null> {\n const { getUserStorageControllerInstance } = options;\n\n try {\n const remoteContactsJsonArray =\n await getUserStorageControllerInstance().performGetStorageAllFeatureEntries(\n USER_STORAGE_FEATURE_NAMES.addressBook,\n );\n\n if (!remoteContactsJsonArray || remoteContactsJsonArray.length === 0) {\n return null;\n }\n\n // Parse each JSON entry and convert from UserStorageContactEntry to AddressBookEntry\n const remoteStorageEntries = remoteContactsJsonArray.map((contactJson) => {\n const entry = JSON.parse(contactJson) as UserStorageContactEntry;\n return mapUserStorageEntryToAddressBookEntry(entry);\n });\n\n return remoteStorageEntries;\n } catch {\n return null;\n }\n}\n\n/**\n * Saves local contacts to user storage\n *\n * @param contacts - The contacts to save to user storage\n * @param options - Parameters used for saving contacts\n * @returns Promise that resolves when contacts are saved\n */\nasync function saveContactsToUserStorage(\n contacts: AddressBookEntry[],\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getUserStorageControllerInstance, trace } = options;\n\n const saveContacts = async () => {\n if (!contacts || contacts.length === 0) {\n return;\n }\n\n // Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs\n const storageEntries: [string, string][] = contacts.map((contact) => {\n const key = createContactKey(contact);\n const storageEntry = mapAddressBookEntryToUserStorageEntry(contact);\n return [key, JSON.stringify(storageEntry)];\n });\n\n await getUserStorageControllerInstance().performBatchSetStorage(\n USER_STORAGE_FEATURE_NAMES.addressBook,\n storageEntries,\n );\n };\n\n return trace\n ? await trace(\n {\n name: TraceName.ContactSyncSaveBatch,\n data: {\n contactCount: contacts.length,\n // Performance scaling indicators\n hasBatchOperations: contacts.length > 1,\n chainCount: new Set(contacts.map((c) => c.chainId)).size,\n hasMemosCount: contacts.filter((c) => c.memo?.length).length,\n },\n },\n saveContacts,\n )\n : await saveContacts();\n}\n\n/**\n * Updates a single contact in remote storage without performing a full sync\n * This is used when a contact is updated locally to efficiently push changes to remote\n *\n * @param contact - The contact that was updated locally\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is updated\n */\nexport async function updateContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { trace } = options;\n\n const updateContact = async () => {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId ||\n !contact.name?.trim()\n ) {\n return;\n }\n\n const { getUserStorageControllerInstance } = options;\n\n // Create an updated entry with timestamp\n const updatedEntry = {\n ...contact,\n lastUpdatedAt: contact.lastUpdatedAt || Date.now(),\n } as SyncAddressBookEntry;\n\n const key = createContactKey(contact);\n const storageEntry = mapAddressBookEntryToUserStorageEntry(updatedEntry);\n\n // Save individual contact to remote storage\n await getUserStorageControllerInstance().performSetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n JSON.stringify(storageEntry),\n );\n };\n\n if (trace) {\n return await trace(\n {\n name: TraceName.ContactSyncUpdateRemote,\n data: {\n chainId: contact.chainId,\n // Performance indicators\n hasTimestamp: Boolean(contact.lastUpdatedAt),\n hasMemo: Boolean(contact.memo?.length),\n isUpdate: Boolean(contact.lastUpdatedAt), // vs new contact\n },\n },\n updateContact,\n );\n }\n\n return await updateContact();\n}\n\n/**\n * Marks a single contact as deleted in remote storage without performing a full sync\n * This is used when a contact is deleted locally to efficiently push the deletion to remote\n *\n * @param contact - The contact that was deleted locally (contains at least address and chainId)\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is marked as deleted\n */\nexport async function deleteContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { trace } = options;\n const deleteContact = async () => {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId ||\n !contact.name?.trim()\n ) {\n return;\n }\n\n const { getUserStorageControllerInstance } = options;\n const key = createContactKey(contact);\n\n try {\n // Try to get the existing contact first\n const existingContactJson =\n await getUserStorageControllerInstance().performGetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n );\n\n if (existingContactJson) {\n // Mark the existing contact as deleted\n const existingStorageEntry = JSON.parse(\n existingContactJson,\n ) as UserStorageContactEntry;\n const existingContact =\n mapUserStorageEntryToAddressBookEntry(existingStorageEntry);\n\n const now = Date.now();\n const deletedContact = {\n ...existingContact,\n deletedAt: now,\n lastUpdatedAt: now,\n } as SyncAddressBookEntry;\n\n const deletedStorageEntry =\n mapAddressBookEntryToUserStorageEntry(deletedContact);\n\n // Save the deleted contact back to storage\n await getUserStorageControllerInstance().performSetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n JSON.stringify(deletedStorageEntry),\n );\n }\n } catch {\n // If contact doesn't exist in remote storage, no need to mark as deleted\n console.warn('Contact not found in remote storage for deletion:', key);\n }\n };\n\n return trace\n ? await trace({ name: TraceName.ContactSyncDeleteRemote }, deleteContact)\n : await deleteContact();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"controller-integration.mjs","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AddressBookEntry } from '@metamask/address-book-controller';\n\nimport { USER_STORAGE_FEATURE_NAMES } from '../../../shared/storage-schema';\nimport { TraceName } from '../constants';\nimport { canPerformContactSyncing } from './sync-utils';\nimport type { ContactSyncingOptions } from './types';\nimport type { UserStorageContactEntry } from './types';\nimport type { SyncAddressBookEntry } from './utils';\nimport {\n mapAddressBookEntryToUserStorageEntry,\n mapUserStorageEntryToAddressBookEntry,\n} from './utils';\nimport { isContactBridgedFromAccounts } from './utils';\n\nexport type SyncContactsWithUserStorageConfig = {\n onContactSyncErroneousSituation?: (\n errorMessage: string,\n sentryContext?: Record<string, unknown>,\n ) => void;\n onContactUpdated?: () => void;\n onContactDeleted?: () => void;\n};\n\n/**\n * Creates a unique key for a contact based on chainId and address\n *\n * @param contact - The contact to create a key for\n * @returns A unique string key\n */\nfunction createContactKey(contact: AddressBookEntry): string {\n if (!contact.address) {\n throw new Error('Contact address is required to create storage key');\n }\n return `${contact.chainId}_${contact.address.toLowerCase()}`;\n}\n\n/**\n * Syncs contacts between local storage and user storage (remote).\n *\n * Handles the following syncing scenarios:\n * 1. First Sync: When local contacts exist but there are no remote contacts, uploads all local contacts.\n * 2. New Device Sync: Downloads remote contacts that don't exist locally (empty local address book).\n * 3. Simple Merge: Ensures both sides (local & remote) have all contacts.\n * 4. Contact Naming Conflicts: When same contact has different names, uses most recent by timestamp.\n * 5. Local Updates: When a contact was updated locally, syncs changes to remote if local is newer.\n * 6. Remote Updates: When a contact was updated remotely, applies changes locally if remote is newer.\n * 7. Local Deletions: Handled by real-time event handlers (deleteContactInRemoteStorage) to prevent false positives.\n * 8. Remote Deletions: When a contact was deleted remotely, applies deletion locally.\n * 9. Concurrent Updates: Resolves conflicts using timestamps to determine the winner.\n * 10. Restore After Delete: If a contact is modified after being deleted, restores it.\n * 11. ChainId Differences: Treats same address on different chains as separate contacts.\n *\n * @param config - Parameters used for syncing callbacks\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when contact synchronization is complete\n */\nexport async function syncContactsWithUserStorage(\n config: SyncContactsWithUserStorageConfig,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getMessenger, getUserStorageControllerInstance, trace } = options;\n const {\n onContactSyncErroneousSituation,\n onContactUpdated,\n onContactDeleted,\n } = config;\n\n // Cannot perform sync, conditions not met\n if (!canPerformContactSyncing(options)) {\n return;\n }\n\n // NOTE: Pre-sync operations (canPerformContactSyncing, AddressBookController:list, getRemoteContacts)\n // are intentionally outside try-catch to let errors bubble up to Sentry for better debugging.\n // Only \"erroneous situation\" errors during sync logic itself should be caught.\n\n // Get all local contacts from AddressBookController (exclude chain \"*\" contacts)\n const localVisibleContacts =\n getMessenger()\n .call('AddressBookController:list')\n .filter((contact) => !isContactBridgedFromAccounts(contact))\n .filter(\n (contact) => contact.address && contact.chainId && contact.name?.trim(),\n ) || [];\n\n // Get remote contacts from user storage API\n const remoteContacts = await getRemoteContacts(options);\n\n // Filter remote contacts to exclude invalid ones (or empty array if no remote contacts)\n const validRemoteContacts =\n remoteContacts?.filter(\n (contact) => contact.address && contact.chainId && contact.name?.trim(),\n ) || [];\n\n const performSync = async () => {\n try {\n // Activate sync semaphore to prevent event loops\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n true,\n );\n\n // Prepare maps for efficient lookup\n const localContactsMap = new Map<string, AddressBookEntry>();\n const remoteContactsMap = new Map<string, SyncAddressBookEntry>();\n\n localVisibleContacts.forEach((contact) => {\n const key = createContactKey(contact);\n localContactsMap.set(key, contact);\n });\n\n validRemoteContacts.forEach((contact) => {\n const key = createContactKey(contact);\n remoteContactsMap.set(key, contact);\n });\n\n // Lists to track contacts that need to be synced\n const contactsToAddOrUpdateLocally: SyncAddressBookEntry[] = [];\n const contactsToDeleteLocally: SyncAddressBookEntry[] = [];\n const contactsToUpdateRemotely: AddressBookEntry[] = [];\n\n // SCENARIO 2 & 6: Process remote contacts - handle new device sync and remote updates\n for (const remoteContact of validRemoteContacts) {\n const key = createContactKey(remoteContact);\n const localContact = localContactsMap.get(key);\n\n // Handle remote contact based on its status and local existence\n if (remoteContact.deletedAt) {\n // SCENARIO 8: Remote deletion - should be applied locally if contact exists locally\n if (localContact) {\n contactsToDeleteLocally.push(remoteContact);\n }\n } else if (!localContact) {\n // SCENARIO 2: New contact from remote - import to local\n contactsToAddOrUpdateLocally.push(remoteContact);\n } else {\n // SCENARIO 4 & 6: Contact exists on both sides - check for conflicts\n const hasContentDifference =\n localContact.name !== remoteContact.name ||\n localContact.memo !== remoteContact.memo;\n\n if (hasContentDifference) {\n // Check timestamps to determine which version to keep\n const localTimestamp = localContact.lastUpdatedAt || 0;\n const remoteTimestamp = remoteContact.lastUpdatedAt || 0;\n\n if (localTimestamp >= remoteTimestamp) {\n // Local is newer (or same age) - use local version\n contactsToUpdateRemotely.push(localContact);\n } else {\n // Remote is newer - use remote version\n contactsToAddOrUpdateLocally.push(remoteContact);\n }\n }\n\n // Else: content is identical, no action needed\n }\n }\n\n // SCENARIO 1, 3 & 5: Process local contacts not in remote - handles first sync and new local contacts\n for (const localContact of localVisibleContacts) {\n const key = createContactKey(localContact);\n const remoteContact = remoteContactsMap.get(key);\n\n if (!remoteContact) {\n // New local contact or first sync - add to remote\n contactsToUpdateRemotely.push(localContact);\n }\n }\n\n // Apply local deletions\n // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n // for debugging. Previous versions silently suppressed these errors which made\n // troubleshooting contact sync issues difficult.\n for (const contact of contactsToDeleteLocally) {\n getMessenger().call(\n 'AddressBookController:delete',\n contact.chainId,\n contact.address,\n );\n\n if (onContactDeleted) {\n onContactDeleted();\n }\n }\n\n // Apply local additions/updates\n // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n // for debugging. Previous versions silently suppressed these errors which made\n // troubleshooting contact sync issues difficult.\n for (const contact of contactsToAddOrUpdateLocally) {\n if (!contact.deletedAt) {\n getMessenger().call(\n 'AddressBookController:set',\n contact.address,\n contact.name || '',\n contact.chainId,\n contact.memo || '',\n contact.addressType,\n );\n\n if (onContactUpdated) {\n onContactUpdated();\n }\n }\n }\n\n // Apply changes to remote storage\n if (contactsToUpdateRemotely.length > 0) {\n const updatedRemoteContacts: Record<string, SyncAddressBookEntry> = {};\n for (const localContact of contactsToUpdateRemotely) {\n const key = createContactKey(localContact);\n updatedRemoteContacts[key] = {\n ...remoteContactsMap.get(key), // Start with an existing remote contact if it exists\n ...localContact, // override with local changes\n lastUpdatedAt: Date.now(), // mark as updated\n };\n }\n // Save updated contacts to remote storage\n await saveContactsToUserStorage(\n Object.values(updatedRemoteContacts),\n options,\n );\n }\n } catch (error) {\n if (onContactSyncErroneousSituation) {\n onContactSyncErroneousSituation('Error synchronizing contacts', {\n error,\n });\n\n // Re-throw the error to be handled by the caller\n throw error;\n }\n } finally {\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n false,\n );\n }\n };\n\n if (trace) {\n // Gather pre-sync metrics for performance analysis\n const initialLocalContacts = localVisibleContacts;\n const initialValidRemoteContacts = validRemoteContacts;\n\n await trace(\n {\n name: TraceName.ContactSyncFull,\n data: {\n localContactCount: initialLocalContacts.length,\n remoteContactCount: initialValidRemoteContacts.length,\n isFirstSync:\n initialValidRemoteContacts.length === 0 &&\n initialLocalContacts.length > 0,\n isNewDeviceSync:\n initialLocalContacts.length === 0 &&\n initialValidRemoteContacts.length > 0,\n isRegularSync:\n initialLocalContacts.length > 0 &&\n initialValidRemoteContacts.length > 0,\n hasDataToSync:\n initialLocalContacts.length > 0 ||\n initialValidRemoteContacts.length > 0,\n expectedWorkload:\n initialLocalContacts.length + initialValidRemoteContacts.length,\n },\n },\n performSync,\n );\n\n return;\n }\n\n await performSync();\n}\n\n/**\n * Retrieves remote contacts from user storage API\n *\n * @param options - Parameters used for retrieving remote contacts\n * @returns Array of contacts from remote storage, or null if none found\n */\nasync function getRemoteContacts(\n options: ContactSyncingOptions,\n): Promise<SyncAddressBookEntry[] | null> {\n const { getUserStorageControllerInstance } = options;\n\n try {\n const remoteContactsJsonArray =\n await getUserStorageControllerInstance().performGetStorageAllFeatureEntries(\n USER_STORAGE_FEATURE_NAMES.addressBook,\n );\n\n if (!remoteContactsJsonArray || remoteContactsJsonArray.length === 0) {\n return null;\n }\n\n // Parse each JSON entry and convert from UserStorageContactEntry to AddressBookEntry\n const remoteStorageEntries = remoteContactsJsonArray.map((contactJson) => {\n const entry = JSON.parse(contactJson) as UserStorageContactEntry;\n return mapUserStorageEntryToAddressBookEntry(entry);\n });\n\n return remoteStorageEntries;\n } catch {\n return null;\n }\n}\n\n/**\n * Saves local contacts to user storage\n *\n * @param contacts - The contacts to save to user storage\n * @param options - Parameters used for saving contacts\n * @returns Promise that resolves when contacts are saved\n */\nasync function saveContactsToUserStorage(\n contacts: AddressBookEntry[],\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getUserStorageControllerInstance, trace } = options;\n\n const saveContacts = async () => {\n if (!contacts || contacts.length === 0) {\n return;\n }\n\n // Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs\n const storageEntries: [string, string][] = contacts.map((contact) => {\n const key = createContactKey(contact);\n const storageEntry = mapAddressBookEntryToUserStorageEntry(contact);\n return [key, JSON.stringify(storageEntry)];\n });\n\n await getUserStorageControllerInstance().performBatchSetStorage(\n USER_STORAGE_FEATURE_NAMES.addressBook,\n storageEntries,\n );\n };\n\n return trace\n ? await trace(\n {\n name: TraceName.ContactSyncSaveBatch,\n data: {\n contactCount: contacts.length,\n // Performance scaling indicators\n hasBatchOperations: contacts.length > 1,\n chainCount: new Set(contacts.map((c) => c.chainId)).size,\n hasMemosCount: contacts.filter((c) => c.memo?.length).length,\n },\n },\n saveContacts,\n )\n : await saveContacts();\n}\n\n/**\n * Updates a single contact in remote storage without performing a full sync\n * This is used when a contact is updated locally to efficiently push changes to remote\n *\n * @param contact - The contact that was updated locally\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is updated\n */\nexport async function updateContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { trace } = options;\n\n const updateContact = async () => {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId ||\n !contact.name?.trim()\n ) {\n return;\n }\n\n const { getUserStorageControllerInstance } = options;\n\n // Create an updated entry with timestamp\n const updatedEntry = {\n ...contact,\n lastUpdatedAt: contact.lastUpdatedAt || Date.now(),\n } as SyncAddressBookEntry;\n\n const key = createContactKey(contact);\n const storageEntry = mapAddressBookEntryToUserStorageEntry(updatedEntry);\n\n // Save individual contact to remote storage\n await getUserStorageControllerInstance().performSetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n JSON.stringify(storageEntry),\n );\n };\n\n if (trace) {\n return await trace(\n {\n name: TraceName.ContactSyncUpdateRemote,\n data: {\n chainId: contact.chainId,\n // Performance indicators\n hasTimestamp: Boolean(contact.lastUpdatedAt),\n hasMemo: Boolean(contact.memo?.length),\n isUpdate: Boolean(contact.lastUpdatedAt), // vs new contact\n },\n },\n updateContact,\n );\n }\n\n return await updateContact();\n}\n\n/**\n * Marks a single contact as deleted in remote storage without performing a full sync\n * This is used when a contact is deleted locally to efficiently push the deletion to remote\n *\n * @param contact - The contact that was deleted locally (contains at least address and chainId)\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is marked as deleted\n */\nexport async function deleteContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { trace } = options;\n const deleteContact = async () => {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId ||\n !contact.name?.trim()\n ) {\n return;\n }\n\n const { getUserStorageControllerInstance } = options;\n const key = createContactKey(contact);\n\n try {\n // Try to get the existing contact first\n const existingContactJson =\n await getUserStorageControllerInstance().performGetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n );\n\n if (existingContactJson) {\n // Mark the existing contact as deleted\n const existingStorageEntry = JSON.parse(\n existingContactJson,\n ) as UserStorageContactEntry;\n const existingContact =\n mapUserStorageEntryToAddressBookEntry(existingStorageEntry);\n\n const now = Date.now();\n const deletedContact = {\n ...existingContact,\n deletedAt: now,\n lastUpdatedAt: now,\n } as SyncAddressBookEntry;\n\n const deletedStorageEntry =\n mapAddressBookEntryToUserStorageEntry(deletedContact);\n\n // Save the deleted contact back to storage\n await getUserStorageControllerInstance().performSetStorage(\n `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n JSON.stringify(deletedStorageEntry),\n );\n }\n } catch {\n // If contact doesn't exist in remote storage, no need to mark as deleted\n console.warn('Contact not found in remote storage for deletion:', key);\n }\n };\n\n return trace\n ? await trace({ name: TraceName.ContactSyncDeleteRemote }, deleteContact)\n : await deleteContact();\n}\n"]}
|