@metamask-previews/profile-sync-controller 16.0.0-preview-510657d5 → 17.0.0-preview-d2bf8ff

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +10 -3
  2. package/dist/controllers/user-storage/UserStorageController.cjs +3 -56
  3. package/dist/controllers/user-storage/UserStorageController.cjs.map +1 -1
  4. package/dist/controllers/user-storage/UserStorageController.d.cts +2 -40
  5. package/dist/controllers/user-storage/UserStorageController.d.cts.map +1 -1
  6. package/dist/controllers/user-storage/UserStorageController.d.mts +2 -40
  7. package/dist/controllers/user-storage/UserStorageController.d.mts.map +1 -1
  8. package/dist/controllers/user-storage/UserStorageController.mjs +0 -53
  9. package/dist/controllers/user-storage/UserStorageController.mjs.map +1 -1
  10. package/dist/controllers/user-storage/constants.cjs +0 -1
  11. package/dist/controllers/user-storage/constants.cjs.map +1 -1
  12. package/dist/controllers/user-storage/constants.d.cts +0 -1
  13. package/dist/controllers/user-storage/constants.d.cts.map +1 -1
  14. package/dist/controllers/user-storage/constants.d.mts +0 -1
  15. package/dist/controllers/user-storage/constants.d.mts.map +1 -1
  16. package/dist/controllers/user-storage/constants.mjs +0 -1
  17. package/dist/controllers/user-storage/constants.mjs.map +1 -1
  18. package/dist/shared/storage-schema.cjs +1 -3
  19. package/dist/shared/storage-schema.cjs.map +1 -1
  20. package/dist/shared/storage-schema.d.cts +0 -2
  21. package/dist/shared/storage-schema.d.cts.map +1 -1
  22. package/dist/shared/storage-schema.d.mts +0 -2
  23. package/dist/shared/storage-schema.d.mts.map +1 -1
  24. package/dist/shared/storage-schema.mjs +1 -3
  25. package/dist/shared/storage-schema.mjs.map +1 -1
  26. package/package.json +1 -1
  27. package/dist/controllers/user-storage/contact-syncing/constants.cjs +0 -12
  28. package/dist/controllers/user-storage/contact-syncing/constants.cjs.map +0 -1
  29. package/dist/controllers/user-storage/contact-syncing/constants.d.cts +0 -9
  30. package/dist/controllers/user-storage/contact-syncing/constants.d.cts.map +0 -1
  31. package/dist/controllers/user-storage/contact-syncing/constants.d.mts +0 -9
  32. package/dist/controllers/user-storage/contact-syncing/constants.d.mts.map +0 -1
  33. package/dist/controllers/user-storage/contact-syncing/constants.mjs +0 -9
  34. package/dist/controllers/user-storage/contact-syncing/constants.mjs.map +0 -1
  35. package/dist/controllers/user-storage/contact-syncing/controller-integration.cjs +0 -299
  36. package/dist/controllers/user-storage/contact-syncing/controller-integration.cjs.map +0 -1
  37. package/dist/controllers/user-storage/contact-syncing/controller-integration.d.cts +0 -44
  38. package/dist/controllers/user-storage/contact-syncing/controller-integration.d.cts.map +0 -1
  39. package/dist/controllers/user-storage/contact-syncing/controller-integration.d.mts +0 -44
  40. package/dist/controllers/user-storage/contact-syncing/controller-integration.d.mts.map +0 -1
  41. package/dist/controllers/user-storage/contact-syncing/controller-integration.mjs +0 -293
  42. package/dist/controllers/user-storage/contact-syncing/controller-integration.mjs.map +0 -1
  43. package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.cjs +0 -50
  44. package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.cjs.map +0 -1
  45. package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.d.cts +0 -8
  46. package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.d.cts.map +0 -1
  47. package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.d.mts +0 -8
  48. package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.d.mts.map +0 -1
  49. package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.mjs +0 -46
  50. package/dist/controllers/user-storage/contact-syncing/setup-subscriptions.mjs.map +0 -1
  51. package/dist/controllers/user-storage/contact-syncing/sync-utils.cjs +0 -23
  52. package/dist/controllers/user-storage/contact-syncing/sync-utils.cjs.map +0 -1
  53. package/dist/controllers/user-storage/contact-syncing/sync-utils.d.cts +0 -9
  54. package/dist/controllers/user-storage/contact-syncing/sync-utils.d.cts.map +0 -1
  55. package/dist/controllers/user-storage/contact-syncing/sync-utils.d.mts +0 -9
  56. package/dist/controllers/user-storage/contact-syncing/sync-utils.d.mts.map +0 -1
  57. package/dist/controllers/user-storage/contact-syncing/sync-utils.mjs +0 -19
  58. package/dist/controllers/user-storage/contact-syncing/sync-utils.mjs.map +0 -1
  59. package/dist/controllers/user-storage/contact-syncing/types.cjs +0 -3
  60. package/dist/controllers/user-storage/contact-syncing/types.cjs.map +0 -1
  61. package/dist/controllers/user-storage/contact-syncing/types.d.cts +0 -35
  62. package/dist/controllers/user-storage/contact-syncing/types.d.cts.map +0 -1
  63. package/dist/controllers/user-storage/contact-syncing/types.d.mts +0 -35
  64. package/dist/controllers/user-storage/contact-syncing/types.d.mts.map +0 -1
  65. package/dist/controllers/user-storage/contact-syncing/types.mjs +0 -2
  66. package/dist/controllers/user-storage/contact-syncing/types.mjs.map +0 -1
  67. package/dist/controllers/user-storage/contact-syncing/utils.cjs +0 -66
  68. package/dist/controllers/user-storage/contact-syncing/utils.cjs.map +0 -1
  69. package/dist/controllers/user-storage/contact-syncing/utils.d.cts +0 -37
  70. package/dist/controllers/user-storage/contact-syncing/utils.d.cts.map +0 -1
  71. package/dist/controllers/user-storage/contact-syncing/utils.d.mts +0 -37
  72. package/dist/controllers/user-storage/contact-syncing/utils.d.mts.map +0 -1
  73. package/dist/controllers/user-storage/contact-syncing/utils.mjs +0 -60
  74. package/dist/controllers/user-storage/contact-syncing/utils.mjs.map +0 -1
@@ -1,299 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteContactInRemoteStorage = exports.updateContactInRemoteStorage = exports.syncContactsWithUserStorage = void 0;
4
- const sync_utils_1 = require("./sync-utils.cjs");
5
- const utils_1 = require("./utils.cjs");
6
- const utils_2 = require("./utils.cjs");
7
- const storage_schema_1 = require("../../../shared/storage-schema.cjs");
8
- /**
9
- * Creates a unique key for a contact based on chainId and address
10
- *
11
- * @param contact - The contact to create a key for
12
- * @returns A unique string key
13
- */
14
- function createContactKey(contact) {
15
- if (!contact.address) {
16
- throw new Error('Contact address is required to create storage key');
17
- }
18
- return `${contact.chainId}_${contact.address.toLowerCase()}`;
19
- }
20
- /**
21
- * Syncs contacts between local storage and user storage (remote).
22
- *
23
- * Handles the following syncing scenarios:
24
- * 1. First Sync: When local contacts exist but there are no remote contacts, uploads all local contacts.
25
- * 2. New Device Sync: Downloads remote contacts that don't exist locally (empty local address book).
26
- * 3. Simple Merge: Ensures both sides (local & remote) have all contacts.
27
- * 4. Contact Naming Conflicts: When same contact has different names, uses most recent by timestamp.
28
- * 5. Local Updates: When a contact was updated locally, syncs changes to remote if local is newer.
29
- * 6. Remote Updates: When a contact was updated remotely, applies changes locally if remote is newer.
30
- * 7. Local Deletions: Handled by real-time event handlers (deleteContactInRemoteStorage) to prevent false positives.
31
- * 8. Remote Deletions: When a contact was deleted remotely, applies deletion locally.
32
- * 9. Concurrent Updates: Resolves conflicts using timestamps to determine the winner.
33
- * 10. Restore After Delete: If a contact is modified after being deleted, restores it.
34
- * 11. ChainId Differences: Treats same address on different chains as separate contacts.
35
- *
36
- * @param config - Parameters used for syncing callbacks
37
- * @param options - Parameters used for syncing operations
38
- */
39
- async function syncContactsWithUserStorage(config, options) {
40
- const { getMessenger, getUserStorageControllerInstance } = options;
41
- const { onContactSyncErroneousSituation, onContactUpdated, onContactDeleted, } = config;
42
- try {
43
- // Cannot perform sync, conditions not met
44
- if (!(0, sync_utils_1.canPerformContactSyncing)(options)) {
45
- return;
46
- }
47
- // Activate sync semaphore to prevent event loops
48
- await getUserStorageControllerInstance().setIsContactSyncingInProgress(true);
49
- // Get all local contacts from AddressBookController (exclude chain "*" contacts)
50
- const localVisibleContacts = getMessenger()
51
- .call('AddressBookController:list')
52
- .filter((contact) => !(0, utils_2.isContactBridgedFromAccounts)(contact))
53
- .filter((contact) => contact.address && contact.chainId) || [];
54
- // Get remote contacts from user storage API
55
- const remoteContacts = await getRemoteContacts(options);
56
- // SCENARIO 1: First Sync - No remote contacts but local contacts exist
57
- if (!remoteContacts || remoteContacts.length === 0) {
58
- if (localVisibleContacts.length > 0) {
59
- await saveContactsToUserStorage(localVisibleContacts, options);
60
- }
61
- return;
62
- }
63
- // Filter remote contacts to exclude invalid ones
64
- const validRemoteContacts = remoteContacts.filter((contact) => contact.address && contact.chainId);
65
- // Prepare maps for efficient lookup
66
- const localContactsMap = new Map();
67
- const remoteContactsMap = new Map();
68
- localVisibleContacts.forEach((contact) => {
69
- const key = createContactKey(contact);
70
- localContactsMap.set(key, contact);
71
- });
72
- validRemoteContacts.forEach((contact) => {
73
- const key = createContactKey(contact);
74
- remoteContactsMap.set(key, contact);
75
- });
76
- // Lists to track contacts that need to be synced
77
- const contactsToAddOrUpdateLocally = [];
78
- const contactsToDeleteLocally = [];
79
- const contactsToUpdateRemotely = [];
80
- // SCENARIO 2 & 6: Process remote contacts - handle new device sync and remote updates
81
- for (const remoteContact of validRemoteContacts) {
82
- const key = createContactKey(remoteContact);
83
- const localContact = localContactsMap.get(key);
84
- // Handle remote contact based on its status and local existence
85
- if (remoteContact.deleted) {
86
- // SCENARIO 8: Remote deletion - should be applied locally if contact exists locally
87
- if (localContact) {
88
- contactsToDeleteLocally.push(remoteContact);
89
- }
90
- }
91
- else if (!localContact) {
92
- // SCENARIO 2: New contact from remote - import to local
93
- contactsToAddOrUpdateLocally.push(remoteContact);
94
- }
95
- else {
96
- // SCENARIO 4 & 6: Contact exists on both sides - check for conflicts
97
- const hasContentDifference = localContact.name !== remoteContact.name ||
98
- localContact.memo !== remoteContact.memo;
99
- if (hasContentDifference) {
100
- // Check timestamps to determine which version to keep
101
- const localTimestamp = localContact.lastUpdatedAt || 0;
102
- const remoteTimestamp = remoteContact.lastUpdatedAt || 0;
103
- if (localTimestamp >= remoteTimestamp) {
104
- // Local is newer (or same age) - use local version
105
- contactsToUpdateRemotely.push(localContact);
106
- }
107
- else {
108
- // Remote is newer - use remote version
109
- contactsToAddOrUpdateLocally.push(remoteContact);
110
- }
111
- }
112
- // Else: content is identical, no action needed
113
- }
114
- }
115
- // SCENARIO 3 & 5: Process local contacts not in remote
116
- for (const localContact of localVisibleContacts) {
117
- const key = createContactKey(localContact);
118
- const remoteContact = remoteContactsMap.get(key);
119
- if (!remoteContact) {
120
- // New local contact - add to remote
121
- contactsToUpdateRemotely.push(localContact);
122
- }
123
- }
124
- // Apply local deletions
125
- for (const contact of contactsToDeleteLocally) {
126
- try {
127
- getMessenger().call('AddressBookController:delete', contact.chainId, contact.address);
128
- if (onContactDeleted) {
129
- onContactDeleted();
130
- }
131
- }
132
- catch (error) {
133
- console.error('Error deleting contact:', error);
134
- }
135
- }
136
- // Apply local additions/updates
137
- for (const contact of contactsToAddOrUpdateLocally) {
138
- if (!contact.deleted) {
139
- try {
140
- getMessenger().call('AddressBookController:set', contact.address, contact.name || '', contact.chainId, contact.memo || '', contact.addressType);
141
- if (onContactUpdated) {
142
- onContactUpdated();
143
- }
144
- }
145
- catch (error) {
146
- console.error('Error updating contact:', error);
147
- }
148
- }
149
- }
150
- // Apply changes to remote storage
151
- if (contactsToUpdateRemotely.length > 0) {
152
- // Update existing remote contacts with new contacts
153
- const updatedRemoteContacts = [...validRemoteContacts];
154
- for (const localContact of contactsToUpdateRemotely) {
155
- const key = createContactKey(localContact);
156
- const existingIndex = updatedRemoteContacts.findIndex((c) => createContactKey(c) === key);
157
- const now = Date.now();
158
- const updatedEntry = {
159
- ...localContact,
160
- lastUpdatedAt: now,
161
- deleted: false, // Ensure it's not deleted
162
- };
163
- if (existingIndex !== -1) {
164
- // Update existing contact
165
- updatedRemoteContacts[existingIndex] = updatedEntry;
166
- }
167
- else {
168
- // Add new contact
169
- updatedRemoteContacts.push(updatedEntry);
170
- }
171
- }
172
- // Save updated contacts to remote storage
173
- await saveContactsToUserStorage(updatedRemoteContacts, options);
174
- }
175
- }
176
- catch (error) {
177
- if (onContactSyncErroneousSituation) {
178
- onContactSyncErroneousSituation('Error synchronizing contacts', {
179
- error,
180
- });
181
- // Re-throw the error to be handled by the caller
182
- throw error;
183
- }
184
- }
185
- finally {
186
- await getUserStorageControllerInstance().setIsContactSyncingInProgress(false);
187
- }
188
- }
189
- exports.syncContactsWithUserStorage = syncContactsWithUserStorage;
190
- /**
191
- * Retrieves remote contacts from user storage API
192
- *
193
- * @param options - Parameters used for retrieving remote contacts
194
- * @returns Array of contacts from remote storage, or null if none found
195
- */
196
- async function getRemoteContacts(options) {
197
- const { getUserStorageControllerInstance } = options;
198
- try {
199
- const remoteContactsJsonArray = await getUserStorageControllerInstance().performGetStorageAllFeatureEntries(storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook);
200
- if (!remoteContactsJsonArray || remoteContactsJsonArray.length === 0) {
201
- return null;
202
- }
203
- // Parse each JSON entry and convert from UserStorageContactEntry to AddressBookEntry
204
- const remoteStorageEntries = remoteContactsJsonArray.map((contactJson) => {
205
- const entry = JSON.parse(contactJson);
206
- return (0, utils_1.mapUserStorageEntryToAddressBookEntry)(entry);
207
- });
208
- return remoteStorageEntries;
209
- }
210
- catch {
211
- return null;
212
- }
213
- }
214
- /**
215
- * Saves local contacts to user storage
216
- *
217
- * @param contacts - The contacts to save to user storage
218
- * @param options - Parameters used for saving contacts
219
- */
220
- async function saveContactsToUserStorage(contacts, options) {
221
- const { getUserStorageControllerInstance } = options;
222
- if (!contacts || contacts.length === 0) {
223
- return;
224
- }
225
- // Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs
226
- const storageEntries = contacts.map((contact) => {
227
- const key = createContactKey(contact);
228
- const storageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(contact);
229
- return [key, JSON.stringify(storageEntry)];
230
- });
231
- await getUserStorageControllerInstance().performBatchSetStorage(storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook, storageEntries);
232
- }
233
- /**
234
- * Updates a single contact in remote storage without performing a full sync
235
- * This is used when a contact is updated locally to efficiently push changes to remote
236
- *
237
- * @param contact - The contact that was updated locally
238
- * @param options - Parameters used for syncing operations
239
- */
240
- async function updateContactInRemoteStorage(contact, options) {
241
- if (!(0, sync_utils_1.canPerformContactSyncing)(options) ||
242
- !contact.address ||
243
- !contact.chainId) {
244
- return;
245
- }
246
- const { getUserStorageControllerInstance } = options;
247
- // Create an updated entry with timestamp
248
- const updatedEntry = {
249
- ...contact,
250
- lastUpdatedAt: contact.lastUpdatedAt || Date.now(),
251
- deleted: false, // Explicitly set to false in case this was previously deleted
252
- };
253
- const key = createContactKey(contact);
254
- const storageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(updatedEntry);
255
- // Save individual contact to remote storage
256
- await getUserStorageControllerInstance().performSetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`, JSON.stringify(storageEntry));
257
- }
258
- exports.updateContactInRemoteStorage = updateContactInRemoteStorage;
259
- /**
260
- * Marks a single contact as deleted in remote storage without performing a full sync
261
- * This is used when a contact is deleted locally to efficiently push the deletion to remote
262
- *
263
- * @param contact - The contact that was deleted locally (contains at least address and chainId)
264
- * @param options - Parameters used for syncing operations
265
- */
266
- async function deleteContactInRemoteStorage(contact, options) {
267
- if (!(0, sync_utils_1.canPerformContactSyncing)(options) ||
268
- !contact.address ||
269
- !contact.chainId) {
270
- return;
271
- }
272
- const { getUserStorageControllerInstance } = options;
273
- const key = createContactKey(contact);
274
- try {
275
- // Try to get the existing contact first
276
- const existingContactJson = await getUserStorageControllerInstance().performGetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`);
277
- if (existingContactJson) {
278
- // Mark the existing contact as deleted
279
- const existingStorageEntry = JSON.parse(existingContactJson);
280
- const existingContact = (0, utils_1.mapUserStorageEntryToAddressBookEntry)(existingStorageEntry);
281
- const now = Date.now();
282
- const deletedContact = {
283
- ...existingContact,
284
- deleted: true,
285
- deletedAt: now,
286
- lastUpdatedAt: now,
287
- };
288
- const deletedStorageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(deletedContact);
289
- // Save the deleted contact back to storage
290
- await getUserStorageControllerInstance().performSetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`, JSON.stringify(deletedStorageEntry));
291
- }
292
- }
293
- catch {
294
- // If contact doesn't exist in remote storage, no need to mark as deleted
295
- console.warn('Contact not found in remote storage for deletion:', key);
296
- }
297
- }
298
- exports.deleteContactInRemoteStorage = deleteContactInRemoteStorage;
299
- //# sourceMappingURL=controller-integration.cjs.map
@@ -1 +0,0 @@
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 { canPerformContactSyncing } from './sync-utils';\nimport type { ContactSyncingOptions } from './types';\nimport type { UserStorageContactEntry } from './types';\nimport {\n mapAddressBookEntryToUserStorageEntry,\n mapUserStorageEntryToAddressBookEntry,\n type SyncAddressBookEntry,\n} from './utils';\nimport { isContactBridgedFromAccounts } from './utils';\nimport { USER_STORAGE_FEATURE_NAMES } from '../../../shared/storage-schema';\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 */\nexport async function syncContactsWithUserStorage(\n config: SyncContactsWithUserStorageConfig,\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getMessenger, getUserStorageControllerInstance } = options;\n const {\n onContactSyncErroneousSituation,\n onContactUpdated,\n onContactDeleted,\n } = config;\n\n try {\n // Cannot perform sync, conditions not met\n if (!canPerformContactSyncing(options)) {\n return;\n }\n\n // Activate sync semaphore to prevent event loops\n await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n true,\n );\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((contact) => contact.address && contact.chainId) || [];\n\n // Get remote contacts from user storage API\n const remoteContacts = await getRemoteContacts(options);\n\n // SCENARIO 1: First Sync - No remote contacts but local contacts exist\n if (!remoteContacts || remoteContacts.length === 0) {\n if (localVisibleContacts.length > 0) {\n await saveContactsToUserStorage(localVisibleContacts, options);\n }\n return;\n }\n\n // Filter remote contacts to exclude invalid ones\n const validRemoteContacts = remoteContacts.filter(\n (contact) => contact.address && contact.chainId,\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.deleted) {\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 3 & 5: Process local contacts not in remote\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 - add to remote\n contactsToUpdateRemotely.push(localContact);\n }\n }\n\n // Apply local deletions\n for (const contact of contactsToDeleteLocally) {\n try {\n getMessenger().call(\n 'AddressBookController:delete',\n contact.chainId,\n contact.address,\n );\n\n if (onContactDeleted) {\n onContactDeleted();\n }\n } catch (error) {\n console.error('Error deleting contact:', error);\n }\n }\n\n // Apply local additions/updates\n for (const contact of contactsToAddOrUpdateLocally) {\n if (!contact.deleted) {\n try {\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 } catch (error) {\n console.error('Error updating contact:', error);\n }\n }\n }\n\n // Apply changes to remote storage\n if (contactsToUpdateRemotely.length > 0) {\n // Update existing remote contacts with new contacts\n const updatedRemoteContacts = [...validRemoteContacts];\n\n for (const localContact of contactsToUpdateRemotely) {\n const key = createContactKey(localContact);\n const existingIndex = updatedRemoteContacts.findIndex(\n (c) => createContactKey(c) === key,\n );\n\n const now = Date.now();\n const updatedEntry = {\n ...localContact,\n lastUpdatedAt: now,\n deleted: false, // Ensure it's not deleted\n } as SyncAddressBookEntry;\n\n if (existingIndex !== -1) {\n // Update existing contact\n updatedRemoteContacts[existingIndex] = updatedEntry;\n } else {\n // Add new contact\n updatedRemoteContacts.push(updatedEntry);\n }\n }\n\n // Save updated contacts to remote storage\n await saveContactsToUserStorage(updatedRemoteContacts, options);\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/**\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 */\nasync function saveContactsToUserStorage(\n contacts: AddressBookEntry[],\n options: ContactSyncingOptions,\n): Promise<void> {\n const { getUserStorageControllerInstance } = options;\n\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/**\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 */\nexport async function updateContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId\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 deleted: false, // Explicitly set to false in case this was previously deleted\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/**\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 */\nexport async function deleteContactInRemoteStorage(\n contact: AddressBookEntry,\n options: ContactSyncingOptions,\n): Promise<void> {\n if (\n !canPerformContactSyncing(options) ||\n !contact.address ||\n !contact.chainId\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 deleted: true,\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"]}
@@ -1,44 +0,0 @@
1
- import type { AddressBookEntry } from "@metamask/address-book-controller";
2
- import type { ContactSyncingOptions } from "./types.cjs";
3
- export type SyncContactsWithUserStorageConfig = {
4
- onContactSyncErroneousSituation?: (errorMessage: string, sentryContext?: Record<string, unknown>) => void;
5
- onContactUpdated?: () => void;
6
- onContactDeleted?: () => void;
7
- };
8
- /**
9
- * Syncs contacts between local storage and user storage (remote).
10
- *
11
- * Handles the following syncing scenarios:
12
- * 1. First Sync: When local contacts exist but there are no remote contacts, uploads all local contacts.
13
- * 2. New Device Sync: Downloads remote contacts that don't exist locally (empty local address book).
14
- * 3. Simple Merge: Ensures both sides (local & remote) have all contacts.
15
- * 4. Contact Naming Conflicts: When same contact has different names, uses most recent by timestamp.
16
- * 5. Local Updates: When a contact was updated locally, syncs changes to remote if local is newer.
17
- * 6. Remote Updates: When a contact was updated remotely, applies changes locally if remote is newer.
18
- * 7. Local Deletions: Handled by real-time event handlers (deleteContactInRemoteStorage) to prevent false positives.
19
- * 8. Remote Deletions: When a contact was deleted remotely, applies deletion locally.
20
- * 9. Concurrent Updates: Resolves conflicts using timestamps to determine the winner.
21
- * 10. Restore After Delete: If a contact is modified after being deleted, restores it.
22
- * 11. ChainId Differences: Treats same address on different chains as separate contacts.
23
- *
24
- * @param config - Parameters used for syncing callbacks
25
- * @param options - Parameters used for syncing operations
26
- */
27
- export declare function syncContactsWithUserStorage(config: SyncContactsWithUserStorageConfig, options: ContactSyncingOptions): Promise<void>;
28
- /**
29
- * Updates a single contact in remote storage without performing a full sync
30
- * This is used when a contact is updated locally to efficiently push changes to remote
31
- *
32
- * @param contact - The contact that was updated locally
33
- * @param options - Parameters used for syncing operations
34
- */
35
- export declare function updateContactInRemoteStorage(contact: AddressBookEntry, options: ContactSyncingOptions): Promise<void>;
36
- /**
37
- * Marks a single contact as deleted in remote storage without performing a full sync
38
- * This is used when a contact is deleted locally to efficiently push the deletion to remote
39
- *
40
- * @param contact - The contact that was deleted locally (contains at least address and chainId)
41
- * @param options - Parameters used for syncing operations
42
- */
43
- export declare function deleteContactInRemoteStorage(contact: AddressBookEntry, options: ContactSyncingOptions): Promise<void>;
44
- //# sourceMappingURL=controller-integration.d.cts.map
@@ -1 +0,0 @@
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;AAG1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAgB;AAUrD,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;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,iCAAiC,EACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAiMf;AAgED;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgDf"}
@@ -1,44 +0,0 @@
1
- import type { AddressBookEntry } from "@metamask/address-book-controller";
2
- import type { ContactSyncingOptions } from "./types.mjs";
3
- export type SyncContactsWithUserStorageConfig = {
4
- onContactSyncErroneousSituation?: (errorMessage: string, sentryContext?: Record<string, unknown>) => void;
5
- onContactUpdated?: () => void;
6
- onContactDeleted?: () => void;
7
- };
8
- /**
9
- * Syncs contacts between local storage and user storage (remote).
10
- *
11
- * Handles the following syncing scenarios:
12
- * 1. First Sync: When local contacts exist but there are no remote contacts, uploads all local contacts.
13
- * 2. New Device Sync: Downloads remote contacts that don't exist locally (empty local address book).
14
- * 3. Simple Merge: Ensures both sides (local & remote) have all contacts.
15
- * 4. Contact Naming Conflicts: When same contact has different names, uses most recent by timestamp.
16
- * 5. Local Updates: When a contact was updated locally, syncs changes to remote if local is newer.
17
- * 6. Remote Updates: When a contact was updated remotely, applies changes locally if remote is newer.
18
- * 7. Local Deletions: Handled by real-time event handlers (deleteContactInRemoteStorage) to prevent false positives.
19
- * 8. Remote Deletions: When a contact was deleted remotely, applies deletion locally.
20
- * 9. Concurrent Updates: Resolves conflicts using timestamps to determine the winner.
21
- * 10. Restore After Delete: If a contact is modified after being deleted, restores it.
22
- * 11. ChainId Differences: Treats same address on different chains as separate contacts.
23
- *
24
- * @param config - Parameters used for syncing callbacks
25
- * @param options - Parameters used for syncing operations
26
- */
27
- export declare function syncContactsWithUserStorage(config: SyncContactsWithUserStorageConfig, options: ContactSyncingOptions): Promise<void>;
28
- /**
29
- * Updates a single contact in remote storage without performing a full sync
30
- * This is used when a contact is updated locally to efficiently push changes to remote
31
- *
32
- * @param contact - The contact that was updated locally
33
- * @param options - Parameters used for syncing operations
34
- */
35
- export declare function updateContactInRemoteStorage(contact: AddressBookEntry, options: ContactSyncingOptions): Promise<void>;
36
- /**
37
- * Marks a single contact as deleted in remote storage without performing a full sync
38
- * This is used when a contact is deleted locally to efficiently push the deletion to remote
39
- *
40
- * @param contact - The contact that was deleted locally (contains at least address and chainId)
41
- * @param options - Parameters used for syncing operations
42
- */
43
- export declare function deleteContactInRemoteStorage(contact: AddressBookEntry, options: ContactSyncingOptions): Promise<void>;
44
- //# sourceMappingURL=controller-integration.d.mts.map
@@ -1 +0,0 @@
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;AAG1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAgB;AAUrD,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;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,iCAAiC,EACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAiMf;AAgED;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgDf"}