@metamask-previews/profile-sync-controller 19.0.0-preview-6fc9506d → 19.0.0-preview-52f43d2

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 (38) hide show
  1. package/CHANGELOG.md +0 -4
  2. package/dist/controllers/user-storage/UserStorageController.cjs +3 -10
  3. package/dist/controllers/user-storage/UserStorageController.cjs.map +1 -1
  4. package/dist/controllers/user-storage/UserStorageController.d.cts +1 -3
  5. package/dist/controllers/user-storage/UserStorageController.d.cts.map +1 -1
  6. package/dist/controllers/user-storage/UserStorageController.d.mts +1 -3
  7. package/dist/controllers/user-storage/UserStorageController.d.mts.map +1 -1
  8. package/dist/controllers/user-storage/UserStorageController.mjs +3 -10
  9. package/dist/controllers/user-storage/UserStorageController.mjs.map +1 -1
  10. package/dist/controllers/user-storage/account-syncing/controller-integration.cjs +168 -184
  11. package/dist/controllers/user-storage/account-syncing/controller-integration.cjs.map +1 -1
  12. package/dist/controllers/user-storage/account-syncing/controller-integration.d.cts +0 -3
  13. package/dist/controllers/user-storage/account-syncing/controller-integration.d.cts.map +1 -1
  14. package/dist/controllers/user-storage/account-syncing/controller-integration.d.mts +0 -3
  15. package/dist/controllers/user-storage/account-syncing/controller-integration.d.mts.map +1 -1
  16. package/dist/controllers/user-storage/account-syncing/controller-integration.mjs +168 -184
  17. package/dist/controllers/user-storage/account-syncing/controller-integration.mjs.map +1 -1
  18. package/dist/controllers/user-storage/account-syncing/types.cjs.map +1 -1
  19. package/dist/controllers/user-storage/account-syncing/types.d.cts +0 -2
  20. package/dist/controllers/user-storage/account-syncing/types.d.cts.map +1 -1
  21. package/dist/controllers/user-storage/account-syncing/types.d.mts +0 -2
  22. package/dist/controllers/user-storage/account-syncing/types.d.mts.map +1 -1
  23. package/dist/controllers/user-storage/account-syncing/types.mjs.map +1 -1
  24. package/dist/controllers/user-storage/contact-syncing/controller-integration.cjs +166 -233
  25. package/dist/controllers/user-storage/contact-syncing/controller-integration.cjs.map +1 -1
  26. package/dist/controllers/user-storage/contact-syncing/controller-integration.d.cts +0 -3
  27. package/dist/controllers/user-storage/contact-syncing/controller-integration.d.cts.map +1 -1
  28. package/dist/controllers/user-storage/contact-syncing/controller-integration.d.mts +0 -3
  29. package/dist/controllers/user-storage/contact-syncing/controller-integration.d.mts.map +1 -1
  30. package/dist/controllers/user-storage/contact-syncing/controller-integration.mjs +166 -233
  31. package/dist/controllers/user-storage/contact-syncing/controller-integration.mjs.map +1 -1
  32. package/dist/controllers/user-storage/contact-syncing/types.cjs.map +1 -1
  33. package/dist/controllers/user-storage/contact-syncing/types.d.cts +0 -2
  34. package/dist/controllers/user-storage/contact-syncing/types.d.cts.map +1 -1
  35. package/dist/controllers/user-storage/contact-syncing/types.d.mts +0 -2
  36. package/dist/controllers/user-storage/contact-syncing/types.d.mts.map +1 -1
  37. package/dist/controllers/user-storage/contact-syncing/types.mjs.map +1 -1
  38. package/package.json +2 -2
@@ -35,167 +35,138 @@ function createContactKey(contact) {
35
35
  *
36
36
  * @param config - Parameters used for syncing callbacks
37
37
  * @param options - Parameters used for syncing operations
38
- * @returns Promise that resolves when contact synchronization is complete
39
38
  */
40
39
  async function syncContactsWithUserStorage(config, options) {
41
- const { getMessenger, getUserStorageControllerInstance, trace } = options;
40
+ const { getMessenger, getUserStorageControllerInstance } = options;
42
41
  const { onContactSyncErroneousSituation, onContactUpdated, onContactDeleted, } = config;
43
- let localVisibleContacts = [];
44
- let validRemoteContacts = [];
45
- const performSync = async () => {
46
- try {
47
- // Cannot perform sync, conditions not met
48
- if (!(0, sync_utils_1.canPerformContactSyncing)(options)) {
49
- return;
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 && contact.name?.trim()) || [];
54
+ // Get remote contacts from user storage API
55
+ const remoteContacts = await getRemoteContacts(options);
56
+ // Filter remote contacts to exclude invalid ones (or empty array if no remote contacts)
57
+ const validRemoteContacts = remoteContacts?.filter((contact) => contact.address && contact.chainId && contact.name?.trim()) || [];
58
+ // Prepare maps for efficient lookup
59
+ const localContactsMap = new Map();
60
+ const remoteContactsMap = new Map();
61
+ localVisibleContacts.forEach((contact) => {
62
+ const key = createContactKey(contact);
63
+ localContactsMap.set(key, contact);
64
+ });
65
+ validRemoteContacts.forEach((contact) => {
66
+ const key = createContactKey(contact);
67
+ remoteContactsMap.set(key, contact);
68
+ });
69
+ // Lists to track contacts that need to be synced
70
+ const contactsToAddOrUpdateLocally = [];
71
+ const contactsToDeleteLocally = [];
72
+ const contactsToUpdateRemotely = [];
73
+ // SCENARIO 2 & 6: Process remote contacts - handle new device sync and remote updates
74
+ for (const remoteContact of validRemoteContacts) {
75
+ const key = createContactKey(remoteContact);
76
+ const localContact = localContactsMap.get(key);
77
+ // Handle remote contact based on its status and local existence
78
+ if (remoteContact.deletedAt) {
79
+ // SCENARIO 8: Remote deletion - should be applied locally if contact exists locally
80
+ if (localContact) {
81
+ contactsToDeleteLocally.push(remoteContact);
82
+ }
50
83
  }
51
- // Activate sync semaphore to prevent event loops
52
- await getUserStorageControllerInstance().setIsContactSyncingInProgress(true);
53
- // Get all local contacts from AddressBookController (exclude chain "*" contacts)
54
- localVisibleContacts =
55
- getMessenger()
56
- .call('AddressBookController:list')
57
- .filter((contact) => !(0, utils_2.isContactBridgedFromAccounts)(contact))
58
- .filter((contact) => contact.address && contact.chainId && contact.name?.trim()) || [];
59
- // Get remote contacts from user storage API
60
- const remoteContacts = await getRemoteContacts(options);
61
- // Filter remote contacts to exclude invalid ones (or empty array if no remote contacts)
62
- validRemoteContacts =
63
- remoteContacts?.filter((contact) => contact.address && contact.chainId && contact.name?.trim()) || [];
64
- // Prepare maps for efficient lookup
65
- const localContactsMap = new Map();
66
- const remoteContactsMap = new Map();
67
- localVisibleContacts.forEach((contact) => {
68
- const key = createContactKey(contact);
69
- localContactsMap.set(key, contact);
70
- });
71
- validRemoteContacts.forEach((contact) => {
72
- const key = createContactKey(contact);
73
- remoteContactsMap.set(key, contact);
74
- });
75
- // Lists to track contacts that need to be synced
76
- const contactsToAddOrUpdateLocally = [];
77
- const contactsToDeleteLocally = [];
78
- const contactsToUpdateRemotely = [];
79
- // SCENARIO 2 & 6: Process remote contacts - handle new device sync and remote updates
80
- for (const remoteContact of validRemoteContacts) {
81
- const key = createContactKey(remoteContact);
82
- const localContact = localContactsMap.get(key);
83
- // Handle remote contact based on its status and local existence
84
- if (remoteContact.deletedAt) {
85
- // SCENARIO 8: Remote deletion - should be applied locally if contact exists locally
86
- if (localContact) {
87
- contactsToDeleteLocally.push(remoteContact);
84
+ else if (!localContact) {
85
+ // SCENARIO 2: New contact from remote - import to local
86
+ contactsToAddOrUpdateLocally.push(remoteContact);
87
+ }
88
+ else {
89
+ // SCENARIO 4 & 6: Contact exists on both sides - check for conflicts
90
+ const hasContentDifference = localContact.name !== remoteContact.name ||
91
+ localContact.memo !== remoteContact.memo;
92
+ if (hasContentDifference) {
93
+ // Check timestamps to determine which version to keep
94
+ const localTimestamp = localContact.lastUpdatedAt || 0;
95
+ const remoteTimestamp = remoteContact.lastUpdatedAt || 0;
96
+ if (localTimestamp >= remoteTimestamp) {
97
+ // Local is newer (or same age) - use local version
98
+ contactsToUpdateRemotely.push(localContact);
88
99
  }
89
- }
90
- else if (!localContact) {
91
- // SCENARIO 2: New contact from remote - import to local
92
- contactsToAddOrUpdateLocally.push(remoteContact);
93
- }
94
- else {
95
- // SCENARIO 4 & 6: Contact exists on both sides - check for conflicts
96
- const hasContentDifference = localContact.name !== remoteContact.name ||
97
- localContact.memo !== remoteContact.memo;
98
- if (hasContentDifference) {
99
- // Check timestamps to determine which version to keep
100
- const localTimestamp = localContact.lastUpdatedAt || 0;
101
- const remoteTimestamp = remoteContact.lastUpdatedAt || 0;
102
- if (localTimestamp >= remoteTimestamp) {
103
- // Local is newer (or same age) - use local version
104
- contactsToUpdateRemotely.push(localContact);
105
- }
106
- else {
107
- // Remote is newer - use remote version
108
- contactsToAddOrUpdateLocally.push(remoteContact);
109
- }
100
+ else {
101
+ // Remote is newer - use remote version
102
+ contactsToAddOrUpdateLocally.push(remoteContact);
110
103
  }
111
- // Else: content is identical, no action needed
112
104
  }
105
+ // Else: content is identical, no action needed
113
106
  }
114
- // SCENARIO 1, 3 & 5: Process local contacts not in remote - handles first sync and new local contacts
115
- for (const localContact of localVisibleContacts) {
116
- const key = createContactKey(localContact);
117
- const remoteContact = remoteContactsMap.get(key);
118
- if (!remoteContact) {
119
- // New local contact or first sync - add to remote
120
- contactsToUpdateRemotely.push(localContact);
107
+ }
108
+ // SCENARIO 1, 3 & 5: Process local contacts not in remote - handles first sync and new local contacts
109
+ for (const localContact of localVisibleContacts) {
110
+ const key = createContactKey(localContact);
111
+ const remoteContact = remoteContactsMap.get(key);
112
+ if (!remoteContact) {
113
+ // New local contact or first sync - add to remote
114
+ contactsToUpdateRemotely.push(localContact);
115
+ }
116
+ }
117
+ // Apply local deletions
118
+ for (const contact of contactsToDeleteLocally) {
119
+ try {
120
+ getMessenger().call('AddressBookController:delete', contact.chainId, contact.address);
121
+ if (onContactDeleted) {
122
+ onContactDeleted();
121
123
  }
122
124
  }
123
- // Apply local deletions
124
- for (const contact of contactsToDeleteLocally) {
125
+ catch (error) {
126
+ console.error('Error deleting contact:', error);
127
+ }
128
+ }
129
+ // Apply local additions/updates
130
+ for (const contact of contactsToAddOrUpdateLocally) {
131
+ if (!contact.deletedAt) {
125
132
  try {
126
- getMessenger().call('AddressBookController:delete', contact.chainId, contact.address);
127
- if (onContactDeleted) {
128
- onContactDeleted();
133
+ getMessenger().call('AddressBookController:set', contact.address, contact.name || '', contact.chainId, contact.memo || '', contact.addressType);
134
+ if (onContactUpdated) {
135
+ onContactUpdated();
129
136
  }
130
137
  }
131
138
  catch (error) {
132
- console.error('Error deleting contact:', error);
133
- }
134
- }
135
- // Apply local additions/updates
136
- for (const contact of contactsToAddOrUpdateLocally) {
137
- if (!contact.deletedAt) {
138
- try {
139
- getMessenger().call('AddressBookController:set', contact.address, contact.name || '', contact.chainId, contact.memo || '', contact.addressType);
140
- if (onContactUpdated) {
141
- onContactUpdated();
142
- }
143
- }
144
- catch (error) {
145
- console.error('Error updating contact:', error);
146
- }
147
- }
148
- }
149
- // Apply changes to remote storage
150
- if (contactsToUpdateRemotely.length > 0) {
151
- const updatedRemoteContacts = {};
152
- for (const localContact of contactsToUpdateRemotely) {
153
- const key = createContactKey(localContact);
154
- updatedRemoteContacts[key] = {
155
- ...remoteContactsMap.get(key),
156
- ...localContact,
157
- lastUpdatedAt: Date.now(), // mark as updated
158
- };
139
+ console.error('Error updating contact:', error);
159
140
  }
160
- // Save updated contacts to remote storage
161
- await saveContactsToUserStorage(Object.values(updatedRemoteContacts), options);
162
141
  }
163
142
  }
164
- catch (error) {
165
- if (onContactSyncErroneousSituation) {
166
- onContactSyncErroneousSituation('Error synchronizing contacts', {
167
- error,
168
- });
169
- // Re-throw the error to be handled by the caller
170
- throw error;
143
+ // Apply changes to remote storage
144
+ if (contactsToUpdateRemotely.length > 0) {
145
+ const updatedRemoteContacts = {};
146
+ for (const localContact of contactsToUpdateRemotely) {
147
+ const key = createContactKey(localContact);
148
+ updatedRemoteContacts[key] = {
149
+ ...remoteContactsMap.get(key),
150
+ ...localContact,
151
+ lastUpdatedAt: Date.now(), // mark as updated
152
+ };
171
153
  }
154
+ // Save updated contacts to remote storage
155
+ await saveContactsToUserStorage(Object.values(updatedRemoteContacts), options);
172
156
  }
173
- finally {
174
- await getUserStorageControllerInstance().setIsContactSyncingInProgress(false);
157
+ }
158
+ catch (error) {
159
+ if (onContactSyncErroneousSituation) {
160
+ onContactSyncErroneousSituation('Error synchronizing contacts', {
161
+ error,
162
+ });
163
+ // Re-throw the error to be handled by the caller
164
+ throw error;
175
165
  }
176
- };
177
- if (trace) {
178
- // Gather pre-sync metrics for performance analysis
179
- const initialLocalContacts = localVisibleContacts;
180
- const initialValidRemoteContacts = validRemoteContacts;
181
- return await trace({
182
- name: 'Contact Sync Full',
183
- data: {
184
- localContactCount: initialLocalContacts.length,
185
- remoteContactCount: initialValidRemoteContacts.length,
186
- isFirstSync: initialValidRemoteContacts.length === 0 &&
187
- initialLocalContacts.length > 0,
188
- isNewDeviceSync: initialLocalContacts.length === 0 &&
189
- initialValidRemoteContacts.length > 0,
190
- isRegularSync: initialLocalContacts.length > 0 &&
191
- initialValidRemoteContacts.length > 0,
192
- hasDataToSync: initialLocalContacts.length > 0 ||
193
- initialValidRemoteContacts.length > 0,
194
- expectedWorkload: initialLocalContacts.length + initialValidRemoteContacts.length,
195
- },
196
- }, performSync);
197
166
  }
198
- return await performSync();
167
+ finally {
168
+ await getUserStorageControllerInstance().setIsContactSyncingInProgress(false);
169
+ }
199
170
  }
200
171
  exports.syncContactsWithUserStorage = syncContactsWithUserStorage;
201
172
  /**
@@ -227,34 +198,19 @@ async function getRemoteContacts(options) {
227
198
  *
228
199
  * @param contacts - The contacts to save to user storage
229
200
  * @param options - Parameters used for saving contacts
230
- * @returns Promise that resolves when contacts are saved
231
201
  */
232
202
  async function saveContactsToUserStorage(contacts, options) {
233
- const { getUserStorageControllerInstance, trace } = options;
234
- const saveContacts = async () => {
235
- if (!contacts || contacts.length === 0) {
236
- return;
237
- }
238
- // Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs
239
- const storageEntries = contacts.map((contact) => {
240
- const key = createContactKey(contact);
241
- const storageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(contact);
242
- return [key, JSON.stringify(storageEntry)];
243
- });
244
- await getUserStorageControllerInstance().performBatchSetStorage(storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook, storageEntries);
245
- };
246
- return trace
247
- ? await trace({
248
- name: 'Contact Sync Save Batch',
249
- data: {
250
- contactCount: contacts.length,
251
- // Performance scaling indicators
252
- hasBatchOperations: contacts.length > 1,
253
- chainCount: new Set(contacts.map((c) => c.chainId)).size,
254
- hasMemosCount: contacts.filter((c) => c.memo?.length).length,
255
- },
256
- }, saveContacts)
257
- : await saveContacts();
203
+ const { getUserStorageControllerInstance } = options;
204
+ if (!contacts || contacts.length === 0) {
205
+ return;
206
+ }
207
+ // Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs
208
+ const storageEntries = contacts.map((contact) => {
209
+ const key = createContactKey(contact);
210
+ const storageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(contact);
211
+ return [key, JSON.stringify(storageEntry)];
212
+ });
213
+ await getUserStorageControllerInstance().performBatchSetStorage(storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook, storageEntries);
258
214
  }
259
215
  /**
260
216
  * Updates a single contact in remote storage without performing a full sync
@@ -262,40 +218,24 @@ async function saveContactsToUserStorage(contacts, options) {
262
218
  *
263
219
  * @param contact - The contact that was updated locally
264
220
  * @param options - Parameters used for syncing operations
265
- * @returns Promise that resolves when the contact is updated
266
221
  */
267
222
  async function updateContactInRemoteStorage(contact, options) {
268
- const { trace } = options;
269
- const updateContact = async () => {
270
- if (!(0, sync_utils_1.canPerformContactSyncing)(options) ||
271
- !contact.address ||
272
- !contact.chainId ||
273
- !contact.name?.trim()) {
274
- return;
275
- }
276
- const { getUserStorageControllerInstance } = options;
277
- // Create an updated entry with timestamp
278
- const updatedEntry = {
279
- ...contact,
280
- lastUpdatedAt: contact.lastUpdatedAt || Date.now(),
281
- };
282
- const key = createContactKey(contact);
283
- const storageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(updatedEntry);
284
- // Save individual contact to remote storage
285
- await getUserStorageControllerInstance().performSetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`, JSON.stringify(storageEntry));
223
+ if (!(0, sync_utils_1.canPerformContactSyncing)(options) ||
224
+ !contact.address ||
225
+ !contact.chainId ||
226
+ !contact.name?.trim()) {
227
+ return;
228
+ }
229
+ const { getUserStorageControllerInstance } = options;
230
+ // Create an updated entry with timestamp
231
+ const updatedEntry = {
232
+ ...contact,
233
+ lastUpdatedAt: contact.lastUpdatedAt || Date.now(),
286
234
  };
287
- return trace
288
- ? await trace({
289
- name: 'Contact Sync Update Remote',
290
- data: {
291
- chainId: contact.chainId,
292
- // Performance indicators
293
- hasTimestamp: Boolean(contact.lastUpdatedAt),
294
- hasMemo: Boolean(contact.memo?.length),
295
- isUpdate: Boolean(contact.lastUpdatedAt), // vs new contact
296
- },
297
- }, updateContact)
298
- : await updateContact();
235
+ const key = createContactKey(contact);
236
+ const storageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(updatedEntry);
237
+ // Save individual contact to remote storage
238
+ await getUserStorageControllerInstance().performSetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`, JSON.stringify(storageEntry));
299
239
  }
300
240
  exports.updateContactInRemoteStorage = updateContactInRemoteStorage;
301
241
  /**
@@ -304,45 +244,38 @@ exports.updateContactInRemoteStorage = updateContactInRemoteStorage;
304
244
  *
305
245
  * @param contact - The contact that was deleted locally (contains at least address and chainId)
306
246
  * @param options - Parameters used for syncing operations
307
- * @returns Promise that resolves when the contact is marked as deleted
308
247
  */
309
248
  async function deleteContactInRemoteStorage(contact, options) {
310
- const { trace } = options;
311
- const deleteContact = async () => {
312
- if (!(0, sync_utils_1.canPerformContactSyncing)(options) ||
313
- !contact.address ||
314
- !contact.chainId ||
315
- !contact.name?.trim()) {
316
- return;
317
- }
318
- const { getUserStorageControllerInstance } = options;
319
- const key = createContactKey(contact);
320
- try {
321
- // Try to get the existing contact first
322
- const existingContactJson = await getUserStorageControllerInstance().performGetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`);
323
- if (existingContactJson) {
324
- // Mark the existing contact as deleted
325
- const existingStorageEntry = JSON.parse(existingContactJson);
326
- const existingContact = (0, utils_1.mapUserStorageEntryToAddressBookEntry)(existingStorageEntry);
327
- const now = Date.now();
328
- const deletedContact = {
329
- ...existingContact,
330
- deletedAt: now,
331
- lastUpdatedAt: now,
332
- };
333
- const deletedStorageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(deletedContact);
334
- // Save the deleted contact back to storage
335
- await getUserStorageControllerInstance().performSetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`, JSON.stringify(deletedStorageEntry));
336
- }
337
- }
338
- catch {
339
- // If contact doesn't exist in remote storage, no need to mark as deleted
340
- console.warn('Contact not found in remote storage for deletion:', key);
249
+ if (!(0, sync_utils_1.canPerformContactSyncing)(options) ||
250
+ !contact.address ||
251
+ !contact.chainId ||
252
+ !contact.name?.trim()) {
253
+ return;
254
+ }
255
+ const { getUserStorageControllerInstance } = options;
256
+ const key = createContactKey(contact);
257
+ try {
258
+ // Try to get the existing contact first
259
+ const existingContactJson = await getUserStorageControllerInstance().performGetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`);
260
+ if (existingContactJson) {
261
+ // Mark the existing contact as deleted
262
+ const existingStorageEntry = JSON.parse(existingContactJson);
263
+ const existingContact = (0, utils_1.mapUserStorageEntryToAddressBookEntry)(existingStorageEntry);
264
+ const now = Date.now();
265
+ const deletedContact = {
266
+ ...existingContact,
267
+ deletedAt: now,
268
+ lastUpdatedAt: now,
269
+ };
270
+ const deletedStorageEntry = (0, utils_1.mapAddressBookEntryToUserStorageEntry)(deletedContact);
271
+ // Save the deleted contact back to storage
272
+ await getUserStorageControllerInstance().performSetStorage(`${storage_schema_1.USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`, JSON.stringify(deletedStorageEntry));
341
273
  }
342
- };
343
- return trace
344
- ? await trace({ name: 'Contact Sync Delete Remote' }, deleteContact)
345
- : await deleteContact();
274
+ }
275
+ catch {
276
+ // If contact doesn't exist in remote storage, no need to mark as deleted
277
+ console.warn('Contact not found in remote storage for deletion:', key);
278
+ }
346
279
  }
347
280
  exports.deleteContactInRemoteStorage = deleteContactInRemoteStorage;
348
281
  //# sourceMappingURL=controller-integration.cjs.map
@@ -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 { 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 * @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 let localVisibleContacts: AddressBookEntry[] = [];\n let validRemoteContacts: SyncAddressBookEntry[] = [];\n\n const performSync = async () => {\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 localVisibleContacts =\n getMessenger()\n .call('AddressBookController:list')\n .filter((contact) => !isContactBridgedFromAccounts(contact))\n .filter(\n (contact) =>\n 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 validRemoteContacts =\n remoteContacts?.filter(\n (contact) =>\n contact.address && contact.chainId && contact.name?.trim(),\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 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.deletedAt) {\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 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 return await trace(\n {\n name: 'Contact Sync Full',\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\n return 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: 'Contact Sync Save Batch',\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 return trace\n ? await trace(\n {\n name: 'Contact Sync Update Remote',\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 : 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\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: 'Contact Sync Delete Remote' }, 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 { 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(\n (contact) =>\n 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 // 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 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.deletedAt) {\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 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/**\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 !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/**\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 !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"]}
@@ -23,7 +23,6 @@ export type SyncContactsWithUserStorageConfig = {
23
23
  *
24
24
  * @param config - Parameters used for syncing callbacks
25
25
  * @param options - Parameters used for syncing operations
26
- * @returns Promise that resolves when contact synchronization is complete
27
26
  */
28
27
  export declare function syncContactsWithUserStorage(config: SyncContactsWithUserStorageConfig, options: ContactSyncingOptions): Promise<void>;
29
28
  /**
@@ -32,7 +31,6 @@ export declare function syncContactsWithUserStorage(config: SyncContactsWithUser
32
31
  *
33
32
  * @param contact - The contact that was updated locally
34
33
  * @param options - Parameters used for syncing operations
35
- * @returns Promise that resolves when the contact is updated
36
34
  */
37
35
  export declare function updateContactInRemoteStorage(contact: AddressBookEntry, options: ContactSyncingOptions): Promise<void>;
38
36
  /**
@@ -41,7 +39,6 @@ export declare function updateContactInRemoteStorage(contact: AddressBookEntry,
41
39
  *
42
40
  * @param contact - The contact that was deleted locally (contains at least address and chainId)
43
41
  * @param options - Parameters used for syncing operations
44
- * @returns Promise that resolves when the contact is marked as deleted
45
42
  */
46
43
  export declare function deleteContactInRemoteStorage(contact: AddressBookEntry, options: ContactSyncingOptions): Promise<void>;
47
44
  //# sourceMappingURL=controller-integration.d.cts.map
@@ -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;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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,iCAAiC,EACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAuNf;AAmFD;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA8Cf;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAwDf"}
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,CAgLf;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"}
@@ -23,7 +23,6 @@ export type SyncContactsWithUserStorageConfig = {
23
23
  *
24
24
  * @param config - Parameters used for syncing callbacks
25
25
  * @param options - Parameters used for syncing operations
26
- * @returns Promise that resolves when contact synchronization is complete
27
26
  */
28
27
  export declare function syncContactsWithUserStorage(config: SyncContactsWithUserStorageConfig, options: ContactSyncingOptions): Promise<void>;
29
28
  /**
@@ -32,7 +31,6 @@ export declare function syncContactsWithUserStorage(config: SyncContactsWithUser
32
31
  *
33
32
  * @param contact - The contact that was updated locally
34
33
  * @param options - Parameters used for syncing operations
35
- * @returns Promise that resolves when the contact is updated
36
34
  */
37
35
  export declare function updateContactInRemoteStorage(contact: AddressBookEntry, options: ContactSyncingOptions): Promise<void>;
38
36
  /**
@@ -41,7 +39,6 @@ export declare function updateContactInRemoteStorage(contact: AddressBookEntry,
41
39
  *
42
40
  * @param contact - The contact that was deleted locally (contains at least address and chainId)
43
41
  * @param options - Parameters used for syncing operations
44
- * @returns Promise that resolves when the contact is marked as deleted
45
42
  */
46
43
  export declare function deleteContactInRemoteStorage(contact: AddressBookEntry, options: ContactSyncingOptions): Promise<void>;
47
44
  //# sourceMappingURL=controller-integration.d.mts.map