@oxyhq/services 14.1.0 → 15.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 (262) hide show
  1. package/lib/commonjs/index.js +0 -17
  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/useAuth.js +14 -33
  12. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  13. package/lib/commonjs/ui/hooks/usePasswordSignIn.js +170 -0
  14. package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +1 -0
  15. package/lib/commonjs/ui/hooks/useProfileEditing.js +2 -5
  16. package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/useSessionManagement.js +2 -2
  18. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  19. package/lib/commonjs/ui/navigation/routes.js +1 -0
  20. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  21. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +93 -68
  22. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
  23. package/lib/commonjs/ui/screens/EditProfileScreen.js +187 -0
  24. package/lib/commonjs/ui/screens/EditProfileScreen.js.map +1 -0
  25. package/lib/commonjs/ui/screens/ManageAccountScreen.js +3 -7
  26. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  27. package/lib/commonjs/ui/screens/OxyAuthScreen.js +231 -95
  28. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/ProfileScreen.js +15 -2
  30. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/linkFormat.js +38 -0
  32. package/lib/commonjs/ui/screens/linkFormat.js.map +1 -0
  33. package/lib/commonjs/ui/session/authStore.js +146 -0
  34. package/lib/commonjs/ui/session/authStore.js.map +1 -0
  35. package/lib/commonjs/ui/session/createSessionClient.js +23 -17
  36. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  37. package/lib/commonjs/ui/session/index.js +13 -7
  38. package/lib/commonjs/ui/session/index.js.map +1 -1
  39. package/lib/commonjs/ui/session/tokenTransport.js +19 -31
  40. package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
  41. package/lib/commonjs/ui/utils/isWebBrowser.js +13 -0
  42. package/lib/commonjs/ui/utils/isWebBrowser.js.map +1 -0
  43. package/lib/commonjs/utils/deviceFlowSignIn.js +13 -16
  44. package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
  45. package/lib/module/index.js +0 -5
  46. package/lib/module/index.js.map +1 -1
  47. package/lib/module/ui/components/SignInAccountChooser.js +183 -0
  48. package/lib/module/ui/components/SignInAccountChooser.js.map +1 -0
  49. package/lib/module/ui/components/SignInModal.js +352 -134
  50. package/lib/module/ui/components/SignInModal.js.map +1 -1
  51. package/lib/module/ui/context/OxyContext.js +342 -1551
  52. package/lib/module/ui/context/OxyContext.js.map +1 -1
  53. package/lib/module/ui/context/hooks/useAuthOperations.js +60 -63
  54. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  55. package/lib/module/ui/hooks/useAuth.js +14 -33
  56. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  57. package/lib/module/ui/hooks/usePasswordSignIn.js +165 -0
  58. package/lib/module/ui/hooks/usePasswordSignIn.js.map +1 -0
  59. package/lib/module/ui/hooks/useProfileEditing.js +2 -5
  60. package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
  61. package/lib/module/ui/hooks/useSessionManagement.js +1 -1
  62. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  63. package/lib/module/ui/navigation/routes.js +1 -0
  64. package/lib/module/ui/navigation/routes.js.map +1 -1
  65. package/lib/module/ui/screens/EditProfileFieldScreen.js +91 -66
  66. package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
  67. package/lib/module/ui/screens/EditProfileScreen.js +182 -0
  68. package/lib/module/ui/screens/EditProfileScreen.js.map +1 -0
  69. package/lib/module/ui/screens/ManageAccountScreen.js +3 -7
  70. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  71. package/lib/module/ui/screens/OxyAuthScreen.js +233 -97
  72. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  73. package/lib/module/ui/screens/ProfileScreen.js +15 -2
  74. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  75. package/lib/module/ui/screens/linkFormat.js +31 -0
  76. package/lib/module/ui/screens/linkFormat.js.map +1 -0
  77. package/lib/module/ui/session/authStore.js +143 -0
  78. package/lib/module/ui/session/authStore.js.map +1 -0
  79. package/lib/module/ui/session/createSessionClient.js +22 -16
  80. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  81. package/lib/module/ui/session/index.js +9 -9
  82. package/lib/module/ui/session/index.js.map +1 -1
  83. package/lib/module/ui/session/tokenTransport.js +20 -33
  84. package/lib/module/ui/session/tokenTransport.js.map +1 -1
  85. package/lib/module/ui/utils/isWebBrowser.js +11 -0
  86. package/lib/module/ui/utils/isWebBrowser.js.map +1 -0
  87. package/lib/module/utils/deviceFlowSignIn.js +14 -17
  88. package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
  89. package/lib/typescript/commonjs/index.d.ts +0 -1
  90. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +29 -0
  92. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +1 -0
  93. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +15 -10
  94. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +36 -42
  96. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +18 -23
  98. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +0 -2
  100. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  102. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +31 -0
  103. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
  104. package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts +0 -1
  105. package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  107. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts +14 -0
  110. package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
  112. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +13 -12
  114. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts +23 -0
  117. package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/ui/session/authStore.d.ts +33 -0
  119. package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -0
  120. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +19 -15
  121. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  122. package/lib/typescript/commonjs/ui/session/index.d.ts +9 -9
  123. package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -1
  124. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +13 -19
  125. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
  126. package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts +9 -0
  127. package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +15 -13
  129. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
  130. package/lib/typescript/module/index.d.ts +0 -1
  131. package/lib/typescript/module/index.d.ts.map +1 -1
  132. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +29 -0
  133. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +1 -0
  134. package/lib/typescript/module/ui/components/SignInModal.d.ts +15 -10
  135. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  136. package/lib/typescript/module/ui/context/OxyContext.d.ts +36 -42
  137. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  138. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +18 -23
  139. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  140. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +0 -2
  141. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  142. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  143. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +31 -0
  144. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
  145. package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts +0 -1
  146. package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
  147. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  148. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  149. package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
  150. package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts +14 -0
  151. package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts.map +1 -0
  152. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
  153. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  154. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +13 -12
  155. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  156. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  157. package/lib/typescript/module/ui/screens/linkFormat.d.ts +23 -0
  158. package/lib/typescript/module/ui/screens/linkFormat.d.ts.map +1 -0
  159. package/lib/typescript/module/ui/session/authStore.d.ts +33 -0
  160. package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -0
  161. package/lib/typescript/module/ui/session/createSessionClient.d.ts +19 -15
  162. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  163. package/lib/typescript/module/ui/session/index.d.ts +9 -9
  164. package/lib/typescript/module/ui/session/index.d.ts.map +1 -1
  165. package/lib/typescript/module/ui/session/tokenTransport.d.ts +13 -19
  166. package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
  167. package/lib/typescript/module/ui/utils/isWebBrowser.d.ts +9 -0
  168. package/lib/typescript/module/ui/utils/isWebBrowser.d.ts.map +1 -0
  169. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +15 -13
  170. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
  171. package/package.json +4 -4
  172. package/src/index.ts +0 -4
  173. package/src/ui/components/SignInAccountChooser.tsx +162 -0
  174. package/src/ui/components/SignInModal.tsx +309 -139
  175. package/src/ui/context/OxyContext.tsx +532 -1785
  176. package/src/ui/context/hooks/useAuthOperations.ts +65 -76
  177. package/src/ui/hooks/useAuth.ts +14 -35
  178. package/src/ui/hooks/usePasswordSignIn.ts +207 -0
  179. package/src/ui/hooks/useProfileEditing.ts +2 -8
  180. package/src/ui/hooks/useSessionManagement.ts +1 -1
  181. package/src/ui/navigation/routes.ts +2 -0
  182. package/src/ui/screens/EditProfileFieldScreen.tsx +102 -66
  183. package/src/ui/screens/EditProfileScreen.tsx +155 -0
  184. package/src/ui/screens/ManageAccountScreen.tsx +3 -7
  185. package/src/ui/screens/OxyAuthScreen.tsx +259 -112
  186. package/src/ui/screens/ProfileScreen.tsx +15 -2
  187. package/src/ui/screens/__tests__/linkFormat.test.ts +73 -0
  188. package/src/ui/screens/linkFormat.ts +37 -0
  189. package/src/ui/session/__tests__/createSessionClient.test.ts +22 -7
  190. package/src/ui/session/__tests__/tokenTransport.test.ts +37 -49
  191. package/src/ui/session/authStore.ts +164 -0
  192. package/src/ui/session/createSessionClient.ts +30 -17
  193. package/src/ui/session/index.ts +10 -9
  194. package/src/ui/session/tokenTransport.ts +26 -36
  195. package/src/ui/utils/isWebBrowser.ts +8 -0
  196. package/src/utils/__tests__/deviceFlowSignIn.test.ts +50 -131
  197. package/src/utils/deviceFlowSignIn.ts +19 -26
  198. package/lib/commonjs/ui/context/inSessionTokenRefresh.js +0 -243
  199. package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +0 -1
  200. package/lib/commonjs/ui/context/silentSessionRestore.js +0 -56
  201. package/lib/commonjs/ui/context/silentSessionRestore.js.map +0 -1
  202. package/lib/commonjs/ui/hooks/useWebSSO.js +0 -28
  203. package/lib/commonjs/ui/hooks/useWebSSO.js.map +0 -1
  204. package/lib/commonjs/ui/session/projectSessionState.js +0 -86
  205. package/lib/commonjs/ui/session/projectSessionState.js.map +0 -1
  206. package/lib/commonjs/ui/session/sessionClientHost.js +0 -30
  207. package/lib/commonjs/ui/session/sessionClientHost.js.map +0 -1
  208. package/lib/commonjs/ui/utils/activeAuthuser.js +0 -142
  209. package/lib/commonjs/ui/utils/activeAuthuser.js.map +0 -1
  210. package/lib/commonjs/utils/crossApex.js +0 -74
  211. package/lib/commonjs/utils/crossApex.js.map +0 -1
  212. package/lib/module/ui/context/inSessionTokenRefresh.js +0 -238
  213. package/lib/module/ui/context/inSessionTokenRefresh.js.map +0 -1
  214. package/lib/module/ui/context/silentSessionRestore.js +0 -53
  215. package/lib/module/ui/context/silentSessionRestore.js.map +0 -1
  216. package/lib/module/ui/hooks/useWebSSO.js +0 -24
  217. package/lib/module/ui/hooks/useWebSSO.js.map +0 -1
  218. package/lib/module/ui/session/projectSessionState.js +0 -79
  219. package/lib/module/ui/session/projectSessionState.js.map +0 -1
  220. package/lib/module/ui/session/sessionClientHost.js +0 -26
  221. package/lib/module/ui/session/sessionClientHost.js.map +0 -1
  222. package/lib/module/ui/utils/activeAuthuser.js +0 -134
  223. package/lib/module/ui/utils/activeAuthuser.js.map +0 -1
  224. package/lib/module/utils/crossApex.js +0 -69
  225. package/lib/module/utils/crossApex.js.map +0 -1
  226. package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +0 -106
  227. package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
  228. package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +0 -36
  229. package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +0 -1
  230. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +0 -19
  231. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +0 -1
  232. package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +0 -45
  233. package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +0 -1
  234. package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +0 -14
  235. package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +0 -1
  236. package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +0 -67
  237. package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +0 -1
  238. package/lib/typescript/commonjs/utils/crossApex.d.ts +0 -55
  239. package/lib/typescript/commonjs/utils/crossApex.d.ts.map +0 -1
  240. package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +0 -106
  241. package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
  242. package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +0 -36
  243. package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +0 -1
  244. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +0 -19
  245. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +0 -1
  246. package/lib/typescript/module/ui/session/projectSessionState.d.ts +0 -45
  247. package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +0 -1
  248. package/lib/typescript/module/ui/session/sessionClientHost.d.ts +0 -14
  249. package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +0 -1
  250. package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +0 -67
  251. package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +0 -1
  252. package/lib/typescript/module/utils/crossApex.d.ts +0 -55
  253. package/lib/typescript/module/utils/crossApex.d.ts.map +0 -1
  254. package/src/ui/context/inSessionTokenRefresh.ts +0 -264
  255. package/src/ui/context/silentSessionRestore.ts +0 -53
  256. package/src/ui/hooks/useWebSSO.ts +0 -23
  257. package/src/ui/session/__tests__/projectSessionState.test.ts +0 -114
  258. package/src/ui/session/__tests__/sessionClientHost.test.ts +0 -43
  259. package/src/ui/session/projectSessionState.ts +0 -85
  260. package/src/ui/session/sessionClientHost.ts +0 -27
  261. package/src/ui/utils/activeAuthuser.ts +0 -142
  262. package/src/utils/crossApex.ts +0 -75
@@ -1,264 +0,0 @@
1
- /**
2
- * In-session access-token refresh for the React Native / Expo SDK.
3
- *
4
- * THE GAP THIS CLOSES: the services `OxyContext` owns the session but never
5
- * installed an `authRefreshHandler` on the owner `HttpService`, so
6
- * `HttpService.refreshAccessToken` short-circuited to `null` — there was NO
7
- * in-session token refresh on the RN path at all. A 15-minute access token
8
- * expired with the tab/app open and nothing re-minted one; cross-apex RP feeds
9
- * (mention.earth, …) then 401'd in a loop while `isAuthenticated` stayed `true`
10
- * (a zombie logged-in state), because the `Domain=oxy.so` refresh cookie can't
11
- * reach `api.<apex>`. (`AuthManager`, which installs a refresh handler on the
12
- * web path, is only ever constructed by `WebOxyProvider` in `@oxyhq/auth`.)
13
- *
14
- * THE FIX, two cooperating pieces both wired from `OxyContext`:
15
- *
16
- * 1. {@link createInSessionRefreshHandler} — an `AuthRefreshHandler` installed
17
- * on the owner client. It re-mints a fresh access token WITHOUT a page
18
- * reload by composing the SAME silent-restore primitive cold boot uses
19
- * ({@link mintSessionViaPerApexIframe}) — not a copy. The linked client
20
- * (`createLinkedClient`) inherits the fix for free: its refresh delegates
21
- * back to the owner's `refreshAccessToken`.
22
- *
23
- * 2. {@link startTokenRefreshScheduler} — a proactive scheduler that refreshes
24
- * ~{@link TOKEN_REFRESH_LEAD_MS} before expiry (and on web tab-focus / native
25
- * app-foreground), so the common case never even reaches the reactive
26
- * 401-then-recover flash.
27
- *
28
- * Concurrency/cooldown/dedup are owned by `HttpService.refreshAccessToken`
29
- * (single in-flight `tokenRefreshPromise` + cooldown) — this module does not
30
- * reimplement them, so the timer / foreground / per-request triggers collapse to
31
- * one network attempt (no refresh storm).
32
- *
33
- * NOTE (session-sync cutover, Task 5): the web arm chain used to fall through
34
- * to a same-apex `oxy_rt` refresh-cookie arm (`refreshAllSessions` +
35
- * `selectActiveRefreshAccount`). That arm is DELETED — the device account set
36
- * is now server-authoritative via `SessionClient` (`@oxyhq/core`), which
37
- * `OxyContext` bootstraps/reprojects independently of in-session token
38
- * refresh. `selectActiveRefreshAccount` had no other caller and was deleted
39
- * with it.
40
- */
41
- import type {
42
- OxyServices,
43
- AuthRefreshHandler,
44
- AuthRefreshReason,
45
- } from '@oxyhq/core';
46
- import { logger as loggerUtil } from '@oxyhq/core';
47
- import { AppState, type AppStateStatus } from 'react-native';
48
- import { isWebBrowser } from '../hooks/useWebSSO';
49
- import { mintSessionViaPerApexIframe } from './silentSessionRestore';
50
-
51
- /**
52
- * Per-arm fail-fast budget (ms) for the web first-party `/auth/silent` iframe
53
- * arm. Slightly more generous than the cold-boot iframe budget (2.5s) because an
54
- * in-session refresh is NOT in the first-paint critical path — a couple hundred
55
- * ms of extra headroom for the same-origin handshake is worth a higher success
56
- * rate. `silentSignIn` still fail-fasts on `iframe.onerror`, so a hard failure
57
- * returns well before this.
58
- */
59
- const SILENT_IFRAME_REFRESH_TIMEOUT = 4000;
60
-
61
- /**
62
- * Lead time (ms) before access-token expiry at which the proactive scheduler
63
- * refreshes. Mirrors `HttpService`'s per-request `TOKEN_REFRESH_LEAD_SECONDS`
64
- * (60s) so the scheduled refresh and the request-time preflight refresh use the
65
- * same window — the scheduler simply fires it during idle/background instead of
66
- * waiting for the next request.
67
- */
68
- export const TOKEN_REFRESH_LEAD_MS = 60_000;
69
-
70
- /**
71
- * A single refresh arm: attempts one silent-restore mechanism and resolves to
72
- * the freshly planted access token, or `null` to fall through to the next arm.
73
- */
74
- type RefreshArm = () => Promise<string | null>;
75
-
76
- /**
77
- * Build the in-session `AuthRefreshHandler` for the owner client.
78
- *
79
- * Arms run in an explicit order; the first to plant a token wins. Each arm
80
- * resolves to the planted token (read back via `getAccessToken()` — the
81
- * underlying SDK call plants it internally) or `null`. A throw in one arm is
82
- * logged and treated as `null` so the chain continues.
83
- *
84
- * NATIVE (Expo): shared cross-app identity key re-mint
85
- * (`signInWithSharedIdentity` → challenge→sign→verify plants the tokens).
86
- * The ONLY silent native arm (mirrors cold boot's `shared-key-signin`); the
87
- * `/auth/silent` web iframe is NEVER attempted on native. Resolves to `null`
88
- * when the device holds no shared identity (e.g. a password-only native
89
- * sign-in) so a genuinely dead session reconciles to logged-out rather than
90
- * staying a zombie.
91
- *
92
- * WEB: Per-apex `/auth/silent` iframe — {@link mintSessionViaPerApexIframe}
93
- * (shared verbatim with cold boot). The durable cross-apex path; also
94
- * covers `*.oxy.so` (the per-apex host IS the central host there). There
95
- * is no FedCM arm — FedCM was removed from the client sign-in/refresh path
96
- * entirely (Chrome-only; see `CrossDomainAuth`'s doc comment in
97
- * `@oxyhq/core` for the production sign-in loop that motivated the
98
- * removal).
99
- *
100
- * NO RECURSION: no arm issues a request through the authed client's
101
- * `refreshAccessToken` path. The iframe transport is postMessage-based; the
102
- * follow-up `/session/user` fetch inside `silentSignIn`
103
- * runs against the just-planted FULL-TTL token (≫ the preflight lead), so it
104
- * never re-enters the refresh path.
105
- */
106
- export function createInSessionRefreshHandler(oxyServices: OxyServices): AuthRefreshHandler {
107
- const runArm = async (label: string, reason: AuthRefreshReason, arm: RefreshArm): Promise<string | null> => {
108
- try {
109
- return await arm();
110
- } catch (error) {
111
- if (__DEV__) {
112
- loggerUtil.debug(
113
- `In-session refresh arm "${label}" failed (falling through)`,
114
- { component: 'inSessionTokenRefresh', method: 'authRefreshHandler', reason },
115
- error,
116
- );
117
- }
118
- return null;
119
- }
120
- };
121
-
122
- const webArms: Array<[string, RefreshArm]> = [
123
- ['silent-iframe', async () =>
124
- (await mintSessionViaPerApexIframe(oxyServices, SILENT_IFRAME_REFRESH_TIMEOUT))
125
- ? oxyServices.getAccessToken()
126
- : null,
127
- ],
128
- ];
129
-
130
- return async (reason: AuthRefreshReason): Promise<string | null> => {
131
- if (!isWebBrowser()) {
132
- return runArm('native-shared-key', reason, async () =>
133
- (await oxyServices.signInWithSharedIdentity?.()) ? oxyServices.getAccessToken() : null,
134
- );
135
- }
136
-
137
- for (const [label, arm] of webArms) {
138
- const token = await runArm(label, reason, arm);
139
- if (token) {
140
- return token;
141
- }
142
- }
143
- return null;
144
- };
145
- }
146
-
147
- /**
148
- * Handle returned by {@link startTokenRefreshScheduler}; call `dispose()` to tear
149
- * down the timer and the foreground listener.
150
- */
151
- export interface TokenRefreshSchedulerHandle {
152
- dispose(): void;
153
- }
154
-
155
- /**
156
- * Start the proactive in-session refresh scheduler against `oxyServices`.
157
- *
158
- * Schedules a single timer to fire {@link TOKEN_REFRESH_LEAD_MS} before the
159
- * current access token's `exp`, calling `httpService.refreshAccessToken`
160
- * ('preflight') — which runs the installed handler and is deduped + cooldown-
161
- * guarded. After every attempt it reschedules from the (possibly rotated) token.
162
- * It also reschedules whenever the token changes (`onTokensChanged` — so a
163
- * sign-out that clears the token cancels the timer) and, on web tab-focus /
164
- * native app-foreground, refreshes immediately if already inside the lead window
165
- * (a long-hidden tab throttles timers, so the token can be expired on return).
166
- *
167
- * The `exp` is derived directly from the JWT via `getAccessTokenExpiry()`. No-ops
168
- * cleanly when there is no token, an opaque/no-`exp` token, or the host lacks
169
- * `getAccessTokenExpiry` (older stubs) — the reactive 401 path stays the only
170
- * refresh trigger in those cases.
171
- */
172
- export function startTokenRefreshScheduler(oxyServices: OxyServices): TokenRefreshSchedulerHandle {
173
- let disposed = false;
174
- let timer: ReturnType<typeof setTimeout> | null = null;
175
-
176
- const clearTimer = (): void => {
177
- if (timer !== null) {
178
- clearTimeout(timer);
179
- timer = null;
180
- }
181
- };
182
-
183
- const runRefresh = (): void => {
184
- // `refreshAccessToken` is deduped + cooldown-guarded internally, so this is
185
- // safe to call from the timer, the foreground listener, and request-time
186
- // preflight concurrently — they collapse to one network attempt.
187
- const refresh = oxyServices.httpService.refreshAccessToken?.('preflight');
188
- if (!refresh) {
189
- return;
190
- }
191
- void refresh
192
- .catch(() => null)
193
- .finally(() => {
194
- if (!disposed) {
195
- schedule();
196
- }
197
- });
198
- };
199
-
200
- const schedule = (): void => {
201
- clearTimer();
202
- if (disposed || !oxyServices.getAccessToken()) {
203
- return;
204
- }
205
- const expSeconds = oxyServices.getAccessTokenExpiry?.() ?? null;
206
- if (expSeconds === null) {
207
- return;
208
- }
209
- const fireInMs = expSeconds * 1000 - Date.now() - TOKEN_REFRESH_LEAD_MS;
210
- timer = setTimeout(runRefresh, Math.max(fireInMs, 0));
211
- };
212
-
213
- const onForeground = (): void => {
214
- if (disposed || !oxyServices.getAccessToken()) {
215
- return;
216
- }
217
- const expSeconds = oxyServices.getAccessTokenExpiry?.() ?? null;
218
- if (expSeconds === null) {
219
- return;
220
- }
221
- const remainingMs = expSeconds * 1000 - Date.now();
222
- if (remainingMs <= TOKEN_REFRESH_LEAD_MS) {
223
- runRefresh();
224
- } else {
225
- schedule();
226
- }
227
- };
228
-
229
- const unsubscribeTokens = oxyServices.onTokensChanged(() => {
230
- if (!disposed) {
231
- schedule();
232
- }
233
- });
234
-
235
- let removeForeground: (() => void) | null = null;
236
- if (isWebBrowser() && typeof document !== 'undefined') {
237
- const handler = (): void => {
238
- if (document.visibilityState === 'visible') {
239
- onForeground();
240
- }
241
- };
242
- document.addEventListener('visibilitychange', handler);
243
- removeForeground = () => document.removeEventListener('visibilitychange', handler);
244
- } else if (!isWebBrowser() && AppState && typeof AppState.addEventListener === 'function') {
245
- const subscription = AppState.addEventListener('change', (state: AppStateStatus) => {
246
- if (state === 'active') {
247
- onForeground();
248
- }
249
- });
250
- removeForeground = () => subscription.remove();
251
- }
252
-
253
- schedule();
254
-
255
- return {
256
- dispose(): void {
257
- disposed = true;
258
- clearTimer();
259
- unsubscribeTokens();
260
- removeForeground?.();
261
- removeForeground = null;
262
- },
263
- };
264
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * Silent, no-reload session-restore PRIMITIVE — the single shared
3
- * implementation used by BOTH cold boot (`OxyContext.restoreSessionsFromStorage`)
4
- * and in-session token refresh (`createInSessionRefreshHandler`). Neither caller
5
- * re-implements it; they compose it. Keeping one home avoids the two paths
6
- * drifting on "how do we mint a first-party token without a page reload".
7
- *
8
- * Platform-agnostic at the type level and returns plain data; the callers own
9
- * the side effects that differ between them (cold boot COMMITS the recovered
10
- * session into provider state; refresh only reads the freshly planted bearer).
11
- *
12
- * NOTE (session-sync cutover, Task 5): this file used to also export
13
- * `selectActiveRefreshAccount`, which picked the active account out of a
14
- * `refreshAllSessions` (`oxy_rt` refresh-cookie) snapshot. Deleted — its only
15
- * caller was the in-session refresh handler's now-deleted `refresh-cookie`
16
- * arm. The device account SET is server-authoritative via `SessionClient`
17
- * (`@oxyhq/core`) now.
18
- */
19
- import type { OxyServices, SessionLoginResponse } from '@oxyhq/core';
20
- import { autoDetectAuthWebUrl } from '@oxyhq/core';
21
-
22
- /**
23
- * Mint a fresh first-party session via the PER-APEX `/auth/silent` iframe — the
24
- * durable cross-domain restore path that works WITHOUT a top-level navigation
25
- * (so it succeeds under Safari ITP / Firefox TCP and in a backgrounded tab).
26
- *
27
- * The instance is configured with the CENTRAL auth URL, so we explicitly point
28
- * the iframe at the per-apex host (`auth.<rp-apex>`) via `autoDetectAuthWebUrl()`
29
- * + `silentSignIn`'s `authWebUrlOverride`. On a `*.oxy.so` app the per-apex host
30
- * IS the central host, so this also covers same-apex. When auto-detection bails
31
- * (localhost / IP / single-label / off-browser) there is no per-apex IdP and we
32
- * return `null`. `silentSignIn` plants the access token internally on success.
33
- *
34
- * @returns the recovered session (token already planted) when complete, else
35
- * `null` (no per-apex IdP, no session, or an incomplete iframe response).
36
- */
37
- export async function mintSessionViaPerApexIframe(
38
- oxyServices: OxyServices,
39
- timeoutMs: number,
40
- ): Promise<SessionLoginResponse | null> {
41
- const perApexAuthUrl = autoDetectAuthWebUrl();
42
- if (!perApexAuthUrl) {
43
- return null;
44
- }
45
- const session = await oxyServices.silentSignIn?.({
46
- authWebUrlOverride: perApexAuthUrl,
47
- timeout: timeoutMs,
48
- });
49
- if (!session?.user || !session.sessionId) {
50
- return null;
51
- }
52
- return session;
53
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * Platform detection helper for the web auth/session-sync surfaces.
3
- *
4
- * This module previously hosted a FedCM-based `useWebSSO` hook (Federated
5
- * Credential Management — Chrome-only). It was removed: FedCM is no longer
6
- * used anywhere in the client sign-in/cold-boot path (see `CrossDomainAuth`'s
7
- * doc comment in `@oxyhq/core` for the production sign-in loop that motivated
8
- * the removal). Cross-domain silent SSO is owned entirely by the
9
- * `silent-iframe` cold-boot step (per-apex `/auth/silent`) plus the terminal
10
- * `/sso` bounce in `OxyContext`.
11
- *
12
- * `isWebBrowser` is kept here (rather than moved) so every existing consumer
13
- * import path stays valid.
14
- */
15
-
16
- /**
17
- * Check if we're running in a web browser environment (not React Native)
18
- */
19
- export function isWebBrowser(): boolean {
20
- return typeof window !== 'undefined' &&
21
- typeof document !== 'undefined' &&
22
- typeof document.documentElement !== 'undefined';
23
- }
@@ -1,114 +0,0 @@
1
- import type { DeviceSessionState } from '@oxyhq/contracts';
2
- import type { User } from '@oxyhq/core';
3
- import {
4
- accountIdsOf,
5
- activeSessionIdOf,
6
- activeUserOf,
7
- deviceStateToClientSessions,
8
- } from '../projectSessionState';
9
-
10
- function makeUser(id: string): User {
11
- return {
12
- id,
13
- publicKey: `pk-${id}`,
14
- username: `user-${id}`,
15
- name: {},
16
- };
17
- }
18
-
19
- // DeviceSessionState.updatedAt is an epoch-ms number on the wire (see
20
- // packages/contracts/src/deviceSession.ts: `updatedAt: z.number()`), not an
21
- // ISO string.
22
- const UPDATED_AT_MS = Date.UTC(2026, 6, 1, 0, 0, 0, 0);
23
- const UPDATED_AT_ISO = new Date(UPDATED_AT_MS).toISOString();
24
-
25
- const state: DeviceSessionState = {
26
- deviceId: 'device-1',
27
- accounts: [
28
- { accountId: 'a1', sessionId: 'sess-a1', authuser: 0 },
29
- { accountId: 'a2', sessionId: 'sess-a2', authuser: 1 },
30
- ],
31
- activeAccountId: 'a2',
32
- revision: 1,
33
- updatedAt: UPDATED_AT_MS,
34
- };
35
-
36
- const usersById = new Map<string, User>([
37
- ['a1', makeUser('a1')],
38
- ['a2', makeUser('a2')],
39
- ]);
40
-
41
- describe('projectSessionState', () => {
42
- describe('activeSessionIdOf', () => {
43
- test('returns the active account sessionId', () => {
44
- expect(activeSessionIdOf(state)).toBe('sess-a2');
45
- });
46
-
47
- test('returns null for null state', () => {
48
- expect(activeSessionIdOf(null)).toBeNull();
49
- });
50
-
51
- test('returns null when activeAccountId is null', () => {
52
- expect(activeSessionIdOf({ ...state, activeAccountId: null })).toBeNull();
53
- });
54
- });
55
-
56
- describe('deviceStateToClientSessions', () => {
57
- test('maps every account in order with isCurrent + authuser', () => {
58
- const sessions = deviceStateToClientSessions(state, usersById);
59
- expect(sessions).toHaveLength(2);
60
- expect(sessions[0]).toEqual({
61
- sessionId: 'sess-a1',
62
- deviceId: 'device-1',
63
- expiresAt: UPDATED_AT_ISO,
64
- lastActive: UPDATED_AT_ISO,
65
- userId: 'a1',
66
- isCurrent: false,
67
- authuser: 0,
68
- });
69
- expect(sessions[1]).toEqual({
70
- sessionId: 'sess-a2',
71
- deviceId: 'device-1',
72
- expiresAt: UPDATED_AT_ISO,
73
- lastActive: UPDATED_AT_ISO,
74
- userId: 'a2',
75
- isCurrent: true,
76
- authuser: 1,
77
- });
78
- });
79
-
80
- test('still projects a session for an account absent from usersById', () => {
81
- const sessions = deviceStateToClientSessions(state, new Map());
82
- expect(sessions).toHaveLength(2);
83
- expect(sessions.map((session) => session.userId)).toEqual(['a1', 'a2']);
84
- });
85
- });
86
-
87
- describe('activeUserOf', () => {
88
- test('returns the active account user', () => {
89
- expect(activeUserOf(state, usersById)).toEqual(makeUser('a2'));
90
- });
91
-
92
- test('returns null for null state', () => {
93
- expect(activeUserOf(null, usersById)).toBeNull();
94
- });
95
-
96
- test('returns null when activeAccountId is null', () => {
97
- expect(activeUserOf({ ...state, activeAccountId: null }, usersById)).toBeNull();
98
- });
99
-
100
- test('returns null when the active account id is absent from usersById', () => {
101
- expect(activeUserOf(state, new Map())).toBeNull();
102
- });
103
- });
104
-
105
- describe('accountIdsOf', () => {
106
- test('returns every account id in order', () => {
107
- expect(accountIdsOf(state)).toEqual(['a1', 'a2']);
108
- });
109
-
110
- test('returns [] for null state', () => {
111
- expect(accountIdsOf(null)).toEqual([]);
112
- });
113
- });
114
- });
@@ -1,43 +0,0 @@
1
- import { createSessionClientHost } from '../sessionClientHost';
2
-
3
- function fakeOxy() {
4
- const listeners = new Set<(t: string | null) => void>();
5
- return {
6
- makeRequest: jest.fn().mockResolvedValue({ ok: true }),
7
- getBaseURL: jest.fn().mockReturnValue('https://api.oxy.so'),
8
- getAccessToken: jest.fn().mockReturnValue('tok'),
9
- setTokens: jest.fn(),
10
- onTokensChanged: jest.fn((l: (t: string | null) => void) => { listeners.add(l); return () => listeners.delete(l); }),
11
- _emit: (t: string | null) => listeners.forEach((l) => l(t)),
12
- };
13
- }
14
-
15
- test('delegates REST + token methods to oxyServices', async () => {
16
- const oxy = fakeOxy();
17
- const host = createSessionClientHost(oxy as never);
18
- await host.makeRequest('GET', '/session/device/state', undefined, { cache: false });
19
- expect(oxy.makeRequest).toHaveBeenCalledWith('GET', '/session/device/state', undefined, { cache: false });
20
- expect(host.getBaseURL()).toBe('https://api.oxy.so');
21
- expect(host.getAccessToken()).toBe('tok');
22
- host.setTokens('new');
23
- expect(oxy.setTokens).toHaveBeenCalledWith('new');
24
- });
25
-
26
- test('getCurrentAccountId reflects setCurrentAccountId', () => {
27
- const host = createSessionClientHost(fakeOxy() as never);
28
- expect(host.getCurrentAccountId()).toBeNull();
29
- host.setCurrentAccountId('u1');
30
- expect(host.getCurrentAccountId()).toBe('u1');
31
- });
32
-
33
- test('onTokensChanged forwards to oxyServices and unsubscribes', () => {
34
- const oxy = fakeOxy();
35
- const host = createSessionClientHost(oxy as never);
36
- const cb = jest.fn();
37
- const unsub = host.onTokensChanged(cb);
38
- oxy._emit(null);
39
- expect(cb).toHaveBeenCalledWith(null);
40
- unsub();
41
- oxy._emit('x');
42
- expect(cb).toHaveBeenCalledTimes(1);
43
- });
@@ -1,85 +0,0 @@
1
- import type { DeviceSessionState } from '@oxyhq/contracts';
2
- import type { ClientSession, User } from '@oxyhq/core';
3
-
4
- /**
5
- * Pure projection helpers: `DeviceSessionState` (the device-scoped
6
- * multi-account session-sync state produced by `@oxyhq/core`'s
7
- * `SessionClient`) -> the shapes `OxyContext` renders today
8
- * (`ClientSession[]`, an active session id, an active `User`).
9
- *
10
- * No I/O. The caller fetches profiles via
11
- * `oxyServices.getUsersByIds(accountIdsOf(state))` and builds `usersById`
12
- * from the result before calling `deviceStateToClientSessions` /
13
- * `activeUserOf`.
14
- */
15
-
16
- /**
17
- * Maps every `SessionAccount` in `state.accounts` to a `ClientSession`.
18
- *
19
- * `DeviceSessionState` carries no per-account `expiresAt` / `lastActive` —
20
- * both are set to `state.updatedAt` (converted to an ISO-8601 string; the
21
- * wire value is an epoch-ms number) as a provisional value. Fase 3-B
22
- * rewrites `ClientSession` to make `expiresAt`/`lastActive` optional (they
23
- * are not derivable from `DeviceSessionState`).
24
- *
25
- * `usersById` is accepted for signature symmetry with `activeUserOf` even
26
- * though `ClientSession` only stores `userId` — a session is still
27
- * projected for an account whose id is absent from `usersById` (no
28
- * placeholder user is fabricated).
29
- */
30
- export function deviceStateToClientSessions(
31
- state: DeviceSessionState,
32
- usersById: Map<string, User>,
33
- ): ClientSession[] {
34
- const provisionalTimestamp = new Date(state.updatedAt).toISOString();
35
- return state.accounts.map((account) => ({
36
- sessionId: account.sessionId,
37
- deviceId: state.deviceId,
38
- // provisional: Fase 3-B rewrites ClientSession to make expiresAt/lastActive
39
- // optional (they are not in DeviceSessionState)
40
- expiresAt: provisionalTimestamp,
41
- lastActive: provisionalTimestamp,
42
- userId: account.accountId,
43
- isCurrent: account.accountId === state.activeAccountId,
44
- authuser: account.authuser,
45
- }));
46
- }
47
-
48
- /**
49
- * The active account's `sessionId`, or `null` when there is no state or no
50
- * active account is set.
51
- */
52
- export function activeSessionIdOf(state: DeviceSessionState | null): string | null {
53
- if (state === null || state.activeAccountId === null) {
54
- return null;
55
- }
56
- const activeAccountId = state.activeAccountId;
57
- const activeAccount = state.accounts.find((account) => account.accountId === activeAccountId);
58
- return activeAccount?.sessionId ?? null;
59
- }
60
-
61
- /**
62
- * The active account's `User`, resolved from `usersById`. `null` when there
63
- * is no state, no active account is set, or the active account id is absent
64
- * from `usersById`.
65
- */
66
- export function activeUserOf(
67
- state: DeviceSessionState | null,
68
- usersById: Map<string, User>,
69
- ): User | null {
70
- if (state === null || state.activeAccountId === null) {
71
- return null;
72
- }
73
- return usersById.get(state.activeAccountId) ?? null;
74
- }
75
-
76
- /**
77
- * All account ids in `state`, suitable for an `oxyServices.getUsersByIds(...)`
78
- * fetch. `[]` for `null` state.
79
- */
80
- export function accountIdsOf(state: DeviceSessionState | null): string[] {
81
- if (state === null) {
82
- return [];
83
- }
84
- return state.accounts.map((account) => account.accountId);
85
- }
@@ -1,27 +0,0 @@
1
- import type { OxyServices, SessionClientHost } from '@oxyhq/core';
2
-
3
- /**
4
- * Thin `SessionClientHost` adapter over an `OxyServices` instance.
5
- *
6
- * `SessionClient` (in `@oxyhq/core`) is host-agnostic: it only needs a REST +
7
- * token surface. `OxyServices` already exposes all of that except
8
- * `getCurrentAccountId`, which has no direct equivalent — the adapter holds a
9
- * mutable ref set by the caller (`OxyContext`, in Fase 3-B) via
10
- * `setCurrentAccountId`.
11
- */
12
- export function createSessionClientHost(
13
- oxyServices: OxyServices,
14
- ): SessionClientHost & { setCurrentAccountId(id: string | null): void } {
15
- let currentAccountId: string | null = null;
16
- return {
17
- makeRequest: (method, url, data, options) => oxyServices.makeRequest(method, url, data, options),
18
- getBaseURL: () => oxyServices.getBaseURL(),
19
- getAccessToken: () => oxyServices.getAccessToken(),
20
- onTokensChanged: (listener) => oxyServices.onTokensChanged(listener),
21
- setTokens: (accessToken) => oxyServices.setTokens(accessToken),
22
- getCurrentAccountId: () => currentAccountId,
23
- setCurrentAccountId: (id) => {
24
- currentAccountId = id;
25
- },
26
- };
27
- }