@oxyhq/services 15.0.0 → 17.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 (280) hide show
  1. package/lib/commonjs/index.js +25 -55
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/client.js +0 -14
  4. package/lib/commonjs/ui/client.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyAccountDialog.js +764 -0
  6. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -0
  7. package/lib/commonjs/ui/components/OxyProvider.js +11 -6
  8. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxySignInButton.js +10 -21
  10. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  11. package/lib/commonjs/ui/components/ProfileButton.js +25 -112
  12. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  13. package/lib/commonjs/ui/components/RequireOxyAuth.js +299 -0
  14. package/lib/commonjs/ui/components/RequireOxyAuth.js.map +1 -0
  15. package/lib/commonjs/ui/context/OxyContext.js +106 -2
  16. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/mutations/mutationKeys.js +1 -1
  18. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +10 -10
  19. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/queries/queryKeys.js +1 -1
  21. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +7 -8
  22. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useAuth.js +19 -30
  24. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  25. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +37 -267
  26. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
  27. package/lib/commonjs/ui/index.js +5 -40
  28. package/lib/commonjs/ui/index.js.map +1 -1
  29. package/lib/commonjs/ui/navigation/accountDialogManager.js +91 -0
  30. package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -0
  31. package/lib/commonjs/ui/navigation/routes.js +0 -2
  32. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  33. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js +17 -18
  34. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/ManageAccountScreen.js +4 -3
  36. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  37. package/lib/commonjs/ui/session/createSessionClient.js +10 -2
  38. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  39. package/lib/module/index.js +20 -17
  40. package/lib/module/index.js.map +1 -1
  41. package/lib/module/ui/client.js +1 -2
  42. package/lib/module/ui/client.js.map +1 -1
  43. package/lib/module/ui/components/OxyAccountDialog.js +761 -0
  44. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -0
  45. package/lib/module/ui/components/OxyProvider.js +11 -6
  46. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  47. package/lib/module/ui/components/OxySignInButton.js +10 -21
  48. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  49. package/lib/module/ui/components/ProfileButton.js +26 -112
  50. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  51. package/lib/module/ui/components/RequireOxyAuth.js +294 -0
  52. package/lib/module/ui/components/RequireOxyAuth.js.map +1 -0
  53. package/lib/module/ui/context/OxyContext.js +107 -3
  54. package/lib/module/ui/context/OxyContext.js.map +1 -1
  55. package/lib/module/ui/hooks/mutations/mutationKeys.js +1 -1
  56. package/lib/module/ui/hooks/mutations/useAccountMutations.js +8 -8
  57. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  58. package/lib/module/ui/hooks/queries/queryKeys.js +1 -1
  59. package/lib/module/ui/hooks/queries/useAccountQueries.js +5 -6
  60. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  61. package/lib/module/ui/hooks/useAuth.js +19 -30
  62. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  63. package/lib/module/ui/hooks/useSwitchableAccounts.js +38 -267
  64. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
  65. package/lib/module/ui/index.js +1 -5
  66. package/lib/module/ui/index.js.map +1 -1
  67. package/lib/module/ui/navigation/accountDialogManager.js +81 -0
  68. package/lib/module/ui/navigation/accountDialogManager.js.map +1 -0
  69. package/lib/module/ui/navigation/routes.js +0 -2
  70. package/lib/module/ui/navigation/routes.js.map +1 -1
  71. package/lib/module/ui/screens/ConnectedAppsScreen.js +19 -20
  72. package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
  73. package/lib/module/ui/screens/ManageAccountScreen.js +4 -3
  74. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  75. package/lib/module/ui/session/createSessionClient.js +10 -2
  76. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  77. package/lib/typescript/commonjs/index.d.ts +6 -13
  78. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/ui/client.d.ts +0 -4
  80. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +35 -0
  82. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -0
  83. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +2 -2
  84. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -23
  87. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts +55 -0
  89. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.map +1 -0
  90. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +13 -1
  91. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +4 -4
  93. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +6 -7
  95. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +13 -152
  98. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/ui/index.d.ts +2 -5
  100. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +49 -0
  102. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  104. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
  107. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +9 -2
  109. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/ui/types/navigation.d.ts +9 -0
  111. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/ui/utils/storageHelpers.d.ts +7 -7
  113. package/lib/typescript/module/index.d.ts +6 -13
  114. package/lib/typescript/module/index.d.ts.map +1 -1
  115. package/lib/typescript/module/ui/client.d.ts +0 -4
  116. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  117. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +35 -0
  118. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -0
  119. package/lib/typescript/module/ui/components/OxyProvider.d.ts +2 -2
  120. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  121. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  122. package/lib/typescript/module/ui/components/ProfileButton.d.ts +11 -23
  123. package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
  124. package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts +55 -0
  125. package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts.map +1 -0
  126. package/lib/typescript/module/ui/context/OxyContext.d.ts +13 -1
  127. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  128. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +4 -4
  129. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  130. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +6 -7
  131. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  132. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  133. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +13 -152
  134. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  135. package/lib/typescript/module/ui/index.d.ts +2 -5
  136. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  137. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +49 -0
  138. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -0
  139. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  140. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  141. package/lib/typescript/module/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  142. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
  143. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  144. package/lib/typescript/module/ui/session/createSessionClient.d.ts +9 -2
  145. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  146. package/lib/typescript/module/ui/types/navigation.d.ts +9 -0
  147. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  148. package/lib/typescript/module/ui/utils/storageHelpers.d.ts +7 -7
  149. package/package.json +4 -4
  150. package/src/index.ts +23 -28
  151. package/src/ui/client.ts +0 -4
  152. package/src/ui/components/OxyAccountDialog.tsx +760 -0
  153. package/src/ui/components/OxyProvider.tsx +12 -6
  154. package/src/ui/components/OxySignInButton.tsx +10 -21
  155. package/src/ui/components/ProfileButton.tsx +23 -126
  156. package/src/ui/components/RequireOxyAuth.tsx +266 -0
  157. package/src/ui/context/OxyContext.tsx +146 -7
  158. package/src/ui/hooks/mutations/mutationKeys.ts +1 -1
  159. package/src/ui/hooks/mutations/useAccountMutations.ts +8 -8
  160. package/src/ui/hooks/queries/queryKeys.ts +1 -1
  161. package/src/ui/hooks/queries/useAccountQueries.ts +8 -9
  162. package/src/ui/hooks/useAuth.ts +19 -31
  163. package/src/ui/hooks/useSwitchableAccounts.ts +42 -376
  164. package/src/ui/index.ts +2 -5
  165. package/src/ui/navigation/accountDialogManager.ts +84 -0
  166. package/src/ui/navigation/routes.ts +1 -5
  167. package/src/ui/screens/ConnectedAppsScreen.tsx +23 -24
  168. package/src/ui/screens/ManageAccountScreen.tsx +3 -2
  169. package/src/ui/session/createSessionClient.ts +9 -1
  170. package/src/ui/types/navigation.ts +9 -0
  171. package/src/ui/utils/storageHelpers.ts +7 -7
  172. package/lib/commonjs/ui/components/AccountMenu.js +0 -598
  173. package/lib/commonjs/ui/components/AccountMenu.js.map +0 -1
  174. package/lib/commonjs/ui/components/AccountMenuButton.js +0 -135
  175. package/lib/commonjs/ui/components/AccountMenuButton.js.map +0 -1
  176. package/lib/commonjs/ui/components/AccountSwitcher.js +0 -780
  177. package/lib/commonjs/ui/components/AccountSwitcher.js.map +0 -1
  178. package/lib/commonjs/ui/components/AnotherDeviceQR.js +0 -116
  179. package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +0 -1
  180. package/lib/commonjs/ui/components/ProfileMenu.js +0 -518
  181. package/lib/commonjs/ui/components/ProfileMenu.js.map +0 -1
  182. package/lib/commonjs/ui/components/SignInAccountChooser.js +0 -189
  183. package/lib/commonjs/ui/components/SignInAccountChooser.js.map +0 -1
  184. package/lib/commonjs/ui/components/SignInModal.js +0 -600
  185. package/lib/commonjs/ui/components/SignInModal.js.map +0 -1
  186. package/lib/commonjs/ui/hooks/useOxyAuthSession.js +0 -501
  187. package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +0 -1
  188. package/lib/commonjs/ui/hooks/usePasswordSignIn.js +0 -170
  189. package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +0 -1
  190. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +0 -51
  191. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +0 -1
  192. package/lib/commonjs/ui/screens/OxyAuthScreen.js +0 -312
  193. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +0 -1
  194. package/lib/commonjs/utils/deviceFlowSignIn.js +0 -109
  195. package/lib/commonjs/utils/deviceFlowSignIn.js.map +0 -1
  196. package/lib/module/ui/components/AccountMenu.js +0 -593
  197. package/lib/module/ui/components/AccountMenu.js.map +0 -1
  198. package/lib/module/ui/components/AccountMenuButton.js +0 -130
  199. package/lib/module/ui/components/AccountMenuButton.js.map +0 -1
  200. package/lib/module/ui/components/AccountSwitcher.js +0 -774
  201. package/lib/module/ui/components/AccountSwitcher.js.map +0 -1
  202. package/lib/module/ui/components/AnotherDeviceQR.js +0 -110
  203. package/lib/module/ui/components/AnotherDeviceQR.js.map +0 -1
  204. package/lib/module/ui/components/ProfileMenu.js +0 -514
  205. package/lib/module/ui/components/ProfileMenu.js.map +0 -1
  206. package/lib/module/ui/components/SignInAccountChooser.js +0 -183
  207. package/lib/module/ui/components/SignInAccountChooser.js.map +0 -1
  208. package/lib/module/ui/components/SignInModal.js +0 -591
  209. package/lib/module/ui/components/SignInModal.js.map +0 -1
  210. package/lib/module/ui/hooks/useOxyAuthSession.js +0 -495
  211. package/lib/module/ui/hooks/useOxyAuthSession.js.map +0 -1
  212. package/lib/module/ui/hooks/usePasswordSignIn.js +0 -165
  213. package/lib/module/ui/hooks/usePasswordSignIn.js.map +0 -1
  214. package/lib/module/ui/screens/AccountSwitcherScreen.js +0 -48
  215. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +0 -1
  216. package/lib/module/ui/screens/OxyAuthScreen.js +0 -306
  217. package/lib/module/ui/screens/OxyAuthScreen.js.map +0 -1
  218. package/lib/module/utils/deviceFlowSignIn.js +0 -106
  219. package/lib/module/utils/deviceFlowSignIn.js.map +0 -1
  220. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +0 -47
  221. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +0 -1
  222. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts +0 -26
  223. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts.map +0 -1
  224. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +0 -46
  225. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +0 -1
  226. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +0 -34
  227. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +0 -1
  228. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +0 -49
  229. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +0 -1
  230. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +0 -29
  231. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +0 -1
  232. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +0 -28
  233. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +0 -1
  234. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +0 -105
  235. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
  236. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +0 -31
  237. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
  238. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts +0 -11
  239. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
  240. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +0 -22
  241. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +0 -1
  242. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +0 -97
  243. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +0 -1
  244. package/lib/typescript/module/ui/components/AccountMenu.d.ts +0 -47
  245. package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +0 -1
  246. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts +0 -26
  247. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts.map +0 -1
  248. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +0 -46
  249. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +0 -1
  250. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +0 -34
  251. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +0 -1
  252. package/lib/typescript/module/ui/components/ProfileMenu.d.ts +0 -49
  253. package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +0 -1
  254. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +0 -29
  255. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +0 -1
  256. package/lib/typescript/module/ui/components/SignInModal.d.ts +0 -28
  257. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +0 -1
  258. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +0 -105
  259. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
  260. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +0 -31
  261. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
  262. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts +0 -11
  263. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
  264. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +0 -22
  265. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +0 -1
  266. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +0 -97
  267. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +0 -1
  268. package/src/ui/components/AccountMenu.tsx +0 -645
  269. package/src/ui/components/AccountMenuButton.tsx +0 -126
  270. package/src/ui/components/AccountSwitcher.tsx +0 -751
  271. package/src/ui/components/AnotherDeviceQR.tsx +0 -119
  272. package/src/ui/components/ProfileMenu.tsx +0 -564
  273. package/src/ui/components/SignInAccountChooser.tsx +0 -162
  274. package/src/ui/components/SignInModal.tsx +0 -530
  275. package/src/ui/hooks/useOxyAuthSession.ts +0 -635
  276. package/src/ui/hooks/usePasswordSignIn.ts +0 -207
  277. package/src/ui/screens/AccountSwitcherScreen.tsx +0 -43
  278. package/src/ui/screens/OxyAuthScreen.tsx +0 -324
  279. package/src/utils/__tests__/deviceFlowSignIn.test.ts +0 -231
  280. package/src/utils/deviceFlowSignIn.ts +0 -152
@@ -1,635 +0,0 @@
1
- /**
2
- * useOxyAuthSession — the single shared engine for the cross-app device-flow
3
- * sign-in ("Sign in with Oxy") used by BOTH containers:
4
- *
5
- * - `SignInModal` (web centered modal)
6
- * - `OxyAuthScreen` (native bottom sheet)
7
- *
8
- * Before this hook existed, those two files each re-implemented ~90% of the
9
- * same auth-session machinery (session-token creation, QR data, socket.io
10
- * `/auth-session` subscription, the HTTP polling fallback, waiting/error/retry
11
- * state, the open-auth handler, and cleanup) — and they had drifted (notably a
12
- * weaker `Math.random()` session token on native vs the crypto-secure one on
13
- * web). This hook owns ALL of it ONCE, with a clean typed surface, so the
14
- * containers only own their layout. The transport is functionally identical to
15
- * the previous implementations — this is a structural de-duplication, not a
16
- * behavior change.
17
- *
18
- * The native-only deep-link return path (`Linking` redirect handling) lives
19
- * HERE, gated to native via `Platform.OS`, so the web container never carries
20
- * native-only code while the two paths still cannot drift.
21
- */
22
-
23
- import { useState, useEffect, useCallback, useRef } from 'react';
24
- import { Linking, Platform } from 'react-native';
25
- import io, { type Socket } from 'socket.io-client';
26
- import type { OxyServices, SessionLoginResponse, User } from '@oxyhq/core';
27
- import { createDebugLogger } from '@oxyhq/core';
28
- import { completeDeviceFlowSignIn } from '../../utils/deviceFlowSignIn';
29
-
30
- const debug = createDebugLogger('useOxyAuthSession');
31
-
32
- /**
33
- * Default destination for the "Create an Oxy account" / "Get Oxy Accounts"
34
- * link, shared by both containers. Reused as-is from the original native
35
- * `OxyAuthScreen` so no new route is invented.
36
- */
37
- export const OXY_ACCOUNTS_WEB_URL = 'https://accounts.oxy.so';
38
-
39
- /** Default central Oxy auth web origin used when no override can be resolved. */
40
- export const OXY_AUTH_WEB_URL = 'https://auth.oxy.so';
41
-
42
- /** Auth session expiration (5 minutes). */
43
- export const AUTH_SESSION_EXPIRY_MS = 5 * 60 * 1000;
44
-
45
- /** Polling interval (fallback if the socket fails) in milliseconds. */
46
- export const POLLING_INTERVAL_MS = 3000;
47
-
48
- /** The active device-flow session this client created and is waiting on. */
49
- export interface AuthSession {
50
- /** The secret high-entropy token that IS the device-flow credential. */
51
- sessionToken: string;
52
- /** Epoch ms after which the server rejects this session. */
53
- expiresAt: number;
54
- }
55
-
56
- /**
57
- * Extended `POST /auth/session/create` response (the "Sign in with Oxy" handoff
58
- * fields, Workstream C2). Both are optional so this hook degrades gracefully
59
- * when the handoff backend is not yet deployed — the legacy `oxyauth://<token>`
60
- * QR (`qrData`) remains the fallback.
61
- */
62
- interface AuthSessionCreateResponse {
63
- /**
64
- * Public, single-use authorize code carried in the QR / deep-link. NEVER the
65
- * secret `sessionToken` — the approver resolves the requesting app's identity
66
- * from this code server-side.
67
- */
68
- authorizeCode?: string;
69
- /** Ready-to-render deep-link / universal-link string (`oxycommons://approve?...`). */
70
- qrPayload?: string;
71
- /** Session lifecycle status (e.g. `'pending'`). */
72
- status?: string;
73
- /** Server-authoritative expiry (epoch ms); the client-proposed value is the fallback. */
74
- expiresAt?: number;
75
- }
76
-
77
- /** Real-time auth-session socket payload (also matches the poll status shape). */
78
- interface AuthUpdatePayload {
79
- status: 'authorized' | 'cancelled' | 'expired';
80
- sessionId?: string;
81
- publicKey?: string;
82
- userId?: string;
83
- username?: string;
84
- }
85
-
86
- export interface UseOxyAuthSessionOptions {
87
- /**
88
- * Called after a fully completed sign-in (bearer claimed, session hydrated).
89
- * The web modal closes itself here; the native screen forwards the user to
90
- * its `onAuthenticated` prop. The hook itself stays presentation-agnostic.
91
- */
92
- onSignedIn?: (user: User) => void;
93
- }
94
-
95
- export interface UseOxyAuthSessionResult {
96
- /** The active device-flow session, or `null` before/while it is created. */
97
- authSession: AuthSession | null;
98
- /** The QR payload string (`oxyauth://<token>`), or `''` when no session. */
99
- qrData: string;
100
- /**
101
- * The PUBLIC, single-use authorize code for the "Sign in with Oxy" handoff,
102
- * or `null` when the handoff backend did not return one. The approver (the
103
- * Oxy identity app) resolves the requesting app's identity from this code —
104
- * it is safe to display; it is NOT the secret `sessionToken`.
105
- */
106
- authorizeCode: string | null;
107
- /**
108
- * The structured "Sign in with Oxy" deep-link payload
109
- * (`oxycommons://approve?...`) to render in the cross-device QR and to open on
110
- * the same device, or `null` when the handoff backend did not return one.
111
- * Render this (preferred over `qrData`) in the QR and pass it to
112
- * `Linking.openURL` for same-device approval.
113
- */
114
- qrPayload: string | null;
115
- /** `true` while the session is being created (initial spinner). */
116
- isLoading: boolean;
117
- /** A user-facing error message, or `null`. Drives the retry UI. */
118
- error: string | null;
119
- /** `true` once a session exists and we are awaiting authorization. */
120
- isWaiting: boolean;
121
- /**
122
- * Open the central Oxy auth approval surface for THIS device-flow session.
123
- * On web this opens a centered approval popup; on native it opens the system
124
- * browser (carrying a `redirect_uri` so the deep-link return path can fire).
125
- * This is the action behind the platform-primary "Continue with Oxy" button.
126
- */
127
- openAuthApproval: () => Promise<void>;
128
- /**
129
- * Same-device "Sign in with Oxy" handoff: deep-link to `qrPayload`
130
- * (`oxycommons://approve?...`) so the native Oxy identity app opens directly
131
- * to approve. No-op when no `qrPayload` was returned. The socket / poll still
132
- * completes the sign-in once the approval lands.
133
- */
134
- openSameDeviceApproval: () => Promise<void>;
135
- /** Tear down the current session and create a fresh one (the retry action). */
136
- retry: () => void;
137
- /** Disconnect the socket and stop polling. Idempotent. */
138
- cleanup: () => void;
139
- }
140
-
141
- /**
142
- * Resolve the central Oxy auth web origin from config or the API base URL.
143
- * Mirrors the resolution both containers previously inlined.
144
- */
145
- function resolveAuthWebBaseUrl(baseURL: string, authWebUrl?: string): string {
146
- if (authWebUrl) {
147
- return authWebUrl;
148
- }
149
-
150
- try {
151
- const url = new URL(baseURL);
152
- if (url.port === '3001') {
153
- url.port = '3002';
154
- return url.origin;
155
- }
156
- if (url.hostname.startsWith('api.')) {
157
- url.hostname = `auth.${url.hostname.slice(4)}`;
158
- return url.origin;
159
- }
160
- } catch {
161
- // Malformed base URL — fall back to the default origin below.
162
- }
163
- return OXY_AUTH_WEB_URL;
164
- }
165
-
166
- /**
167
- * Resolve the deep-link redirect URI used on native so the auth web flow can
168
- * bounce back into the app. Prefers an explicit config value, otherwise derives
169
- * a clean (query/hash-stripped) URI from the app's initial deep link.
170
- */
171
- async function resolveAuthRedirectUri(authRedirectUri?: string): Promise<string | null> {
172
- if (authRedirectUri) {
173
- return authRedirectUri;
174
- }
175
-
176
- try {
177
- const initialUrl = await Linking.getInitialURL();
178
- if (!initialUrl) {
179
- return null;
180
- }
181
-
182
- const parsed = new URL(initialUrl);
183
- parsed.search = '';
184
- parsed.hash = '';
185
- return parsed.toString();
186
- } catch {
187
- return null;
188
- }
189
- }
190
-
191
- /** Parse the `session_id` / `error` params from a deep-link redirect URL. */
192
- function getRedirectParams(url: string): { sessionId?: string; error?: string } | null {
193
- try {
194
- const parsed = new URL(url);
195
- const sessionId = parsed.searchParams.get('session_id') ?? undefined;
196
- const error = parsed.searchParams.get('error') ?? undefined;
197
-
198
- if (!sessionId && !error) {
199
- return null;
200
- }
201
-
202
- return { sessionId, error };
203
- } catch {
204
- return null;
205
- }
206
- }
207
-
208
- /**
209
- * Generate a cryptographically random session token.
210
- *
211
- * 16 random bytes -> 32 hex chars (128 bits of entropy) — unguessable.
212
- * `crypto.getRandomValues` is guaranteed available because importing
213
- * `@oxyhq/core` installs a polyfill via `expo-crypto` on React Native. This is
214
- * the secure generator the web modal already used; the native screen previously
215
- * used a weaker `Math.random()` generator, which this consolidation removes.
216
- */
217
- function generateSessionToken(): string {
218
- const bytes = new Uint8Array(16);
219
- crypto.getRandomValues(bytes);
220
- return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
221
- }
222
-
223
- /**
224
- * Owns the full device-flow auth-session lifecycle for one mounted sign-in
225
- * surface. Both `SignInModal` and `OxyAuthScreen` consume this; neither
226
- * re-implements the socket / polling / deep-link transport.
227
- *
228
- * Subscriptions (socket connect/disconnect, the poll interval, the native
229
- * deep-link listener, and unmount cleanup) are the legitimate `useEffect` use
230
- * WITH cleanup. No effect here computes derived UI state.
231
- */
232
- export function useOxyAuthSession(
233
- oxyServices: OxyServices,
234
- clientId: string | null,
235
- commitSession: ((session: SessionLoginResponse) => Promise<void>) | undefined,
236
- options: UseOxyAuthSessionOptions = {},
237
- ): UseOxyAuthSessionResult {
238
- const { onSignedIn } = options;
239
-
240
- const [authSession, setAuthSession] = useState<AuthSession | null>(null);
241
- const [isLoading, setIsLoading] = useState(true);
242
- const [error, setError] = useState<string | null>(null);
243
- const [isWaiting, setIsWaiting] = useState(false);
244
- // "Sign in with Oxy" handoff fields surfaced from the `create` response.
245
- const [authorizeCode, setAuthorizeCode] = useState<string | null>(null);
246
- const [qrPayload, setQrPayload] = useState<string | null>(null);
247
-
248
- const socketRef = useRef<Socket | null>(null);
249
- const pollingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
250
- const isProcessingRef = useRef(false);
251
- const linkingHandledRef = useRef(false);
252
-
253
- // The latest `onSignedIn` callback, read via ref so the success handler does
254
- // not need it in its dependency list (which would otherwise rebuild the
255
- // socket/poll callbacks on every render where the container passes a new
256
- // closure).
257
- const onSignedInRef = useRef(onSignedIn);
258
- onSignedInRef.current = onSignedIn;
259
-
260
- // Cleanup socket and polling. Idempotent.
261
- const cleanup = useCallback(() => {
262
- setIsWaiting(false);
263
-
264
- if (socketRef.current) {
265
- socketRef.current.disconnect();
266
- socketRef.current = null;
267
- }
268
-
269
- if (pollingIntervalRef.current) {
270
- clearInterval(pollingIntervalRef.current);
271
- pollingIntervalRef.current = null;
272
- }
273
- }, []);
274
-
275
- // Handle successful authorization.
276
- //
277
- // The auth-session socket / poll (or deep-link return) hands us the
278
- // authorized `sessionId`. Before any session-management code can use it we
279
- // MUST first exchange the secret `sessionToken` (held only by this client,
280
- // generated for THIS flow) for the first access token via
281
- // `claimSessionByToken` — the device-flow equivalent of OAuth's
282
- // code-for-token exchange (RFC 8628 §3.4).
283
- //
284
- // Without that exchange the SDK has no bearer token — the session is
285
- // authorized server-side but the app never becomes authenticated and the UI
286
- // sits "Waiting for authorization..." forever. Once `claimSessionByToken`
287
- // plants the tokens in the HttpService, the claimed session is committed via
288
- // `commitSession` (`useOxy().handleWebSession`) — the SAME path a fresh
289
- // password sign-in or a silent cross-domain restore uses to register the
290
- // account into the device's server-authoritative session set. It is NOT yet a
291
- // member of that set, so
292
- // `switchSession` (an account-SWITCH between accounts already on the
293
- // device) is the wrong primitive here. Shared with both containers via
294
- // `completeDeviceFlowSignIn` so the two paths cannot drift.
295
- const handleAuthSuccess = useCallback(
296
- async (sessionId: string, sessionToken: string) => {
297
- if (isProcessingRef.current) return;
298
- isProcessingRef.current = true;
299
-
300
- try {
301
- if (!commitSession) {
302
- throw new Error('Session commit unavailable');
303
- }
304
- const user = await completeDeviceFlowSignIn({
305
- oxyServices,
306
- sessionId,
307
- sessionToken,
308
- commitSession,
309
- });
310
- onSignedInRef.current?.(user);
311
- } catch (err) {
312
- debug.error('Error completing auth:', err);
313
- setError('Authorization successful but failed to complete sign in. Please try again.');
314
- isProcessingRef.current = false;
315
- }
316
- },
317
- [oxyServices, commitSession],
318
- );
319
-
320
- // Start polling for authorization.
321
- //
322
- // Idempotent: if a poll interval is already running this is a no-op, so the
323
- // `connect_error` path (which also calls this) cannot stack a second interval
324
- // on top of the always-on poll started in `generateAuthSession`.
325
- const startPolling = useCallback(
326
- (sessionToken: string) => {
327
- if (pollingIntervalRef.current) return;
328
-
329
- pollingIntervalRef.current = setInterval(async () => {
330
- if (isProcessingRef.current) return;
331
-
332
- try {
333
- const response: {
334
- authorized: boolean;
335
- sessionId?: string;
336
- publicKey?: string;
337
- status?: string;
338
- } = await oxyServices.makeRequest(
339
- 'GET',
340
- `/auth/session/status/${sessionToken}`,
341
- undefined,
342
- { cache: false },
343
- );
344
-
345
- if (response.authorized && response.sessionId) {
346
- cleanup();
347
- // Pass the original sessionToken (in closure) through; the claim
348
- // exchange needs it to mint the first access token.
349
- handleAuthSuccess(response.sessionId, sessionToken);
350
- } else if (response.status === 'cancelled') {
351
- cleanup();
352
- setError('Authorization was denied.');
353
- } else if (response.status === 'expired') {
354
- cleanup();
355
- setError('Session expired. Please try again.');
356
- }
357
- } catch (err) {
358
- // Transient poll error — the next tick retries. Logged, never thrown.
359
- debug.log('Auth polling error:', err);
360
- }
361
- }, POLLING_INTERVAL_MS);
362
- },
363
- [oxyServices, handleAuthSuccess, cleanup],
364
- );
365
-
366
- // Connect to the auth-session socket for real-time updates.
367
- const connectSocket = useCallback(
368
- (sessionToken: string) => {
369
- const baseURL = oxyServices.getBaseURL();
370
-
371
- // Connect to the auth-session namespace (no authentication required).
372
- const socket = io(`${baseURL}/auth-session`, {
373
- transports: ['websocket', 'polling'],
374
- reconnection: true,
375
- reconnectionAttempts: 3,
376
- reconnectionDelay: 1000,
377
- });
378
-
379
- socketRef.current = socket;
380
-
381
- socket.on('connect', () => {
382
- debug.log('Auth socket connected');
383
- socket.emit('join', sessionToken);
384
- });
385
-
386
- socket.on('joined', () => {
387
- debug.log('Joined auth session room');
388
- });
389
-
390
- socket.on('auth_update', (payload: AuthUpdatePayload) => {
391
- debug.log('Auth update received:', payload);
392
-
393
- if (payload.status === 'authorized' && payload.sessionId) {
394
- cleanup();
395
- // `sessionToken` is this flow's secret credential (in closure) — pass
396
- // it through so `handleAuthSuccess` can claim the first access token.
397
- handleAuthSuccess(payload.sessionId, sessionToken);
398
- } else if (payload.status === 'cancelled') {
399
- cleanup();
400
- setError('Authorization was denied.');
401
- } else if (payload.status === 'expired') {
402
- cleanup();
403
- setError('Session expired. Please try again.');
404
- }
405
- });
406
-
407
- socket.on('connect_error', (err) => {
408
- debug.log(
409
- 'Socket connection error, falling back to polling:',
410
- err instanceof Error ? err.message : null,
411
- );
412
- // Realtime transport errored — fall back to polling. The poll is
413
- // already running (started unconditionally in `generateAuthSession`),
414
- // so `startPolling` here is a no-op backstop.
415
- socket.disconnect();
416
- startPolling(sessionToken);
417
- });
418
-
419
- socket.on('disconnect', () => {
420
- debug.log('Auth socket disconnected');
421
- });
422
- },
423
- [oxyServices, handleAuthSuccess, cleanup, startPolling],
424
- );
425
-
426
- // Generate a new auth session.
427
- const generateAuthSession = useCallback(async () => {
428
- setIsLoading(true);
429
- setError(null);
430
- // Reset the handoff fields for the fresh session (also clears them on retry).
431
- setAuthorizeCode(null);
432
- setQrPayload(null);
433
- isProcessingRef.current = false;
434
-
435
- // The cross-app device sign-in flow identifies the requesting app by its
436
- // real registered OAuth client id (ApplicationCredential publicKey).
437
- // Without it the API cannot resolve the consent identity, so we fail fast
438
- // with a clear configuration error rather than creating a session the
439
- // server would reject.
440
- if (!clientId) {
441
- setError('This app is not configured for sign-in (missing clientId).');
442
- setIsLoading(false);
443
- return;
444
- }
445
-
446
- try {
447
- const sessionToken = generateSessionToken();
448
- const expiresAt = Date.now() + AUTH_SESSION_EXPIRY_MS;
449
-
450
- // Register the auth session with the server. The response carries the
451
- // "Sign in with Oxy" handoff fields (public `authorizeCode` + structured
452
- // `qrPayload`) when the handoff backend is deployed; both are optional, so
453
- // the legacy `oxyauth://<token>` QR path keeps working without them.
454
- const createResponse = await oxyServices.makeRequest<AuthSessionCreateResponse>(
455
- 'POST',
456
- '/auth/session/create',
457
- { sessionToken, expiresAt, clientId },
458
- { cache: false },
459
- );
460
-
461
- setAuthSession({ sessionToken, expiresAt });
462
- setAuthorizeCode(createResponse?.authorizeCode ?? null);
463
- setQrPayload(createResponse?.qrPayload ?? null);
464
- setIsWaiting(true);
465
-
466
- // Socket is the fast path; the poll is a transport-independent backstop
467
- // that guarantees completion even if the socket connects but silently
468
- // never delivers auth_update (RN transport / idle-timeout).
469
- connectSocket(sessionToken);
470
- startPolling(sessionToken);
471
- } catch (err: unknown) {
472
- setError((err instanceof Error ? err.message : null) || 'Failed to create auth session');
473
- } finally {
474
- setIsLoading(false);
475
- }
476
- }, [oxyServices, connectSocket, startPolling, clientId]);
477
-
478
- // Open the central Oxy auth approval surface for this device-flow session.
479
- // On web this is a centered popup; on native it opens the system browser,
480
- // carrying a `redirect_uri` so the deep-link return path can complete the
481
- // flow even if the socket/poll is slow.
482
- const openAuthApproval = useCallback(async () => {
483
- if (!authSession) return;
484
-
485
- const authBaseUrl = resolveAuthWebBaseUrl(
486
- oxyServices.getBaseURL(),
487
- oxyServices.config?.authWebUrl,
488
- );
489
- const webUrl = new URL('/authorize', authBaseUrl);
490
- webUrl.searchParams.set('token', authSession.sessionToken);
491
-
492
- if (Platform.OS === 'web') {
493
- // Open a separate approval window on web for the device-flow token.
494
- const width = 500;
495
- const height = 650;
496
- const screenWidth = window.screen?.width ?? width;
497
- const screenHeight = window.screen?.height ?? height;
498
- const left = (screenWidth - width) / 2;
499
- const top = (screenHeight - height) / 2;
500
-
501
- window.open(
502
- webUrl.toString(),
503
- 'oxy-auth-approval',
504
- `width=${width},height=${height},left=${left},top=${top}`,
505
- );
506
- return;
507
- }
508
-
509
- // Native: carry a redirect URI so the auth web flow can bounce back in.
510
- const redirectUri = await resolveAuthRedirectUri(oxyServices.config?.authRedirectUri);
511
- if (redirectUri) {
512
- webUrl.searchParams.set('redirect_uri', redirectUri);
513
- }
514
-
515
- try {
516
- await Linking.openURL(webUrl.toString());
517
- } catch (err) {
518
- debug.error('Unable to open Oxy Auth:', err);
519
- setError('Unable to open Oxy Auth. Please try again or use the QR code.');
520
- }
521
- }, [authSession, oxyServices]);
522
-
523
- // Same-device "Sign in with Oxy" handoff: deep-link to the `qrPayload`
524
- // (`oxycommons://approve?...`) so the native Oxy identity app opens directly
525
- // to approve. The socket / poll already resolves the flow once the approval
526
- // lands, so this only needs to launch the deep link. No-op when the handoff
527
- // backend returned no `qrPayload`.
528
- const openSameDeviceApproval = useCallback(async () => {
529
- if (!qrPayload) {
530
- return;
531
- }
532
- try {
533
- await Linking.openURL(qrPayload);
534
- } catch (err) {
535
- debug.error('Unable to open the Oxy app for approval:', err);
536
- setError('Unable to open the Oxy app. Scan the QR code from another device instead.');
537
- }
538
- }, [qrPayload]);
539
-
540
- // Tear down and recreate the session (the retry action).
541
- const retry = useCallback(() => {
542
- cleanup();
543
- generateAuthSession();
544
- }, [generateAuthSession, cleanup]);
545
-
546
- // Handle a native deep-link return carrying the authorized session_id.
547
- const handleAuthRedirect = useCallback(
548
- (url: string) => {
549
- const params = getRedirectParams(url);
550
- if (!params) {
551
- return;
552
- }
553
-
554
- if (params.error) {
555
- cleanup();
556
- setError('Authorization was denied.');
557
- return;
558
- }
559
-
560
- if (params.sessionId) {
561
- // The deep-link return carries only `session_id` — the secret
562
- // `sessionToken` for this flow lives in component state (generated in
563
- // `generateAuthSession`). Without it we cannot claim the first access
564
- // token, so the flow would 401 in `handleAuthSuccess`. If it is somehow
565
- // unavailable, fall through to the socket/poll path (which carries the
566
- // token in closure) rather than attempting an unauthenticated claim.
567
- const flowSessionToken = authSession?.sessionToken;
568
- if (!flowSessionToken) {
569
- return;
570
- }
571
- cleanup();
572
- handleAuthSuccess(params.sessionId, flowSessionToken);
573
- }
574
- },
575
- [authSession, cleanup, handleAuthSuccess],
576
- );
577
-
578
- // Initialize the auth session once on mount.
579
- // biome-ignore lint/correctness/useExhaustiveDependencies: this must run exactly once on mount; re-running on `generateAuthSession` identity changes would recreate the session mid-flow.
580
- useEffect(() => {
581
- generateAuthSession();
582
- // eslint-disable-next-line react-hooks/exhaustive-deps
583
- }, []);
584
-
585
- // Native-only: handle the deep-link return from the auth web flow. Gated to
586
- // native so the web container never carries this listener. The socket / poll
587
- // path still resolves the flow if no deep link arrives.
588
- useEffect(() => {
589
- if (Platform.OS === 'web') {
590
- return;
591
- }
592
-
593
- const subscription = Linking.addEventListener('url', ({ url }) => {
594
- linkingHandledRef.current = true;
595
- handleAuthRedirect(url);
596
- });
597
-
598
- Linking.getInitialURL()
599
- .then((url) => {
600
- if (url && !linkingHandledRef.current) {
601
- handleAuthRedirect(url);
602
- }
603
- })
604
- .catch(() => {
605
- // Ignore linking errors; auth will still resolve via socket/polling.
606
- });
607
-
608
- return () => {
609
- subscription.remove();
610
- };
611
- }, [handleAuthRedirect]);
612
-
613
- // Clean up subscriptions on unmount.
614
- useEffect(() => {
615
- return () => {
616
- cleanup();
617
- };
618
- }, [cleanup]);
619
-
620
- const qrData = authSession ? `oxyauth://${authSession.sessionToken}` : '';
621
-
622
- return {
623
- authSession,
624
- qrData,
625
- authorizeCode,
626
- qrPayload,
627
- isLoading,
628
- error,
629
- isWaiting,
630
- openAuthApproval,
631
- openSameDeviceApproval,
632
- retry,
633
- cleanup,
634
- };
635
- }