@oxyhq/services 5.15.9 → 5.16.0

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 (147) hide show
  1. package/lib/commonjs/core/OxyServices.js +0 -1
  2. package/lib/commonjs/core/OxyServices.js.map +1 -1
  3. package/lib/commonjs/core/mixins/OxyServices.auth.js +3 -6
  4. package/lib/commonjs/core/mixins/OxyServices.auth.js.map +1 -1
  5. package/lib/commonjs/core/mixins/OxyServices.devices.js +1 -1
  6. package/lib/commonjs/core/mixins/OxyServices.devices.js.map +1 -1
  7. package/lib/commonjs/core/mixins/index.js +11 -12
  8. package/lib/commonjs/core/mixins/index.js.map +1 -1
  9. package/lib/commonjs/crypto/signatureService.js +3 -2
  10. package/lib/commonjs/crypto/signatureService.js.map +1 -1
  11. package/lib/commonjs/i18n/locales/ar-SA.json +1 -9
  12. package/lib/commonjs/i18n/locales/ca-ES.json +1 -9
  13. package/lib/commonjs/i18n/locales/de-DE.json +1 -9
  14. package/lib/commonjs/i18n/locales/en-US.json +3 -21
  15. package/lib/commonjs/i18n/locales/es-ES.json +3 -21
  16. package/lib/commonjs/i18n/locales/fr-FR.json +1 -9
  17. package/lib/commonjs/i18n/locales/it-IT.json +1 -9
  18. package/lib/commonjs/i18n/locales/ja-JP.json +1 -9
  19. package/lib/commonjs/i18n/locales/ko-KR.json +1 -9
  20. package/lib/commonjs/i18n/locales/pt-PT.json +1 -9
  21. package/lib/commonjs/i18n/locales/zh-CN.json +1 -9
  22. package/lib/commonjs/ui/context/OxyContext.js +24 -4
  23. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  24. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +217 -100
  25. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  26. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +2 -319
  27. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  28. package/lib/commonjs/ui/screens/OxyAuthScreen.js +16 -7
  29. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  30. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +0 -1
  31. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
  32. package/lib/commonjs/ui/screens/SessionManagementScreen.js +43 -29
  33. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  34. package/lib/commonjs/ui/stores/authStore.js +14 -1
  35. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  36. package/lib/module/core/OxyServices.js +0 -1
  37. package/lib/module/core/OxyServices.js.map +1 -1
  38. package/lib/module/core/mixins/OxyServices.auth.js +3 -6
  39. package/lib/module/core/mixins/OxyServices.auth.js.map +1 -1
  40. package/lib/module/core/mixins/OxyServices.devices.js +1 -1
  41. package/lib/module/core/mixins/OxyServices.devices.js.map +1 -1
  42. package/lib/module/core/mixins/index.js +1 -2
  43. package/lib/module/core/mixins/index.js.map +1 -1
  44. package/lib/module/crypto/signatureService.js +3 -2
  45. package/lib/module/crypto/signatureService.js.map +1 -1
  46. package/lib/module/i18n/locales/ar-SA.json +1 -9
  47. package/lib/module/i18n/locales/ca-ES.json +1 -9
  48. package/lib/module/i18n/locales/de-DE.json +1 -9
  49. package/lib/module/i18n/locales/en-US.json +3 -21
  50. package/lib/module/i18n/locales/es-ES.json +3 -21
  51. package/lib/module/i18n/locales/fr-FR.json +1 -9
  52. package/lib/module/i18n/locales/it-IT.json +1 -9
  53. package/lib/module/i18n/locales/ja-JP.json +1 -9
  54. package/lib/module/i18n/locales/ko-KR.json +1 -9
  55. package/lib/module/i18n/locales/pt-PT.json +1 -9
  56. package/lib/module/i18n/locales/zh-CN.json +1 -9
  57. package/lib/module/ui/context/OxyContext.js +24 -4
  58. package/lib/module/ui/context/OxyContext.js.map +1 -1
  59. package/lib/module/ui/context/hooks/useAuthOperations.js +217 -100
  60. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  61. package/lib/module/ui/screens/AccountSettingsScreen.js +2 -319
  62. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  63. package/lib/module/ui/screens/OxyAuthScreen.js +16 -7
  64. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  65. package/lib/module/ui/screens/PrivacySettingsScreen.js +0 -1
  66. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
  67. package/lib/module/ui/screens/SessionManagementScreen.js +44 -29
  68. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  69. package/lib/module/ui/stores/authStore.js +14 -1
  70. package/lib/module/ui/stores/authStore.js.map +1 -1
  71. package/lib/typescript/core/OxyServices.d.ts +0 -1
  72. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  73. package/lib/typescript/core/mixins/OxyServices.auth.d.ts +3 -4
  74. package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -1
  75. package/lib/typescript/core/mixins/OxyServices.devices.d.ts +1 -4
  76. package/lib/typescript/core/mixins/OxyServices.devices.d.ts.map +1 -1
  77. package/lib/typescript/core/mixins/index.d.ts +1 -64
  78. package/lib/typescript/core/mixins/index.d.ts.map +1 -1
  79. package/lib/typescript/crypto/signatureService.d.ts +2 -1
  80. package/lib/typescript/crypto/signatureService.d.ts.map +1 -1
  81. package/lib/typescript/models/interfaces.d.ts +1 -1
  82. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  83. package/lib/typescript/types/bip39.d.ts +1 -0
  84. package/lib/typescript/types/buffer.d.ts +1 -0
  85. package/lib/typescript/types/color.d.ts +1 -0
  86. package/lib/typescript/types/elliptic.d.ts +1 -0
  87. package/lib/typescript/types/expo-crypto.d.ts +1 -0
  88. package/lib/typescript/types/expo-secure-store.d.ts +1 -0
  89. package/lib/typescript/ui/context/OxyContext.d.ts +11 -3
  90. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  91. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +13 -5
  92. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  93. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  94. package/lib/typescript/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  95. package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  96. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  97. package/lib/typescript/ui/stores/authStore.d.ts +4 -0
  98. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  99. package/package.json +6 -5
  100. package/src/core/OxyServices.ts +0 -1
  101. package/src/core/mixins/OxyServices.auth.ts +3 -8
  102. package/src/core/mixins/OxyServices.devices.ts +1 -4
  103. package/src/core/mixins/index.ts +2 -5
  104. package/src/crypto/index.ts +1 -0
  105. package/src/crypto/keyManager.ts +1 -0
  106. package/src/crypto/polyfill.ts +1 -0
  107. package/src/crypto/recoveryPhrase.ts +1 -0
  108. package/src/crypto/signatureService.ts +4 -5
  109. package/src/i18n/locales/ar-SA.json +1 -9
  110. package/src/i18n/locales/ca-ES.json +1 -9
  111. package/src/i18n/locales/de-DE.json +1 -9
  112. package/src/i18n/locales/en-US.json +3 -21
  113. package/src/i18n/locales/es-ES.json +3 -21
  114. package/src/i18n/locales/fr-FR.json +1 -9
  115. package/src/i18n/locales/it-IT.json +1 -9
  116. package/src/i18n/locales/ja-JP.json +1 -9
  117. package/src/i18n/locales/ko-KR.json +1 -9
  118. package/src/i18n/locales/pt-PT.json +1 -9
  119. package/src/i18n/locales/zh-CN.json +1 -9
  120. package/src/models/interfaces.ts +1 -1
  121. package/src/types/bip39.d.ts +1 -0
  122. package/src/types/buffer.d.ts +1 -0
  123. package/src/types/color.d.ts +1 -0
  124. package/src/types/elliptic.d.ts +1 -0
  125. package/src/types/expo-crypto.d.ts +1 -0
  126. package/src/types/expo-secure-store.d.ts +1 -0
  127. package/src/ui/context/OxyContext.tsx +35 -3
  128. package/src/ui/context/hooks/useAuthOperations.ts +212 -98
  129. package/src/ui/screens/AccountSettingsScreen.tsx +1 -201
  130. package/src/ui/screens/OxyAuthScreen.tsx +16 -8
  131. package/src/ui/screens/PrivacySettingsScreen.tsx +0 -2
  132. package/src/ui/screens/SessionManagementScreen.tsx +43 -26
  133. package/src/ui/stores/authStore.ts +31 -2
  134. package/lib/commonjs/core/mixins/OxyServices.totp.js +0 -53
  135. package/lib/commonjs/core/mixins/OxyServices.totp.js.map +0 -1
  136. package/lib/commonjs/ui/components/profile/TwoFactorSetupModal.js +0 -467
  137. package/lib/commonjs/ui/components/profile/TwoFactorSetupModal.js.map +0 -1
  138. package/lib/module/core/mixins/OxyServices.totp.js +0 -49
  139. package/lib/module/core/mixins/OxyServices.totp.js.map +0 -1
  140. package/lib/module/ui/components/profile/TwoFactorSetupModal.js +0 -460
  141. package/lib/module/ui/components/profile/TwoFactorSetupModal.js.map +0 -1
  142. package/lib/typescript/core/mixins/OxyServices.totp.d.ts +0 -66
  143. package/lib/typescript/core/mixins/OxyServices.totp.d.ts.map +0 -1
  144. package/lib/typescript/ui/components/profile/TwoFactorSetupModal.d.ts +0 -11
  145. package/lib/typescript/ui/components/profile/TwoFactorSetupModal.d.ts.map +0 -1
  146. package/src/core/mixins/OxyServices.totp.ts +0 -36
  147. package/src/ui/components/profile/TwoFactorSetupModal.tsx +0 -442
@@ -36,10 +36,8 @@ import { EditEmailModal } from '../components/profile/EditEmailModal';
36
36
  import { EditBioModal } from '../components/profile/EditBioModal';
37
37
  import { EditLocationModal } from '../components/profile/EditLocationModal';
38
38
  import { EditLinksModal } from '../components/profile/EditLinksModal';
39
- import { TwoFactorSetupModal } from '../components/profile/TwoFactorSetupModal';
40
39
  import { getDisplayName } from '../utils/user-utils';
41
40
  import { TTLCache, registerCacheForCleanup } from '../../utils/cache';
42
- import QRCode from 'react-native-qrcode-svg';
43
41
  import { useOxy } from '../context/OxyContext';
44
42
  import {
45
43
  SCREEN_PADDING_HORIZONTAL,
@@ -102,13 +100,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
102
100
  const [quickActionsSectionY, setQuickActionsSectionY] = useState<number | null>(null);
103
101
  const [securitySectionY, setSecuritySectionY] = useState<number | null>(null);
104
102
 
105
- // Two-Factor (TOTP) state
106
- const [totpSetupUrl, setTotpSetupUrl] = useState<string | null>(null);
107
- const [totpCode, setTotpCode] = useState('');
108
- const [isTotpBusy, setIsTotpBusy] = useState(false);
109
- const [showRecoveryModal, setShowRecoveryModal] = useState(false);
110
- const [generatedBackupCodes, setGeneratedBackupCodes] = useState<string[] | null>(null);
111
- const [generatedRecoveryKey, setGeneratedRecoveryKey] = useState<string | null>(null);
112
103
 
113
104
  // Animation refs
114
105
  const saveButtonScale = useRef(new Animated.Value(1)).current;
@@ -130,7 +121,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
130
121
  const [showEditBioModal, setShowEditBioModal] = useState(false);
131
122
  const [showEditLocationModal, setShowEditLocationModal] = useState(false);
132
123
  const [showEditLinksModal, setShowEditLinksModal] = useState(false);
133
- const [showTwoFactorModal, setShowTwoFactorModal] = useState(false);
134
124
 
135
125
  // Location and links state (for display only - modals handle editing)
136
126
  const [locations, setLocations] = useState<Array<{
@@ -342,9 +332,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
342
332
  case 'links':
343
333
  setTempLinksWithMetadata([...linksMetadata]);
344
334
  break;
345
- case 'twoFactor':
346
- // No temp state needed for twoFactor
347
- break;
348
335
  }
349
336
  }, [displayName, lastName, username, email, bio, locations, linksMetadata]);
350
337
 
@@ -423,8 +410,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
423
410
  return bio;
424
411
  case 'location':
425
412
  case 'links':
426
- case 'twoFactor':
427
- return '';
428
413
  default:
429
414
  return '';
430
415
  }
@@ -529,7 +514,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
529
514
  const handleOpenBioModal = useCallback(() => setShowEditBioModal(true), []);
530
515
  const handleOpenLocationModal = useCallback(() => setShowEditLocationModal(true), []);
531
516
  const handleOpenLinksModal = useCallback(() => setShowEditLinksModal(true), []);
532
- const handleOpenTwoFactorModal = useCallback(() => setShowTwoFactorModal(true), []);
533
517
 
534
518
  // Handler to refresh data after modal saves
535
519
  // Note: Access user directly from store when invoked to get latest value,
@@ -611,9 +595,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
611
595
  case 'links':
612
596
  setShowEditLinksModal(true);
613
597
  break;
614
- case 'twoFactor':
615
- setShowTwoFactorModal(true);
616
- break;
617
598
  }
618
599
  }, 300);
619
600
  }
@@ -760,135 +741,9 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
760
741
  // Memoize display name for avatar
761
742
  const displayNameForAvatar = useMemo(() => getDisplayName(user), [user]);
762
743
 
763
- // Legacy renderEditingField function (still used for twoFactor and fallback)
744
+ // Legacy renderEditingField function (fallback)
764
745
  const renderEditingField = (type: string | null) => {
765
746
  if (!type) return null;
766
-
767
- if (type === 'twoFactor') {
768
- const enabled = !!user?.privacySettings?.twoFactorEnabled;
769
- return (
770
- <View style={[styles.editingFieldContainer, { backgroundColor: colors.background }]}>
771
- <View style={styles.editingFieldContent}>
772
- <View style={styles.newValueSection}>
773
- <View style={styles.editingFieldHeader}>
774
- <Text style={[styles.editingFieldLabel, { color: colors.text }]}>Two‑Factor Authentication (TOTP)</Text>
775
- </View>
776
-
777
- {!enabled ? (
778
- <>
779
- <Text style={styles.editingFieldDescription}>
780
- Protect your account with a 6‑digit code from an authenticator app. Scan the QR code then enter the code to enable.
781
- </Text>
782
- {!totpSetupUrl ? (
783
- <TouchableOpacity
784
- style={[styles.primaryButton, { backgroundColor: colors.iconSecurity }]}
785
- disabled={isTotpBusy}
786
- onPress={async () => {
787
- if (!activeSessionId) { toast.error(t('editProfile.toasts.noActiveSession') || 'No active session'); return; }
788
- setIsTotpBusy(true);
789
- try {
790
- const { otpauthUrl } = await oxyServices.startTotpEnrollment(activeSessionId);
791
- setTotpSetupUrl(otpauthUrl);
792
- } catch (e: any) {
793
- toast.error(e?.message || (t('editProfile.toasts.totpStartFailed') || 'Failed to start TOTP enrollment'));
794
- } finally {
795
- setIsTotpBusy(false);
796
- }
797
- }}
798
- >
799
- <Ionicons name="shield-checkmark" size={18} color="#fff" />
800
- <Text style={styles.primaryButtonText}>Generate QR Code</Text>
801
- </TouchableOpacity>
802
- ) : (
803
- <View style={{ alignItems: 'center', gap: 16 }}>
804
- <View style={{ padding: 16, backgroundColor: '#fff', borderRadius: 16 }}>
805
- <QRCode value={totpSetupUrl} size={180} />
806
- </View>
807
- <View>
808
- <Text style={styles.editingFieldLabel}>Enter 6‑digit code</Text>
809
- <TextInput
810
- style={styles.editingFieldInput}
811
- keyboardType="number-pad"
812
- placeholder="123456"
813
- value={totpCode}
814
- onChangeText={setTotpCode}
815
- maxLength={6}
816
- />
817
- </View>
818
- <TouchableOpacity
819
- style={[styles.primaryButton, { backgroundColor: colors.iconSecurity }]}
820
- disabled={isTotpBusy || totpCode.length !== 6}
821
- onPress={async () => {
822
- if (!activeSessionId) { toast.error(t('editProfile.toasts.noActiveSession') || 'No active session'); return; }
823
- setIsTotpBusy(true);
824
- try {
825
- const result = await oxyServices.verifyTotpEnrollment(activeSessionId, totpCode);
826
- await updateUser({ privacySettings: { twoFactorEnabled: true } }, oxyServices);
827
- if (result?.backupCodes || result?.recoveryKey) {
828
- setGeneratedBackupCodes(result.backupCodes || null);
829
- setGeneratedRecoveryKey(result.recoveryKey || null);
830
- setShowRecoveryModal(true);
831
- } else {
832
- toast.success(t('editProfile.toasts.twoFactorEnabled') || 'Two‑Factor Authentication enabled');
833
- setEditingField(null);
834
- }
835
- } catch (e: any) {
836
- toast.error(e?.message || (t('editProfile.toasts.invalidCode') || 'Invalid code'));
837
- } finally {
838
- setIsTotpBusy(false);
839
- }
840
- }}
841
- >
842
- <Ionicons name="checkmark-circle" size={18} color="#fff" />
843
- <Text style={styles.primaryButtonText}>Verify & Enable</Text>
844
- </TouchableOpacity>
845
- </View>
846
- )}
847
- </>
848
- ) : (
849
- <>
850
- <Text style={styles.editingFieldDescription}>
851
- Two‑Factor Authentication is currently enabled. To disable, enter a code from your authenticator app.
852
- </Text>
853
- <View>
854
- <Text style={styles.editingFieldLabel}>Enter 6‑digit code</Text>
855
- <TextInput
856
- style={styles.editingFieldInput}
857
- keyboardType="number-pad"
858
- placeholder="123456"
859
- value={totpCode}
860
- onChangeText={setTotpCode}
861
- maxLength={6}
862
- />
863
- </View>
864
- <TouchableOpacity
865
- style={[styles.primaryButton, { backgroundColor: colors.sidebarIconSharing }]}
866
- disabled={isTotpBusy || totpCode.length !== 6}
867
- onPress={async () => {
868
- if (!activeSessionId) { toast.error(t('editProfile.toasts.noActiveSession') || 'No active session'); return; }
869
- setIsTotpBusy(true);
870
- try {
871
- await oxyServices.disableTotp(activeSessionId, totpCode);
872
- await updateUser({ privacySettings: { twoFactorEnabled: false } }, oxyServices);
873
- toast.success(t('editProfile.toasts.twoFactorDisabled') || 'Two‑Factor Authentication disabled');
874
- setEditingField(null);
875
- } catch (e: any) {
876
- toast.error(e?.message || t('editProfile.toasts.disableFailed') || 'Failed to disable');
877
- } finally {
878
- setIsTotpBusy(false);
879
- }
880
- }}
881
- >
882
- <Ionicons name="close-circle" size={18} color="#fff" />
883
- <Text style={styles.primaryButtonText}>Disable 2FA</Text>
884
- </TouchableOpacity>
885
- </>
886
- )}
887
- </View>
888
- </View>
889
- </View>
890
- );
891
- }
892
747
  if (type === 'displayName') {
893
748
  return (
894
749
  <View style={[styles.editingFieldContainer, { backgroundColor: colors.background }]}>
@@ -1368,41 +1223,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
1368
1223
  </Text>
1369
1224
  </View>
1370
1225
 
1371
- {showRecoveryModal && (
1372
- <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.6)', zIndex: 50, padding: 16, justifyContent: 'center' }}>
1373
- <View style={{ backgroundColor: '#fff', borderRadius: 20, padding: 20, maxHeight: '80%' }}>
1374
- <Text style={{ fontSize: 18, fontWeight: '700', marginBottom: 12 }}>Save These Codes Now</Text>
1375
- <Text style={{ fontSize: 14, color: '#444', marginBottom: 12 }}>
1376
- Backup codes and your Recovery Key are shown only once. Store them securely (paper or password manager).
1377
- </Text>
1378
- {generatedBackupCodes && generatedBackupCodes.length > 0 && (
1379
- <View style={{ marginBottom: 12 }}>
1380
- <Text style={{ fontSize: 16, fontWeight: '600', marginBottom: 8 }}>Backup Codes</Text>
1381
- <View style={{ backgroundColor: '#F8F9FA', borderRadius: 12, padding: 12 }}>
1382
- {generatedBackupCodes.map((c, idx) => (
1383
- <Text key={idx} style={{ fontFamily: Platform.OS === 'web' ? 'monospace' as any : 'monospace', fontSize: 14, marginBottom: 4 }}>{c}</Text>
1384
- ))}
1385
- </View>
1386
- </View>
1387
- )}
1388
- {generatedRecoveryKey && (
1389
- <View style={{ marginBottom: 12 }}>
1390
- <Text style={{ fontSize: 16, fontWeight: '600', marginBottom: 8 }}>Recovery Key</Text>
1391
- <View style={{ backgroundColor: '#F8F9FA', borderRadius: 12, padding: 12 }}>
1392
- <Text style={{ fontFamily: Platform.OS === 'web' ? 'monospace' as any : 'monospace', fontSize: 14 }}>{generatedRecoveryKey}</Text>
1393
- </View>
1394
- </View>
1395
- )}
1396
- <TouchableOpacity
1397
- style={[styles.primaryButton, { backgroundColor: colors.iconSecurity, alignSelf: 'flex-end', marginTop: 8 }]}
1398
- onPress={() => { setShowRecoveryModal(false); setEditingField(null); toast.success(t('editProfile.toasts.twoFactorEnabled') || 'Two‑Factor Authentication enabled'); }}
1399
- >
1400
- <Ionicons name="checkmark" size={18} color="#fff" />
1401
- <Text style={styles.primaryButtonText}>I saved them</Text>
1402
- </TouchableOpacity>
1403
- </View>
1404
- </View>
1405
- )}
1406
1226
  {/* Profile Picture Section */}
1407
1227
  <View
1408
1228
  ref={(ref) => {
@@ -1634,20 +1454,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
1634
1454
  {t('editProfile.sections.security') || 'SECURITY'}
1635
1455
  </Text>
1636
1456
  <View style={styles.groupedSectionWrapper}>
1637
- <GroupedSection
1638
- items={[
1639
- {
1640
- id: 'two-factor',
1641
- icon: 'shield-lock',
1642
- iconColor: colors.sidebarIconSecurity,
1643
- title: t('editProfile.items.twoFactor.title') || 'Two‑Factor Authentication',
1644
- subtitle: user?.privacySettings?.twoFactorEnabled
1645
- ? (t('editProfile.items.twoFactor.enabled') || 'Enabled')
1646
- : (t('editProfile.items.twoFactor.disabled') || 'Disabled (recommended)'),
1647
- onPress: handleOpenTwoFactorModal,
1648
- },
1649
- ]}
1650
- />
1651
1457
  </View>
1652
1458
  </View>
1653
1459
  </>
@@ -1698,12 +1504,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
1698
1504
  theme={normalizedTheme}
1699
1505
  onSave={handleModalSave}
1700
1506
  />
1701
- <TwoFactorSetupModal
1702
- visible={showTwoFactorModal}
1703
- onClose={() => setShowTwoFactorModal(false)}
1704
- isEnabled={!!user?.privacySettings?.twoFactorEnabled}
1705
- theme={normalizedTheme}
1706
- />
1707
1507
  </View>
1708
1508
  );
1709
1509
  };
@@ -60,7 +60,7 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
60
60
  }) => {
61
61
  const themeValue = (theme === 'light' || theme === 'dark') ? theme : 'light';
62
62
  const colors = useThemeColors(themeValue);
63
- const { oxyServices, signIn } = useOxy();
63
+ const { oxyServices, signIn, switchSession } = useOxy();
64
64
 
65
65
  const [authSession, setAuthSession] = useState<AuthSession | null>(null);
66
66
  const [isLoading, setIsLoading] = useState(true);
@@ -78,12 +78,19 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
78
78
  isProcessingRef.current = true;
79
79
 
80
80
  try {
81
- // Get token and user data
82
- await oxyServices.getTokenBySession(sessionId);
83
- const user = await oxyServices.getUserBySession(sessionId);
84
-
85
- if (onAuthenticated) {
86
- onAuthenticated(user);
81
+ // Switch to the new session (this will get token, user data, and update state)
82
+ if (switchSession) {
83
+ const user = await switchSession(sessionId);
84
+ if (onAuthenticated) {
85
+ onAuthenticated(user);
86
+ }
87
+ } else {
88
+ // Fallback if switchSession not available (shouldn't happen, but for safety)
89
+ await oxyServices.getTokenBySession(sessionId);
90
+ const user = await oxyServices.getUserBySession(sessionId);
91
+ if (onAuthenticated) {
92
+ onAuthenticated(user);
93
+ }
87
94
  }
88
95
  } catch (err) {
89
96
  if (__DEV__) {
@@ -92,7 +99,7 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
92
99
  setError('Authorization successful but failed to complete sign in. Please try again.');
93
100
  isProcessingRef.current = false;
94
101
  }
95
- }, [oxyServices, onAuthenticated]);
102
+ }, [oxyServices, switchSession, onAuthenticated]);
96
103
 
97
104
  // Connect to socket for real-time updates
98
105
  const connectSocket = useCallback((sessionToken: string) => {
@@ -517,3 +524,4 @@ const styles = StyleSheet.create({
517
524
 
518
525
  export default OxyAuthScreen;
519
526
 
527
+
@@ -21,7 +21,6 @@ interface PrivacySettings {
21
21
  hideOnlineStatus: boolean;
22
22
  hideLastSeen: boolean;
23
23
  profileVisibility: boolean;
24
- twoFactorEnabled: boolean;
25
24
  loginAlerts: boolean;
26
25
  blockScreenshots: boolean;
27
26
  login: boolean;
@@ -52,7 +51,6 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
52
51
  hideOnlineStatus: false,
53
52
  hideLastSeen: false,
54
53
  profileVisibility: true,
55
- twoFactorEnabled: false,
56
54
  loginAlerts: true,
57
55
  blockScreenshots: false,
58
56
  login: true,
@@ -21,6 +21,17 @@ import { useThemeStyles } from '../hooks/useThemeStyles';
21
21
  import { normalizeTheme } from '../utils/themeUtils';
22
22
  import { useOxy } from '../context/OxyContext';
23
23
 
24
+ // Button background colors for session actions
25
+ const SWITCH_BUTTON_BG = {
26
+ dark: '#1E2A38',
27
+ light: '#E6F2FF',
28
+ } as const;
29
+
30
+ const LOGOUT_BUTTON_BG = {
31
+ dark: '#3A1E1E',
32
+ light: '#FFEBEE',
33
+ } as const;
34
+
24
35
  const SessionManagementScreen: React.FC<BaseScreenProps> = ({
25
36
  onClose,
26
37
  theme,
@@ -45,7 +56,7 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
45
56
  const normalizedTheme = normalizeTheme(theme);
46
57
  const themeStyles = useThemeStyles(normalizedTheme);
47
58
  // Extract commonly used colors for readability
48
- const { textColor, backgroundColor, secondaryBackgroundColor, borderColor, primaryColor, dangerColor, successColor, isDarkTheme } = themeStyles;
59
+ const { textColor, backgroundColor, borderColor, primaryColor, dangerColor, successColor, isDarkTheme } = themeStyles;
49
60
 
50
61
  // Memoized load sessions function - prevents unnecessary re-renders
51
62
  const loadSessions = useCallback(async (isRefresh = false) => {
@@ -92,9 +103,14 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
92
103
  });
93
104
  }, [logout, refreshSessions]);
94
105
 
106
+ // Memoized bulk action items - prevents unnecessary re-renders when dependencies haven't changed
107
+ const otherSessionsCount = useMemo(() =>
108
+ userSessions.filter(s => s.sessionId !== activeSessionId).length,
109
+ [userSessions, activeSessionId]
110
+ );
111
+
95
112
  // Memoized logout other sessions handler - prevents unnecessary re-renders
96
113
  const handleLogoutOtherSessions = useCallback(async () => {
97
- const otherSessionsCount = userSessions.filter(s => s.sessionId !== activeSessionId).length;
98
114
  if (otherSessionsCount === 0) {
99
115
  toast.info('No other sessions to logout.');
100
116
  return;
@@ -119,7 +135,7 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
119
135
  }
120
136
  }
121
137
  );
122
- }, [userSessions, activeSessionId, logout, refreshSessions]);
138
+ }, [otherSessionsCount, userSessions, activeSessionId, logout, refreshSessions]);
123
139
 
124
140
  // Memoized logout all sessions handler - prevents unnecessary re-renders
125
141
  const handleLogoutAllSessions = useCallback(async () => {
@@ -132,6 +148,7 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
132
148
  } catch (error) {
133
149
  console.error('Logout all sessions failed:', error);
134
150
  toast.error('Failed to logout all sessions. Please try again.');
151
+ } finally {
135
152
  setActionLoading(null);
136
153
  }
137
154
  }
@@ -171,22 +188,18 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
171
188
  }
172
189
  }, [activeSessionId, switchSession]);
173
190
 
191
+ // Memoized refresh handler for pull-to-refresh
192
+ const handleRefresh = useCallback(() => {
193
+ loadSessions(true);
194
+ }, [loadSessions]);
195
+
174
196
  useEffect(() => {
175
197
  loadSessions();
176
198
  }, [loadSessions]);
177
199
 
178
- if (loading) {
179
- return (
180
- <View style={[styles.container, styles.centerContent, { backgroundColor }]}>
181
- <ActivityIndicator size="large" color={primaryColor} />
182
- <Text style={[styles.loadingText, { color: textColor }]}>Loading sessions...</Text>
183
- </View>
184
- );
185
- }
186
-
187
200
  // Memoized session items - prevents unnecessary re-renders when dependencies haven't changed
188
201
  const sessionItems = useMemo(() => {
189
- return userSessions.map((session: ClientSession) => {
202
+ return userSessions.map((session) => {
190
203
  const isCurrent = session.sessionId === activeSessionId;
191
204
  const subtitleParts: string[] = [];
192
205
  if (session.deviceId) subtitleParts.push(`Device ${session.deviceId.substring(0, 10)}...`);
@@ -205,7 +218,7 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
205
218
  <View style={styles.sessionActionsRow}>
206
219
  <TouchableOpacity
207
220
  onPress={() => handleSwitchSession(session.sessionId)}
208
- style={[styles.sessionPillButton, { backgroundColor: isDarkTheme ? '#1E2A38' : '#E6F2FF', borderColor: primaryColor }]}
221
+ style={[styles.sessionPillButton, { backgroundColor: isDarkTheme ? SWITCH_BUTTON_BG.dark : SWITCH_BUTTON_BG.light, borderColor: primaryColor }]}
209
222
  disabled={switchLoading === session.sessionId || actionLoading === session.sessionId}
210
223
  >
211
224
  {switchLoading === session.sessionId ? (
@@ -216,7 +229,7 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
216
229
  </TouchableOpacity>
217
230
  <TouchableOpacity
218
231
  onPress={() => handleLogoutSession(session.sessionId)}
219
- style={[styles.sessionPillButton, { backgroundColor: isDarkTheme ? '#3A1E1E' : '#FFEBEE', borderColor: dangerColor }]}
232
+ style={[styles.sessionPillButton, { backgroundColor: isDarkTheme ? LOGOUT_BUTTON_BG.dark : LOGOUT_BUTTON_BG.light, borderColor: dangerColor }]}
220
233
  disabled={actionLoading === session.sessionId || switchLoading === session.sessionId}
221
234
  >
222
235
  {actionLoading === session.sessionId ? (
@@ -237,12 +250,6 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
237
250
  });
238
251
  }, [userSessions, activeSessionId, formatRelative, successColor, primaryColor, isDarkTheme, switchLoading, actionLoading, handleSwitchSession, handleLogoutSession, dangerColor]);
239
252
 
240
- // Memoized bulk action items - prevents unnecessary re-renders when dependencies haven't changed
241
- const otherSessionsCount = useMemo(() =>
242
- userSessions.filter(s => s.sessionId !== activeSessionId).length,
243
- [userSessions, activeSessionId]
244
- );
245
-
246
253
  const bulkItems = useMemo(() => [
247
254
  {
248
255
  id: 'logout-others',
@@ -270,6 +277,15 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
270
277
  },
271
278
  ], [otherSessionsCount, primaryColor, dangerColor, handleLogoutOtherSessions, handleLogoutAllSessions, actionLoading]);
272
279
 
280
+ if (loading) {
281
+ return (
282
+ <View style={[styles.container, styles.centerContent, { backgroundColor }]}>
283
+ <ActivityIndicator size="large" color={primaryColor} />
284
+ <Text style={[styles.loadingText, { color: textColor }]}>Loading sessions...</Text>
285
+ </View>
286
+ );
287
+ }
288
+
273
289
  return (
274
290
  <View style={[styles.container, { backgroundColor }]}>
275
291
  <Header
@@ -285,7 +301,7 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
285
301
  refreshControl={
286
302
  <RefreshControl
287
303
  refreshing={refreshing}
288
- onRefresh={() => loadSessions(true)}
304
+ onRefresh={handleRefresh}
289
305
  tintColor={primaryColor}
290
306
  />
291
307
  }
@@ -293,12 +309,12 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
293
309
  {userSessions.length > 0 ? (
294
310
  <>
295
311
  {lastRefreshed && (
296
- <Text style={[styles.metaText, { color: isDarkTheme ? '#777' : '#777', marginBottom: 6 }]}>Last refreshed {formatRelative(lastRefreshed.toISOString())}</Text>
312
+ <Text style={[styles.metaText, { color: '#777', marginBottom: 6 }]}>Last refreshed {formatRelative(lastRefreshed.toISOString())}</Text>
297
313
  )}
298
314
  <View style={styles.fullBleed}>
299
315
  <GroupedSection items={sessionItems} />
300
316
  </View>
301
- <View style={{ height: 12 }} />
317
+ <View style={styles.sectionSpacer} />
302
318
  <View style={styles.fullBleed}>
303
319
  <GroupedSection items={bulkItems} />
304
320
  </View>
@@ -326,8 +342,6 @@ const styles = StyleSheet.create({
326
342
  justifyContent: 'center',
327
343
  alignItems: 'center',
328
344
  },
329
-
330
-
331
345
  scrollView: {
332
346
  flex: 1,
333
347
  },
@@ -376,6 +390,9 @@ const styles = StyleSheet.create({
376
390
  width: '100%',
377
391
  alignSelf: 'stretch',
378
392
  },
393
+ sectionSpacer: {
394
+ height: 12,
395
+ },
379
396
  emptyState: {
380
397
  alignItems: 'center',
381
398
  paddingVertical: 40,
@@ -7,12 +7,21 @@ export interface AuthState {
7
7
  isLoading: boolean;
8
8
  error: string | null;
9
9
  lastUserFetch: number | null; // Timestamp of last user fetch for caching
10
+
11
+ // Identity sync state (offline-first)
12
+ isIdentitySynced: boolean;
13
+ isSyncing: boolean;
14
+
10
15
  loginSuccess: (user: User) => void;
11
16
  loginFailure: (error: string) => void;
12
17
  logout: () => void;
13
18
  fetchUser: (oxyServices: { getCurrentUser: () => Promise<User> }, forceRefresh?: boolean) => Promise<void>;
14
19
  updateUser: (updates: Partial<User>, oxyServices: { updateProfile: (updates: Partial<User>) => Promise<User>; getCurrentUser: () => Promise<User> }) => Promise<void>;
15
20
  setUser: (user: User) => void; // Direct user setter for caching
21
+
22
+ // Identity sync actions
23
+ setIdentitySynced: (synced: boolean) => void;
24
+ setSyncing: (syncing: boolean) => void;
16
25
  }
17
26
 
18
27
  export const useAuthStore = create<AuthState>((set: (state: Partial<AuthState>) => void, get: () => AuthState) => ({
@@ -21,10 +30,30 @@ export const useAuthStore = create<AuthState>((set: (state: Partial<AuthState>)
21
30
  isLoading: false,
22
31
  error: null,
23
32
  lastUserFetch: null,
24
- loginSuccess: (user: User) => set({ isLoading: false, isAuthenticated: true, user, lastUserFetch: Date.now() }),
33
+
34
+ // Identity sync state (offline-first)
35
+ isIdentitySynced: true, // Assume synced until proven otherwise
36
+ isSyncing: false,
37
+
38
+ loginSuccess: (user: User) => set({
39
+ isLoading: false,
40
+ isAuthenticated: true,
41
+ user,
42
+ lastUserFetch: Date.now(),
43
+ isIdentitySynced: true, // If login succeeded, identity is synced
44
+ }),
25
45
  loginFailure: (error: string) => set({ isLoading: false, error }),
26
- logout: () => set({ user: null, isAuthenticated: false, lastUserFetch: null }),
46
+ logout: () => set({
47
+ user: null,
48
+ isAuthenticated: false,
49
+ lastUserFetch: null,
50
+ // Keep identity sync state - user might still have local identity
51
+ }),
27
52
  setUser: (user: User) => set({ user, lastUserFetch: Date.now() }),
53
+
54
+ // Identity sync actions
55
+ setIdentitySynced: (synced: boolean) => set({ isIdentitySynced: synced }),
56
+ setSyncing: (syncing: boolean) => set({ isSyncing: syncing }),
28
57
  fetchUser: async (oxyServices, forceRefresh = false) => {
29
58
  const state = get();
30
59
  const now = Date.now();
@@ -1,53 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.OxyServicesTotpMixin = OxyServicesTotpMixin;
7
- /**
8
- * TOTP Enrollment Methods Mixin
9
- */
10
-
11
- function OxyServicesTotpMixin(Base) {
12
- return class extends Base {
13
- constructor(...args) {
14
- super(...args);
15
- }
16
- async startTotpEnrollment(sessionId) {
17
- try {
18
- return await this.makeRequest('POST', '/api/auth/totp/enroll/start', {
19
- sessionId
20
- }, {
21
- cache: false
22
- });
23
- } catch (error) {
24
- throw this.handleError(error);
25
- }
26
- }
27
- async verifyTotpEnrollment(sessionId, code) {
28
- try {
29
- return await this.makeRequest('POST', '/api/auth/totp/enroll/verify', {
30
- sessionId,
31
- code
32
- }, {
33
- cache: false
34
- });
35
- } catch (error) {
36
- throw this.handleError(error);
37
- }
38
- }
39
- async disableTotp(sessionId, code) {
40
- try {
41
- return await this.makeRequest('POST', '/api/auth/totp/disable', {
42
- sessionId,
43
- code
44
- }, {
45
- cache: false
46
- });
47
- } catch (error) {
48
- throw this.handleError(error);
49
- }
50
- }
51
- };
52
- }
53
- //# sourceMappingURL=OxyServices.totp.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["OxyServicesTotpMixin","Base","constructor","args","startTotpEnrollment","sessionId","makeRequest","cache","error","handleError","verifyTotpEnrollment","code","disableTotp"],"sourceRoot":"../../../../src","sources":["core/mixins/OxyServices.totp.ts"],"mappings":";;;;;;AAAA;AACA;AACA;;AAGO,SAASA,oBAAoBA,CAAmCC,IAAO,EAAE;EAC9E,OAAO,cAAcA,IAAI,CAAC;IACxBC,WAAWA,CAAC,GAAGC,IAAW,EAAE;MAC1B,KAAK,CAAC,GAAIA,IAAc,CAAC;IAC3B;IACA,MAAMC,mBAAmBA,CAACC,SAAiB,EAAkF;MAC3H,IAAI;QACF,OAAO,MAAM,IAAI,CAACC,WAAW,CAAC,MAAM,EAAE,6BAA6B,EAAE;UAAED;QAAU,CAAC,EAAE;UAAEE,KAAK,EAAE;QAAM,CAAC,CAAC;MACvG,CAAC,CAAC,OAAOC,KAAK,EAAE;QACd,MAAM,IAAI,CAACC,WAAW,CAACD,KAAK,CAAC;MAC/B;IACF;IAEA,MAAME,oBAAoBA,CAACL,SAAiB,EAAEM,IAAY,EAA+E;MACvI,IAAI;QACF,OAAO,MAAM,IAAI,CAACL,WAAW,CAAC,MAAM,EAAE,8BAA8B,EAAE;UAAED,SAAS;UAAEM;QAAK,CAAC,EAAE;UAAEJ,KAAK,EAAE;QAAM,CAAC,CAAC;MAC9G,CAAC,CAAC,OAAOC,KAAK,EAAE;QACd,MAAM,IAAI,CAACC,WAAW,CAACD,KAAK,CAAC;MAC/B;IACF;IAEA,MAAMI,WAAWA,CAACP,SAAiB,EAAEM,IAAY,EAAkC;MACjF,IAAI;QACF,OAAO,MAAM,IAAI,CAACL,WAAW,CAAC,MAAM,EAAE,wBAAwB,EAAE;UAAED,SAAS;UAAEM;QAAK,CAAC,EAAE;UAAEJ,KAAK,EAAE;QAAM,CAAC,CAAC;MACxG,CAAC,CAAC,OAAOC,KAAK,EAAE;QACd,MAAM,IAAI,CAACC,WAAW,CAACD,KAAK,CAAC;MAC/B;IACF;EACF,CAAC;AACH","ignoreList":[]}