@oxyhq/services 5.16.30 → 5.16.31
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/services/SessionService.js +2 -1
- package/lib/commonjs/core/services/SessionService.js.map +1 -1
- package/lib/commonjs/core/services/TokenService.js +17 -9
- package/lib/commonjs/core/services/TokenService.js.map +1 -1
- package/lib/commonjs/models/interfaces.js +9 -8
- package/lib/commonjs/models/interfaces.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +40 -4
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +25 -14
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +12 -4
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +8 -0
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/utils/sessionHelpers.js +26 -11
- package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/commonjs/utils/sessionUtils.js +8 -1
- package/lib/commonjs/utils/sessionUtils.js.map +1 -1
- package/lib/module/core/services/SessionService.js +2 -1
- package/lib/module/core/services/SessionService.js.map +1 -1
- package/lib/module/core/services/TokenService.js +17 -9
- package/lib/module/core/services/TokenService.js.map +1 -1
- package/lib/module/models/interfaces.js +9 -8
- package/lib/module/models/interfaces.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +40 -4
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +25 -14
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/hooks/queries/useServicesQueries.js +13 -5
- package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js +8 -0
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/utils/sessionHelpers.js +26 -11
- package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/module/utils/sessionUtils.js +8 -1
- package/lib/module/utils/sessionUtils.js.map +1 -1
- package/lib/typescript/core/services/SessionService.d.ts +4 -2
- package/lib/typescript/core/services/SessionService.d.ts.map +1 -1
- package/lib/typescript/core/services/TokenService.d.ts +8 -3
- package/lib/typescript/core/services/TokenService.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +9 -8
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +4 -2
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/ui/utils/sessionHelpers.d.ts +6 -2
- package/lib/typescript/ui/utils/sessionHelpers.d.ts.map +1 -1
- package/lib/typescript/utils/sessionUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/services/SessionService.ts +4 -2
- package/src/core/services/TokenService.ts +18 -10
- package/src/models/interfaces.ts +11 -10
- package/src/models/session.ts +5 -3
- package/src/ui/context/OxyContext.tsx +56 -20
- package/src/ui/context/hooks/useAuthOperations.ts +23 -15
- package/src/ui/hooks/auth/index.ts +1 -0
- package/src/ui/hooks/queries/useServicesQueries.ts +8 -3
- package/src/ui/hooks/useSessionManagement.ts +8 -1
- package/src/ui/utils/sessionHelpers.ts +32 -15
- package/src/utils/sessionUtils.ts +8 -1
|
@@ -222,7 +222,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
222
222
|
// CRITICAL: Invalidate cache on app startup to ensure fresh state check
|
|
223
223
|
// This prevents stale cache from previous session from showing incorrect state
|
|
224
224
|
KeyManager.invalidateCache();
|
|
225
|
-
|
|
225
|
+
|
|
226
226
|
// Check if identity exists and verify integrity
|
|
227
227
|
const hasIdentity = await KeyManager.hasIdentity();
|
|
228
228
|
if (hasIdentity) {
|
|
@@ -421,7 +421,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
421
421
|
if (pendingTransfers.length > 0) {
|
|
422
422
|
const activeTransferId = getActiveTransferId();
|
|
423
423
|
const hasActiveTransfer = activeTransferId && pendingTransfers.some((t: { transferId: string; data: any }) => t.transferId === activeTransferId);
|
|
424
|
-
|
|
424
|
+
|
|
425
425
|
if (hasActiveTransfer) {
|
|
426
426
|
throw new Error(
|
|
427
427
|
'Cannot delete identity: An active identity transfer is in progress. ' +
|
|
@@ -485,11 +485,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
485
485
|
if (wasOffline) {
|
|
486
486
|
const now = Date.now();
|
|
487
487
|
const timeSinceLastLog = now - lastReconnectionLog;
|
|
488
|
-
|
|
488
|
+
|
|
489
489
|
if (timeSinceLastLog >= RECONNECTION_LOG_DEBOUNCE_MS) {
|
|
490
490
|
logger('Network reconnected, checking identity sync...');
|
|
491
491
|
lastReconnectionLog = now;
|
|
492
|
-
|
|
492
|
+
|
|
493
493
|
// Sync identity first (if not synced)
|
|
494
494
|
try {
|
|
495
495
|
const hasIdentityValue = await hasIdentity();
|
|
@@ -520,7 +520,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
520
520
|
// This is handled by useCheckPendingTransfers hook which runs automatically
|
|
521
521
|
// when authenticated and online
|
|
522
522
|
}
|
|
523
|
-
|
|
523
|
+
|
|
524
524
|
// TanStack Query will automatically retry pending mutations
|
|
525
525
|
// Reset flag immediately after processing (whether logged or not)
|
|
526
526
|
wasOffline = false;
|
|
@@ -580,6 +580,22 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
580
580
|
setTokenReady(false);
|
|
581
581
|
|
|
582
582
|
try {
|
|
583
|
+
// CRITICAL: Get current local identity (publicKey) before restoring sessions
|
|
584
|
+
// Sessions must belong to the current local identity stored in Accounts app
|
|
585
|
+
let currentPublicKey: string | null = null;
|
|
586
|
+
try {
|
|
587
|
+
if (Platform.OS !== 'web') {
|
|
588
|
+
// Only check identity on native platforms (web doesn't have local identity)
|
|
589
|
+
// KeyManager is already imported at the top of the file
|
|
590
|
+
currentPublicKey = await KeyManager.getPublicKey();
|
|
591
|
+
}
|
|
592
|
+
} catch (identityError) {
|
|
593
|
+
if (__DEV__) {
|
|
594
|
+
logger('Failed to get current local identity during session restoration', identityError);
|
|
595
|
+
}
|
|
596
|
+
// Continue without identity check if it fails (e.g., on web platform)
|
|
597
|
+
}
|
|
598
|
+
|
|
583
599
|
const storedSessionIdsJson = await storage.getItem(storageKeys.sessionIds);
|
|
584
600
|
const storedSessionIds: string[] = storedSessionIdsJson ? JSON.parse(storedSessionIdsJson) : [];
|
|
585
601
|
const storedActiveSessionId = await storage.getItem(storageKeys.activeSessionId);
|
|
@@ -592,12 +608,25 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
592
608
|
const validation = await oxyServices.validateSession(sessionId, { useHeaderValidation: true });
|
|
593
609
|
if (validation?.valid && validation.user) {
|
|
594
610
|
const now = new Date();
|
|
611
|
+
// user.id is now publicKey (canonical identity)
|
|
612
|
+
const sessionPublicKey = validation.user.publicKey || validation.user.id || '';
|
|
613
|
+
|
|
614
|
+
// IDENTITY BINDING: Only restore sessions that match current local identity
|
|
615
|
+
// If we have a current local identity, filter out sessions that don't match
|
|
616
|
+
if (currentPublicKey && sessionPublicKey !== currentPublicKey) {
|
|
617
|
+
if (__DEV__) {
|
|
618
|
+
logger(`Skipping session ${sessionId.substring(0, 8)}... - belongs to different identity (${sessionPublicKey.substring(0, 16)}... vs ${currentPublicKey.substring(0, 16)}...)`);
|
|
619
|
+
}
|
|
620
|
+
continue; // Skip this session - it belongs to a different identity
|
|
621
|
+
}
|
|
622
|
+
|
|
595
623
|
validSessions.push({
|
|
596
624
|
sessionId,
|
|
597
625
|
deviceId: '',
|
|
598
626
|
expiresAt: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
599
627
|
lastActive: now.toISOString(),
|
|
600
|
-
|
|
628
|
+
publicKey: sessionPublicKey, // Canonical user identity
|
|
629
|
+
userId: validation.user.id?.toString(), // Optional MongoDB ObjectId for backward compatibility
|
|
601
630
|
isCurrent: sessionId === storedActiveSessionId,
|
|
602
631
|
});
|
|
603
632
|
}
|
|
@@ -615,6 +644,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
615
644
|
|
|
616
645
|
if (validSessions.length > 0) {
|
|
617
646
|
updateSessions(validSessions, { merge: false });
|
|
647
|
+
} else if (currentPublicKey && storedSessionIds.length > 0) {
|
|
648
|
+
// All sessions were filtered out due to identity mismatch - clear stored sessions
|
|
649
|
+
if (__DEV__) {
|
|
650
|
+
logger('All stored sessions belong to different identity - clearing session storage');
|
|
651
|
+
}
|
|
652
|
+
await storage.removeItem(storageKeys.sessionIds);
|
|
653
|
+
await storage.removeItem(storageKeys.activeSessionId);
|
|
618
654
|
}
|
|
619
655
|
}
|
|
620
656
|
|
|
@@ -726,9 +762,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
726
762
|
return isAuthenticatedFromStore || isAuthenticatedFromSessions;
|
|
727
763
|
}, [isAuthenticatedFromStore, isAuthenticatedFromSessions]);
|
|
728
764
|
|
|
729
|
-
// Get userId from JWT token
|
|
730
|
-
//
|
|
731
|
-
//
|
|
765
|
+
// Get userId from JWT token for socket room matching
|
|
766
|
+
// Note: JWT token may contain MongoDB ObjectId internally, but user.id is publicKey (canonical identity)
|
|
767
|
+
// Socket rooms may need internal mapping from publicKey to MongoDB ObjectId if backend requires it
|
|
732
768
|
const userId = oxyServices.getCurrentUserId() || user?.id;
|
|
733
769
|
|
|
734
770
|
// Use Zustand store for transfer state management
|
|
@@ -748,16 +784,16 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
748
784
|
// Load transfer codes
|
|
749
785
|
const storedCodes = await storage.getItem(TRANSFER_CODES_STORAGE_KEY);
|
|
750
786
|
const storedActiveTransferId = await storage.getItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
751
|
-
|
|
787
|
+
|
|
752
788
|
const parsedCodes = storedCodes ? JSON.parse(storedCodes) : {};
|
|
753
789
|
const activeTransferId = storedActiveTransferId || null;
|
|
754
|
-
|
|
790
|
+
|
|
755
791
|
// Restore to Zustand store (store handles validation and expiration)
|
|
756
792
|
restoreFromStorage(parsedCodes, activeTransferId);
|
|
757
793
|
markRestored();
|
|
758
|
-
|
|
794
|
+
|
|
759
795
|
if (__DEV__ && Object.keys(parsedCodes).length > 0) {
|
|
760
|
-
logger('Restored transfer codes from storage', {
|
|
796
|
+
logger('Restored transfer codes from storage', {
|
|
761
797
|
count: Object.keys(parsedCodes).length,
|
|
762
798
|
hasActiveTransfer: !!activeTransferId,
|
|
763
799
|
});
|
|
@@ -782,7 +818,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
782
818
|
const persistTransferCodes = async () => {
|
|
783
819
|
try {
|
|
784
820
|
await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(transferCodes));
|
|
785
|
-
|
|
821
|
+
|
|
786
822
|
if (activeTransferId) {
|
|
787
823
|
await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, activeTransferId);
|
|
788
824
|
} else {
|
|
@@ -810,7 +846,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
810
846
|
// Transfer code management functions using Zustand store
|
|
811
847
|
const storeTransferCode = useCallback(async (transferId: string, code: string, sourceDeviceId: string | null, publicKey: string) => {
|
|
812
848
|
storeTransferCodeStore(transferId, code, sourceDeviceId, publicKey);
|
|
813
|
-
|
|
849
|
+
|
|
814
850
|
if (__DEV__) {
|
|
815
851
|
logger('Stored transfer code', { transferId, sourceDeviceId, publicKey: publicKey.substring(0, 16) + '...' });
|
|
816
852
|
}
|
|
@@ -822,7 +858,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
822
858
|
|
|
823
859
|
const updateTransferState = useCallback(async (transferId: string, state: 'pending' | 'completed' | 'failed') => {
|
|
824
860
|
updateTransferStateStore(transferId, state);
|
|
825
|
-
|
|
861
|
+
|
|
826
862
|
if (__DEV__) {
|
|
827
863
|
logger('Updated transfer state', { transferId, state });
|
|
828
864
|
}
|
|
@@ -830,7 +866,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
830
866
|
|
|
831
867
|
const clearTransferCode = useCallback(async (transferId: string) => {
|
|
832
868
|
clearTransferCodeStore(transferId);
|
|
833
|
-
|
|
869
|
+
|
|
834
870
|
if (__DEV__) {
|
|
835
871
|
logger('Cleared transfer code', { transferId });
|
|
836
872
|
}
|
|
@@ -872,7 +908,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
872
908
|
const storedTransfer = getTransferCode(data.transferId);
|
|
873
909
|
|
|
874
910
|
if (!storedTransfer) {
|
|
875
|
-
logger('Transfer code not found for transferId', {
|
|
911
|
+
logger('Transfer code not found for transferId', {
|
|
876
912
|
transferId: data.transferId,
|
|
877
913
|
});
|
|
878
914
|
toast.error('Transfer verification failed: Code not found. Identity will not be deleted.');
|
|
@@ -894,7 +930,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
894
930
|
// Verify deviceId matches - very lenient since publicKey is the critical check
|
|
895
931
|
// If publicKey matches, we allow deletion even if deviceId doesn't match exactly
|
|
896
932
|
// This handles cases where deviceId might not be available or slightly different
|
|
897
|
-
const deviceIdMatches =
|
|
933
|
+
const deviceIdMatches =
|
|
898
934
|
// Exact match
|
|
899
935
|
(data.sourceDeviceId && data.sourceDeviceId === currentDeviceId) ||
|
|
900
936
|
// Stored sourceDeviceId matches current deviceId
|
|
@@ -974,7 +1010,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
974
1010
|
}
|
|
975
1011
|
|
|
976
1012
|
await deleteIdentityAndClearAccount(false, false, true);
|
|
977
|
-
|
|
1013
|
+
|
|
978
1014
|
// Verify identity was actually deleted
|
|
979
1015
|
const identityDeleted = !(await KeyManager.hasIdentity());
|
|
980
1016
|
if (!identityDeleted) {
|
|
@@ -146,9 +146,9 @@ export const useAuthOperations = ({
|
|
|
146
146
|
const localDeviceId = `device_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
147
147
|
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // 7 days
|
|
148
148
|
|
|
149
|
-
// Create minimal user object with publicKey as id
|
|
149
|
+
// Create minimal user object with publicKey as id (canonical identity)
|
|
150
150
|
fullUser = {
|
|
151
|
-
id: publicKey, //
|
|
151
|
+
id: publicKey, // publicKey is the canonical user identity
|
|
152
152
|
publicKey,
|
|
153
153
|
username: '',
|
|
154
154
|
privacySettings: {},
|
|
@@ -170,7 +170,7 @@ export const useAuthOperations = ({
|
|
|
170
170
|
deviceId: localDeviceId,
|
|
171
171
|
expiresAt,
|
|
172
172
|
lastActive: new Date().toISOString(),
|
|
173
|
-
|
|
173
|
+
publicKey, // Canonical user identity
|
|
174
174
|
isCurrent: true,
|
|
175
175
|
};
|
|
176
176
|
|
|
@@ -204,17 +204,10 @@ export const useAuthOperations = ({
|
|
|
204
204
|
// Get full user data
|
|
205
205
|
fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
206
206
|
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (fullUser.id && !/^[0-9a-fA-F]{24}$/.test(fullUser.id)) {
|
|
212
|
-
console.warn('[useAuthOperations] User.id is not MongoDB ObjectId format:', {
|
|
213
|
-
id: fullUser.id.substring(0, 20),
|
|
214
|
-
publicKey: fullUser.publicKey.substring(0, 20),
|
|
215
|
-
message: 'API should return MongoDB ObjectId as user.id, not publicKey'
|
|
216
|
-
});
|
|
217
|
-
// Don't override - let the API fix this issue
|
|
207
|
+
// user.id is now publicKey (canonical identity) - this is correct
|
|
208
|
+
// Ensure publicKey field is set for consistency
|
|
209
|
+
if (!fullUser.publicKey && fullUser.id) {
|
|
210
|
+
fullUser.publicKey = fullUser.id;
|
|
218
211
|
}
|
|
219
212
|
|
|
220
213
|
// Fetch device sessions
|
|
@@ -222,7 +215,8 @@ export const useAuthOperations = ({
|
|
|
222
215
|
try {
|
|
223
216
|
allDeviceSessions = await fetchSessionsWithFallback(oxyServices, sessionResponse.sessionId, {
|
|
224
217
|
fallbackDeviceId: sessionResponse.deviceId,
|
|
225
|
-
fallbackUserId: fullUser.id,
|
|
218
|
+
fallbackUserId: fullUser.id, // Still pass for backward compatibility
|
|
219
|
+
fallbackPublicKey: fullUser.publicKey || fullUser.id, // publicKey is canonical identity
|
|
226
220
|
logger,
|
|
227
221
|
});
|
|
228
222
|
} catch (error) {
|
|
@@ -292,6 +286,13 @@ export const useAuthOperations = ({
|
|
|
292
286
|
setAuthState({ isLoading: true, error: null });
|
|
293
287
|
|
|
294
288
|
try {
|
|
289
|
+
// SESSION CLEANUP: Clear all sessions before creating new identity
|
|
290
|
+
// New identity means old sessions are no longer valid
|
|
291
|
+
await clearSessionState();
|
|
292
|
+
if (__DEV__ && logger) {
|
|
293
|
+
logger('Cleared all sessions before creating new identity');
|
|
294
|
+
}
|
|
295
|
+
|
|
295
296
|
// Generate new key pair directly (works offline)
|
|
296
297
|
const { publicKey, privateKey } = await KeyManager.generateKeyPair();
|
|
297
298
|
await KeyManager.importKeyPair(privateKey);
|
|
@@ -463,6 +464,13 @@ export const useAuthOperations = ({
|
|
|
463
464
|
setAuthState({ isLoading: true, error: null });
|
|
464
465
|
|
|
465
466
|
try {
|
|
467
|
+
// SESSION CLEANUP: Clear all sessions before importing new identity
|
|
468
|
+
// Importing a different identity means old sessions are no longer valid
|
|
469
|
+
await clearSessionState();
|
|
470
|
+
if (__DEV__ && logger) {
|
|
471
|
+
logger('Cleared all sessions before importing identity');
|
|
472
|
+
}
|
|
473
|
+
|
|
466
474
|
// Decrypt private key from backup data
|
|
467
475
|
const Crypto = await import('expo-crypto');
|
|
468
476
|
|
|
@@ -8,7 +8,7 @@ import { fetchSessionsWithFallback, mapSessionsToClient } from '../../utils/sess
|
|
|
8
8
|
* Get all active sessions for the current user
|
|
9
9
|
*/
|
|
10
10
|
export const useSessions = (userId?: string, options?: { enabled?: boolean }) => {
|
|
11
|
-
const { oxyServices, activeSessionId } = useOxy();
|
|
11
|
+
const { oxyServices, activeSessionId, user } = useOxy();
|
|
12
12
|
|
|
13
13
|
return useQuery({
|
|
14
14
|
queryKey: queryKeys.sessions.list(userId),
|
|
@@ -17,12 +17,14 @@ export const useSessions = (userId?: string, options?: { enabled?: boolean }) =>
|
|
|
17
17
|
throw new Error('No active session');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const publicKey = user?.publicKey || user?.id || ''; // user.id is now publicKey
|
|
20
21
|
const sessions = await fetchSessionsWithFallback(oxyServices, activeSessionId, {
|
|
21
22
|
fallbackDeviceId: undefined,
|
|
22
23
|
fallbackUserId: userId,
|
|
24
|
+
fallbackPublicKey: publicKey,
|
|
23
25
|
});
|
|
24
26
|
|
|
25
|
-
return
|
|
27
|
+
return sessions; // Already mapped by fetchSessionsWithFallback
|
|
26
28
|
},
|
|
27
29
|
enabled: (options?.enabled !== false) && !!activeSessionId,
|
|
28
30
|
staleTime: 2 * 60 * 1000, // 2 minutes (sessions change frequently)
|
|
@@ -49,12 +51,15 @@ export const useSession = (sessionId: string | null, options?: { enabled?: boole
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
const now = new Date();
|
|
54
|
+
// user.id is now publicKey (canonical identity)
|
|
55
|
+
const publicKey = validation.user.publicKey || validation.user.id || '';
|
|
52
56
|
return {
|
|
53
57
|
sessionId,
|
|
54
58
|
deviceId: '', // Device ID not available from validation response
|
|
55
59
|
expiresAt: validation.expiresAt || new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
56
60
|
lastActive: validation.lastActivity || now.toISOString(),
|
|
57
|
-
|
|
61
|
+
publicKey, // Canonical user identity
|
|
62
|
+
userId: validation.user.id?.toString(), // Optional MongoDB ObjectId
|
|
58
63
|
isCurrent: false,
|
|
59
64
|
} as ClientSession;
|
|
60
65
|
},
|
|
@@ -255,8 +255,10 @@ export const useSessionManagement = ({
|
|
|
255
255
|
await activateSession(sessionId, user);
|
|
256
256
|
|
|
257
257
|
try {
|
|
258
|
+
const publicKey = user.publicKey || user.id || ''; // user.id is now publicKey
|
|
258
259
|
const deviceSessions = await fetchSessionsWithFallback(oxyServices, sessionId, {
|
|
259
|
-
fallbackUserId: user.id,
|
|
260
|
+
fallbackUserId: user.id, // Still pass for backward compatibility
|
|
261
|
+
fallbackPublicKey: publicKey,
|
|
260
262
|
logger,
|
|
261
263
|
});
|
|
262
264
|
updateSessions(deviceSessions, { merge: true });
|
|
@@ -330,8 +332,13 @@ export const useSessionManagement = ({
|
|
|
330
332
|
|
|
331
333
|
const refreshPromise = (async () => {
|
|
332
334
|
try {
|
|
335
|
+
// Get publicKey from active session or current user
|
|
336
|
+
const activeSession = sessions.find(s => s.sessionId === activeSessionId);
|
|
337
|
+
const publicKey = activeSession?.publicKey || activeUserId || ''; // Fallback to userId if publicKey not available
|
|
338
|
+
|
|
333
339
|
const deviceSessions = await fetchSessionsWithFallback(oxyServices, activeSessionId, {
|
|
334
340
|
fallbackUserId: activeUserId,
|
|
341
|
+
fallbackPublicKey: publicKey,
|
|
335
342
|
logger,
|
|
336
343
|
});
|
|
337
344
|
updateSessions(deviceSessions, { merge: true });
|
|
@@ -7,14 +7,16 @@ interface DeviceSession {
|
|
|
7
7
|
deviceName?: string;
|
|
8
8
|
expiresAt?: string;
|
|
9
9
|
lastActive?: string;
|
|
10
|
-
user?: { id?: string; _id?: { toString(): string } };
|
|
10
|
+
user?: { id?: string; publicKey?: string; _id?: { toString(): string } };
|
|
11
11
|
userId?: string;
|
|
12
|
+
publicKey?: string;
|
|
12
13
|
isCurrent?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface FetchSessionsWithFallbackOptions {
|
|
16
17
|
fallbackDeviceId?: string;
|
|
17
18
|
fallbackUserId?: string;
|
|
19
|
+
fallbackPublicKey?: string; // Canonical user identity
|
|
18
20
|
logger?: (message: string, error?: unknown) => void;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -37,27 +39,41 @@ export interface SessionValidationResult {
|
|
|
37
39
|
* @param sessions - Raw session array returned from the API
|
|
38
40
|
* @param fallbackDeviceId - Device identifier to use when missing from payload
|
|
39
41
|
* @param fallbackUserId - User identifier to use when missing from payload
|
|
42
|
+
* @param fallbackPublicKey - Public key to use when missing from payload (canonical identity)
|
|
40
43
|
*/
|
|
41
44
|
export const mapSessionsToClient = (
|
|
42
45
|
sessions: DeviceSession[],
|
|
43
46
|
fallbackDeviceId?: string,
|
|
44
47
|
fallbackUserId?: string,
|
|
48
|
+
fallbackPublicKey?: string,
|
|
45
49
|
): ClientSession[] => {
|
|
46
50
|
const now = new Date();
|
|
47
51
|
|
|
48
|
-
return sessions.map((session) =>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
session.
|
|
52
|
+
return sessions.map((session) => {
|
|
53
|
+
// publicKey is the canonical user identity (user.id now equals publicKey)
|
|
54
|
+
// Extract from user.id (which is now publicKey), user.publicKey, or session.publicKey
|
|
55
|
+
const publicKey =
|
|
56
|
+
session.user?.id || // user.id is now publicKey (canonical identifier)
|
|
57
|
+
session.user?.publicKey ||
|
|
58
|
+
session.publicKey ||
|
|
59
|
+
fallbackPublicKey ||
|
|
60
|
+
'';
|
|
61
|
+
|
|
62
|
+
// userId is MongoDB ObjectId (internal backend reference, optional)
|
|
63
|
+
const userId =
|
|
55
64
|
session.userId ||
|
|
56
|
-
(session.user?._id ? session.user._id.toString() : undefined)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
(session.user?._id ? session.user._id.toString() : undefined);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
sessionId: session.sessionId,
|
|
69
|
+
deviceId: session.deviceId || fallbackDeviceId || '',
|
|
70
|
+
expiresAt: session.expiresAt || new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
71
|
+
lastActive: session.lastActive || now.toISOString(),
|
|
72
|
+
publicKey, // Canonical user identity - REQUIRED
|
|
73
|
+
userId, // MongoDB ObjectId (optional, for backward compatibility)
|
|
74
|
+
isCurrent: Boolean(session.isCurrent),
|
|
75
|
+
};
|
|
76
|
+
});
|
|
61
77
|
};
|
|
62
78
|
|
|
63
79
|
/**
|
|
@@ -73,19 +89,20 @@ export const fetchSessionsWithFallback = async (
|
|
|
73
89
|
{
|
|
74
90
|
fallbackDeviceId,
|
|
75
91
|
fallbackUserId,
|
|
92
|
+
fallbackPublicKey,
|
|
76
93
|
logger,
|
|
77
94
|
}: FetchSessionsWithFallbackOptions = {},
|
|
78
95
|
): Promise<ClientSession[]> => {
|
|
79
96
|
try {
|
|
80
97
|
const deviceSessions = await oxyServices.getDeviceSessions(sessionId);
|
|
81
|
-
return mapSessionsToClient(deviceSessions, fallbackDeviceId, fallbackUserId);
|
|
98
|
+
return mapSessionsToClient(deviceSessions, fallbackDeviceId, fallbackUserId, fallbackPublicKey);
|
|
82
99
|
} catch (error) {
|
|
83
100
|
if (__DEV__ && logger) {
|
|
84
101
|
logger('Failed to get device sessions, falling back to user sessions', error);
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
const userSessions = await oxyServices.getSessionsBySessionId(sessionId);
|
|
88
|
-
return mapSessionsToClient(userSessions, fallbackDeviceId, fallbackUserId);
|
|
105
|
+
return mapSessionsToClient(userSessions, fallbackDeviceId, fallbackUserId, fallbackPublicKey);
|
|
89
106
|
}
|
|
90
107
|
};
|
|
91
108
|
|
|
@@ -12,12 +12,19 @@ import type { ClientSession } from '../models/session';
|
|
|
12
12
|
*/
|
|
13
13
|
export function normalizeSession(session: Partial<ClientSession> & { sessionId: string }): ClientSession {
|
|
14
14
|
const now = new Date().toISOString();
|
|
15
|
+
|
|
16
|
+
// publicKey is required (canonical user identity)
|
|
17
|
+
if (!session.publicKey) {
|
|
18
|
+
throw new Error(`Session ${session.sessionId} is missing required publicKey field`);
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
return {
|
|
16
22
|
sessionId: session.sessionId,
|
|
17
23
|
deviceId: session.deviceId || '',
|
|
18
24
|
expiresAt: session.expiresAt || now,
|
|
19
25
|
lastActive: session.lastActive || now,
|
|
20
|
-
|
|
26
|
+
publicKey: session.publicKey, // Canonical user identity - required
|
|
27
|
+
userId: session.userId, // Optional MongoDB ObjectId for backward compatibility
|
|
21
28
|
};
|
|
22
29
|
}
|
|
23
30
|
|