@oxyhq/services 5.17.5 → 5.17.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/crypto/keyManager.js +6 -161
- package/lib/commonjs/crypto/keyManager.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +20 -543
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContextBase.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +14 -331
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +8 -112
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +2 -27
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +2 -27
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +2 -88
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/crypto/keyManager.js +6 -161
- package/lib/module/crypto/keyManager.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +20 -543
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/OxyContextBase.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +14 -330
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +8 -112
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +2 -27
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/module/ui/hooks/queries/useServicesQueries.js +2 -27
- package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +2 -88
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/typescript/crypto/keyManager.d.ts +3 -20
- package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
- package/lib/typescript/crypto/types.d.ts +4 -0
- package/lib/typescript/crypto/types.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContextBase.d.ts +0 -37
- package/lib/typescript/ui/context/OxyContextBase.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +1 -20
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts +1 -14
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/crypto/keyManager.ts +4 -170
- package/src/crypto/types.ts +4 -0
- package/src/ui/context/OxyContext.tsx +17 -588
- package/src/ui/context/OxyContextBase.tsx +2 -20
- package/src/ui/context/hooks/useAuthOperations.ts +22 -347
- package/src/ui/hooks/mutations/useAccountMutations.ts +12 -110
- package/src/ui/hooks/queries/useAccountQueries.ts +3 -27
- package/src/ui/hooks/queries/useServicesQueries.ts +3 -27
- package/src/ui/hooks/useSessionSocket.ts +2 -106
|
@@ -17,7 +17,6 @@ Object.defineProperty(exports, "useOxy", {
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
var _react = require("react");
|
|
20
|
-
var _reactNative = require("react-native");
|
|
21
20
|
var _core = require("../../core");
|
|
22
21
|
var _sonner = require("../../lib/sonner");
|
|
23
22
|
var _authStore = require("../stores/authStore");
|
|
@@ -34,7 +33,6 @@ var _bottomSheetManager = require("../navigation/bottomSheetManager");
|
|
|
34
33
|
var _reactQuery = require("@tanstack/react-query");
|
|
35
34
|
var _queryClient = require("../hooks/queryClient");
|
|
36
35
|
var _queryKeys = require("../hooks/queries/queryKeys");
|
|
37
|
-
var _crypto = require("../../crypto");
|
|
38
36
|
var _i18n = require("../../i18n");
|
|
39
37
|
var _avatarUtils = require("../utils/avatarUtils");
|
|
40
38
|
var _accountStore = require("../stores/accountStore");
|
|
@@ -97,24 +95,14 @@ const OxyProvider = ({
|
|
|
97
95
|
error,
|
|
98
96
|
loginSuccess,
|
|
99
97
|
loginFailure,
|
|
100
|
-
logoutStore
|
|
101
|
-
// Identity sync state and actions
|
|
102
|
-
isIdentitySyncedStore,
|
|
103
|
-
isSyncing,
|
|
104
|
-
setIdentitySynced,
|
|
105
|
-
setSyncing
|
|
98
|
+
logoutStore
|
|
106
99
|
} = (0, _authStore.useAuthStore)((0, _shallow.useShallow)(state => ({
|
|
107
100
|
isAuthenticated: state.isAuthenticated,
|
|
108
101
|
isLoading: state.isLoading,
|
|
109
102
|
error: state.error,
|
|
110
103
|
loginSuccess: state.loginSuccess,
|
|
111
104
|
loginFailure: state.loginFailure,
|
|
112
|
-
logoutStore: state.logout
|
|
113
|
-
// Identity sync state and actions
|
|
114
|
-
isIdentitySyncedStore: state.isIdentitySynced,
|
|
115
|
-
isSyncing: state.isSyncing,
|
|
116
|
-
setIdentitySynced: state.setIdentitySynced,
|
|
117
|
-
setSyncing: state.setSyncing
|
|
105
|
+
logoutStore: state.logout
|
|
118
106
|
})));
|
|
119
107
|
const [tokenReady, setTokenReady] = (0, _react.useState)(true);
|
|
120
108
|
const initializedRef = (0, _react.useRef)(false);
|
|
@@ -137,45 +125,6 @@ const OxyProvider = ({
|
|
|
137
125
|
logger
|
|
138
126
|
});
|
|
139
127
|
|
|
140
|
-
// Identity integrity check and auto-restore on startup
|
|
141
|
-
// Skip on web platform - identity storage is only available on native platforms
|
|
142
|
-
(0, _react.useEffect)(() => {
|
|
143
|
-
if (!storage || !isStorageReady) return;
|
|
144
|
-
if (_reactNative.Platform.OS === 'web') return; // Identity operations are native-only
|
|
145
|
-
|
|
146
|
-
const checkAndRestoreIdentity = async () => {
|
|
147
|
-
try {
|
|
148
|
-
// Check if identity exists and verify integrity
|
|
149
|
-
const hasIdentity = await _crypto.KeyManager.hasIdentity();
|
|
150
|
-
if (hasIdentity) {
|
|
151
|
-
const isValid = await _crypto.KeyManager.verifyIdentityIntegrity();
|
|
152
|
-
if (!isValid) {
|
|
153
|
-
// Try to restore from backup
|
|
154
|
-
const restored = await _crypto.KeyManager.restoreIdentityFromBackup();
|
|
155
|
-
if (__DEV__) {
|
|
156
|
-
logger(restored ? 'Identity restored from backup successfully' : 'Identity integrity check failed - user may need to restore from backup file');
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
// Identity is valid - ensure backup is up to date
|
|
160
|
-
await _crypto.KeyManager.backupIdentity();
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
// No identity - try to restore from backup
|
|
164
|
-
const restored = await _crypto.KeyManager.restoreIdentityFromBackup();
|
|
165
|
-
if (restored && __DEV__) {
|
|
166
|
-
logger('Identity restored from backup on startup');
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
} catch (error) {
|
|
170
|
-
if (__DEV__) {
|
|
171
|
-
logger('Error during identity integrity check', error);
|
|
172
|
-
}
|
|
173
|
-
// Don't block app startup - user can recover with backup file
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
checkAndRestoreIdentity();
|
|
177
|
-
}, [storage, isStorageReady, logger]);
|
|
178
|
-
|
|
179
128
|
// Offline queuing is now handled by TanStack Query mutations
|
|
180
129
|
// No need for custom offline queue
|
|
181
130
|
|
|
@@ -243,15 +192,9 @@ const OxyProvider = ({
|
|
|
243
192
|
});
|
|
244
193
|
const user = userData ?? null;
|
|
245
194
|
const {
|
|
246
|
-
createIdentity,
|
|
247
|
-
importIdentity: importIdentityBase,
|
|
248
195
|
signIn,
|
|
249
196
|
logout,
|
|
250
|
-
logoutAll
|
|
251
|
-
hasIdentity,
|
|
252
|
-
getPublicKey,
|
|
253
|
-
isIdentitySynced,
|
|
254
|
-
syncIdentity: syncIdentityBase
|
|
197
|
+
logoutAll
|
|
255
198
|
} = (0, _useAuthOperations.useAuthOperations)({
|
|
256
199
|
oxyServices,
|
|
257
200
|
storage,
|
|
@@ -269,29 +212,10 @@ const OxyProvider = ({
|
|
|
269
212
|
loginFailure,
|
|
270
213
|
logoutStore,
|
|
271
214
|
setAuthState,
|
|
272
|
-
setIdentitySynced,
|
|
273
|
-
setSyncing,
|
|
274
215
|
logger
|
|
275
216
|
});
|
|
276
217
|
|
|
277
|
-
//
|
|
278
|
-
const syncIdentity = (0, _react.useCallback)(() => syncIdentityBase(), [syncIdentityBase]);
|
|
279
|
-
|
|
280
|
-
// Wrapper for importIdentity to handle legacy calls gracefully
|
|
281
|
-
const importIdentity = (0, _react.useCallback)(async (backupData, password) => {
|
|
282
|
-
// Handle legacy calls with single string argument (old recovery phrase signature)
|
|
283
|
-
if (typeof backupData === 'string') {
|
|
284
|
-
throw new Error('Recovery phrase import is no longer supported. Please use backup file import or QR code transfer instead.');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Validate that password is provided
|
|
288
|
-
if (!password || typeof password !== 'string') {
|
|
289
|
-
throw new Error('Password is required for backup file import.');
|
|
290
|
-
}
|
|
291
|
-
return importIdentityBase(backupData, password);
|
|
292
|
-
}, [importIdentityBase]);
|
|
293
|
-
|
|
294
|
-
// Clear all account data when identity is lost (for accounts app)
|
|
218
|
+
// Clear all account data when logging out (for accounts app)
|
|
295
219
|
// In accounts app, identity = account, so losing identity means losing everything
|
|
296
220
|
const clearAllAccountData = (0, _react.useCallback)(async () => {
|
|
297
221
|
// Clear TanStack Query cache (in-memory)
|
|
@@ -309,19 +233,6 @@ const OxyProvider = ({
|
|
|
309
233
|
// Clear session state (sessions, activeSessionId, storage)
|
|
310
234
|
await clearSessionState();
|
|
311
235
|
|
|
312
|
-
// Clear identity sync state from storage
|
|
313
|
-
if (storage) {
|
|
314
|
-
try {
|
|
315
|
-
await storage.removeItem('oxy_identity_synced');
|
|
316
|
-
} catch (error) {
|
|
317
|
-
logger('Failed to clear identity sync state', error);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Reset auth store identity sync state
|
|
322
|
-
_authStore.useAuthStore.getState().setIdentitySynced(false);
|
|
323
|
-
_authStore.useAuthStore.getState().setSyncing(false);
|
|
324
|
-
|
|
325
236
|
// Reset account store
|
|
326
237
|
_accountStore.useAccountStore.getState().reset();
|
|
327
238
|
|
|
@@ -329,162 +240,50 @@ const OxyProvider = ({
|
|
|
329
240
|
oxyServices.clearCache();
|
|
330
241
|
}, [queryClient, storage, clearSessionState, logger, oxyServices]);
|
|
331
242
|
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
const pending = [];
|
|
335
|
-
transferCodesRef.current.forEach((data, transferId) => {
|
|
336
|
-
if (data.state === 'pending') {
|
|
337
|
-
pending.push({
|
|
338
|
-
transferId,
|
|
339
|
-
data
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
return pending;
|
|
344
|
-
}, []);
|
|
345
|
-
const getActiveTransferId = (0, _react.useCallback)(() => {
|
|
346
|
-
return activeTransferIdRef.current;
|
|
347
|
-
}, []);
|
|
348
|
-
|
|
349
|
-
// Delete identity and clear all account data
|
|
350
|
-
// In accounts app, deleting identity means losing the account completely
|
|
351
|
-
const deleteIdentityAndClearAccount = (0, _react.useCallback)(async (skipBackup = false, force = false, userConfirmed = false) => {
|
|
352
|
-
// CRITICAL: Check for active transfers before deletion (unless force is true)
|
|
353
|
-
// This prevents accidental identity loss during transfer
|
|
354
|
-
if (!force) {
|
|
355
|
-
const pendingTransfers = getAllPendingTransfers();
|
|
356
|
-
if (pendingTransfers.length > 0) {
|
|
357
|
-
const activeTransferId = getActiveTransferId();
|
|
358
|
-
const hasActiveTransfer = activeTransferId && pendingTransfers.some(t => t.transferId === activeTransferId);
|
|
359
|
-
if (hasActiveTransfer) {
|
|
360
|
-
throw new Error('Cannot delete identity: An active identity transfer is in progress. ' + 'Please wait for the transfer to complete or cancel it first. ' + 'If you proceed, you may lose access to your identity permanently.');
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// First, clear all account data
|
|
366
|
-
await clearAllAccountData();
|
|
367
|
-
|
|
368
|
-
// Then delete the identity keys
|
|
369
|
-
await _crypto.KeyManager.deleteIdentity(skipBackup, force, userConfirmed);
|
|
370
|
-
}, [clearAllAccountData, getAllPendingTransfers, getActiveTransferId]);
|
|
371
|
-
|
|
372
|
-
// Network reconnect sync - TanStack Query automatically retries mutations on reconnect
|
|
373
|
-
// We only need to sync identity if it's not synced
|
|
243
|
+
// Network reconnect - TanStack Query automatically retries mutations on reconnect
|
|
244
|
+
// Network reconnect - TanStack Query automatically retries mutations on reconnect
|
|
374
245
|
(0, _react.useEffect)(() => {
|
|
375
246
|
if (!storage) return;
|
|
376
247
|
let wasOffline = false;
|
|
377
248
|
let checkTimeout = null;
|
|
378
|
-
let lastReconnectionLog = 0;
|
|
379
|
-
const RECONNECTION_LOG_DEBOUNCE_MS = 5000; // 5 seconds
|
|
380
|
-
|
|
381
|
-
// Circuit breaker and exponential backoff state
|
|
382
|
-
const stateRef = {
|
|
383
|
-
consecutiveFailures: 0,
|
|
384
|
-
currentInterval: 10000,
|
|
385
|
-
// Start with 10 seconds
|
|
386
|
-
baseInterval: 10000,
|
|
387
|
-
// Base interval in milliseconds
|
|
388
|
-
maxInterval: 60000,
|
|
389
|
-
// Maximum interval (60 seconds)
|
|
390
|
-
maxFailures: 5 // Circuit breaker threshold
|
|
391
|
-
};
|
|
392
249
|
const scheduleNextCheck = () => {
|
|
393
250
|
if (checkTimeout) {
|
|
394
251
|
clearTimeout(checkTimeout);
|
|
395
252
|
}
|
|
396
253
|
checkTimeout = setTimeout(() => {
|
|
397
|
-
|
|
398
|
-
},
|
|
254
|
+
checkNetworkStatus();
|
|
255
|
+
}, 30000); // Check every 30 seconds
|
|
399
256
|
};
|
|
400
|
-
const
|
|
257
|
+
const checkNetworkStatus = async () => {
|
|
401
258
|
try {
|
|
402
259
|
// Try a lightweight health check to see if we're online
|
|
403
|
-
await oxyServices.healthCheck()
|
|
404
|
-
wasOffline = true;
|
|
405
|
-
throw new Error('Health check failed');
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
// Health check succeeded - reset circuit breaker and backoff
|
|
409
|
-
if (stateRef.consecutiveFailures > 0) {
|
|
410
|
-
stateRef.consecutiveFailures = 0;
|
|
411
|
-
stateRef.currentInterval = stateRef.baseInterval;
|
|
412
|
-
}
|
|
260
|
+
await oxyServices.healthCheck();
|
|
413
261
|
|
|
414
|
-
// If we were offline and now we're online
|
|
262
|
+
// If we were offline and now we're online
|
|
415
263
|
if (wasOffline) {
|
|
416
|
-
|
|
417
|
-
const timeSinceLastLog = now - lastReconnectionLog;
|
|
418
|
-
if (timeSinceLastLog >= RECONNECTION_LOG_DEBOUNCE_MS) {
|
|
419
|
-
logger('Network reconnected, checking identity sync...');
|
|
420
|
-
lastReconnectionLog = now;
|
|
421
|
-
|
|
422
|
-
// Sync identity first (if not synced)
|
|
423
|
-
try {
|
|
424
|
-
const hasIdentityValue = await hasIdentity();
|
|
425
|
-
if (hasIdentityValue) {
|
|
426
|
-
// Check sync status directly - sync if not explicitly 'true'
|
|
427
|
-
// undefined = not synced yet, 'false' = explicitly not synced, 'true' = synced
|
|
428
|
-
const syncStatus = await storage.getItem('oxy_identity_synced');
|
|
429
|
-
if (syncStatus !== 'true') {
|
|
430
|
-
await syncIdentity();
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
} catch (syncError) {
|
|
434
|
-
// Skip sync silently if username is required (expected when offline onboarding)
|
|
435
|
-
if (syncError?.code === 'USERNAME_REQUIRED' || syncError?.message === 'USERNAME_REQUIRED') {
|
|
436
|
-
if (__DEV__) {
|
|
437
|
-
_loggerUtils.logger.debug('Sync skipped - username required', {
|
|
438
|
-
component: 'OxyContext',
|
|
439
|
-
method: 'checkNetworkAndSync'
|
|
440
|
-
}, syncError);
|
|
441
|
-
}
|
|
442
|
-
// Don't log or show error - username will be set later
|
|
443
|
-
} else if (!(0, _errorHandlers.isTimeoutOrNetworkError)(syncError)) {
|
|
444
|
-
// Only log unexpected errors - timeouts/network issues are expected when offline
|
|
445
|
-
logger('Error syncing identity on reconnect', syncError);
|
|
446
|
-
} else if (__DEV__) {
|
|
447
|
-
_loggerUtils.logger.debug('Identity sync timeout (expected when offline)', {
|
|
448
|
-
component: 'OxyContext',
|
|
449
|
-
method: 'checkNetworkAndSync'
|
|
450
|
-
}, syncError);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
264
|
+
logger('Network reconnected');
|
|
455
265
|
// TanStack Query will automatically retry pending mutations
|
|
456
|
-
// Reset flag immediately after processing (whether logged or not)
|
|
457
266
|
wasOffline = false;
|
|
458
267
|
}
|
|
459
268
|
} catch (error) {
|
|
460
269
|
// Network check failed - we're offline
|
|
461
|
-
wasOffline
|
|
462
|
-
|
|
463
|
-
// Increment failure count and apply exponential backoff
|
|
464
|
-
stateRef.consecutiveFailures++;
|
|
465
|
-
|
|
466
|
-
// Calculate new interval with exponential backoff, capped at maxInterval
|
|
467
|
-
const backoffMultiplier = Math.min(Math.pow(2, stateRef.consecutiveFailures - 1), stateRef.maxInterval / stateRef.baseInterval);
|
|
468
|
-
stateRef.currentInterval = Math.min(stateRef.baseInterval * backoffMultiplier, stateRef.maxInterval);
|
|
469
|
-
|
|
470
|
-
// If we hit the circuit breaker threshold, use max interval
|
|
471
|
-
if (stateRef.consecutiveFailures >= stateRef.maxFailures) {
|
|
472
|
-
stateRef.currentInterval = stateRef.maxInterval;
|
|
270
|
+
if (!wasOffline && __DEV__) {
|
|
271
|
+
logger('Network appears offline');
|
|
473
272
|
}
|
|
273
|
+
wasOffline = true;
|
|
474
274
|
} finally {
|
|
475
|
-
// Always schedule next check (will use updated interval)
|
|
476
275
|
scheduleNextCheck();
|
|
477
276
|
}
|
|
478
277
|
};
|
|
479
278
|
|
|
480
279
|
// Check immediately
|
|
481
|
-
|
|
280
|
+
checkNetworkStatus();
|
|
482
281
|
return () => {
|
|
483
282
|
if (checkTimeout) {
|
|
484
283
|
clearTimeout(checkTimeout);
|
|
485
284
|
}
|
|
486
285
|
};
|
|
487
|
-
}, [oxyServices, storage,
|
|
286
|
+
}, [oxyServices, storage, logger]);
|
|
488
287
|
const {
|
|
489
288
|
getDeviceSessions,
|
|
490
289
|
logoutAllDeviceSessions,
|
|
@@ -593,171 +392,6 @@ const OxyProvider = ({
|
|
|
593
392
|
|
|
594
393
|
// Get userId from JWT token (MongoDB ObjectId) for socket room matching
|
|
595
394
|
const userId = oxyServices.getCurrentUserId();
|
|
596
|
-
|
|
597
|
-
// Transfer code storage interface
|
|
598
|
-
|
|
599
|
-
// Store transfer codes in memory for verification (also persisted to storage)
|
|
600
|
-
// Map: transferId -> TransferCodeData
|
|
601
|
-
const transferCodesRef = (0, _react.useRef)(new Map());
|
|
602
|
-
const activeTransferIdRef = (0, _react.useRef)(null);
|
|
603
|
-
const TRANSFER_CODES_STORAGE_KEY = `${storageKeyPrefix}_transfer_codes`;
|
|
604
|
-
const ACTIVE_TRANSFER_STORAGE_KEY = `${storageKeyPrefix}_active_transfer_id`;
|
|
605
|
-
|
|
606
|
-
// Clear stale transfer codes on startup
|
|
607
|
-
// Transfers are ephemeral and should not persist across app restarts
|
|
608
|
-
(0, _react.useEffect)(() => {
|
|
609
|
-
if (!storage || !isStorageReady) return;
|
|
610
|
-
const clearStaleTransfers = async () => {
|
|
611
|
-
try {
|
|
612
|
-
// Clear any stored transfer codes - they're only valid during active transfer sessions
|
|
613
|
-
await storage.removeItem(TRANSFER_CODES_STORAGE_KEY);
|
|
614
|
-
await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
615
|
-
if (__DEV__) {
|
|
616
|
-
logger('Cleared stale transfer codes on startup');
|
|
617
|
-
}
|
|
618
|
-
} catch (error) {
|
|
619
|
-
if (__DEV__) {
|
|
620
|
-
logger('Failed to clear stale transfer codes', error);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
|
-
clearStaleTransfers();
|
|
625
|
-
}, [storage, isStorageReady, logger, storageKeyPrefix]);
|
|
626
|
-
|
|
627
|
-
// Persist transfer codes to storage whenever they change
|
|
628
|
-
const persistTransferCodes = (0, _react.useCallback)(async () => {
|
|
629
|
-
if (!storage) return;
|
|
630
|
-
try {
|
|
631
|
-
const codesToStore = {};
|
|
632
|
-
transferCodesRef.current.forEach((value, key) => {
|
|
633
|
-
codesToStore[key] = value;
|
|
634
|
-
});
|
|
635
|
-
await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(codesToStore));
|
|
636
|
-
} catch (error) {
|
|
637
|
-
if (__DEV__) {
|
|
638
|
-
logger('Failed to persist transfer codes', error);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}, [storage, logger]);
|
|
642
|
-
|
|
643
|
-
// Cleanup old transfer codes (older than 15 minutes)
|
|
644
|
-
(0, _react.useEffect)(() => {
|
|
645
|
-
const cleanup = setInterval(async () => {
|
|
646
|
-
const now = Date.now();
|
|
647
|
-
const fifteenMinutes = 15 * 60 * 1000;
|
|
648
|
-
let needsPersist = false;
|
|
649
|
-
transferCodesRef.current.forEach((value, key) => {
|
|
650
|
-
if (now - value.timestamp > fifteenMinutes) {
|
|
651
|
-
transferCodesRef.current.delete(key);
|
|
652
|
-
needsPersist = true;
|
|
653
|
-
if (__DEV__) {
|
|
654
|
-
logger('Cleaned up expired transfer code', {
|
|
655
|
-
transferId: key
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
// Clear active transfer if it was deleted
|
|
662
|
-
if (activeTransferIdRef.current && !transferCodesRef.current.has(activeTransferIdRef.current)) {
|
|
663
|
-
activeTransferIdRef.current = null;
|
|
664
|
-
if (storage) {
|
|
665
|
-
try {
|
|
666
|
-
await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
667
|
-
} catch (error) {
|
|
668
|
-
// Ignore storage errors
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
if (needsPersist) {
|
|
673
|
-
await persistTransferCodes();
|
|
674
|
-
}
|
|
675
|
-
}, 60000); // Check every minute
|
|
676
|
-
|
|
677
|
-
return () => clearInterval(cleanup);
|
|
678
|
-
}, [logger, persistTransferCodes, storage]);
|
|
679
|
-
|
|
680
|
-
// Transfer code management functions
|
|
681
|
-
const storeTransferCode = (0, _react.useCallback)(async (transferId, code, sourceDeviceId, publicKey) => {
|
|
682
|
-
const transferData = {
|
|
683
|
-
code,
|
|
684
|
-
sourceDeviceId,
|
|
685
|
-
publicKey,
|
|
686
|
-
timestamp: Date.now(),
|
|
687
|
-
state: 'pending'
|
|
688
|
-
};
|
|
689
|
-
transferCodesRef.current.set(transferId, transferData);
|
|
690
|
-
activeTransferIdRef.current = transferId;
|
|
691
|
-
|
|
692
|
-
// Persist to storage
|
|
693
|
-
await persistTransferCodes();
|
|
694
|
-
if (storage) {
|
|
695
|
-
try {
|
|
696
|
-
await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, transferId);
|
|
697
|
-
} catch (error) {
|
|
698
|
-
if (__DEV__) {
|
|
699
|
-
logger('Failed to persist active transfer ID', error);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
if (__DEV__) {
|
|
704
|
-
logger('Stored transfer code', {
|
|
705
|
-
transferId,
|
|
706
|
-
sourceDeviceId,
|
|
707
|
-
publicKey: publicKey.substring(0, 16) + '...'
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
}, [logger, persistTransferCodes, storage]);
|
|
711
|
-
const getTransferCode = (0, _react.useCallback)(transferId => {
|
|
712
|
-
return transferCodesRef.current.get(transferId) || null;
|
|
713
|
-
}, []);
|
|
714
|
-
const updateTransferState = (0, _react.useCallback)(async (transferId, state) => {
|
|
715
|
-
const transferData = transferCodesRef.current.get(transferId);
|
|
716
|
-
if (transferData) {
|
|
717
|
-
transferData.state = state;
|
|
718
|
-
transferCodesRef.current.set(transferId, transferData);
|
|
719
|
-
|
|
720
|
-
// Clear active transfer if completed or failed
|
|
721
|
-
if (state === 'completed' || state === 'failed') {
|
|
722
|
-
if (activeTransferIdRef.current === transferId) {
|
|
723
|
-
activeTransferIdRef.current = null;
|
|
724
|
-
if (storage) {
|
|
725
|
-
try {
|
|
726
|
-
await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
727
|
-
} catch (error) {
|
|
728
|
-
// Ignore storage errors
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
await persistTransferCodes();
|
|
734
|
-
if (__DEV__) {
|
|
735
|
-
logger('Updated transfer state', {
|
|
736
|
-
transferId,
|
|
737
|
-
state
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}, [logger, persistTransferCodes, storage]);
|
|
742
|
-
const clearTransferCode = (0, _react.useCallback)(async transferId => {
|
|
743
|
-
transferCodesRef.current.delete(transferId);
|
|
744
|
-
if (activeTransferIdRef.current === transferId) {
|
|
745
|
-
activeTransferIdRef.current = null;
|
|
746
|
-
if (storage) {
|
|
747
|
-
try {
|
|
748
|
-
await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
749
|
-
} catch (error) {
|
|
750
|
-
// Ignore storage errors
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
await persistTransferCodes();
|
|
755
|
-
if (__DEV__) {
|
|
756
|
-
logger('Cleared transfer code', {
|
|
757
|
-
transferId
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
}, [logger, persistTransferCodes, storage]);
|
|
761
395
|
const refreshSessionsWithUser = (0, _react.useCallback)(() => refreshSessions(userId || undefined), [refreshSessions, userId]);
|
|
762
396
|
const handleSessionRemoved = (0, _react.useCallback)(sessionId => {
|
|
763
397
|
trackRemovedSession(sessionId);
|
|
@@ -766,143 +400,6 @@ const OxyProvider = ({
|
|
|
766
400
|
_sonner.toast.info('You have been signed out remotely.');
|
|
767
401
|
logout().catch(remoteError => logger('Failed to process remote sign out', remoteError));
|
|
768
402
|
}, [logger, logout]);
|
|
769
|
-
const handleIdentityTransferComplete = (0, _react.useCallback)(async data => {
|
|
770
|
-
try {
|
|
771
|
-
logger('Received identity transfer complete notification', {
|
|
772
|
-
transferId: data.transferId,
|
|
773
|
-
sourceDeviceId: data.sourceDeviceId,
|
|
774
|
-
currentDeviceId,
|
|
775
|
-
hasActiveSession: activeSessionId !== null,
|
|
776
|
-
publicKey: data.publicKey.substring(0, 16) + '...'
|
|
777
|
-
});
|
|
778
|
-
const storedTransfer = getTransferCode(data.transferId);
|
|
779
|
-
if (!storedTransfer) {
|
|
780
|
-
logger('Transfer code not found for transferId', {
|
|
781
|
-
transferId: data.transferId
|
|
782
|
-
});
|
|
783
|
-
_sonner.toast.error('Transfer verification failed: Code not found. Identity will not be deleted.');
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// Verify publicKey matches first (most important check)
|
|
788
|
-
const publicKeyMatches = data.publicKey === storedTransfer.publicKey;
|
|
789
|
-
if (!publicKeyMatches) {
|
|
790
|
-
logger('Public key mismatch for transfer', {
|
|
791
|
-
transferId: data.transferId,
|
|
792
|
-
receivedPublicKey: data.publicKey.substring(0, 16) + '...',
|
|
793
|
-
storedPublicKey: storedTransfer.publicKey.substring(0, 16) + '...'
|
|
794
|
-
});
|
|
795
|
-
_sonner.toast.error('Transfer verification failed: Public key mismatch. Identity will not be deleted.');
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Verify deviceId matches - very lenient since publicKey is the critical check
|
|
800
|
-
// If publicKey matches, we allow deletion even if deviceId doesn't match exactly
|
|
801
|
-
// This handles cases where deviceId might not be available or slightly different
|
|
802
|
-
const deviceIdMatches =
|
|
803
|
-
// Exact match
|
|
804
|
-
data.sourceDeviceId && data.sourceDeviceId === currentDeviceId ||
|
|
805
|
-
// Stored sourceDeviceId matches current deviceId
|
|
806
|
-
storedTransfer.sourceDeviceId && storedTransfer.sourceDeviceId === currentDeviceId;
|
|
807
|
-
|
|
808
|
-
// If publicKey matches, we're very lenient with deviceId - only warn but don't block
|
|
809
|
-
if (!deviceIdMatches && publicKeyMatches) {
|
|
810
|
-
logger('Device ID mismatch for transfer, but publicKey matches - proceeding with deletion', {
|
|
811
|
-
transferId: data.transferId,
|
|
812
|
-
receivedDeviceId: data.sourceDeviceId,
|
|
813
|
-
storedDeviceId: storedTransfer.sourceDeviceId,
|
|
814
|
-
currentDeviceId,
|
|
815
|
-
hasActiveSession: activeSessionId !== null
|
|
816
|
-
});
|
|
817
|
-
// Proceed with deletion - publicKey match is the critical verification
|
|
818
|
-
} else if (!deviceIdMatches && !publicKeyMatches) {
|
|
819
|
-
// Both don't match - this is suspicious, block deletion
|
|
820
|
-
logger('Device ID and publicKey mismatch for transfer', {
|
|
821
|
-
transferId: data.transferId,
|
|
822
|
-
receivedDeviceId: data.sourceDeviceId,
|
|
823
|
-
currentDeviceId
|
|
824
|
-
});
|
|
825
|
-
_sonner.toast.error('Transfer verification failed: Device and key mismatch. Identity will not be deleted.');
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// Verify transfer code matches (if provided)
|
|
830
|
-
// Transfer code is optional - if not provided, we still proceed if publicKey matches
|
|
831
|
-
if (data.transferCode) {
|
|
832
|
-
const codeMatches = data.transferCode.toUpperCase() === storedTransfer.code.toUpperCase();
|
|
833
|
-
if (!codeMatches) {
|
|
834
|
-
logger('Transfer code mismatch, but publicKey matches - proceeding with deletion', {
|
|
835
|
-
transferId: data.transferId,
|
|
836
|
-
receivedCode: data.transferCode,
|
|
837
|
-
storedCode: storedTransfer.code.substring(0, 2) + '****'
|
|
838
|
-
});
|
|
839
|
-
// Don't block - publicKey match is sufficient, code mismatch might be due to user error
|
|
840
|
-
// Log warning but proceed
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Check if transfer is too old (safety timeout - 10 minutes)
|
|
845
|
-
const transferAge = Date.now() - storedTransfer.timestamp;
|
|
846
|
-
const tenMinutes = 10 * 60 * 1000;
|
|
847
|
-
if (transferAge > tenMinutes) {
|
|
848
|
-
logger('Transfer confirmation received too late', {
|
|
849
|
-
transferId: data.transferId,
|
|
850
|
-
age: transferAge,
|
|
851
|
-
ageMinutes: Math.round(transferAge / 60000)
|
|
852
|
-
});
|
|
853
|
-
_sonner.toast.error('Transfer verification failed: Confirmation received too late. Identity will not be deleted.');
|
|
854
|
-
clearTransferCode(data.transferId);
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// NOTE: Target device verification already happened server-side when notifyTransferComplete was called
|
|
859
|
-
// The server verified that the target device is authenticated and has the matching public key
|
|
860
|
-
// Additional client-side verification is not necessary and would require source device authentication
|
|
861
|
-
// which may not be available. The existing checks (public key match, transfer code, device ID) are sufficient.
|
|
862
|
-
|
|
863
|
-
logger('All transfer verifications passed, deleting identity from source device', {
|
|
864
|
-
transferId: data.transferId,
|
|
865
|
-
sourceDeviceId: data.sourceDeviceId,
|
|
866
|
-
publicKey: data.publicKey.substring(0, 16) + '...'
|
|
867
|
-
});
|
|
868
|
-
try {
|
|
869
|
-
// Verify identity still exists before deletion (safety check)
|
|
870
|
-
const identityStillExists = await _crypto.KeyManager.hasIdentity();
|
|
871
|
-
if (!identityStillExists) {
|
|
872
|
-
logger('Identity already deleted - skipping deletion', {
|
|
873
|
-
transferId: data.transferId
|
|
874
|
-
});
|
|
875
|
-
await updateTransferState(data.transferId, 'completed');
|
|
876
|
-
await clearTransferCode(data.transferId);
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
await deleteIdentityAndClearAccount(false, false, true);
|
|
880
|
-
|
|
881
|
-
// Verify identity was actually deleted
|
|
882
|
-
const identityDeleted = !(await _crypto.KeyManager.hasIdentity());
|
|
883
|
-
if (!identityDeleted) {
|
|
884
|
-
logger('Identity deletion failed - identity still exists', {
|
|
885
|
-
transferId: data.transferId
|
|
886
|
-
});
|
|
887
|
-
await updateTransferState(data.transferId, 'failed');
|
|
888
|
-
throw new Error('Identity deletion failed - identity still exists');
|
|
889
|
-
}
|
|
890
|
-
await updateTransferState(data.transferId, 'completed');
|
|
891
|
-
await clearTransferCode(data.transferId);
|
|
892
|
-
logger('Identity successfully deleted and transfer code cleared', {
|
|
893
|
-
transferId: data.transferId
|
|
894
|
-
});
|
|
895
|
-
_sonner.toast.success('Identity successfully transferred and removed from this device');
|
|
896
|
-
} catch (deleteError) {
|
|
897
|
-
logger('Error during identity deletion', deleteError);
|
|
898
|
-
await updateTransferState(data.transferId, 'failed');
|
|
899
|
-
throw deleteError;
|
|
900
|
-
}
|
|
901
|
-
} catch (error) {
|
|
902
|
-
logger('Failed to delete identity after transfer', error);
|
|
903
|
-
_sonner.toast.error(error?.message || 'Failed to remove identity from this device. Please try again manually from Security Settings.');
|
|
904
|
-
}
|
|
905
|
-
}, [deleteIdentityAndClearAccount, logger, getTransferCode, clearTransferCode, updateTransferState, currentDeviceId, activeSessionId, oxyServices]);
|
|
906
403
|
(0, _useSessionSocket.useSessionSocket)({
|
|
907
404
|
userId,
|
|
908
405
|
activeSessionId,
|
|
@@ -912,10 +409,8 @@ const OxyProvider = ({
|
|
|
912
409
|
clearSessionState,
|
|
913
410
|
baseURL: oxyServices.getBaseURL(),
|
|
914
411
|
getAccessToken: () => oxyServices.getAccessToken(),
|
|
915
|
-
getTransferCode: getTransferCode,
|
|
916
412
|
onRemoteSignOut: handleRemoteSignOut,
|
|
917
|
-
onSessionRemoved: handleSessionRemoved
|
|
918
|
-
onIdentityTransferComplete: handleIdentityTransferComplete
|
|
413
|
+
onSessionRemoved: handleSessionRemoved
|
|
919
414
|
});
|
|
920
415
|
const switchSessionForContext = (0, _react.useCallback)(async sessionId => {
|
|
921
416
|
await switchSession(sessionId);
|
|
@@ -949,7 +444,6 @@ const OxyProvider = ({
|
|
|
949
444
|
await (0, _avatarUtils.updateProfileWithAvatar)({
|
|
950
445
|
avatar: file.id
|
|
951
446
|
}, oxyServices, activeSessionId, queryClient, {
|
|
952
|
-
syncIdentity,
|
|
953
447
|
deviceId: currentDeviceId || undefined
|
|
954
448
|
});
|
|
955
449
|
_sonner.toast.success((0, _i18n.translate)(currentLanguage, 'editProfile.toasts.avatarUpdated') || 'Avatar updated');
|
|
@@ -959,7 +453,7 @@ const OxyProvider = ({
|
|
|
959
453
|
}
|
|
960
454
|
}
|
|
961
455
|
});
|
|
962
|
-
}, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient
|
|
456
|
+
}, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient]);
|
|
963
457
|
const contextValue = (0, _react.useMemo)(() => ({
|
|
964
458
|
user,
|
|
965
459
|
sessions,
|
|
@@ -974,24 +468,7 @@ const OxyProvider = ({
|
|
|
974
468
|
currentLanguageMetadata,
|
|
975
469
|
currentLanguageName,
|
|
976
470
|
currentNativeLanguageName,
|
|
977
|
-
createIdentity,
|
|
978
|
-
importIdentity,
|
|
979
471
|
signIn,
|
|
980
|
-
hasIdentity,
|
|
981
|
-
getPublicKey,
|
|
982
|
-
isIdentitySynced,
|
|
983
|
-
syncIdentity,
|
|
984
|
-
deleteIdentityAndClearAccount,
|
|
985
|
-
storeTransferCode,
|
|
986
|
-
getTransferCode,
|
|
987
|
-
clearTransferCode,
|
|
988
|
-
getAllPendingTransfers,
|
|
989
|
-
getActiveTransferId,
|
|
990
|
-
updateTransferState,
|
|
991
|
-
identitySyncState: {
|
|
992
|
-
isSynced: isIdentitySyncedStore ?? true,
|
|
993
|
-
isSyncing: isSyncing ?? false
|
|
994
|
-
},
|
|
995
472
|
logout,
|
|
996
473
|
logoutAll,
|
|
997
474
|
switchSession: switchSessionForContext,
|
|
@@ -1007,7 +484,7 @@ const OxyProvider = ({
|
|
|
1007
484
|
useFollow: useFollowHook,
|
|
1008
485
|
showBottomSheet: showBottomSheetForContext,
|
|
1009
486
|
openAvatarPicker
|
|
1010
|
-
}), [activeSessionId, currentDeviceId,
|
|
487
|
+
}), [activeSessionId, currentDeviceId, signIn, currentLanguage, currentLanguageMetadata, currentLanguageName, currentNativeLanguageName, error, getDeviceSessions, isAuthenticated, isLoading, logout, logoutAll, logoutAllDeviceSessions, oxyServices, refreshSessionsWithUser, sessions, setLanguage, switchSessionForContext, tokenReady, isStorageReady, updateDeviceName, clearAllAccountData, useFollowHook, user, showBottomSheetForContext, openAvatarPicker]);
|
|
1011
488
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyContextBase.OxyContext.Provider, {
|
|
1012
489
|
value: contextValue,
|
|
1013
490
|
children: children
|