@oxyhq/services 5.16.37 → 5.16.39

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 (37) hide show
  1. package/lib/commonjs/core/mixins/OxyServices.auth.js +10 -3
  2. package/lib/commonjs/core/mixins/OxyServices.auth.js.map +1 -1
  3. package/lib/commonjs/index.js +0 -7
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/ui/context/OxyContext.js +84 -14
  6. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  7. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +93 -37
  8. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  9. package/lib/commonjs/ui/stores/transferStore.js +4 -0
  10. package/lib/commonjs/ui/stores/transferStore.js.map +1 -1
  11. package/lib/module/core/mixins/OxyServices.auth.js +10 -3
  12. package/lib/module/core/mixins/OxyServices.auth.js.map +1 -1
  13. package/lib/module/index.js +1 -1
  14. package/lib/module/index.js.map +1 -1
  15. package/lib/module/ui/context/OxyContext.js +85 -15
  16. package/lib/module/ui/context/OxyContext.js.map +1 -1
  17. package/lib/module/ui/context/hooks/useAuthOperations.js +93 -37
  18. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  19. package/lib/module/ui/stores/transferStore.js +4 -0
  20. package/lib/module/ui/stores/transferStore.js.map +1 -1
  21. package/lib/typescript/core/mixins/OxyServices.auth.d.ts +2 -1
  22. package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -1
  23. package/lib/typescript/core/mixins/index.d.ts +1 -1
  24. package/lib/typescript/index.d.ts +2 -1
  25. package/lib/typescript/index.d.ts.map +1 -1
  26. package/lib/typescript/ui/context/OxyContext.d.ts +3 -3
  27. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  28. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +2 -2
  29. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  30. package/lib/typescript/ui/stores/transferStore.d.ts +1 -0
  31. package/lib/typescript/ui/stores/transferStore.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/core/mixins/OxyServices.auth.ts +12 -3
  34. package/src/index.ts +1 -1
  35. package/src/ui/context/OxyContext.tsx +84 -17
  36. package/src/ui/context/hooks/useAuthOperations.ts +89 -38
  37. package/src/ui/stores/transferStore.ts +6 -0
@@ -34,7 +34,7 @@ export interface UseAuthOperationsOptions {
34
34
 
35
35
  export interface UseAuthOperationsResult {
36
36
  /** Create a new identity locally (offline-first) and optionally sync with server */
37
- createIdentity: () => Promise<{ synced: boolean }>;
37
+ createIdentity: (username?: string) => Promise<{ synced: boolean }>;
38
38
  /** Import an existing identity from backup file data */
39
39
  importIdentity: (backupData: BackupData, password: string) => Promise<{ synced: boolean }>;
40
40
  /** Sign in with existing identity on device */
@@ -50,7 +50,7 @@ export interface UseAuthOperationsResult {
50
50
  /** Check if identity is synced with server */
51
51
  isIdentitySynced: () => Promise<boolean>;
52
52
  /** Sync local identity with server (when online) */
53
- syncIdentity: () => Promise<User>;
53
+ syncIdentity: (username?: string) => Promise<User>;
54
54
  }
55
55
 
56
56
  const LOGIN_ERROR_CODE = 'LOGIN_ERROR';
@@ -91,13 +91,28 @@ export const useAuthOperations = ({
91
91
  const clearSessionsIfIdentityChanged = useCallback(
92
92
  async (oldPublicKey: string | null, newPublicKey: string): Promise<void> => {
93
93
  if (oldPublicKey && oldPublicKey !== newPublicKey) {
94
+ if (__DEV__ && logger) {
95
+ logger('CRITICAL: Identity changed - clearing all session data', {
96
+ oldPublicKey: oldPublicKey.substring(0, 16) + '...',
97
+ newPublicKey: newPublicKey.substring(0, 16) + '...',
98
+ });
99
+ }
100
+
94
101
  // Clear all session state to prevent old identity's data from showing up
95
102
  await clearSessionState();
96
- // Logout from auth store
103
+
104
+ // Logout from auth store (clears user, isAuthenticated, etc.)
97
105
  logoutStore();
106
+
107
+ // Force KeyManager cache invalidation
108
+ KeyManager.invalidateCache();
109
+
110
+ if (__DEV__ && logger) {
111
+ logger('Session state cleared for new identity');
112
+ }
98
113
  }
99
114
  },
100
- [clearSessionState, logoutStore]
115
+ [clearSessionState, logoutStore, logger]
101
116
  );
102
117
 
103
118
  /**
@@ -300,9 +315,14 @@ export const useAuthOperations = ({
300
315
  /**
301
316
  * Create a new identity (offline-first)
302
317
  * Identity is purely cryptographic - no username or email required
318
+ *
319
+ * This function generates keys locally and does NOT register with the server yet.
320
+ * Registration will happen during syncIdentity() or when username is provided.
321
+ *
322
+ * @param username - Optional username to set during registration (if online)
303
323
  */
304
324
  const createIdentity = useCallback(
305
- async (): Promise<{ synced: boolean }> => {
325
+ async (username?: string): Promise<{ synced: boolean }> => {
306
326
  if (!storage) throw new Error('Storage not initialized');
307
327
 
308
328
  setAuthState({ isLoading: true, error: null });
@@ -311,40 +331,61 @@ export const useAuthOperations = ({
311
331
  // CRITICAL: Get old public key before creating new identity
312
332
  // If identity changes, we must clear all session data to prevent data leakage
313
333
  const oldPublicKey = await KeyManager.getPublicKey().catch(() => null);
334
+
335
+ if (__DEV__ && logger) {
336
+ logger('Creating new identity', { hadPreviousIdentity: !!oldPublicKey });
337
+ }
314
338
 
315
339
  // Generate new key pair directly (works offline)
316
340
  const { publicKey, privateKey } = await KeyManager.generateKeyPair();
317
341
  await KeyManager.importKeyPair(privateKey);
342
+
343
+ if (__DEV__ && logger) {
344
+ logger('Identity keys generated', { publicKey: publicKey.substring(0, 16) + '...' });
345
+ }
318
346
 
319
347
  // Clear sessions if identity changed (prevents data leakage)
320
348
  await clearSessionsIfIdentityChanged(oldPublicKey, publicKey);
321
349
 
322
- // Mark as not synced
350
+ // Mark as not synced initially
323
351
  await storage.setItem('oxy_identity_synced', 'false');
324
352
  setIdentitySynced(false);
325
353
 
326
- // Try to sync with server (will succeed if online)
327
- try {
328
- const { signature, timestamp } = await SignatureService.createRegistrationSignature();
329
- await oxyServices.register(publicKey, signature, timestamp);
330
-
331
- // Mark as synced (Zustand store + storage)
332
- await storage.setItem('oxy_identity_synced', 'true');
333
- setIdentitySynced(true);
334
-
335
- return {
336
- synced: true,
337
- };
338
- } catch (syncError) {
339
- // Offline or server error - identity is created locally but not synced
340
- if (__DEV__) {
341
- console.log('[Auth] Identity created locally, will sync when online:', syncError);
354
+ // If username provided, try to register immediately (online only)
355
+ if (username) {
356
+ // Validate username format before attempting registration
357
+ const trimmedUsername = username.trim();
358
+ if (trimmedUsername && /^[a-zA-Z0-9]{3,30}$/.test(trimmedUsername)) {
359
+ try {
360
+ const { signature, timestamp } = await SignatureService.createRegistrationSignature();
361
+ await oxyServices.register(publicKey, signature, timestamp, trimmedUsername);
362
+
363
+ // Mark as synced (Zustand store + storage)
364
+ await storage.setItem('oxy_identity_synced', 'true');
365
+ setIdentitySynced(true);
366
+
367
+ if (__DEV__ && logger) {
368
+ logger('Identity synced with server successfully with username');
369
+ }
370
+
371
+ return { synced: true };
372
+ } catch (syncError) {
373
+ // Offline or server error - identity created locally but not synced
374
+ if (__DEV__ && logger) {
375
+ logger('Identity created locally with username (offline), will sync when online', syncError);
376
+ }
377
+
378
+ return { synced: false };
379
+ }
342
380
  }
343
-
344
- return {
345
- synced: false,
346
- };
347
381
  }
382
+
383
+ // No username provided - defer registration until later
384
+ if (__DEV__ && logger) {
385
+ logger('Identity created locally without username, will register during sync');
386
+ }
387
+
388
+ return { synced: false };
348
389
  } catch (error) {
349
390
  // CRITICAL: Never delete identity on error - it may have been successfully created
350
391
  // Only log the error and let the user recover using their backup file
@@ -360,7 +401,7 @@ export const useAuthOperations = ({
360
401
  await storage.setItem('oxy_identity_synced', 'false').catch(() => {});
361
402
  setIdentitySynced(false);
362
403
  if (__DEV__ && logger) {
363
- logger('Identity was created but sync failed - user can sync later using backup file');
404
+ logger('Identity was created but sync failed - user can sync later');
364
405
  }
365
406
  } else {
366
407
  // No identity exists - this was a generation failure, safe to clean up sync flag
@@ -398,9 +439,11 @@ export const useAuthOperations = ({
398
439
  /**
399
440
  * Sync local identity with server (call when online)
400
441
  * TanStack Query handles offline mutations automatically
442
+ *
443
+ * @param username - Optional username to set during sync
401
444
  */
402
445
  const syncIdentity = useCallback(
403
- async (): Promise<User> => {
446
+ async (username?: string): Promise<User> => {
404
447
  if (!storage) throw new Error('Storage not initialized');
405
448
 
406
449
  setAuthState({ isLoading: true, error: null });
@@ -425,7 +468,7 @@ export const useAuthOperations = ({
425
468
  if (!registered) {
426
469
  // Register with server (identity is just the publicKey)
427
470
  const { signature, timestamp } = await SignatureService.createRegistrationSignature();
428
- await oxyServices.register(publicKey, signature, timestamp);
471
+ await oxyServices.register(publicKey, signature, timestamp, username);
429
472
  }
430
473
 
431
474
  // Mark as synced (Zustand store + storage)
@@ -435,13 +478,6 @@ export const useAuthOperations = ({
435
478
  // Sign in
436
479
  const user = await performSignIn(publicKey);
437
480
 
438
- // Check if user has username - required for syncing
439
- if (!user.username) {
440
- const usernameError = new Error('USERNAME_REQUIRED');
441
- (usernameError as any).code = 'USERNAME_REQUIRED';
442
- throw usernameError;
443
- }
444
-
445
481
  // TanStack Query will automatically retry any pending mutations
446
482
 
447
483
  return user;
@@ -465,9 +501,13 @@ export const useAuthOperations = ({
465
501
 
466
502
  /**
467
503
  * Import identity from backup file data (offline-first)
504
+ *
505
+ * @param backupData - The backup data to import
506
+ * @param password - Password to decrypt the backup
507
+ * @param username - Optional username to set during registration
468
508
  */
469
509
  const importIdentity = useCallback(
470
- async (backupData: BackupData, password: string): Promise<{ synced: boolean }> => {
510
+ async (backupData: BackupData, password: string, username?: string): Promise<{ synced: boolean }> => {
471
511
  if (!storage) throw new Error('Storage not initialized');
472
512
 
473
513
  // Validate arguments - ensure backupData is an object, not a string (old signature)
@@ -489,6 +529,13 @@ export const useAuthOperations = ({
489
529
  // CRITICAL: Get old public key before importing new identity
490
530
  // If identity changes, we must clear all session data to prevent data leakage
491
531
  const oldPublicKey = await KeyManager.getPublicKey().catch(() => null);
532
+
533
+ if (__DEV__ && logger) {
534
+ logger('Importing identity from backup', {
535
+ hadPreviousIdentity: !!oldPublicKey,
536
+ backupPublicKey: backupData.publicKey.substring(0, 16) + '...'
537
+ });
538
+ }
492
539
 
493
540
  // Decrypt private key from backup data
494
541
  const Crypto = await import('expo-crypto');
@@ -525,6 +572,10 @@ export const useAuthOperations = ({
525
572
 
526
573
  // Import the key pair
527
574
  const publicKey = await KeyManager.importKeyPair(privateKey);
575
+
576
+ if (__DEV__ && logger) {
577
+ logger('Identity keys imported', { publicKey: publicKey.substring(0, 16) + '...' });
578
+ }
528
579
 
529
580
  // Verify public key matches
530
581
  if (publicKey !== backupData.publicKey) {
@@ -551,7 +602,7 @@ export const useAuthOperations = ({
551
602
  } else {
552
603
  // Need to register this identity (identity is just the publicKey)
553
604
  const { signature, timestamp } = await SignatureService.createRegistrationSignature();
554
- await oxyServices.register(publicKey, signature, timestamp);
605
+ await oxyServices.register(publicKey, signature, timestamp, username);
555
606
 
556
607
  await storage.setItem('oxy_identity_synced', 'true');
557
608
  setIdentitySynced(true);
@@ -31,6 +31,7 @@ export interface TransferState {
31
31
  markRestored: () => void;
32
32
  cleanupExpired: () => void;
33
33
  reset: () => void;
34
+ clearAll: () => void; // Alias for reset - use when clearing on identity change for semantic clarity
34
35
  }
35
36
 
36
37
  const FIFTEEN_MINUTES = 15 * 60 * 1000;
@@ -178,6 +179,11 @@ export const useTransferStore = create<TransferState>((set, get) => ({
178
179
  reset: () => {
179
180
  set(initialState);
180
181
  },
182
+
183
+ clearAll: () => {
184
+ // Delegate to reset to maintain single source of truth
185
+ get().reset();
186
+ },
181
187
  }));
182
188
 
183
189
  /**