@oxyhq/services 5.16.38 → 5.16.40
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/core/mixins/OxyServices.auth.js +10 -3
- package/lib/commonjs/core/mixins/OxyServices.auth.js.map +1 -1
- package/lib/commonjs/index.js +0 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +99 -53
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.auth.js +10 -3
- package/lib/module/core/mixins/OxyServices.auth.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +99 -53
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts +2 -1
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -1
- package/lib/typescript/core/mixins/index.d.ts +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +3 -3
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +2 -2
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/mixins/OxyServices.auth.ts +12 -3
- package/src/index.ts +1 -1
- package/src/ui/context/OxyContext.tsx +3 -3
- package/src/ui/context/hooks/useAuthOperations.ts +95 -54
|
@@ -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';
|
|
@@ -313,18 +313,27 @@ export const useAuthOperations = ({
|
|
|
313
313
|
);
|
|
314
314
|
|
|
315
315
|
/**
|
|
316
|
-
* Create a new identity (offline-first)
|
|
317
|
-
*
|
|
316
|
+
* Create a new identity (offline-first).
|
|
317
|
+
*
|
|
318
|
+
* Generates cryptographic keys locally without requiring server connection.
|
|
319
|
+
* Identity is based on the public/private key pair - no username or email required.
|
|
320
|
+
*
|
|
321
|
+
* IMPORTANT: This function only clears session data if the identity actually changes
|
|
322
|
+
* (i.e., a new key pair is generated). Retrying registration with the same identity
|
|
323
|
+
* will NOT clear session data.
|
|
324
|
+
*
|
|
325
|
+
* @param username - Optional username to set during registration (if online)
|
|
326
|
+
* @returns Object with synced status indicating if identity was registered with server
|
|
318
327
|
*/
|
|
319
328
|
const createIdentity = useCallback(
|
|
320
|
-
async (): Promise<{ synced: boolean }> => {
|
|
329
|
+
async (username?: string): Promise<{ synced: boolean }> => {
|
|
321
330
|
if (!storage) throw new Error('Storage not initialized');
|
|
322
331
|
|
|
323
332
|
setAuthState({ isLoading: true, error: null });
|
|
324
333
|
|
|
325
334
|
try {
|
|
326
|
-
//
|
|
327
|
-
//
|
|
335
|
+
// Get old public key before creating new identity
|
|
336
|
+
// This is used to detect if identity actually changed
|
|
328
337
|
const oldPublicKey = await KeyManager.getPublicKey().catch(() => null);
|
|
329
338
|
|
|
330
339
|
if (__DEV__ && logger) {
|
|
@@ -339,39 +348,56 @@ export const useAuthOperations = ({
|
|
|
339
348
|
logger('Identity keys generated', { publicKey: publicKey.substring(0, 16) + '...' });
|
|
340
349
|
}
|
|
341
350
|
|
|
342
|
-
//
|
|
351
|
+
// Only clear sessions if identity actually changed
|
|
352
|
+
// This prevents clearing sessions on registration retries
|
|
343
353
|
await clearSessionsIfIdentityChanged(oldPublicKey, publicKey);
|
|
344
354
|
|
|
345
|
-
// Mark as not synced
|
|
355
|
+
// Mark as not synced initially
|
|
346
356
|
await storage.setItem('oxy_identity_synced', 'false');
|
|
347
357
|
setIdentitySynced(false);
|
|
348
358
|
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
359
|
+
// If username provided, try to register immediately (online only)
|
|
360
|
+
if (username) {
|
|
361
|
+
// Validate username format before attempting registration
|
|
362
|
+
const trimmedUsername = username.trim();
|
|
363
|
+
if (trimmedUsername && /^[a-zA-Z0-9]{3,30}$/.test(trimmedUsername)) {
|
|
364
|
+
try {
|
|
365
|
+
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
366
|
+
await oxyServices.register(publicKey, signature, timestamp, trimmedUsername);
|
|
367
|
+
|
|
368
|
+
// Mark as synced (Zustand store + storage)
|
|
369
|
+
await storage.setItem('oxy_identity_synced', 'true');
|
|
370
|
+
setIdentitySynced(true);
|
|
371
|
+
|
|
372
|
+
if (__DEV__ && logger) {
|
|
373
|
+
logger('Identity synced with server successfully with username');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return { synced: true };
|
|
377
|
+
} catch (syncError) {
|
|
378
|
+
// Offline or server error - identity created locally but not synced
|
|
379
|
+
if (__DEV__ && logger) {
|
|
380
|
+
logger('Identity created locally with username (offline), will sync when online', syncError);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return { synced: false };
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
// Invalid username format - log the issue
|
|
387
|
+
if (__DEV__ && logger) {
|
|
388
|
+
logger('Invalid username format, identity created without username', {
|
|
389
|
+
providedUsername: username.substring(0, 20),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
360
392
|
}
|
|
393
|
+
}
|
|
361
394
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
} catch (syncError) {
|
|
366
|
-
// Offline or server error - identity is created locally but not synced
|
|
367
|
-
if (__DEV__ && logger) {
|
|
368
|
-
logger('Identity created locally (offline), will sync when online', syncError);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return {
|
|
372
|
-
synced: false,
|
|
373
|
-
};
|
|
395
|
+
// No username provided or invalid format - defer registration until later
|
|
396
|
+
if (__DEV__ && logger) {
|
|
397
|
+
logger('Identity created locally without username, will register during sync');
|
|
374
398
|
}
|
|
399
|
+
|
|
400
|
+
return { synced: false };
|
|
375
401
|
} catch (error) {
|
|
376
402
|
// CRITICAL: Never delete identity on error - it may have been successfully created
|
|
377
403
|
// Only log the error and let the user recover using their backup file
|
|
@@ -387,7 +413,7 @@ export const useAuthOperations = ({
|
|
|
387
413
|
await storage.setItem('oxy_identity_synced', 'false').catch(() => {});
|
|
388
414
|
setIdentitySynced(false);
|
|
389
415
|
if (__DEV__ && logger) {
|
|
390
|
-
logger('Identity was created but sync failed - user can sync later
|
|
416
|
+
logger('Identity was created but sync failed - user can sync later');
|
|
391
417
|
}
|
|
392
418
|
} else {
|
|
393
419
|
// No identity exists - this was a generation failure, safe to clean up sync flag
|
|
@@ -423,11 +449,19 @@ export const useAuthOperations = ({
|
|
|
423
449
|
}, [storage, setIdentitySynced]);
|
|
424
450
|
|
|
425
451
|
/**
|
|
426
|
-
* Sync local identity with server
|
|
427
|
-
*
|
|
452
|
+
* Sync local identity with server.
|
|
453
|
+
*
|
|
454
|
+
* Registers the identity with the server if not already registered.
|
|
455
|
+
* This function is idempotent - calling it multiple times is safe.
|
|
456
|
+
*
|
|
457
|
+
* TanStack Query handles offline mutations automatically, so this will
|
|
458
|
+
* retry automatically when connection is restored.
|
|
459
|
+
*
|
|
460
|
+
* @param username - Optional username to set during sync/registration
|
|
461
|
+
* @returns User object after successful sync and sign-in
|
|
428
462
|
*/
|
|
429
463
|
const syncIdentity = useCallback(
|
|
430
|
-
async (): Promise<User> => {
|
|
464
|
+
async (username?: string): Promise<User> => {
|
|
431
465
|
if (!storage) throw new Error('Storage not initialized');
|
|
432
466
|
|
|
433
467
|
setAuthState({ isLoading: true, error: null });
|
|
@@ -443,6 +477,7 @@ export const useAuthOperations = ({
|
|
|
443
477
|
const alreadySynced = await storage.getItem('oxy_identity_synced');
|
|
444
478
|
if (alreadySynced === 'true') {
|
|
445
479
|
setIdentitySynced(true);
|
|
480
|
+
// Identity is already synced, just sign in
|
|
446
481
|
return await performSignIn(publicKey);
|
|
447
482
|
}
|
|
448
483
|
|
|
@@ -452,7 +487,7 @@ export const useAuthOperations = ({
|
|
|
452
487
|
if (!registered) {
|
|
453
488
|
// Register with server (identity is just the publicKey)
|
|
454
489
|
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
455
|
-
await oxyServices.register(publicKey, signature, timestamp);
|
|
490
|
+
await oxyServices.register(publicKey, signature, timestamp, username);
|
|
456
491
|
}
|
|
457
492
|
|
|
458
493
|
// Mark as synced (Zustand store + storage)
|
|
@@ -462,13 +497,6 @@ export const useAuthOperations = ({
|
|
|
462
497
|
// Sign in
|
|
463
498
|
const user = await performSignIn(publicKey);
|
|
464
499
|
|
|
465
|
-
// Check if user has username - required for syncing
|
|
466
|
-
if (!user.username) {
|
|
467
|
-
const usernameError = new Error('USERNAME_REQUIRED');
|
|
468
|
-
(usernameError as any).code = 'USERNAME_REQUIRED';
|
|
469
|
-
throw usernameError;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
500
|
// TanStack Query will automatically retry any pending mutations
|
|
473
501
|
|
|
474
502
|
return user;
|
|
@@ -491,10 +519,22 @@ export const useAuthOperations = ({
|
|
|
491
519
|
);
|
|
492
520
|
|
|
493
521
|
/**
|
|
494
|
-
* Import identity from backup file
|
|
522
|
+
* Import identity from encrypted backup file.
|
|
523
|
+
*
|
|
524
|
+
* Restores a previously created identity from an encrypted backup.
|
|
525
|
+
* The backup is decrypted using the provided password.
|
|
526
|
+
*
|
|
527
|
+
* IMPORTANT: This function clears session data only if importing a different
|
|
528
|
+
* identity than the one currently stored. Re-importing the same identity
|
|
529
|
+
* will NOT clear session data.
|
|
530
|
+
*
|
|
531
|
+
* @param backupData - The encrypted backup data object
|
|
532
|
+
* @param password - Password to decrypt the backup
|
|
533
|
+
* @param username - Optional username to set during registration if not yet registered
|
|
534
|
+
* @returns Object with synced status indicating if identity was registered with server
|
|
495
535
|
*/
|
|
496
536
|
const importIdentity = useCallback(
|
|
497
|
-
async (backupData: BackupData, password: string): Promise<{ synced: boolean }> => {
|
|
537
|
+
async (backupData: BackupData, password: string, username?: string): Promise<{ synced: boolean }> => {
|
|
498
538
|
if (!storage) throw new Error('Storage not initialized');
|
|
499
539
|
|
|
500
540
|
// Validate arguments - ensure backupData is an object, not a string (old signature)
|
|
@@ -513,8 +553,8 @@ export const useAuthOperations = ({
|
|
|
513
553
|
setAuthState({ isLoading: true, error: null });
|
|
514
554
|
|
|
515
555
|
try {
|
|
516
|
-
//
|
|
517
|
-
//
|
|
556
|
+
// Get old public key before importing identity
|
|
557
|
+
// This is used to detect if identity actually changed
|
|
518
558
|
const oldPublicKey = await KeyManager.getPublicKey().catch(() => null);
|
|
519
559
|
|
|
520
560
|
if (__DEV__ && logger) {
|
|
@@ -569,10 +609,11 @@ export const useAuthOperations = ({
|
|
|
569
609
|
throw new Error('Backup file is corrupted or password is incorrect');
|
|
570
610
|
}
|
|
571
611
|
|
|
572
|
-
//
|
|
612
|
+
// Only clear sessions if identity actually changed
|
|
613
|
+
// This prevents clearing sessions when re-importing the same identity
|
|
573
614
|
await clearSessionsIfIdentityChanged(oldPublicKey, publicKey);
|
|
574
615
|
|
|
575
|
-
// Mark as not synced
|
|
616
|
+
// Mark as not synced initially (will check server status below)
|
|
576
617
|
await storage.setItem('oxy_identity_synced', 'false');
|
|
577
618
|
setIdentitySynced(false);
|
|
578
619
|
|
|
@@ -582,21 +623,21 @@ export const useAuthOperations = ({
|
|
|
582
623
|
const { registered } = await oxyServices.checkPublicKeyRegistered(publicKey);
|
|
583
624
|
|
|
584
625
|
if (registered) {
|
|
585
|
-
// Identity exists, mark as synced
|
|
626
|
+
// Identity exists on server, mark as synced
|
|
586
627
|
await storage.setItem('oxy_identity_synced', 'true');
|
|
587
628
|
setIdentitySynced(true);
|
|
588
629
|
return { synced: true };
|
|
589
630
|
} else {
|
|
590
|
-
// Need to register this identity
|
|
631
|
+
// Need to register this identity
|
|
591
632
|
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
592
|
-
await oxyServices.register(publicKey, signature, timestamp);
|
|
633
|
+
await oxyServices.register(publicKey, signature, timestamp, username);
|
|
593
634
|
|
|
594
635
|
await storage.setItem('oxy_identity_synced', 'true');
|
|
595
636
|
setIdentitySynced(true);
|
|
596
637
|
return { synced: true };
|
|
597
638
|
}
|
|
598
639
|
} catch (syncError) {
|
|
599
|
-
// Offline - identity restored locally but not synced
|
|
640
|
+
// Offline or server error - identity restored locally but not synced
|
|
600
641
|
if (__DEV__) {
|
|
601
642
|
console.log('[Auth] Identity imported locally, will sync when online:', syncError);
|
|
602
643
|
}
|