@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
@@ -46,14 +46,6 @@
46
46
  "status": {
47
47
  "accountSwitched": "Utilisation de {{name}}"
48
48
  },
49
- "totp": {
50
- "title": "Code à deux facteurs",
51
- "subtitle": "Entrez le code à 6 chiffres de votre application d'authentification pour @{{username}}",
52
- "invalidCode": "Code invalide. Veuillez réessayer.",
53
- "noAccess": "Pas d'accès à votre authentificateur ?",
54
- "useBackupCode": "Utiliser le code de sauvegarde",
55
- "useRecoveryKey": "Utiliser la clé de récupération"
56
- }
57
49
  },
58
50
  "signup": {
59
51
  "welcome": {
@@ -98,7 +90,7 @@
98
90
  "password": "Mot de passe"
99
91
  },
100
92
  "notSet": "Non défini",
101
- "securityTip": "Pour une sécurité renforcée, activez l'authentification à deux facteurs (TOTP) dans les paramètres du compte après avoir créé votre compte.",
93
+ "securityTip": "Pour une sécurité renforcée, activez l'authentification biométrique dans les paramètres du compte après avoir créé votre compte.",
102
94
  "legalReminder": "En créant un compte, vous acceptez nos Conditions d'utilisation et notre Politique de confidentialité."
103
95
  }
104
96
  },
@@ -46,14 +46,6 @@
46
46
  "status": {
47
47
  "accountSwitched": "Ora usando {{name}}"
48
48
  },
49
- "totp": {
50
- "title": "Codice a due fattori",
51
- "subtitle": "Inserisci il codice a 6 cifre dalla tua app autenticatore per @{{username}}",
52
- "invalidCode": "Codice non valido. Riprova.",
53
- "noAccess": "Nessun accesso al tuo autenticatore?",
54
- "useBackupCode": "Usa codice di backup",
55
- "useRecoveryKey": "Usa chiave di recupero"
56
- }
57
49
  },
58
50
  "signup": {
59
51
  "welcome": {
@@ -98,7 +90,7 @@
98
90
  "password": "Password"
99
91
  },
100
92
  "notSet": "Non impostato",
101
- "securityTip": "Per una maggiore sicurezza, abilita l'autenticazione a due fattori (TOTP) dalle Impostazioni account dopo aver creato il tuo account.",
93
+ "securityTip": "Per una maggiore sicurezza, abilita l'autenticazione biometrica dalle Impostazioni account dopo aver creato il tuo account.",
102
94
  "legalReminder": "Creando un account, accetti i nostri Termini di servizio e l'Informativa sulla privacy."
103
95
  }
104
96
  },
@@ -46,14 +46,6 @@
46
46
  "status": {
47
47
  "accountSwitched": "{{name}}を使用中"
48
48
  },
49
- "totp": {
50
- "title": "二要素認証コード",
51
- "subtitle": "@{{username}}の認証アプリから6桁のコードを入力してください",
52
- "invalidCode": "無効なコードです。もう一度お試しください。",
53
- "noAccess": "認証アプリにアクセスできませんか?",
54
- "useBackupCode": "バックアップコードを使用",
55
- "useRecoveryKey": "回復キーを使用"
56
- }
57
49
  },
58
50
  "signup": {
59
51
  "welcome": {
@@ -98,7 +90,7 @@
98
90
  "password": "パスワード"
99
91
  },
100
92
  "notSet": "未設定",
101
- "securityTip": "より強力なセキュリティのため、アカウント作成後、アカウント設定から二要素認証(TOTP)を有効にしてください。",
93
+ "securityTip": "より強力なセキュリティのため、アカウント作成後、アカウント設定から生体認証を有効にしてください。",
102
94
  "legalReminder": "アカウントを作成することで、利用規約とプライバシーポリシーに同意したことになります。"
103
95
  }
104
96
  },
@@ -46,14 +46,6 @@
46
46
  "status": {
47
47
  "accountSwitched": "{{name}} 사용 중"
48
48
  },
49
- "totp": {
50
- "title": "2단계 인증 코드",
51
- "subtitle": "@{{username}}의 인증 앱에서 6자리 코드를 입력하세요",
52
- "invalidCode": "잘못된 코드입니다. 다시 시도해주세요.",
53
- "noAccess": "인증 앱에 액세스할 수 없나요?",
54
- "useBackupCode": "백업 코드 사용",
55
- "useRecoveryKey": "복구 키 사용"
56
- }
57
49
  },
58
50
  "signup": {
59
51
  "welcome": {
@@ -98,7 +90,7 @@
98
90
  "password": "비밀번호"
99
91
  },
100
92
  "notSet": "설정되지 않음",
101
- "securityTip": "더 강력한 보안을 위해 계정을 만든 후 계정 설정에서 2단계 인증(TOTP)을 활성화하세요.",
93
+ "securityTip": "더 강력한 보안을 위해 계정을 만든 후 계정 설정에서 생체 인증을 활성화하세요.",
102
94
  "legalReminder": "계정을 만들면 서비스 약관 및 개인정보 보호정책에 동의하는 것입니다."
103
95
  }
104
96
  },
@@ -46,14 +46,6 @@
46
46
  "status": {
47
47
  "accountSwitched": "Agora a usar {{name}}"
48
48
  },
49
- "totp": {
50
- "title": "Código de dois fatores",
51
- "subtitle": "Introduza o código de 6 dígitos da sua app autenticadora para @{{username}}",
52
- "invalidCode": "Código inválido. Por favor, tente novamente.",
53
- "noAccess": "Sem acesso ao seu autenticador?",
54
- "useBackupCode": "Usar código de backup",
55
- "useRecoveryKey": "Usar chave de recuperação"
56
- }
57
49
  },
58
50
  "signup": {
59
51
  "welcome": {
@@ -98,7 +90,7 @@
98
90
  "password": "Palavra-passe"
99
91
  },
100
92
  "notSet": "Não definido",
101
- "securityTip": "Para maior segurança, ative a Autenticação de Dois Fatores (TOTP) nas Definições da conta após criar a sua conta.",
93
+ "securityTip": "Para maior segurança, ative a autenticação biométrica nas Definições da conta após criar a sua conta.",
102
94
  "legalReminder": "Ao criar uma conta, concorda com os nossos Termos de Serviço e Política de Privacidade."
103
95
  }
104
96
  },
@@ -46,14 +46,6 @@
46
46
  "status": {
47
47
  "accountSwitched": "正在使用{{name}}"
48
48
  },
49
- "totp": {
50
- "title": "双因素验证码",
51
- "subtitle": "输入@{{username}}的验证器应用中的6位数字代码",
52
- "invalidCode": "无效的代码。请重试。",
53
- "noAccess": "无法访问您的验证器?",
54
- "useBackupCode": "使用备份代码",
55
- "useRecoveryKey": "使用恢复密钥"
56
- }
57
49
  },
58
50
  "signup": {
59
51
  "welcome": {
@@ -98,7 +90,7 @@
98
90
  "password": "密码"
99
91
  },
100
92
  "notSet": "未设置",
101
- "securityTip": "为了更强的安全性,创建账户后,请在账户设置中启用双因素身份验证(TOTP)。",
93
+ "securityTip": "为了更强的安全性,创建账户后,请在账户设置中启用生物识别身份验证。",
102
94
  "legalReminder": "创建账户即表示您同意我们的服务条款和隐私政策。"
103
95
  }
104
96
  },
@@ -30,7 +30,6 @@ export interface User {
30
30
  avatar?: string;
31
31
  // Privacy and security settings
32
32
  privacySettings?: {
33
- twoFactorEnabled?: boolean;
34
33
  [key: string]: unknown;
35
34
  };
36
35
  name?: {
@@ -60,6 +59,7 @@ export interface User {
60
59
  followers?: number;
61
60
  following?: number;
62
61
  };
62
+ accountExpiresAfterInactivityDays?: number | null; // Days of inactivity before account expires (null = never expire)
63
63
  [key: string]: unknown;
64
64
  }
65
65
 
@@ -29,3 +29,4 @@ declare module 'bip39' {
29
29
  export function mnemonicToSeedHexSync(mnemonic: string, passphrase?: string): string;
30
30
  }
31
31
 
32
+
@@ -94,3 +94,4 @@ declare module 'buffer' {
94
94
 
95
95
  type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex';
96
96
  }
97
+
@@ -17,3 +17,4 @@ declare module 'color' {
17
17
  export default color;
18
18
  }
19
19
 
20
+
@@ -59,3 +59,4 @@ declare module 'elliptic' {
59
59
  export const ec: ECConstructor;
60
60
  }
61
61
 
62
+
@@ -27,3 +27,4 @@ declare module 'expo-crypto' {
27
27
  export function getRandomUUIDAsync(): Promise<string>;
28
28
  }
29
29
 
30
+
@@ -19,3 +19,4 @@ declare module 'expo-secure-store' {
19
19
  export async function isAvailableAsync(): Promise<boolean>;
20
20
  }
21
21
 
22
+
@@ -40,12 +40,20 @@ export interface OxyContextState {
40
40
  currentLanguageName: string;
41
41
  currentNativeLanguageName: string;
42
42
 
43
- // Identity management (public key authentication)
44
- createIdentity: (username: string, email?: string) => Promise<{ user: User; recoveryPhrase: string[] }>;
45
- importIdentity: (phrase: string, username?: string, email?: string) => Promise<User>;
43
+ // Identity management (public key authentication - offline-first)
44
+ createIdentity: () => Promise<{ recoveryPhrase: string[]; synced: boolean }>;
45
+ importIdentity: (phrase: string) => Promise<{ synced: boolean }>;
46
46
  signIn: (deviceName?: string) => Promise<User>;
47
47
  hasIdentity: () => Promise<boolean>;
48
48
  getPublicKey: () => Promise<string | null>;
49
+ isIdentitySynced: () => Promise<boolean>;
50
+ syncIdentity: () => Promise<User>;
51
+
52
+ // Identity sync state (reactive, from Zustand store)
53
+ identitySyncState: {
54
+ isSynced: boolean;
55
+ isSyncing: boolean;
56
+ };
49
57
 
50
58
  // Session management
51
59
  logout: (targetSessionId?: string) => Promise<void>;
@@ -140,6 +148,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
140
148
  loginSuccess,
141
149
  loginFailure,
142
150
  logoutStore,
151
+ // Identity sync state and actions
152
+ isIdentitySyncedStore,
153
+ isSyncing,
154
+ setIdentitySynced,
155
+ setSyncing,
143
156
  } = useAuthStore(
144
157
  useShallow((state: AuthState) => ({
145
158
  user: state.user,
@@ -149,6 +162,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
149
162
  loginSuccess: state.loginSuccess,
150
163
  loginFailure: state.loginFailure,
151
164
  logoutStore: state.logout,
165
+ // Identity sync state and actions
166
+ isIdentitySyncedStore: state.isIdentitySynced,
167
+ isSyncing: state.isSyncing,
168
+ setIdentitySynced: state.setIdentitySynced,
169
+ setSyncing: state.setSyncing,
152
170
  })),
153
171
  );
154
172
 
@@ -212,6 +230,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
212
230
  logoutAll,
213
231
  hasIdentity,
214
232
  getPublicKey,
233
+ isIdentitySynced,
234
+ syncIdentity,
215
235
  } = useAuthOperations({
216
236
  oxyServices,
217
237
  storage,
@@ -229,6 +249,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
229
249
  loginFailure,
230
250
  logoutStore,
231
251
  setAuthState,
252
+ setIdentitySynced,
253
+ setSyncing,
232
254
  logger,
233
255
  });
234
256
 
@@ -392,6 +414,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
392
414
  signIn,
393
415
  hasIdentity,
394
416
  getPublicKey,
417
+ isIdentitySynced,
418
+ syncIdentity,
419
+ identitySyncState: {
420
+ isSynced: isIdentitySyncedStore ?? true,
421
+ isSyncing: isSyncing ?? false,
422
+ },
395
423
  logout,
396
424
  logoutAll,
397
425
  switchSession: switchSessionForContext,
@@ -411,6 +439,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
411
439
  signIn,
412
440
  hasIdentity,
413
441
  getPublicKey,
442
+ isIdentitySynced,
443
+ syncIdentity,
444
+ isIdentitySyncedStore,
445
+ isSyncing,
414
446
  currentLanguage,
415
447
  currentLanguageMetadata,
416
448
  currentLanguageName,
@@ -26,14 +26,17 @@ export interface UseAuthOperationsOptions {
26
26
  loginFailure: (message: string) => void;
27
27
  logoutStore: () => void;
28
28
  setAuthState: (state: Partial<AuthState>) => void;
29
+ // Identity sync store actions
30
+ setIdentitySynced: (synced: boolean) => void;
31
+ setSyncing: (syncing: boolean) => void;
29
32
  logger?: (message: string, error?: unknown) => void;
30
33
  }
31
34
 
32
35
  export interface UseAuthOperationsResult {
33
- /** Create a new identity and register with the server */
34
- createIdentity: (username: string, email?: string) => Promise<{ user: User; recoveryPhrase: string[] }>;
36
+ /** Create a new identity locally (offline-first) and optionally sync with server */
37
+ createIdentity: () => Promise<{ recoveryPhrase: string[]; synced: boolean }>;
35
38
  /** Import an existing identity from recovery phrase */
36
- importIdentity: (phrase: string, username?: string, email?: string) => Promise<User>;
39
+ importIdentity: (phrase: string) => Promise<{ synced: boolean }>;
37
40
  /** Sign in with existing identity on device */
38
41
  signIn: (deviceName?: string) => Promise<User>;
39
42
  /** Logout from current session */
@@ -44,6 +47,10 @@ export interface UseAuthOperationsResult {
44
47
  hasIdentity: () => Promise<boolean>;
45
48
  /** Get the public key of the stored identity */
46
49
  getPublicKey: () => Promise<string | null>;
50
+ /** Check if identity is synced with server */
51
+ isIdentitySynced: () => Promise<boolean>;
52
+ /** Sync local identity with server (when online) */
53
+ syncIdentity: () => Promise<User>;
47
54
  }
48
55
 
49
56
  const LOGIN_ERROR_CODE = 'LOGIN_ERROR';
@@ -71,103 +78,12 @@ export const useAuthOperations = ({
71
78
  loginSuccess,
72
79
  loginFailure,
73
80
  logoutStore,
74
- setAuthState,
75
- logger,
81
+ setAuthState,
82
+ setIdentitySynced,
83
+ setSyncing,
84
+ logger,
76
85
  }: UseAuthOperationsOptions): UseAuthOperationsResult => {
77
86
 
78
- /**
79
- * Create a new identity with recovery phrase
80
- */
81
- const createIdentity = useCallback(
82
- async (username: string, email?: string): Promise<{ user: User; recoveryPhrase: string[] }> => {
83
- if (!storage) throw new Error('Storage not initialized');
84
-
85
- setAuthState({ isLoading: true, error: null });
86
-
87
- try {
88
- // Generate new identity with recovery phrase
89
- const { phrase, words, publicKey } = await RecoveryPhraseService.generateIdentityWithRecovery();
90
-
91
- // Create registration signature
92
- const { signature, timestamp } = await SignatureService.createRegistrationSignature(username, email);
93
-
94
- // Register with server
95
- const { user } = await oxyServices.register(publicKey, username, signature, timestamp, email);
96
-
97
- // Now sign in to create a session
98
- const fullUser = await performSignIn(publicKey);
99
-
100
- return {
101
- user: fullUser,
102
- recoveryPhrase: words,
103
- };
104
- } catch (error) {
105
- // Clean up identity if registration failed
106
- await KeyManager.deleteIdentity().catch(() => {});
107
-
108
- const message = handleAuthError(error, {
109
- defaultMessage: 'Failed to create identity',
110
- code: REGISTER_ERROR_CODE,
111
- onError,
112
- setAuthError: (msg) => setAuthState({ error: msg }),
113
- logger,
114
- });
115
- loginFailure(message);
116
- throw error;
117
- } finally {
118
- setAuthState({ isLoading: false });
119
- }
120
- },
121
- [oxyServices, storage, setAuthState, loginFailure, onError, logger],
122
- );
123
-
124
- /**
125
- * Import identity from recovery phrase
126
- */
127
- const importIdentity = useCallback(
128
- async (phrase: string, username?: string, email?: string): Promise<User> => {
129
- if (!storage) throw new Error('Storage not initialized');
130
-
131
- setAuthState({ isLoading: true, error: null });
132
-
133
- try {
134
- // Restore identity from phrase
135
- const publicKey = await RecoveryPhraseService.restoreFromPhrase(phrase);
136
-
137
- // Check if this identity is already registered
138
- const { registered } = await oxyServices.checkPublicKeyRegistered(publicKey);
139
-
140
- if (registered) {
141
- // Identity exists, just sign in
142
- return await performSignIn(publicKey);
143
- } else {
144
- // Need to register this identity
145
- if (!username) {
146
- throw new Error('Username is required for new registration');
147
- }
148
-
149
- const { signature, timestamp } = await SignatureService.createRegistrationSignature(username, email);
150
- await oxyServices.register(publicKey, username, signature, timestamp, email);
151
-
152
- return await performSignIn(publicKey);
153
- }
154
- } catch (error) {
155
- const message = handleAuthError(error, {
156
- defaultMessage: 'Failed to import identity',
157
- code: REGISTER_ERROR_CODE,
158
- onError,
159
- setAuthError: (msg) => setAuthState({ error: msg }),
160
- logger,
161
- });
162
- loginFailure(message);
163
- throw error;
164
- } finally {
165
- setAuthState({ isLoading: false });
166
- }
167
- },
168
- [oxyServices, storage, setAuthState, loginFailure, onError, logger],
169
- );
170
-
171
87
  /**
172
88
  * Internal function to perform challenge-response sign in
173
89
  */
@@ -181,6 +97,10 @@ export const useAuthOperations = ({
181
97
  // Request challenge
182
98
  const { challenge } = await oxyServices.requestChallenge(publicKey);
183
99
 
100
+ // Note: Biometric authentication check should be handled by the app layer
101
+ // (e.g., accounts app) before calling signIn. The biometric preference is stored
102
+ // in local storage as 'oxy_biometric_enabled' and can be checked there.
103
+
184
104
  // Sign the challenge
185
105
  const { challenge: signature, timestamp } = await SignatureService.signChallenge(challenge);
186
106
 
@@ -261,6 +181,198 @@ export const useAuthOperations = ({
261
181
  ],
262
182
  );
263
183
 
184
+ /**
185
+ * Create a new identity with recovery phrase (offline-first)
186
+ * Identity is purely cryptographic - no username or email required
187
+ */
188
+ const createIdentity = useCallback(
189
+ async (): Promise<{ recoveryPhrase: string[]; synced: boolean }> => {
190
+ if (!storage) throw new Error('Storage not initialized');
191
+
192
+ setAuthState({ isLoading: true, error: null });
193
+
194
+ try {
195
+ // Generate new identity with recovery phrase (works offline)
196
+ const { phrase, words, publicKey } = await RecoveryPhraseService.generateIdentityWithRecovery();
197
+
198
+ // Mark as not synced
199
+ await storage.setItem('oxy_identity_synced', 'false');
200
+ setIdentitySynced(false);
201
+
202
+ // Try to sync with server (will succeed if online)
203
+ try {
204
+ const { signature, timestamp } = await SignatureService.createRegistrationSignature();
205
+ await oxyServices.register(publicKey, signature, timestamp);
206
+
207
+ // Mark as synced (Zustand store + storage)
208
+ await storage.setItem('oxy_identity_synced', 'true');
209
+ setIdentitySynced(true);
210
+
211
+ return {
212
+ recoveryPhrase: words,
213
+ synced: true,
214
+ };
215
+ } catch (syncError) {
216
+ // Offline or server error - identity is created locally but not synced
217
+ if (__DEV__) {
218
+ console.log('[Auth] Identity created locally, will sync when online:', syncError);
219
+ }
220
+
221
+ return {
222
+ recoveryPhrase: words,
223
+ synced: false,
224
+ };
225
+ }
226
+ } catch (error) {
227
+ // Clean up identity if generation failed
228
+ await KeyManager.deleteIdentity().catch(() => {});
229
+ await storage.removeItem('oxy_identity_synced').catch(() => {});
230
+ setIdentitySynced(true);
231
+
232
+ const message = handleAuthError(error, {
233
+ defaultMessage: 'Failed to create identity',
234
+ code: REGISTER_ERROR_CODE,
235
+ onError,
236
+ setAuthError: (msg) => setAuthState({ error: msg }),
237
+ logger,
238
+ });
239
+ loginFailure(message);
240
+ throw error;
241
+ } finally {
242
+ setAuthState({ isLoading: false });
243
+ }
244
+ },
245
+ [oxyServices, storage, setAuthState, loginFailure, onError, logger, setIdentitySynced],
246
+ );
247
+
248
+ /**
249
+ * Check if identity is synced with server (reads from storage for persistence)
250
+ */
251
+ const isIdentitySyncedFn = useCallback(async (): Promise<boolean> => {
252
+ if (!storage) return true;
253
+ const synced = await storage.getItem('oxy_identity_synced');
254
+ const isSynced = synced !== 'false';
255
+ setIdentitySynced(isSynced);
256
+ return isSynced;
257
+ }, [storage, setIdentitySynced]);
258
+
259
+ /**
260
+ * Sync local identity with server (call when online)
261
+ */
262
+ const syncIdentity = useCallback(
263
+ async (): Promise<User> => {
264
+ if (!storage) throw new Error('Storage not initialized');
265
+
266
+ setAuthState({ isLoading: true, error: null });
267
+ setSyncing(true);
268
+
269
+ try {
270
+ const publicKey = await KeyManager.getPublicKey();
271
+ if (!publicKey) {
272
+ throw new Error('No identity found on this device');
273
+ }
274
+
275
+ // Check if already synced
276
+ const alreadySynced = await storage.getItem('oxy_identity_synced');
277
+ if (alreadySynced === 'true') {
278
+ // Already synced, just sign in
279
+ setIdentitySynced(true);
280
+ return await performSignIn(publicKey);
281
+ }
282
+
283
+ // Check if already registered on server
284
+ const { registered } = await oxyServices.checkPublicKeyRegistered(publicKey);
285
+
286
+ if (!registered) {
287
+ // Register with server (identity is just the publicKey)
288
+ const { signature, timestamp } = await SignatureService.createRegistrationSignature();
289
+ await oxyServices.register(publicKey, signature, timestamp);
290
+ }
291
+
292
+ // Mark as synced (Zustand store + storage)
293
+ await storage.setItem('oxy_identity_synced', 'true');
294
+ setIdentitySynced(true);
295
+
296
+ // Sign in
297
+ return await performSignIn(publicKey);
298
+ } catch (error) {
299
+ const message = handleAuthError(error, {
300
+ defaultMessage: 'Failed to sync identity',
301
+ code: REGISTER_ERROR_CODE,
302
+ onError,
303
+ setAuthError: (msg) => setAuthState({ error: msg }),
304
+ logger,
305
+ });
306
+ loginFailure(message);
307
+ throw error;
308
+ } finally {
309
+ setAuthState({ isLoading: false });
310
+ setSyncing(false);
311
+ }
312
+ },
313
+ [oxyServices, storage, setAuthState, performSignIn, loginFailure, onError, logger, setSyncing, setIdentitySynced],
314
+ );
315
+
316
+ /**
317
+ * Import identity from recovery phrase (offline-first)
318
+ */
319
+ const importIdentity = useCallback(
320
+ async (phrase: string): Promise<{ synced: boolean }> => {
321
+ if (!storage) throw new Error('Storage not initialized');
322
+
323
+ setAuthState({ isLoading: true, error: null });
324
+
325
+ try {
326
+ // Restore identity from phrase (works offline)
327
+ const publicKey = await RecoveryPhraseService.restoreFromPhrase(phrase);
328
+
329
+ // Mark as not synced
330
+ await storage.setItem('oxy_identity_synced', 'false');
331
+ setIdentitySynced(false);
332
+
333
+ // Try to sync with server
334
+ try {
335
+ // Check if this identity is already registered
336
+ const { registered } = await oxyServices.checkPublicKeyRegistered(publicKey);
337
+
338
+ if (registered) {
339
+ // Identity exists, mark as synced
340
+ await storage.setItem('oxy_identity_synced', 'true');
341
+ setIdentitySynced(true);
342
+ return { synced: true };
343
+ } else {
344
+ // Need to register this identity (identity is just the publicKey)
345
+ const { signature, timestamp } = await SignatureService.createRegistrationSignature();
346
+ await oxyServices.register(publicKey, signature, timestamp);
347
+
348
+ await storage.setItem('oxy_identity_synced', 'true');
349
+ setIdentitySynced(true);
350
+ return { synced: true };
351
+ }
352
+ } catch (syncError) {
353
+ // Offline - identity restored locally but not synced
354
+ if (__DEV__) {
355
+ console.log('[Auth] Identity imported locally, will sync when online:', syncError);
356
+ }
357
+ return { synced: false };
358
+ }
359
+ } catch (error) {
360
+ const message = handleAuthError(error, {
361
+ defaultMessage: 'Failed to import identity',
362
+ code: REGISTER_ERROR_CODE,
363
+ onError,
364
+ setAuthError: (msg) => setAuthState({ error: msg }),
365
+ logger,
366
+ });
367
+ loginFailure(message);
368
+ throw error;
369
+ } finally {
370
+ setAuthState({ isLoading: false });
371
+ }
372
+ },
373
+ [oxyServices, storage, setAuthState, loginFailure, onError, logger, setIdentitySynced],
374
+ );
375
+
264
376
  /**
265
377
  * Sign in with existing identity on device
266
378
  */
@@ -396,5 +508,7 @@ export const useAuthOperations = ({
396
508
  logoutAll,
397
509
  hasIdentity,
398
510
  getPublicKey,
511
+ isIdentitySynced: isIdentitySyncedFn,
512
+ syncIdentity,
399
513
  };
400
514
  };