@oxyhq/services 16.0.0 → 18.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 (278) hide show
  1. package/README.md +77 -98
  2. package/lib/commonjs/index.js +40 -51
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/ui/client.js +0 -14
  5. package/lib/commonjs/ui/client.js.map +1 -1
  6. package/lib/commonjs/ui/components/OxyAccountDialog.js +742 -0
  7. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -0
  8. package/lib/commonjs/ui/components/OxyConsentScreen.js +461 -0
  9. package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -0
  10. package/lib/commonjs/ui/components/OxyProvider.js +13 -6
  11. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  12. package/lib/commonjs/ui/components/OxySignInButton.js +180 -22
  13. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  14. package/lib/commonjs/ui/components/ProfileButton.js +25 -112
  15. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  16. package/lib/commonjs/ui/components/RequireOxyAuth.js +299 -0
  17. package/lib/commonjs/ui/components/RequireOxyAuth.js.map +1 -0
  18. package/lib/commonjs/ui/components/oauthNavigation.js +73 -0
  19. package/lib/commonjs/ui/components/oauthNavigation.js.map +1 -0
  20. package/lib/commonjs/ui/context/OxyContext.js +122 -4
  21. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  22. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +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/ManageAccountScreen.js +4 -3
  34. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  35. package/lib/commonjs/ui/session/createSessionClient.js +10 -2
  36. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  37. package/lib/module/index.js +19 -16
  38. package/lib/module/index.js.map +1 -1
  39. package/lib/module/ui/client.js +1 -2
  40. package/lib/module/ui/client.js.map +1 -1
  41. package/lib/module/ui/components/OxyAccountDialog.js +739 -0
  42. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -0
  43. package/lib/module/ui/components/OxyConsentScreen.js +454 -0
  44. package/lib/module/ui/components/OxyConsentScreen.js.map +1 -0
  45. package/lib/module/ui/components/OxyProvider.js +13 -6
  46. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  47. package/lib/module/ui/components/OxySignInButton.js +181 -22
  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/components/oauthNavigation.js +70 -0
  54. package/lib/module/ui/components/oauthNavigation.js.map +1 -0
  55. package/lib/module/ui/context/OxyContext.js +123 -5
  56. package/lib/module/ui/context/OxyContext.js.map +1 -1
  57. package/lib/module/ui/hooks/mutations/useAccountMutations.js +1 -1
  58. package/lib/module/ui/hooks/useAuth.js +19 -30
  59. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  60. package/lib/module/ui/hooks/useSwitchableAccounts.js +38 -267
  61. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
  62. package/lib/module/ui/index.js +1 -5
  63. package/lib/module/ui/index.js.map +1 -1
  64. package/lib/module/ui/navigation/accountDialogManager.js +81 -0
  65. package/lib/module/ui/navigation/accountDialogManager.js.map +1 -0
  66. package/lib/module/ui/navigation/routes.js +0 -2
  67. package/lib/module/ui/navigation/routes.js.map +1 -1
  68. package/lib/module/ui/screens/ManageAccountScreen.js +4 -3
  69. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  70. package/lib/module/ui/session/createSessionClient.js +10 -2
  71. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  72. package/lib/typescript/commonjs/index.d.ts +8 -12
  73. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/ui/client.d.ts +0 -4
  75. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +51 -0
  77. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts +45 -0
  79. package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts.map +1 -0
  80. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +2 -2
  81. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts +56 -0
  83. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -23
  85. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts +55 -0
  87. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.map +1 -0
  88. package/lib/typescript/commonjs/ui/components/oauthNavigation.d.ts +36 -0
  89. package/lib/typescript/commonjs/ui/components/oauthNavigation.d.ts.map +1 -0
  90. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +27 -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.map +1 -1
  93. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts +1 -1
  94. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +13 -152
  97. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/ui/index.d.ts +2 -5
  99. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +49 -0
  101. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -0
  102. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  103. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  104. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
  105. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +9 -2
  107. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/types/navigation.d.ts +27 -0
  109. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  110. package/lib/typescript/module/index.d.ts +8 -12
  111. package/lib/typescript/module/index.d.ts.map +1 -1
  112. package/lib/typescript/module/ui/client.d.ts +0 -4
  113. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  114. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +51 -0
  115. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -0
  116. package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts +45 -0
  117. package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts.map +1 -0
  118. package/lib/typescript/module/ui/components/OxyProvider.d.ts +2 -2
  119. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  120. package/lib/typescript/module/ui/components/OxySignInButton.d.ts +56 -0
  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/components/oauthNavigation.d.ts +36 -0
  127. package/lib/typescript/module/ui/components/oauthNavigation.d.ts.map +1 -0
  128. package/lib/typescript/module/ui/context/OxyContext.d.ts +27 -1
  129. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  130. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  131. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts +1 -1
  132. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  133. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  134. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +13 -152
  135. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  136. package/lib/typescript/module/ui/index.d.ts +2 -5
  137. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  138. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +49 -0
  139. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -0
  140. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  141. package/lib/typescript/module/ui/navigation/routes.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 +27 -0
  147. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  148. package/package.json +4 -4
  149. package/src/index.ts +33 -27
  150. package/src/ui/client.ts +0 -4
  151. package/src/ui/components/OxyAccountDialog.tsx +735 -0
  152. package/src/ui/components/OxyConsentScreen.tsx +456 -0
  153. package/src/ui/components/OxyProvider.tsx +14 -6
  154. package/src/ui/components/OxySignInButton.tsx +227 -22
  155. package/src/ui/components/ProfileButton.tsx +23 -126
  156. package/src/ui/components/RequireOxyAuth.tsx +266 -0
  157. package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +130 -0
  158. package/src/ui/components/oauthNavigation.ts +89 -0
  159. package/src/ui/context/OxyContext.tsx +176 -9
  160. package/src/ui/context/__tests__/OxyContext.coldBoot.test.tsx +165 -0
  161. package/src/ui/hooks/mutations/useAccountMutations.ts +1 -1
  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 +0 -4
  167. package/src/ui/screens/ManageAccountScreen.tsx +3 -2
  168. package/src/ui/session/createSessionClient.ts +9 -1
  169. package/src/ui/types/navigation.ts +27 -0
  170. package/lib/commonjs/ui/components/AccountMenu.js +0 -598
  171. package/lib/commonjs/ui/components/AccountMenu.js.map +0 -1
  172. package/lib/commonjs/ui/components/AccountMenuButton.js +0 -135
  173. package/lib/commonjs/ui/components/AccountMenuButton.js.map +0 -1
  174. package/lib/commonjs/ui/components/AccountSwitcher.js +0 -780
  175. package/lib/commonjs/ui/components/AccountSwitcher.js.map +0 -1
  176. package/lib/commonjs/ui/components/AnotherDeviceQR.js +0 -116
  177. package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +0 -1
  178. package/lib/commonjs/ui/components/ProfileMenu.js +0 -518
  179. package/lib/commonjs/ui/components/ProfileMenu.js.map +0 -1
  180. package/lib/commonjs/ui/components/SignInAccountChooser.js +0 -189
  181. package/lib/commonjs/ui/components/SignInAccountChooser.js.map +0 -1
  182. package/lib/commonjs/ui/components/SignInModal.js +0 -600
  183. package/lib/commonjs/ui/components/SignInModal.js.map +0 -1
  184. package/lib/commonjs/ui/hooks/useOxyAuthSession.js +0 -501
  185. package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +0 -1
  186. package/lib/commonjs/ui/hooks/usePasswordSignIn.js +0 -170
  187. package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +0 -1
  188. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +0 -51
  189. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +0 -1
  190. package/lib/commonjs/ui/screens/OxyAuthScreen.js +0 -312
  191. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +0 -1
  192. package/lib/commonjs/utils/deviceFlowSignIn.js +0 -109
  193. package/lib/commonjs/utils/deviceFlowSignIn.js.map +0 -1
  194. package/lib/module/ui/components/AccountMenu.js +0 -593
  195. package/lib/module/ui/components/AccountMenu.js.map +0 -1
  196. package/lib/module/ui/components/AccountMenuButton.js +0 -130
  197. package/lib/module/ui/components/AccountMenuButton.js.map +0 -1
  198. package/lib/module/ui/components/AccountSwitcher.js +0 -774
  199. package/lib/module/ui/components/AccountSwitcher.js.map +0 -1
  200. package/lib/module/ui/components/AnotherDeviceQR.js +0 -110
  201. package/lib/module/ui/components/AnotherDeviceQR.js.map +0 -1
  202. package/lib/module/ui/components/ProfileMenu.js +0 -514
  203. package/lib/module/ui/components/ProfileMenu.js.map +0 -1
  204. package/lib/module/ui/components/SignInAccountChooser.js +0 -183
  205. package/lib/module/ui/components/SignInAccountChooser.js.map +0 -1
  206. package/lib/module/ui/components/SignInModal.js +0 -591
  207. package/lib/module/ui/components/SignInModal.js.map +0 -1
  208. package/lib/module/ui/hooks/useOxyAuthSession.js +0 -495
  209. package/lib/module/ui/hooks/useOxyAuthSession.js.map +0 -1
  210. package/lib/module/ui/hooks/usePasswordSignIn.js +0 -165
  211. package/lib/module/ui/hooks/usePasswordSignIn.js.map +0 -1
  212. package/lib/module/ui/screens/AccountSwitcherScreen.js +0 -48
  213. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +0 -1
  214. package/lib/module/ui/screens/OxyAuthScreen.js +0 -306
  215. package/lib/module/ui/screens/OxyAuthScreen.js.map +0 -1
  216. package/lib/module/utils/deviceFlowSignIn.js +0 -106
  217. package/lib/module/utils/deviceFlowSignIn.js.map +0 -1
  218. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +0 -47
  219. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +0 -1
  220. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts +0 -26
  221. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts.map +0 -1
  222. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +0 -46
  223. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +0 -1
  224. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +0 -34
  225. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +0 -1
  226. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +0 -49
  227. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +0 -1
  228. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +0 -29
  229. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +0 -1
  230. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +0 -28
  231. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +0 -1
  232. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +0 -105
  233. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
  234. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +0 -31
  235. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
  236. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts +0 -11
  237. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
  238. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +0 -22
  239. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +0 -1
  240. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +0 -97
  241. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +0 -1
  242. package/lib/typescript/module/ui/components/AccountMenu.d.ts +0 -47
  243. package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +0 -1
  244. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts +0 -26
  245. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts.map +0 -1
  246. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +0 -46
  247. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +0 -1
  248. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +0 -34
  249. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +0 -1
  250. package/lib/typescript/module/ui/components/ProfileMenu.d.ts +0 -49
  251. package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +0 -1
  252. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +0 -29
  253. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +0 -1
  254. package/lib/typescript/module/ui/components/SignInModal.d.ts +0 -28
  255. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +0 -1
  256. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +0 -105
  257. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
  258. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +0 -31
  259. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
  260. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts +0 -11
  261. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
  262. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +0 -22
  263. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +0 -1
  264. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +0 -97
  265. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +0 -1
  266. package/src/ui/components/AccountMenu.tsx +0 -645
  267. package/src/ui/components/AccountMenuButton.tsx +0 -126
  268. package/src/ui/components/AccountSwitcher.tsx +0 -751
  269. package/src/ui/components/AnotherDeviceQR.tsx +0 -119
  270. package/src/ui/components/ProfileMenu.tsx +0 -564
  271. package/src/ui/components/SignInAccountChooser.tsx +0 -162
  272. package/src/ui/components/SignInModal.tsx +0 -530
  273. package/src/ui/hooks/useOxyAuthSession.ts +0 -635
  274. package/src/ui/hooks/usePasswordSignIn.ts +0 -207
  275. package/src/ui/screens/AccountSwitcherScreen.tsx +0 -43
  276. package/src/ui/screens/OxyAuthScreen.tsx +0 -324
  277. package/src/utils/__tests__/deviceFlowSignIn.test.ts +0 -231
  278. package/src/utils/deviceFlowSignIn.ts +0 -152
@@ -9,6 +9,7 @@ import {
9
9
  useState,
10
10
  type ReactNode,
11
11
  } from 'react';
12
+ import { Linking } from 'react-native';
12
13
  import { OxyServices, oxyClient } from '@oxyhq/core';
13
14
  import type {
14
15
  User,
@@ -19,14 +20,21 @@ import type {
19
20
  ClientSession,
20
21
  AuthStateStore,
21
22
  PersistedAuthState,
23
+ AccountDialogController,
24
+ AccountDialogView,
22
25
  } from '@oxyhq/core';
23
26
  import {
24
27
  KeyManager,
25
28
  runSessionColdBoot,
26
29
  installAuthRefreshHandler,
27
30
  startTokenRefreshScheduler,
31
+ createAccountDialogController,
28
32
  logger as loggerUtil,
29
33
  } from '@oxyhq/core';
34
+ import {
35
+ registerAccountDialogControls,
36
+ notifyAccountDialogVisibility,
37
+ } from '../navigation/accountDialogManager';
30
38
  import { useAuthStore, type AuthState } from '../stores/authStore';
31
39
  import { useShallow } from 'zustand/react/shallow';
32
40
  import type { UseFollowHook } from '../hooks/useFollow.types';
@@ -157,6 +165,22 @@ export interface OxyContextState {
157
165
  showBottomSheet?: (screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> }) => void;
158
166
  openAvatarPicker: () => void;
159
167
 
168
+ // Unified account dialog (the single switcher + sign-in surface). The headless
169
+ // state machine lives in `@oxyhq/core`; `OxyAccountDialog` (mounted by
170
+ // `OxyProvider`) binds to it. `null` in the no-provider loading state.
171
+ /** The headless controller driving {@link openAccountDialog}. `null` before mount. */
172
+ accountDialogController: AccountDialogController | null;
173
+ /** Whether the unified account dialog is currently presented. */
174
+ isAccountDialogOpen: boolean;
175
+ /**
176
+ * Open the unified account dialog. `accounts` (default) shows the switcher;
177
+ * `signin` / `add` open the sign-in entry. Replaces every prior device/account
178
+ * surface and the standalone sign-in modal.
179
+ */
180
+ openAccountDialog: (view?: AccountDialogView) => void;
181
+ /** Dismiss the unified account dialog and cancel any in-flight sign-in flow. */
182
+ closeAccountDialog: () => void;
183
+
160
184
  // Unified account graph (self, owned orgs/projects/bots, accounts shared with
161
185
  // the caller). The cryptographic Commons/DID "identity" is a SEPARATE concept.
162
186
  //
@@ -210,6 +234,20 @@ export interface OxyContextProviderProps {
210
234
  * for the cross-app device sign-in flow. See {@link OxyContextState.clientId}.
211
235
  */
212
236
  clientId?: string;
237
+ /**
238
+ * Whether this provider is the device-first **session authority**. `true`
239
+ * (default) runs `runSessionColdBoot` on mount and opens the signed-out
240
+ * device-state socket — the correct behavior for every Relying Party app.
241
+ *
242
+ * `false` is the IdP host (`auth.oxy.so`) opt-out: the IdP is NOT a session
243
+ * authority (handoff "IdP vs RP"), so it must NOT restore or reproject an
244
+ * ambient device session. With `coldBoot={false}` the cold boot never runs
245
+ * and the signed-out device socket never opens; auth resolves immediately as
246
+ * signed out. Interactive sign-in still commits a normal session on this
247
+ * origin — only the automatic restore/reproject is suppressed.
248
+ * @default true
249
+ */
250
+ coldBoot?: boolean;
213
251
  onAuthStateChange?: (user: User | null) => void;
214
252
  onError?: (error: ApiError) => void;
215
253
  }
@@ -312,6 +350,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
312
350
  authRedirectUri,
313
351
  storageKeyPrefix = 'oxy_session',
314
352
  clientId: clientIdProp,
353
+ coldBoot = true,
315
354
  onAuthStateChange,
316
355
  onError,
317
356
  }) => {
@@ -367,6 +406,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
367
406
  userRef.current = user;
368
407
  isAuthenticatedRef.current = isAuthenticated;
369
408
  const [initialized, setInitialized] = useState(false);
409
+ const [accountDialogOpen, setAccountDialogOpen] = useState(false);
370
410
  const setAuthState = useAuthStore.setState;
371
411
 
372
412
  // Keep the shared `oxyClient` singleton's token store in lockstep with the
@@ -505,18 +545,47 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
505
545
  const markAuthResolvedRef = useRef(markAuthResolved);
506
546
  markAuthResolvedRef.current = markAuthResolved;
507
547
 
548
+ // Re-boot indirection: the SessionClient is built in the ref initializer below
549
+ // (before `runColdBoot` is defined), but its `onSessionAppeared` must re-run the
550
+ // cold boot to self-acquire when a sibling signs in on this device. A ref bridges
551
+ // the ordering; it is assigned right after `runColdBoot` is declared.
552
+ const runColdBootRef = useRef<(() => Promise<void>) | null>(null);
553
+
508
554
  // Server-authoritative device session client. Built ONCE per `oxyServices`
509
555
  // instance. `onUnauthenticated` (a device signout-all pushed over the socket,
510
556
  // or bootstrapped as zero-account) clears the persisted store + local state so
511
557
  // a reload does not try to restore a session the device no longer has.
558
+ // `signedOutSocketAuth` + `onSessionAppeared` power the signed-out sync channel:
559
+ // an idle tab joins its `device:<id>` room (web via the `oxy_device` cookie,
560
+ // native via the shared device token) and self-acquires on a sibling sign-in.
512
561
  const sessionClientPairRef = useRef<ReturnType<typeof createSessionClient> | null>(null);
513
562
  if (!sessionClientPairRef.current) {
514
- sessionClientPairRef.current = createSessionClient(oxyServices, authStore, () => {
515
- clearPersistedAuthSafe(authStore, logger);
516
- void clearSessionStateRef.current().catch((clearError) => {
517
- logger('Failed to clear local state on remote sign-out', clearError);
518
- });
519
- });
563
+ sessionClientPairRef.current = createSessionClient(
564
+ oxyServices,
565
+ authStore,
566
+ () => {
567
+ clearPersistedAuthSafe(authStore, logger);
568
+ void clearSessionStateRef.current().catch((clearError) => {
569
+ logger('Failed to clear local state on remote sign-out', clearError);
570
+ });
571
+ },
572
+ {
573
+ signedOutSocketAuth: async () => {
574
+ // Web (`*.oxy.so`): the first-party `oxy_device` cookie rides the
575
+ // same-site handshake automatically — just connect.
576
+ if (isWebBrowser()) return true;
577
+ // Native (no cookie jar): present the shared device token if we have one.
578
+ try {
579
+ return (await authStore.loadDeviceToken()) ?? false;
580
+ } catch {
581
+ return false;
582
+ }
583
+ },
584
+ onSessionAppeared: () => {
585
+ void runColdBootRef.current?.();
586
+ },
587
+ },
588
+ );
520
589
  }
521
590
  const { client: sessionClient, host: sessionClientHost } = sessionClientPairRef.current;
522
591
 
@@ -804,6 +873,61 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
804
873
  [commitSession],
805
874
  );
806
875
 
876
+ // ── Unified account dialog ─────────────────────────────────────────────────
877
+ // The single account-chooser + sign-in surface. Built ONCE per provider mount
878
+ // and bound to the live `oxyServices` + `sessionClient` + this provider's
879
+ // `handleWebSession` commit funnel. `handleWebSession` is threaded through a ref
880
+ // so the controller keeps a STABLE `commitSession` (rebuilding the controller
881
+ // on every commit-identity change would drop its subscription + state).
882
+ const handleWebSessionRef = useRef(handleWebSession);
883
+ handleWebSessionRef.current = handleWebSession;
884
+
885
+ const accountDialogControllerRef = useRef<AccountDialogController | null>(null);
886
+ if (!accountDialogControllerRef.current) {
887
+ accountDialogControllerRef.current = createAccountDialogController({
888
+ oxyServices,
889
+ sessionClient,
890
+ clientId,
891
+ locale: currentLanguage,
892
+ commitSession: (session) => handleWebSessionRef.current(session),
893
+ onSignedIn: () => setAccountDialogOpen(false),
894
+ openUrl: (url) => {
895
+ void Linking.openURL(url);
896
+ },
897
+ });
898
+ }
899
+ const accountDialogController = accountDialogControllerRef.current;
900
+
901
+ const openAccountDialog = useCallback((view?: AccountDialogView): void => {
902
+ accountDialogControllerRef.current?.setView(view ?? 'accounts');
903
+ setAccountDialogOpen(true);
904
+ }, []);
905
+
906
+ const closeAccountDialog = useCallback((): void => {
907
+ accountDialogControllerRef.current?.cancelSignIn();
908
+ setAccountDialogOpen(false);
909
+ }, []);
910
+
911
+ // Start driving the dialog on mount; tear it down on unmount.
912
+ useEffect(() => {
913
+ const controller = accountDialogControllerRef.current;
914
+ controller?.start();
915
+ return () => controller?.destroy();
916
+ }, []);
917
+
918
+ // Expose the live open/close controls to the imperative manager so
919
+ // `showSignInModal()` (and any app-level imperative "sign in" handler) works.
920
+ useEffect(
921
+ () => registerAccountDialogControls({ open: openAccountDialog, close: closeAccountDialog }),
922
+ [openAccountDialog, closeAccountDialog],
923
+ );
924
+
925
+ // Broadcast visibility so `OxySignInButton`'s "Signing in…" affordance stays
926
+ // accurate regardless of what opened or dismissed the dialog.
927
+ useEffect(() => {
928
+ notifyAccountDialogVisibility(accountDialogOpen);
929
+ }, [accountDialogOpen]);
930
+
807
931
  // Keyless password sign-in: username/email + password.
808
932
  const signInWithPassword = useCallback(
809
933
  async (
@@ -908,6 +1032,18 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
908
1032
  },
909
1033
  onSignedOut: () => {
910
1034
  markAuthResolvedRef.current();
1035
+ // Open the signed-out realtime channel so this idle tab joins its
1036
+ // `device:<id>` room (web `oxy_device` cookie / native device token)
1037
+ // and self-acquires when a sibling signs in. Idempotent, best-effort.
1038
+ void sessionClient.start().catch((startError) => {
1039
+ if (__DEV__) {
1040
+ loggerUtil.debug(
1041
+ 'signed-out socket start failed (non-fatal)',
1042
+ { component: 'OxyContext', method: 'runColdBoot' },
1043
+ startError as unknown,
1044
+ );
1045
+ }
1046
+ });
911
1047
  },
912
1048
  onStepError: (id, error) => {
913
1049
  if (__DEV__) {
@@ -931,10 +1067,26 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
931
1067
  // Backstop: resolve on every exit path so the gate can never hang.
932
1068
  markAuthResolvedRef.current();
933
1069
  }
934
- }, [oxyServices, authStore]);
1070
+ }, [oxyServices, authStore, sessionClient]);
1071
+ // Bridge for `onSessionAppeared` (SessionClient) → re-run the cold boot to
1072
+ // self-acquire when a sibling signs in on this device while this tab is idle.
1073
+ runColdBootRef.current = runColdBoot;
935
1074
 
936
1075
  useEffect(() => {
937
- if (!storage || initialized) {
1076
+ if (initialized) {
1077
+ return;
1078
+ }
1079
+ // IdP mode (`coldBoot={false}`): this provider is NOT the ecosystem session
1080
+ // authority, so it never runs the device-first restore and never opens the
1081
+ // signed-out device-state socket (`runColdBoot` → `onSignedOut` is the sole
1082
+ // place that socket starts). Resolve auth immediately as signed out so there
1083
+ // is no boot spinner; a deliberate sign-in still commits a normal session.
1084
+ if (!coldBoot) {
1085
+ setInitialized(true);
1086
+ markAuthResolved();
1087
+ return;
1088
+ }
1089
+ if (!storage) {
938
1090
  return;
939
1091
  }
940
1092
  setInitialized(true);
@@ -943,7 +1095,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
943
1095
  logger('Cold boot failed', error);
944
1096
  }
945
1097
  });
946
- }, [runColdBoot, storage, initialized, logger]);
1098
+ }, [coldBoot, runColdBoot, storage, initialized, logger, markAuthResolved]);
947
1099
 
948
1100
  // Exposed `refreshSessions`: re-bootstrap the server-authoritative device
949
1101
  // state and reproject — the manual counterpart to the realtime socket.
@@ -1018,6 +1170,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1018
1170
  useEffect(() => {
1019
1171
  if (isAuthenticated && initialized && tokenReady) {
1020
1172
  refreshAccounts();
1173
+ // Reload the dialog's own account graph too, so a session restored OUTSIDE
1174
+ // the dialog (cold boot / password sign-in) surfaces its graph accounts.
1175
+ void accountDialogControllerRef.current?.refresh();
1021
1176
  }
1022
1177
  }, [isAuthenticated, initialized, tokenReady, refreshAccounts]);
1023
1178
 
@@ -1115,6 +1270,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1115
1270
  useFollow: useFollowHook,
1116
1271
  showBottomSheet: showBottomSheetForContext,
1117
1272
  openAvatarPicker,
1273
+ accountDialogController,
1274
+ isAccountDialogOpen: accountDialogOpen,
1275
+ openAccountDialog,
1276
+ closeAccountDialog,
1118
1277
  accounts,
1119
1278
  switchToAccount,
1120
1279
  refreshAccounts,
@@ -1159,6 +1318,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1159
1318
  useFollowHook,
1160
1319
  showBottomSheetForContext,
1161
1320
  openAvatarPicker,
1321
+ accountDialogController,
1322
+ accountDialogOpen,
1323
+ openAccountDialog,
1324
+ closeAccountDialog,
1162
1325
  accounts,
1163
1326
  switchToAccount,
1164
1327
  refreshAccounts,
@@ -1221,6 +1384,10 @@ const LOADING_STATE: OxyContextState = {
1221
1384
  clientId: null,
1222
1385
  oxyServices: LOADING_STATE_OXY_SERVICES,
1223
1386
  openAvatarPicker: () => {},
1387
+ accountDialogController: null,
1388
+ isAccountDialogOpen: false,
1389
+ openAccountDialog: () => {},
1390
+ closeAccountDialog: () => {},
1224
1391
  accounts: [],
1225
1392
  switchToAccount: () => rejectMissingProvider<void>(),
1226
1393
  refreshAccounts: () => rejectMissingProvider<void>(),
@@ -0,0 +1,165 @@
1
+ /**
2
+ * `OxyContextProvider` cold-boot opt-out (IdP mode).
3
+ *
4
+ * The provider is the ecosystem's device-first session authority by default:
5
+ * it runs `runSessionColdBoot` on mount and opens the signed-out device-state
6
+ * socket. The IdP host (`auth.oxy.so`) is NOT a session authority, so it mounts
7
+ * the provider with `coldBoot={false}` — the cold boot must never run and the
8
+ * device socket must never open, yet interactive sign-in must still commit a
9
+ * normal session on that origin.
10
+ */
11
+ import { render, act, waitFor } from '@testing-library/react';
12
+ import type { ReactNode } from 'react';
13
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
14
+ import { OxyServices, runSessionColdBoot } from '@oxyhq/core';
15
+ import type { User } from '@oxyhq/core';
16
+ import type { LoginSessionResult } from '@oxyhq/contracts';
17
+ import { OxyContextProvider, useOxy, type OxyContextState } from '../OxyContext';
18
+ import { createSessionClient } from '../../session';
19
+
20
+ // Spy on the device-first cold boot without touching the rest of core.
21
+ jest.mock('@oxyhq/core', () => {
22
+ const actual = jest.requireActual('@oxyhq/core');
23
+ return {
24
+ __esModule: true,
25
+ ...actual,
26
+ runSessionColdBoot: jest.fn(() => Promise.resolve()),
27
+ };
28
+ });
29
+
30
+ // Replace the real SessionClient with an inert fake so no socket connects and
31
+ // no network is hit. `getState()` returns null, so `syncFromClient` short-
32
+ // circuits and never reaches profile resolution.
33
+ jest.mock('../../session', () => {
34
+ const actual = jest.requireActual('../../session');
35
+ return {
36
+ __esModule: true,
37
+ ...actual,
38
+ createSessionClient: jest.fn(() => {
39
+ const listeners = new Set<() => void>();
40
+ const client = {
41
+ getState: jest.fn(() => null),
42
+ subscribe: jest.fn((listener: () => void) => {
43
+ listeners.add(listener);
44
+ return () => {
45
+ listeners.delete(listener);
46
+ };
47
+ }),
48
+ start: jest.fn(() => Promise.resolve()),
49
+ bootstrap: jest.fn(() => Promise.resolve()),
50
+ addCurrentAccount: jest.fn(() => Promise.resolve()),
51
+ registerAndActivate: jest.fn(() => Promise.resolve()),
52
+ switchAccount: jest.fn(() => Promise.resolve()),
53
+ };
54
+ const host = { setCurrentAccountId: jest.fn() };
55
+ return { client, host };
56
+ }),
57
+ };
58
+ });
59
+
60
+ const coldBootMock = runSessionColdBoot as jest.Mock;
61
+ const createSessionClientMock = createSessionClient as jest.Mock;
62
+
63
+ function renderProvider(props: { coldBoot?: boolean; oxyServices: OxyServices }) {
64
+ let latest: OxyContextState | null = null;
65
+ const Probe = (): ReactNode => {
66
+ latest = useOxy();
67
+ return null;
68
+ };
69
+ const queryClient = new QueryClient({
70
+ defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
71
+ });
72
+ const view = render(
73
+ <QueryClientProvider client={queryClient}>
74
+ <OxyContextProvider oxyServices={props.oxyServices} coldBoot={props.coldBoot}>
75
+ <Probe />
76
+ </OxyContextProvider>
77
+ </QueryClientProvider>,
78
+ );
79
+ return { ...view, getState: (): OxyContextState | null => latest };
80
+ }
81
+
82
+ /** The most recently constructed fake SessionClient. */
83
+ function lastSessionClient(): { start: jest.Mock } {
84
+ const results = createSessionClientMock.mock.results;
85
+ return results[results.length - 1].value.client as { start: jest.Mock };
86
+ }
87
+
88
+ beforeEach(() => {
89
+ coldBootMock.mockClear();
90
+ createSessionClientMock.mockClear();
91
+ });
92
+
93
+ describe('OxyContextProvider coldBoot opt-out (IdP mode)', () => {
94
+ it('coldBoot={false}: never runs the cold boot and resolves auth immediately as signed out', async () => {
95
+ const oxy = new OxyServices({ baseURL: 'https://api.test.local' });
96
+ const { getState } = renderProvider({ coldBoot: false, oxyServices: oxy });
97
+
98
+ await waitFor(() => {
99
+ expect(getState()?.isAuthResolved).toBe(true);
100
+ });
101
+
102
+ expect(coldBootMock).not.toHaveBeenCalled();
103
+ expect(getState()?.isAuthenticated).toBe(false);
104
+ expect(getState()?.user).toBeNull();
105
+ // No boot spinner: private-API readiness settles to a definitive "no".
106
+ expect(getState()?.isPrivateApiPending).toBe(false);
107
+ expect(getState()?.canUsePrivateApi).toBe(false);
108
+ // The signed-out device-state socket must never open in IdP mode.
109
+ expect(lastSessionClient().start).not.toHaveBeenCalled();
110
+ });
111
+
112
+ it('coldBoot={false}: interactive password sign-in still commits a normal session', async () => {
113
+ const oxy = new OxyServices({ baseURL: 'https://api.test.local' });
114
+ const loginResult: LoginSessionResult = {
115
+ sessionId: 's-1',
116
+ deviceId: 'd-1',
117
+ expiresAt: new Date(Date.now() + 3_600_000).toISOString(),
118
+ accessToken: 'access-1',
119
+ refreshToken: 'refresh-1',
120
+ user: { id: 'u-1', username: 'alice' },
121
+ };
122
+ jest.spyOn(oxy, 'passwordSignIn').mockResolvedValue(loginResult);
123
+ jest.spyOn(oxy, 'getCurrentUser').mockResolvedValue({
124
+ id: 'u-1',
125
+ username: 'alice',
126
+ name: { displayName: 'Alice' },
127
+ } as User);
128
+
129
+ const { getState } = renderProvider({ coldBoot: false, oxyServices: oxy });
130
+ await waitFor(() => {
131
+ expect(getState()?.isAuthResolved).toBe(true);
132
+ });
133
+
134
+ const state = getState();
135
+ if (!state) {
136
+ throw new Error('context did not resolve');
137
+ }
138
+ await act(async () => {
139
+ const result = await state.signInWithPassword('alice', 'secret');
140
+ expect(result.status).toBe('ok');
141
+ });
142
+
143
+ await waitFor(() => {
144
+ expect(getState()?.isAuthenticated).toBe(true);
145
+ });
146
+ expect(getState()?.user?.id).toBe('u-1');
147
+ expect(oxy.passwordSignIn).toHaveBeenCalledWith(
148
+ 'alice',
149
+ 'secret',
150
+ expect.objectContaining({ deviceToken: undefined }),
151
+ );
152
+ });
153
+
154
+ it('default (coldBoot omitted): runs the device-first cold boot', async () => {
155
+ const oxy = new OxyServices({ baseURL: 'https://api.test.local' });
156
+ const { getState } = renderProvider({ oxyServices: oxy });
157
+
158
+ await waitFor(() => {
159
+ expect(coldBootMock).toHaveBeenCalledTimes(1);
160
+ });
161
+ await waitFor(() => {
162
+ expect(getState()?.isAuthResolved).toBe(true);
163
+ });
164
+ });
165
+ });
@@ -109,7 +109,7 @@ export const useUpdateProfile = () => {
109
109
  refreshAvatarInStore(activeSessionId, updates.avatar, oxyServices);
110
110
  }
111
111
 
112
- // Invalidate all related queries so every consumer (AccountSwitcher,
112
+ // Invalidate all related queries so every consumer (the account dialog,
113
113
  // session lists, managed accounts, etc.) refetches the fresh profile.
114
114
  // This is critical right after `username` is set the first time, when
115
115
  // every cached "session profile" still reports the user as unnamed.
@@ -23,11 +23,10 @@
23
23
  * - Manual sign-in: signIn() redirects to the IdP (web) or opens the auth sheet (native)
24
24
  */
25
25
 
26
- import { useCallback, useState } from 'react';
26
+ import { useCallback } from 'react';
27
27
  import { useOxy } from '../context/OxyContext';
28
28
  import type { User } from '@oxyhq/core';
29
29
  import { isWebBrowser } from '../utils/isWebBrowser';
30
- import { showSignInModal } from '../components/SignInModal';
31
30
 
32
31
  export interface AuthState {
33
32
  /** Current authenticated user, null if not authenticated */
@@ -138,44 +137,33 @@ export function useAuth(): UseAuthReturn {
138
137
  getPublicKey,
139
138
  showBottomSheet,
140
139
  openAvatarPicker,
140
+ openAccountDialog,
141
141
  } = useOxy();
142
142
 
143
143
  const signIn = useCallback(async (publicKey?: string): Promise<User> => {
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.
152
- return new Promise<User>(() => undefined);
153
- }
154
-
155
- // Native: use the cryptographic identity directly when a public key is given.
144
+ // Native: sign in directly with the cryptographic identity when a public key
145
+ // is provided, or an existing keychain identity is found.
156
146
  if (publicKey) {
157
147
  return oxySignIn(publicKey);
158
148
  }
159
-
160
- // Native with an existing keychain identity: sign in with it.
161
- const hasExisting = await hasIdentity();
162
- if (hasExisting) {
163
- const existingKey = await getPublicKey();
164
- if (existingKey) {
165
- return oxySignIn(existingKey);
149
+ if (!isWebBrowser()) {
150
+ const hasExisting = await hasIdentity();
151
+ if (hasExisting) {
152
+ const existingKey = await getPublicKey();
153
+ if (existingKey) {
154
+ return oxySignIn(existingKey);
155
+ }
166
156
  }
167
157
  }
168
158
 
169
- // Native with no identity: open the auth sheet (password / QR device flow).
170
- if (showBottomSheet) {
171
- showBottomSheet('OxyAuth');
172
- return new Promise((_, reject) => {
173
- reject(new Error('Please complete sign-in in the auth sheet'));
174
- });
175
- }
176
-
177
- throw new Error('No authentication method available');
178
- }, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet]);
159
+ // Web, or native without a keychain identity: open the unified account dialog
160
+ // on its sign-in view (device flow / QR / password hand-off). There is NO
161
+ // automatic navigation to a login page — the device-first cold boot already
162
+ // restored a session if one existed. The caller reacts to `isAuthenticated`,
163
+ // so this promise intentionally never resolves.
164
+ openAccountDialog('signin');
165
+ return new Promise<User>(() => undefined);
166
+ }, [oxySignIn, hasIdentity, getPublicKey, openAccountDialog]);
179
167
 
180
168
  const signOut = useCallback(async (): Promise<void> => {
181
169
  await logout();