@oxyhq/services 14.1.0 → 16.0.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 (292) hide show
  1. package/lib/commonjs/index.js +4 -21
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/components/SignInAccountChooser.js +189 -0
  4. package/lib/commonjs/ui/components/SignInAccountChooser.js.map +1 -0
  5. package/lib/commonjs/ui/components/SignInModal.js +350 -131
  6. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  7. package/lib/commonjs/ui/context/OxyContext.js +340 -1547
  8. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  9. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +61 -64
  10. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/mutations/mutationKeys.js +1 -1
  12. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +9 -9
  13. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  14. package/lib/commonjs/ui/hooks/queries/queryKeys.js +1 -1
  15. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +7 -8
  16. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/useAuth.js +14 -33
  18. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  19. package/lib/commonjs/ui/hooks/usePasswordSignIn.js +170 -0
  20. package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +1 -0
  21. package/lib/commonjs/ui/hooks/useProfileEditing.js +2 -5
  22. package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useSessionManagement.js +2 -2
  24. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  25. package/lib/commonjs/ui/navigation/routes.js +1 -0
  26. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  27. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js +17 -18
  28. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +93 -68
  30. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/EditProfileScreen.js +187 -0
  32. package/lib/commonjs/ui/screens/EditProfileScreen.js.map +1 -0
  33. package/lib/commonjs/ui/screens/ManageAccountScreen.js +3 -7
  34. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/OxyAuthScreen.js +231 -95
  36. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/ProfileScreen.js +15 -2
  38. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/linkFormat.js +38 -0
  40. package/lib/commonjs/ui/screens/linkFormat.js.map +1 -0
  41. package/lib/commonjs/ui/session/authStore.js +146 -0
  42. package/lib/commonjs/ui/session/authStore.js.map +1 -0
  43. package/lib/commonjs/ui/session/createSessionClient.js +23 -17
  44. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  45. package/lib/commonjs/ui/session/index.js +13 -7
  46. package/lib/commonjs/ui/session/index.js.map +1 -1
  47. package/lib/commonjs/ui/session/tokenTransport.js +19 -31
  48. package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
  49. package/lib/commonjs/ui/utils/isWebBrowser.js +13 -0
  50. package/lib/commonjs/ui/utils/isWebBrowser.js.map +1 -0
  51. package/lib/commonjs/utils/deviceFlowSignIn.js +13 -16
  52. package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
  53. package/lib/module/index.js +2 -7
  54. package/lib/module/index.js.map +1 -1
  55. package/lib/module/ui/components/SignInAccountChooser.js +183 -0
  56. package/lib/module/ui/components/SignInAccountChooser.js.map +1 -0
  57. package/lib/module/ui/components/SignInModal.js +352 -134
  58. package/lib/module/ui/components/SignInModal.js.map +1 -1
  59. package/lib/module/ui/context/OxyContext.js +342 -1551
  60. package/lib/module/ui/context/OxyContext.js.map +1 -1
  61. package/lib/module/ui/context/hooks/useAuthOperations.js +60 -63
  62. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  63. package/lib/module/ui/hooks/mutations/mutationKeys.js +1 -1
  64. package/lib/module/ui/hooks/mutations/useAccountMutations.js +7 -7
  65. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  66. package/lib/module/ui/hooks/queries/queryKeys.js +1 -1
  67. package/lib/module/ui/hooks/queries/useAccountQueries.js +5 -6
  68. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  69. package/lib/module/ui/hooks/useAuth.js +14 -33
  70. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  71. package/lib/module/ui/hooks/usePasswordSignIn.js +165 -0
  72. package/lib/module/ui/hooks/usePasswordSignIn.js.map +1 -0
  73. package/lib/module/ui/hooks/useProfileEditing.js +2 -5
  74. package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
  75. package/lib/module/ui/hooks/useSessionManagement.js +1 -1
  76. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  77. package/lib/module/ui/navigation/routes.js +1 -0
  78. package/lib/module/ui/navigation/routes.js.map +1 -1
  79. package/lib/module/ui/screens/ConnectedAppsScreen.js +19 -20
  80. package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
  81. package/lib/module/ui/screens/EditProfileFieldScreen.js +91 -66
  82. package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
  83. package/lib/module/ui/screens/EditProfileScreen.js +182 -0
  84. package/lib/module/ui/screens/EditProfileScreen.js.map +1 -0
  85. package/lib/module/ui/screens/ManageAccountScreen.js +3 -7
  86. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  87. package/lib/module/ui/screens/OxyAuthScreen.js +233 -97
  88. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  89. package/lib/module/ui/screens/ProfileScreen.js +15 -2
  90. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  91. package/lib/module/ui/screens/linkFormat.js +31 -0
  92. package/lib/module/ui/screens/linkFormat.js.map +1 -0
  93. package/lib/module/ui/session/authStore.js +143 -0
  94. package/lib/module/ui/session/authStore.js.map +1 -0
  95. package/lib/module/ui/session/createSessionClient.js +22 -16
  96. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  97. package/lib/module/ui/session/index.js +9 -9
  98. package/lib/module/ui/session/index.js.map +1 -1
  99. package/lib/module/ui/session/tokenTransport.js +20 -33
  100. package/lib/module/ui/session/tokenTransport.js.map +1 -1
  101. package/lib/module/ui/utils/isWebBrowser.js +11 -0
  102. package/lib/module/ui/utils/isWebBrowser.js.map +1 -0
  103. package/lib/module/utils/deviceFlowSignIn.js +14 -17
  104. package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
  105. package/lib/typescript/commonjs/index.d.ts +2 -3
  106. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +29 -0
  108. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +15 -10
  110. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +36 -42
  112. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +18 -23
  114. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +4 -6
  116. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +6 -7
  118. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  120. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +31 -0
  121. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts +0 -1
  123. package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
  124. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  125. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  126. package/lib/typescript/commonjs/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
  128. package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts +14 -0
  129. package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts.map +1 -0
  130. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
  131. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  132. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +13 -12
  133. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  134. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts +23 -0
  136. package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/ui/session/authStore.d.ts +33 -0
  138. package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -0
  139. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +19 -15
  140. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/ui/session/index.d.ts +9 -9
  142. package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +13 -19
  144. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts +9 -0
  146. package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts.map +1 -0
  147. package/lib/typescript/commonjs/ui/utils/storageHelpers.d.ts +7 -7
  148. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +15 -13
  149. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
  150. package/lib/typescript/module/index.d.ts +2 -3
  151. package/lib/typescript/module/index.d.ts.map +1 -1
  152. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +29 -0
  153. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +1 -0
  154. package/lib/typescript/module/ui/components/SignInModal.d.ts +15 -10
  155. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  156. package/lib/typescript/module/ui/context/OxyContext.d.ts +36 -42
  157. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  158. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +18 -23
  159. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  160. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +4 -6
  161. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  162. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +6 -7
  163. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  164. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  165. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +31 -0
  166. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
  167. package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts +0 -1
  168. package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
  169. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  170. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  171. package/lib/typescript/module/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  172. package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
  173. package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts +14 -0
  174. package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts.map +1 -0
  175. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
  176. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  177. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +13 -12
  178. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  179. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  180. package/lib/typescript/module/ui/screens/linkFormat.d.ts +23 -0
  181. package/lib/typescript/module/ui/screens/linkFormat.d.ts.map +1 -0
  182. package/lib/typescript/module/ui/session/authStore.d.ts +33 -0
  183. package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -0
  184. package/lib/typescript/module/ui/session/createSessionClient.d.ts +19 -15
  185. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  186. package/lib/typescript/module/ui/session/index.d.ts +9 -9
  187. package/lib/typescript/module/ui/session/index.d.ts.map +1 -1
  188. package/lib/typescript/module/ui/session/tokenTransport.d.ts +13 -19
  189. package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
  190. package/lib/typescript/module/ui/utils/isWebBrowser.d.ts +9 -0
  191. package/lib/typescript/module/ui/utils/isWebBrowser.d.ts.map +1 -0
  192. package/lib/typescript/module/ui/utils/storageHelpers.d.ts +7 -7
  193. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +15 -13
  194. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
  195. package/package.json +4 -4
  196. package/src/index.ts +2 -6
  197. package/src/ui/components/SignInAccountChooser.tsx +162 -0
  198. package/src/ui/components/SignInModal.tsx +309 -139
  199. package/src/ui/context/OxyContext.tsx +532 -1785
  200. package/src/ui/context/hooks/useAuthOperations.ts +65 -76
  201. package/src/ui/hooks/mutations/mutationKeys.ts +1 -1
  202. package/src/ui/hooks/mutations/useAccountMutations.ts +7 -7
  203. package/src/ui/hooks/queries/queryKeys.ts +1 -1
  204. package/src/ui/hooks/queries/useAccountQueries.ts +8 -9
  205. package/src/ui/hooks/useAuth.ts +14 -35
  206. package/src/ui/hooks/usePasswordSignIn.ts +207 -0
  207. package/src/ui/hooks/useProfileEditing.ts +2 -8
  208. package/src/ui/hooks/useSessionManagement.ts +1 -1
  209. package/src/ui/navigation/routes.ts +3 -1
  210. package/src/ui/screens/ConnectedAppsScreen.tsx +23 -24
  211. package/src/ui/screens/EditProfileFieldScreen.tsx +102 -66
  212. package/src/ui/screens/EditProfileScreen.tsx +155 -0
  213. package/src/ui/screens/ManageAccountScreen.tsx +3 -7
  214. package/src/ui/screens/OxyAuthScreen.tsx +259 -112
  215. package/src/ui/screens/ProfileScreen.tsx +15 -2
  216. package/src/ui/screens/__tests__/linkFormat.test.ts +73 -0
  217. package/src/ui/screens/linkFormat.ts +37 -0
  218. package/src/ui/session/__tests__/createSessionClient.test.ts +22 -7
  219. package/src/ui/session/__tests__/tokenTransport.test.ts +37 -49
  220. package/src/ui/session/authStore.ts +164 -0
  221. package/src/ui/session/createSessionClient.ts +30 -17
  222. package/src/ui/session/index.ts +10 -9
  223. package/src/ui/session/tokenTransport.ts +26 -36
  224. package/src/ui/utils/isWebBrowser.ts +8 -0
  225. package/src/ui/utils/storageHelpers.ts +7 -7
  226. package/src/utils/__tests__/deviceFlowSignIn.test.ts +50 -131
  227. package/src/utils/deviceFlowSignIn.ts +19 -26
  228. package/lib/commonjs/ui/context/inSessionTokenRefresh.js +0 -243
  229. package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +0 -1
  230. package/lib/commonjs/ui/context/silentSessionRestore.js +0 -56
  231. package/lib/commonjs/ui/context/silentSessionRestore.js.map +0 -1
  232. package/lib/commonjs/ui/hooks/useWebSSO.js +0 -28
  233. package/lib/commonjs/ui/hooks/useWebSSO.js.map +0 -1
  234. package/lib/commonjs/ui/session/projectSessionState.js +0 -86
  235. package/lib/commonjs/ui/session/projectSessionState.js.map +0 -1
  236. package/lib/commonjs/ui/session/sessionClientHost.js +0 -30
  237. package/lib/commonjs/ui/session/sessionClientHost.js.map +0 -1
  238. package/lib/commonjs/ui/utils/activeAuthuser.js +0 -142
  239. package/lib/commonjs/ui/utils/activeAuthuser.js.map +0 -1
  240. package/lib/commonjs/utils/crossApex.js +0 -74
  241. package/lib/commonjs/utils/crossApex.js.map +0 -1
  242. package/lib/module/ui/context/inSessionTokenRefresh.js +0 -238
  243. package/lib/module/ui/context/inSessionTokenRefresh.js.map +0 -1
  244. package/lib/module/ui/context/silentSessionRestore.js +0 -53
  245. package/lib/module/ui/context/silentSessionRestore.js.map +0 -1
  246. package/lib/module/ui/hooks/useWebSSO.js +0 -24
  247. package/lib/module/ui/hooks/useWebSSO.js.map +0 -1
  248. package/lib/module/ui/session/projectSessionState.js +0 -79
  249. package/lib/module/ui/session/projectSessionState.js.map +0 -1
  250. package/lib/module/ui/session/sessionClientHost.js +0 -26
  251. package/lib/module/ui/session/sessionClientHost.js.map +0 -1
  252. package/lib/module/ui/utils/activeAuthuser.js +0 -134
  253. package/lib/module/ui/utils/activeAuthuser.js.map +0 -1
  254. package/lib/module/utils/crossApex.js +0 -69
  255. package/lib/module/utils/crossApex.js.map +0 -1
  256. package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +0 -106
  257. package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
  258. package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +0 -36
  259. package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +0 -1
  260. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +0 -19
  261. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +0 -1
  262. package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +0 -45
  263. package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +0 -1
  264. package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +0 -14
  265. package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +0 -1
  266. package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +0 -67
  267. package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +0 -1
  268. package/lib/typescript/commonjs/utils/crossApex.d.ts +0 -55
  269. package/lib/typescript/commonjs/utils/crossApex.d.ts.map +0 -1
  270. package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +0 -106
  271. package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
  272. package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +0 -36
  273. package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +0 -1
  274. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +0 -19
  275. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +0 -1
  276. package/lib/typescript/module/ui/session/projectSessionState.d.ts +0 -45
  277. package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +0 -1
  278. package/lib/typescript/module/ui/session/sessionClientHost.d.ts +0 -14
  279. package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +0 -1
  280. package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +0 -67
  281. package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +0 -1
  282. package/lib/typescript/module/utils/crossApex.d.ts +0 -55
  283. package/lib/typescript/module/utils/crossApex.d.ts.map +0 -1
  284. package/src/ui/context/inSessionTokenRefresh.ts +0 -264
  285. package/src/ui/context/silentSessionRestore.ts +0 -53
  286. package/src/ui/hooks/useWebSSO.ts +0 -23
  287. package/src/ui/session/__tests__/projectSessionState.test.ts +0 -114
  288. package/src/ui/session/__tests__/sessionClientHost.test.ts +0 -43
  289. package/src/ui/session/projectSessionState.ts +0 -85
  290. package/src/ui/session/sessionClientHost.ts +0 -27
  291. package/src/ui/utils/activeAuthuser.ts +0 -142
  292. package/src/utils/crossApex.ts +0 -75
@@ -1,5 +1,5 @@
1
1
  import { useCallback } from 'react';
2
- import type { ApiError, SessionClient, User } from '@oxyhq/core';
2
+ import type { ApiError, AuthStateStore, SessionClient, User } from '@oxyhq/core';
3
3
  import type { AuthState } from '../../stores/authStore';
4
4
  import type { ClientSession, SessionLoginResponse } from '@oxyhq/core';
5
5
  import { DeviceManager } from '@oxyhq/core';
@@ -8,30 +8,24 @@ import { handleAuthError, isInvalidSessionError } from '../../utils/errorHandler
8
8
  import type { StorageInterface } from '../../utils/storageHelpers';
9
9
  import type { OxyServices } from '@oxyhq/core';
10
10
  import { SignatureService } from '@oxyhq/core';
11
- import { isWebBrowser } from '../../hooks/useWebSSO';
12
- import {
13
- clearSsoBounceState,
14
- markSignedOut,
15
- clearSignedOut,
16
- } from '../../utils/activeAuthuser';
17
- import { isCrossApexWeb, CrossApexDirectSignInError } from '../../../utils/crossApex';
18
11
 
19
12
  export interface UseAuthOperationsOptions {
20
13
  oxyServices: OxyServices;
21
14
  storage: StorageInterface | null;
15
+ /**
16
+ * The device-first persisted auth-state store. On EXPLICIT full sign-out the
17
+ * session blob is cleared (`store.clear()`) so a reload's cold boot finds no
18
+ * refresh family to restore; on sign-in a returned rotating refresh token is
19
+ * persisted so the next boot warm-restores without a redirect. The long-lived
20
+ * device token SURVIVES sign-out (the device is unchanged), so it is never
21
+ * cleared here.
22
+ */
23
+ store: AuthStateStore;
22
24
  activeSessionId: string | null;
23
25
  setActiveSessionId: (sessionId: string | null) => void;
24
26
  updateSessions: (sessions: ClientSession[], options?: { merge?: boolean }) => void;
25
27
  saveActiveSessionId: (sessionId: string) => Promise<void>;
26
28
  clearSessionState: () => Promise<void>;
27
- /**
28
- * Clear the durable returning-user hint (`storageKeys.priorSession`). Called
29
- * ONLY on EXPLICIT full sign-out — alongside `clearSsoBounceState()` — so the
30
- * next cold boot treats this device as a first-time anonymous visitor (no
31
- * forced `/sso` bounce). NEVER called on the passive token-expiry path, so an
32
- * expired session still recovers via a returning-user bounce. Best-effort.
33
- */
34
- clearPriorSessionHint: () => Promise<void>;
35
29
  /** Used only by `performSignIn`'s same-user duplicate-session dedup (legacy session-validate path; unrelated to the SessionClient device-account set). */
36
30
  switchSession: (sessionId: string) => Promise<User>;
37
31
  /**
@@ -67,25 +61,19 @@ const LOGOUT_ERROR_CODE = 'LOGOUT_ERROR';
67
61
  const LOGOUT_ALL_ERROR_CODE = 'LOGOUT_ALL_ERROR';
68
62
 
69
63
  /**
70
- * Fire-and-forget the durable returning-user hint clear on explicit sign-out.
71
- *
72
- * Mirrors the synchronous, non-blocking nature of the sibling
73
- * `clearSsoBounceState()`: sign-out must NEVER block on (or fail because of) a
74
- * best-effort storage write. The clear is invoked synchronously (so unit tests
75
- * can assert it ran) but its async settle is detached; any rejection is logged,
76
- * never thrown.
77
- *
78
- * Exported so `OxyContext`'s `syncFromClient` zero-account branch (a REMOTE
79
- * full sign-out) can invoke the EXACT same cleanup as the LOCAL `logout` /
80
- * `logoutAll` paths below — a remote sign-out must be indistinguishable from
81
- * a local one to the next cold boot.
64
+ * Clear the persisted refresh-token family on an explicit full sign-out.
65
+ * Best-effort and non-blocking: sign-out must never fail because a storage
66
+ * write threw. Exported so `OxyContext`'s zero-account branch (a REMOTE full
67
+ * sign-out pushed over the socket) runs the EXACT same cleanup as the local
68
+ * `logout` / `logoutAll` paths a remote sign-out is indistinguishable from a
69
+ * local one to the next cold boot.
82
70
  */
83
- export function clearPriorSessionHintSafe(
84
- clearPriorSessionHint: () => Promise<void>,
71
+ export function clearPersistedAuthSafe(
72
+ store: AuthStateStore,
85
73
  logger?: (message: string, error?: unknown) => void,
86
74
  ): void {
87
- clearPriorSessionHint().catch((hintError) => {
88
- logger?.('Failed to clear prior-session hint on sign-out', hintError);
75
+ store.clear().catch((clearError) => {
76
+ logger?.('Failed to clear persisted auth state on sign-out', clearError);
89
77
  });
90
78
  }
91
79
 
@@ -95,12 +83,12 @@ export function clearPriorSessionHintSafe(
95
83
  */
96
84
  export const useAuthOperations = ({
97
85
  oxyServices,
86
+ store,
98
87
  activeSessionId,
99
88
  setActiveSessionId,
100
89
  updateSessions,
101
90
  saveActiveSessionId,
102
91
  clearSessionState,
103
- clearPriorSessionHint,
104
92
  switchSession,
105
93
  sessionClient,
106
94
  syncFromClient,
@@ -148,21 +136,40 @@ export const useAuthOperations = ({
148
136
  deviceFingerprint,
149
137
  );
150
138
 
151
- // Deliberate sign-in re-enables automatic silent restore: clear the durable
152
- // "deliberately signed out" flag so a prior sign-out no longer suppresses
153
- // the per-apex iframe cold-boot step.
154
- clearSignedOut();
139
+ // Persist the per-origin refresh family so a reload warm-restores this
140
+ // session without a redirect. `verifyChallenge` plants the access token
141
+ // internally; when the (trusted-lane) response also carries a rotating
142
+ // refresh token, persist the durable blob. Best-effort — a failed persist
143
+ // never fails the sign-in (native additionally re-mints via the shared
144
+ // keychain on the next cold boot). `SessionLoginResponse` does not type
145
+ // `refreshToken`; read it defensively from the runtime payload.
146
+ const refreshToken = (sessionResponse as { refreshToken?: string }).refreshToken;
147
+ if (refreshToken) {
148
+ try {
149
+ const deviceToken = (await store.loadDeviceToken()) ?? undefined;
150
+ await store.save({
151
+ sessionId: sessionResponse.sessionId,
152
+ refreshToken,
153
+ userId: sessionResponse.user.id,
154
+ ...(deviceToken ? { deviceToken } : {}),
155
+ ...(sessionResponse.accessToken ? { accessToken: sessionResponse.accessToken } : {}),
156
+ ...(sessionResponse.expiresAt ? { expiresAt: sessionResponse.expiresAt } : {}),
157
+ });
158
+ } catch (persistError) {
159
+ logger?.('Failed to persist auth state after sign-in', persistError);
160
+ }
161
+ }
155
162
 
156
163
  // Register this recovered account+session into the server-authoritative
157
- // device-session set: `sessionClient.addCurrentAccount()` ->
158
- // `POST /session/device/add` derives identity from the bearer
159
- // `verifyChallenge` already planted internally, then `syncFromClient()`
160
- // reprojects the resulting server state onto the exposed
161
- // sessions/activeSessionId/user. Best-effort: a failure here must NEVER
162
- // fail the sign-in itself — cold boot re-registers this account into the
163
- // device set on the next load regardless.
164
+ // device-session set AND make it active: `registerAndActivate()` adds via
165
+ // `POST /session/device/add` (identity derived from the bearer
166
+ // `verifyChallenge` already planted) then switches to it, and
167
+ // `syncFromClient()` reprojects the resulting server state onto the
168
+ // exposed sessions/activeSessionId/user. Best-effort: a failure here must
169
+ // NEVER fail the sign-in itself — cold boot re-registers this account into
170
+ // the device set on the next load regardless.
164
171
  try {
165
- await sessionClient.addCurrentAccount();
172
+ await sessionClient.registerAndActivate(sessionResponse.user.id);
166
173
  await syncFromClient();
167
174
  } catch (registrationError) {
168
175
  logger?.('Failed to register sign-in into device session set', registrationError);
@@ -230,6 +237,7 @@ export const useAuthOperations = ({
230
237
  saveActiveSessionId,
231
238
  sessionClient,
232
239
  setActiveSessionId,
240
+ store,
233
241
  switchSession,
234
242
  syncFromClient,
235
243
  updateSessions,
@@ -241,13 +249,6 @@ export const useAuthOperations = ({
241
249
  */
242
250
  const signIn = useCallback(
243
251
  async (publicKey: string, deviceName?: string): Promise<User> => {
244
- // On a cross-apex web RP a direct public-key sign-in mints a bearer against
245
- // the Oxy API but establishes no `fedcm_session`, so the session would be
246
- // lost on reload. Refuse it and direct the app to the durable IdP popup
247
- // ("Continue with Oxy"). Native and same-apex `*.oxy.so` are unaffected.
248
- if (isCrossApexWeb()) {
249
- throw new CrossApexDirectSignInError();
250
- }
251
252
  setAuthState({ isLoading: true, error: null });
252
253
 
253
254
  try {
@@ -300,23 +301,18 @@ export const useAuthOperations = ({
300
301
  await syncFromClient();
301
302
 
302
303
  if (sessionToLogout === activeSessionId && remainingAccounts.length === 0) {
303
- // Genuine FULL sign-out (no sessions remain): clear the per-origin
304
- // SSO bounce state so a fresh deliberate sign-in can re-probe, and
305
- // SET the deliberately-signed-out flag so the silent cold-boot step
306
- // (per-apex iframe) does not re-mint a session from a
307
- // still-live IdP session on the next reload (mirrors `logoutAll`).
308
- markSignedOut();
309
- clearSsoBounceState();
310
- clearPriorSessionHintSafe(clearPriorSessionHint, logger);
304
+ // Genuine FULL sign-out (no sessions remain): clear the persisted
305
+ // refresh family so a reload's cold boot finds nothing to restore,
306
+ // then tear down local state.
307
+ clearPersistedAuthSafe(store, logger);
311
308
  await clearSessionState();
312
309
  }
313
310
  } catch (error) {
314
311
  const isInvalid = isInvalidSessionError(error);
315
312
 
316
313
  if (isInvalid && targetSessionId === activeSessionId) {
317
- // The active session is invalid → full sign-out; clear SSO state too.
318
- clearSsoBounceState();
319
- clearPriorSessionHintSafe(clearPriorSessionHint, logger);
314
+ // The active session is invalid → full sign-out; clear persisted state.
315
+ clearPersistedAuthSafe(store, logger);
320
316
  await clearSessionState();
321
317
  return;
322
318
  }
@@ -334,7 +330,7 @@ export const useAuthOperations = ({
334
330
  [
335
331
  activeSessionId,
336
332
  clearSessionState,
337
- clearPriorSessionHint,
333
+ store,
338
334
  logger,
339
335
  onError,
340
336
  sessionClient,
@@ -360,17 +356,10 @@ export const useAuthOperations = ({
360
356
  // `{ all: true }`) — replaces the bearer `logoutAllSessions` +
361
357
  // web-cookie `logoutAllSessionsViaCookie` pair.
362
358
  await sessionClient.signOut({ all: true });
363
- if (isWebBrowser()) {
364
- // Deliberate full sign-out: suppress automatic silent restore on the next
365
- // cold boot so a still-live IdP session does not re-mint a session.
366
- markSignedOut();
367
- }
368
- // logoutAll is ALWAYS a full sign-out: clear the per-origin SSO bounce
369
- // state (web-guarded internally) so a fresh sign-in can re-probe, and drop
370
- // the durable returning-user hint so the next cold boot is treated as a
371
- // first-time anonymous visitor (no forced `/sso` bounce after sign-out).
372
- clearSsoBounceState();
373
- clearPriorSessionHintSafe(clearPriorSessionHint, logger);
359
+ // logoutAll is ALWAYS a full sign-out: clear the persisted refresh family
360
+ // so the next cold boot finds no session to restore, then tear down local
361
+ // state. The long-lived device token survives (the device is unchanged).
362
+ clearPersistedAuthSafe(store, logger);
374
363
  await clearSessionState();
375
364
  } catch (error) {
376
365
  handleAuthError(error, {
@@ -382,7 +371,7 @@ export const useAuthOperations = ({
382
371
  });
383
372
  throw error instanceof Error ? error : new Error('Logout all failed');
384
373
  }
385
- }, [activeSessionId, clearSessionState, clearPriorSessionHint, logger, onError, sessionClient, setAuthState]);
374
+ }, [activeSessionId, clearSessionState, store, logger, onError, sessionClient, setAuthState]);
386
375
 
387
376
  return {
388
377
  signIn,
@@ -32,7 +32,7 @@ export const mutationKeys = {
32
32
  removeDevice: ['mutation', 'session', 'removeDevice'] as const,
33
33
  },
34
34
 
35
- // Connected apps (FedCM grants)
35
+ // Connected apps (OAuth grants)
36
36
  connectedApps: {
37
37
  revoke: ['mutation', 'connectedApps', 'revoke'] as const,
38
38
  },
@@ -632,20 +632,20 @@ export const useUpdateUserPreferences = () => {
632
632
  };
633
633
 
634
634
  /**
635
- * Revoke the authenticated user's authorization for a specific RP origin.
636
- * Removes the FedCM grant so the origin no longer appears in the user's
637
- * "Connected apps" list — next sign-in from that origin will require explicit
635
+ * Revoke the current user's grant for a connected application (by its
636
+ * `applicationId`). Removes the grant so the app no longer appears in the user's
637
+ * "Connected apps" list — next sign-in for that app will require explicit
638
638
  * re-consent.
639
639
  */
640
- export const useRevokeAuthorizedApp = () => {
640
+ export const useRevokeConnectedApp = () => {
641
641
  const { oxyServices, activeSessionId } = useOxy();
642
642
  const queryClient = useQueryClient();
643
643
 
644
644
  return useMutation({
645
645
  mutationKey: [...mutationKeys.connectedApps.revoke],
646
- mutationFn: async (origin: string) => {
646
+ mutationFn: async (applicationId: string) => {
647
647
  return authenticatedApiCall<void>(oxyServices, activeSessionId, () =>
648
- oxyServices.revokeAuthorizedApp(origin)
648
+ oxyServices.revokeAppGrant(applicationId)
649
649
  );
650
650
  },
651
651
  onSuccess: () => {
@@ -653,7 +653,7 @@ export const useRevokeAuthorizedApp = () => {
653
653
  },
654
654
  onError: (error) => {
655
655
  toast.error(
656
- error instanceof Error ? error.message : 'Failed to revoke authorized app'
656
+ error instanceof Error ? error.message : 'Failed to revoke connected app'
657
657
  );
658
658
  },
659
659
  });
@@ -77,7 +77,7 @@ export const queryKeys = {
77
77
  usage: (accountScope?: string) => [...queryKeys.storage.all, 'usage', accountScope] as const,
78
78
  },
79
79
 
80
- // Connected apps (FedCM grants the user has authorized)
80
+ // Connected apps (OAuth grants the user has authorized)
81
81
  connectedApps: {
82
82
  all: ['connectedApps'] as const,
83
83
  list: () => [...queryKeys.connectedApps.all, 'list'] as const,
@@ -1,7 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useQuery, useQueries, useQueryClient } from '@tanstack/react-query';
3
3
  import { authenticatedApiCall } from '@oxyhq/core';
4
- import type { AuthorizedApp, User } from '@oxyhq/core';
4
+ import type { ConnectedApp, User } from '@oxyhq/core';
5
5
  import { queryKeys } from './queryKeys';
6
6
  import { mutationKeys } from '../mutations/mutationKeys';
7
7
  import { useOxy } from '../../context/OxyContext';
@@ -205,21 +205,20 @@ export const useUsersBySessions = (sessionIds: string[], options?: { enabled?: b
205
205
  };
206
206
 
207
207
  /**
208
- * List the authenticated user's authorized RP apps (FedCM grants).
209
- *
210
- * Returns the intersection of the user's grants with the currently-approved
211
- * RP catalog. Drives the "Connected apps" management screen.
208
+ * List the third-party OAuth applications the current user has connected via
209
+ * the consent flow (`GET /auth/grants`). Drives the "Connected apps" management
210
+ * screen.
212
211
  */
213
- export const useAuthorizedApps = (options?: { enabled?: boolean }) => {
212
+ export const useConnectedApps = (options?: { enabled?: boolean }) => {
214
213
  const { oxyServices, activeSessionId, isAuthenticated } = useOxy();
215
214
 
216
- return useQuery<AuthorizedApp[]>({
215
+ return useQuery<ConnectedApp[]>({
217
216
  queryKey: queryKeys.connectedApps.list(),
218
217
  queryFn: async () => {
219
- return authenticatedApiCall<AuthorizedApp[]>(
218
+ return authenticatedApiCall<ConnectedApp[]>(
220
219
  oxyServices,
221
220
  activeSessionId,
222
- () => oxyServices.listAuthorizedApps()
221
+ () => oxyServices.listConnectedApps()
223
222
  );
224
223
  },
225
224
  enabled: (options?.enabled !== false) && isAuthenticated,
@@ -26,7 +26,8 @@
26
26
  import { useCallback, useState } from 'react';
27
27
  import { useOxy } from '../context/OxyContext';
28
28
  import type { User } from '@oxyhq/core';
29
- import { isWebBrowser } from './useWebSSO';
29
+ import { isWebBrowser } from '../utils/isWebBrowser';
30
+ import { showSignInModal } from '../components/SignInModal';
30
31
 
31
32
  export interface AuthState {
32
33
  /** Current authenticated user, null if not authenticated */
@@ -140,35 +141,24 @@ export function useAuth(): UseAuthReturn {
140
141
  } = useOxy();
141
142
 
142
143
  const signIn = useCallback(async (publicKey?: string): Promise<User> => {
143
- // Check if we're on the identity provider itself
144
- // Only the IdP has local login forms - other apps are client apps
145
- const authWebUrl = oxyServices.config?.authWebUrl;
146
- let idpHostname = 'auth.oxy.so';
147
- if (authWebUrl) {
148
- try { idpHostname = new URL(authWebUrl).hostname; } catch { /* malformed URL, use default */ }
149
- }
150
- const isIdentityProvider = isWebBrowser() &&
151
- window.location.hostname === idpHostname;
152
-
153
- // Web (not on IdP): use the tokenless redirect SSO flow. The silent
154
- // cross-domain restore (per-apex `/auth/silent` iframe + `/sso` bounce)
155
- // already ran on page load; an explicit click needs interactive auth.
156
- if (isWebBrowser() && !publicKey && !isIdentityProvider) {
157
- oxyServices.signInWithRedirect?.({
158
- redirectUri: window.location.href,
159
- });
144
+ // Web (no key): open the in-app "Sign in with Oxy" modal. There is NO
145
+ // automatic navigation to any login page the device-first cold boot
146
+ // already restored a session if one existed; an explicit click presents the
147
+ // SDK sign-in surface (password / QR device flow / add account).
148
+ if (isWebBrowser() && !publicKey) {
149
+ showSignInModal();
150
+ // Resolves when the modal commits a session; the caller typically reacts
151
+ // to `isAuthenticated` rather than this promise.
160
152
  return new Promise<User>(() => undefined);
161
153
  }
162
154
 
163
- // Native: Use cryptographic identity
164
- // If public key provided, use it directly
155
+ // Native: use the cryptographic identity directly when a public key is given.
165
156
  if (publicKey) {
166
157
  return oxySignIn(publicKey);
167
158
  }
168
159
 
169
- // Try to get existing identity
160
+ // Native with an existing keychain identity: sign in with it.
170
161
  const hasExisting = await hasIdentity();
171
-
172
162
  if (hasExisting) {
173
163
  const existingKey = await getPublicKey();
174
164
  if (existingKey) {
@@ -176,27 +166,16 @@ export function useAuth(): UseAuthReturn {
176
166
  }
177
167
  }
178
168
 
179
- // No identity - show auth UI
169
+ // Native with no identity: open the auth sheet (password / QR device flow).
180
170
  if (showBottomSheet) {
181
171
  showBottomSheet('OxyAuth');
182
- // Return a promise that resolves when auth completes
183
172
  return new Promise((_, reject) => {
184
173
  reject(new Error('Please complete sign-in in the auth sheet'));
185
174
  });
186
175
  }
187
176
 
188
- // Web fallback: navigate to login page on auth domain
189
- if (isWebBrowser()) {
190
- const authBase = authWebUrl || 'https://accounts.oxy.so';
191
- const loginUrl = window.location.hostname.includes('oxy.so')
192
- ? '/login'
193
- : `${authBase}/login`;
194
- window.location.href = loginUrl;
195
- return new Promise(() => {}); // Never resolves, page will redirect
196
- }
197
-
198
177
  throw new Error('No authentication method available');
199
- }, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet, oxyServices]);
178
+ }, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet]);
200
179
 
201
180
  const signOut = useCallback(async (): Promise<void> => {
202
181
  await logout();
@@ -0,0 +1,207 @@
1
+ /**
2
+ * `usePasswordSignIn` — the first-party password sign-in state machine.
3
+ *
4
+ * Drives the in-app "Sign in with Oxy" password flow shared by the web
5
+ * `SignInModal` and the native `OxyAuthScreen`: identifier → password → optional
6
+ * two-factor. It is a thin UI state machine over the context methods
7
+ * `signInWithPassword` + `completeTwoFactorSignIn` (which own the network call,
8
+ * the persisted-refresh commit, and the device-set registration) — so both
9
+ * surfaces present an identical flow without re-implementing any transport.
10
+ *
11
+ * On a committed session `onSignedIn` fires (the modal/screen closes); the
12
+ * device-first cold boot / SessionClient projection then drive the app into the
13
+ * authenticated state.
14
+ */
15
+ import { useCallback, useMemo, useRef, useState } from 'react';
16
+ import { useOxy } from '../context/OxyContext';
17
+ import { handleAuthError } from '../utils/errorHandlers';
18
+
19
+ export type PasswordSignInStep = 'identifier' | 'password' | 'twoFactor';
20
+
21
+ export interface UsePasswordSignInOptions {
22
+ /** Fired once a session has been committed (the surface should close). */
23
+ onSignedIn?: () => void;
24
+ }
25
+
26
+ export interface UsePasswordSignInResult {
27
+ step: PasswordSignInStep;
28
+ identifier: string;
29
+ setIdentifier: (value: string) => void;
30
+ password: string;
31
+ setPassword: (value: string) => void;
32
+ /** The current 2FA input — a TOTP code or a backup code, per `useBackupCode`. */
33
+ code: string;
34
+ setCode: (value: string) => void;
35
+ useBackupCode: boolean;
36
+ setUseBackupCode: (value: boolean) => void;
37
+ error: string | null;
38
+ isSubmitting: boolean;
39
+ /** Advance identifier → password (validates a non-empty identifier). */
40
+ submitIdentifier: () => void;
41
+ /** Submit the password: commits a one-step session, or advances to 2FA. */
42
+ submitPassword: () => Promise<void>;
43
+ /** Submit the 2FA code / backup code and commit the session. */
44
+ submitTwoFactor: () => Promise<void>;
45
+ /** Step back one screen (2FA → password → identifier), clearing the error. */
46
+ back: () => void;
47
+ /** Reset every field back to the identifier step. */
48
+ reset: () => void;
49
+ }
50
+
51
+ export function usePasswordSignIn(options: UsePasswordSignInOptions = {}): UsePasswordSignInResult {
52
+ const { signInWithPassword, completeTwoFactorSignIn } = useOxy();
53
+ const { onSignedIn } = options;
54
+
55
+ const [step, setStep] = useState<PasswordSignInStep>('identifier');
56
+ const [identifier, setIdentifier] = useState('');
57
+ const [password, setPassword] = useState('');
58
+ const [code, setCode] = useState('');
59
+ const [useBackupCode, setUseBackupCode] = useState(false);
60
+ const [error, setError] = useState<string | null>(null);
61
+ const [isSubmitting, setIsSubmitting] = useState(false);
62
+ const [loginToken, setLoginToken] = useState<string | null>(null);
63
+
64
+ // Synchronous in-flight guard. `isSubmitting` state drives the UI, but a rapid
65
+ // double-tap fires both handlers in the SAME tick — before React re-renders —
66
+ // so a state read would still see `false` on the second call and double-fire
67
+ // the network request (rate-limit + race). This ref is set/checked
68
+ // synchronously, so the second call is a true no-op.
69
+ const submittingRef = useRef(false);
70
+
71
+ const surfaceError = useCallback((err: unknown, defaultMessage: string): void => {
72
+ setError(handleAuthError(err, { defaultMessage, code: 'PASSWORD_SIGN_IN_ERROR' }));
73
+ }, []);
74
+
75
+ const submitIdentifier = useCallback(() => {
76
+ if (!identifier.trim()) {
77
+ setError('Enter your username or email');
78
+ return;
79
+ }
80
+ setError(null);
81
+ setStep('password');
82
+ }, [identifier]);
83
+
84
+ const submitPassword = useCallback(async () => {
85
+ // A second call while one is already in flight is a no-op (double-tap guard).
86
+ if (submittingRef.current) {
87
+ return;
88
+ }
89
+ if (!password) {
90
+ setError('Enter your password');
91
+ return;
92
+ }
93
+ setError(null);
94
+ submittingRef.current = true;
95
+ setIsSubmitting(true);
96
+ try {
97
+ const result = await signInWithPassword(identifier.trim(), password);
98
+ if (result.status === '2fa_required') {
99
+ setLoginToken(result.loginToken);
100
+ setCode('');
101
+ setUseBackupCode(false);
102
+ setStep('twoFactor');
103
+ return;
104
+ }
105
+ onSignedIn?.();
106
+ } catch (err) {
107
+ surfaceError(err, 'Sign in failed');
108
+ } finally {
109
+ submittingRef.current = false;
110
+ setIsSubmitting(false);
111
+ }
112
+ }, [identifier, password, signInWithPassword, onSignedIn, surfaceError]);
113
+
114
+ const submitTwoFactor = useCallback(async () => {
115
+ // A second call while one is already in flight is a no-op (double-tap guard).
116
+ if (submittingRef.current) {
117
+ return;
118
+ }
119
+ if (!loginToken) {
120
+ setError('Your sign-in session expired. Start again.');
121
+ setStep('password');
122
+ return;
123
+ }
124
+ if (!code.trim()) {
125
+ setError(useBackupCode ? 'Enter a backup code' : 'Enter your 6-digit code');
126
+ return;
127
+ }
128
+ setError(null);
129
+ submittingRef.current = true;
130
+ setIsSubmitting(true);
131
+ try {
132
+ await completeTwoFactorSignIn({
133
+ loginToken,
134
+ ...(useBackupCode ? { backupCode: code.trim() } : { token: code.trim() }),
135
+ });
136
+ onSignedIn?.();
137
+ } catch (err) {
138
+ surfaceError(err, 'Verification failed');
139
+ } finally {
140
+ submittingRef.current = false;
141
+ setIsSubmitting(false);
142
+ }
143
+ }, [loginToken, code, useBackupCode, completeTwoFactorSignIn, onSignedIn, surfaceError]);
144
+
145
+ const back = useCallback(() => {
146
+ setError(null);
147
+ setStep((current) => {
148
+ if (current === 'twoFactor') {
149
+ setLoginToken(null);
150
+ setCode('');
151
+ return 'password';
152
+ }
153
+ if (current === 'password') {
154
+ setPassword('');
155
+ return 'identifier';
156
+ }
157
+ return current;
158
+ });
159
+ }, []);
160
+
161
+ const reset = useCallback(() => {
162
+ setStep('identifier');
163
+ setIdentifier('');
164
+ setPassword('');
165
+ setCode('');
166
+ setUseBackupCode(false);
167
+ setLoginToken(null);
168
+ setError(null);
169
+ submittingRef.current = false;
170
+ setIsSubmitting(false);
171
+ }, []);
172
+
173
+ return useMemo(
174
+ () => ({
175
+ step,
176
+ identifier,
177
+ setIdentifier,
178
+ password,
179
+ setPassword,
180
+ code,
181
+ setCode,
182
+ useBackupCode,
183
+ setUseBackupCode,
184
+ error,
185
+ isSubmitting,
186
+ submitIdentifier,
187
+ submitPassword,
188
+ submitTwoFactor,
189
+ back,
190
+ reset,
191
+ }),
192
+ [
193
+ step,
194
+ identifier,
195
+ password,
196
+ code,
197
+ useBackupCode,
198
+ error,
199
+ isSubmitting,
200
+ submitIdentifier,
201
+ submitPassword,
202
+ submitTwoFactor,
203
+ back,
204
+ reset,
205
+ ],
206
+ );
207
+ }
@@ -24,7 +24,6 @@ export interface ProfileUpdateData {
24
24
  username?: string;
25
25
  email?: string;
26
26
  bio?: string;
27
- location?: string;
28
27
  locations?: ProfileLocation[];
29
28
  links?: string[];
30
29
  linksMetadata?: ProfileLinkMetadata[];
@@ -67,13 +66,8 @@ export const useProfileEditing = () => {
67
66
  if (updates.bio !== undefined) {
68
67
  updateData.bio = updates.bio;
69
68
  }
70
- if (updates.location !== undefined || updates.locations !== undefined) {
71
- updateData.location = updates.locations && updates.locations.length > 0
72
- ? updates.locations[0].name
73
- : updates.location || '';
74
- if (updates.locations) {
75
- updateData.locations = updates.locations;
76
- }
69
+ if (updates.locations !== undefined) {
70
+ updateData.locations = updates.locations;
77
71
  }
78
72
  if (updates.links !== undefined) {
79
73
  updateData.links = updates.links;
@@ -8,7 +8,7 @@ import { handleAuthError, isInvalidSessionError } from '../utils/errorHandlers';
8
8
  import type { OxyServices } from '@oxyhq/core';
9
9
  import type { QueryClient } from '@tanstack/react-query';
10
10
  import { clearQueryCache } from './queryClient';
11
- import { isWebBrowser } from './useWebSSO';
11
+ import { isWebBrowser } from '../utils/isWebBrowser';
12
12
 
13
13
  export interface UseSessionManagementOptions {
14
14
  oxyServices: OxyServices;