@oxyhq/services 16.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 (251) hide show
  1. package/lib/commonjs/index.js +21 -51
  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/useAccountMutations.js +1 -1
  18. package/lib/commonjs/ui/hooks/useAuth.js +19 -30
  19. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  20. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +37 -267
  21. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
  22. package/lib/commonjs/ui/index.js +5 -40
  23. package/lib/commonjs/ui/index.js.map +1 -1
  24. package/lib/commonjs/ui/navigation/accountDialogManager.js +91 -0
  25. package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -0
  26. package/lib/commonjs/ui/navigation/routes.js +0 -2
  27. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  28. package/lib/commonjs/ui/screens/ManageAccountScreen.js +4 -3
  29. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  30. package/lib/commonjs/ui/session/createSessionClient.js +10 -2
  31. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  32. package/lib/module/index.js +18 -15
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/ui/client.js +1 -2
  35. package/lib/module/ui/client.js.map +1 -1
  36. package/lib/module/ui/components/OxyAccountDialog.js +761 -0
  37. package/lib/module/ui/components/OxyAccountDialog.js.map +1 -0
  38. package/lib/module/ui/components/OxyProvider.js +11 -6
  39. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  40. package/lib/module/ui/components/OxySignInButton.js +10 -21
  41. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  42. package/lib/module/ui/components/ProfileButton.js +26 -112
  43. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  44. package/lib/module/ui/components/RequireOxyAuth.js +294 -0
  45. package/lib/module/ui/components/RequireOxyAuth.js.map +1 -0
  46. package/lib/module/ui/context/OxyContext.js +107 -3
  47. package/lib/module/ui/context/OxyContext.js.map +1 -1
  48. package/lib/module/ui/hooks/mutations/useAccountMutations.js +1 -1
  49. package/lib/module/ui/hooks/useAuth.js +19 -30
  50. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  51. package/lib/module/ui/hooks/useSwitchableAccounts.js +38 -267
  52. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
  53. package/lib/module/ui/index.js +1 -5
  54. package/lib/module/ui/index.js.map +1 -1
  55. package/lib/module/ui/navigation/accountDialogManager.js +81 -0
  56. package/lib/module/ui/navigation/accountDialogManager.js.map +1 -0
  57. package/lib/module/ui/navigation/routes.js +0 -2
  58. package/lib/module/ui/navigation/routes.js.map +1 -1
  59. package/lib/module/ui/screens/ManageAccountScreen.js +4 -3
  60. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  61. package/lib/module/ui/session/createSessionClient.js +10 -2
  62. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  63. package/lib/typescript/commonjs/index.d.ts +4 -11
  64. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/ui/client.d.ts +0 -4
  66. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +35 -0
  68. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -0
  69. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +2 -2
  70. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -23
  73. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts +55 -0
  75. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +13 -1
  77. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +13 -152
  81. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/ui/index.d.ts +2 -5
  83. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +49 -0
  85. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  87. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
  89. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  90. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +9 -2
  91. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/ui/types/navigation.d.ts +9 -0
  93. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  94. package/lib/typescript/module/index.d.ts +4 -11
  95. package/lib/typescript/module/index.d.ts.map +1 -1
  96. package/lib/typescript/module/ui/client.d.ts +0 -4
  97. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  98. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +35 -0
  99. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -0
  100. package/lib/typescript/module/ui/components/OxyProvider.d.ts +2 -2
  101. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  102. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  103. package/lib/typescript/module/ui/components/ProfileButton.d.ts +11 -23
  104. package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
  105. package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts +55 -0
  106. package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts.map +1 -0
  107. package/lib/typescript/module/ui/context/OxyContext.d.ts +13 -1
  108. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  109. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  110. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  111. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +13 -152
  112. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  113. package/lib/typescript/module/ui/index.d.ts +2 -5
  114. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  115. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +49 -0
  116. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -0
  117. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  118. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  119. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
  120. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  121. package/lib/typescript/module/ui/session/createSessionClient.d.ts +9 -2
  122. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  123. package/lib/typescript/module/ui/types/navigation.d.ts +9 -0
  124. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  125. package/package.json +2 -2
  126. package/src/index.ts +21 -26
  127. package/src/ui/client.ts +0 -4
  128. package/src/ui/components/OxyAccountDialog.tsx +760 -0
  129. package/src/ui/components/OxyProvider.tsx +12 -6
  130. package/src/ui/components/OxySignInButton.tsx +10 -21
  131. package/src/ui/components/ProfileButton.tsx +23 -126
  132. package/src/ui/components/RequireOxyAuth.tsx +266 -0
  133. package/src/ui/context/OxyContext.tsx +146 -7
  134. package/src/ui/hooks/mutations/useAccountMutations.ts +1 -1
  135. package/src/ui/hooks/useAuth.ts +19 -31
  136. package/src/ui/hooks/useSwitchableAccounts.ts +42 -376
  137. package/src/ui/index.ts +2 -5
  138. package/src/ui/navigation/accountDialogManager.ts +84 -0
  139. package/src/ui/navigation/routes.ts +0 -4
  140. package/src/ui/screens/ManageAccountScreen.tsx +3 -2
  141. package/src/ui/session/createSessionClient.ts +9 -1
  142. package/src/ui/types/navigation.ts +9 -0
  143. package/lib/commonjs/ui/components/AccountMenu.js +0 -598
  144. package/lib/commonjs/ui/components/AccountMenu.js.map +0 -1
  145. package/lib/commonjs/ui/components/AccountMenuButton.js +0 -135
  146. package/lib/commonjs/ui/components/AccountMenuButton.js.map +0 -1
  147. package/lib/commonjs/ui/components/AccountSwitcher.js +0 -780
  148. package/lib/commonjs/ui/components/AccountSwitcher.js.map +0 -1
  149. package/lib/commonjs/ui/components/AnotherDeviceQR.js +0 -116
  150. package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +0 -1
  151. package/lib/commonjs/ui/components/ProfileMenu.js +0 -518
  152. package/lib/commonjs/ui/components/ProfileMenu.js.map +0 -1
  153. package/lib/commonjs/ui/components/SignInAccountChooser.js +0 -189
  154. package/lib/commonjs/ui/components/SignInAccountChooser.js.map +0 -1
  155. package/lib/commonjs/ui/components/SignInModal.js +0 -600
  156. package/lib/commonjs/ui/components/SignInModal.js.map +0 -1
  157. package/lib/commonjs/ui/hooks/useOxyAuthSession.js +0 -501
  158. package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +0 -1
  159. package/lib/commonjs/ui/hooks/usePasswordSignIn.js +0 -170
  160. package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +0 -1
  161. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +0 -51
  162. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +0 -1
  163. package/lib/commonjs/ui/screens/OxyAuthScreen.js +0 -312
  164. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +0 -1
  165. package/lib/commonjs/utils/deviceFlowSignIn.js +0 -109
  166. package/lib/commonjs/utils/deviceFlowSignIn.js.map +0 -1
  167. package/lib/module/ui/components/AccountMenu.js +0 -593
  168. package/lib/module/ui/components/AccountMenu.js.map +0 -1
  169. package/lib/module/ui/components/AccountMenuButton.js +0 -130
  170. package/lib/module/ui/components/AccountMenuButton.js.map +0 -1
  171. package/lib/module/ui/components/AccountSwitcher.js +0 -774
  172. package/lib/module/ui/components/AccountSwitcher.js.map +0 -1
  173. package/lib/module/ui/components/AnotherDeviceQR.js +0 -110
  174. package/lib/module/ui/components/AnotherDeviceQR.js.map +0 -1
  175. package/lib/module/ui/components/ProfileMenu.js +0 -514
  176. package/lib/module/ui/components/ProfileMenu.js.map +0 -1
  177. package/lib/module/ui/components/SignInAccountChooser.js +0 -183
  178. package/lib/module/ui/components/SignInAccountChooser.js.map +0 -1
  179. package/lib/module/ui/components/SignInModal.js +0 -591
  180. package/lib/module/ui/components/SignInModal.js.map +0 -1
  181. package/lib/module/ui/hooks/useOxyAuthSession.js +0 -495
  182. package/lib/module/ui/hooks/useOxyAuthSession.js.map +0 -1
  183. package/lib/module/ui/hooks/usePasswordSignIn.js +0 -165
  184. package/lib/module/ui/hooks/usePasswordSignIn.js.map +0 -1
  185. package/lib/module/ui/screens/AccountSwitcherScreen.js +0 -48
  186. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +0 -1
  187. package/lib/module/ui/screens/OxyAuthScreen.js +0 -306
  188. package/lib/module/ui/screens/OxyAuthScreen.js.map +0 -1
  189. package/lib/module/utils/deviceFlowSignIn.js +0 -106
  190. package/lib/module/utils/deviceFlowSignIn.js.map +0 -1
  191. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +0 -47
  192. package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +0 -1
  193. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts +0 -26
  194. package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts.map +0 -1
  195. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +0 -46
  196. package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +0 -1
  197. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +0 -34
  198. package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +0 -1
  199. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +0 -49
  200. package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +0 -1
  201. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +0 -29
  202. package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +0 -1
  203. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +0 -28
  204. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +0 -1
  205. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +0 -105
  206. package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
  207. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +0 -31
  208. package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
  209. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts +0 -11
  210. package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
  211. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +0 -22
  212. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +0 -1
  213. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +0 -97
  214. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +0 -1
  215. package/lib/typescript/module/ui/components/AccountMenu.d.ts +0 -47
  216. package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +0 -1
  217. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts +0 -26
  218. package/lib/typescript/module/ui/components/AccountMenuButton.d.ts.map +0 -1
  219. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +0 -46
  220. package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +0 -1
  221. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +0 -34
  222. package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +0 -1
  223. package/lib/typescript/module/ui/components/ProfileMenu.d.ts +0 -49
  224. package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +0 -1
  225. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +0 -29
  226. package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +0 -1
  227. package/lib/typescript/module/ui/components/SignInModal.d.ts +0 -28
  228. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +0 -1
  229. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +0 -105
  230. package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
  231. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +0 -31
  232. package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
  233. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts +0 -11
  234. package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
  235. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +0 -22
  236. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +0 -1
  237. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +0 -97
  238. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +0 -1
  239. package/src/ui/components/AccountMenu.tsx +0 -645
  240. package/src/ui/components/AccountMenuButton.tsx +0 -126
  241. package/src/ui/components/AccountSwitcher.tsx +0 -751
  242. package/src/ui/components/AnotherDeviceQR.tsx +0 -119
  243. package/src/ui/components/ProfileMenu.tsx +0 -564
  244. package/src/ui/components/SignInAccountChooser.tsx +0 -162
  245. package/src/ui/components/SignInModal.tsx +0 -530
  246. package/src/ui/hooks/useOxyAuthSession.ts +0 -635
  247. package/src/ui/hooks/usePasswordSignIn.ts +0 -207
  248. package/src/ui/screens/AccountSwitcherScreen.tsx +0 -43
  249. package/src/ui/screens/OxyAuthScreen.tsx +0 -324
  250. package/src/utils/__tests__/deviceFlowSignIn.test.ts +0 -231
  251. 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
  //
@@ -367,6 +391,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
367
391
  userRef.current = user;
368
392
  isAuthenticatedRef.current = isAuthenticated;
369
393
  const [initialized, setInitialized] = useState(false);
394
+ const [accountDialogOpen, setAccountDialogOpen] = useState(false);
370
395
  const setAuthState = useAuthStore.setState;
371
396
 
372
397
  // Keep the shared `oxyClient` singleton's token store in lockstep with the
@@ -505,18 +530,47 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
505
530
  const markAuthResolvedRef = useRef(markAuthResolved);
506
531
  markAuthResolvedRef.current = markAuthResolved;
507
532
 
533
+ // Re-boot indirection: the SessionClient is built in the ref initializer below
534
+ // (before `runColdBoot` is defined), but its `onSessionAppeared` must re-run the
535
+ // cold boot to self-acquire when a sibling signs in on this device. A ref bridges
536
+ // the ordering; it is assigned right after `runColdBoot` is declared.
537
+ const runColdBootRef = useRef<(() => Promise<void>) | null>(null);
538
+
508
539
  // Server-authoritative device session client. Built ONCE per `oxyServices`
509
540
  // instance. `onUnauthenticated` (a device signout-all pushed over the socket,
510
541
  // or bootstrapped as zero-account) clears the persisted store + local state so
511
542
  // a reload does not try to restore a session the device no longer has.
543
+ // `signedOutSocketAuth` + `onSessionAppeared` power the signed-out sync channel:
544
+ // an idle tab joins its `device:<id>` room (web via the `oxy_device` cookie,
545
+ // native via the shared device token) and self-acquires on a sibling sign-in.
512
546
  const sessionClientPairRef = useRef<ReturnType<typeof createSessionClient> | null>(null);
513
547
  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
- });
548
+ sessionClientPairRef.current = createSessionClient(
549
+ oxyServices,
550
+ authStore,
551
+ () => {
552
+ clearPersistedAuthSafe(authStore, logger);
553
+ void clearSessionStateRef.current().catch((clearError) => {
554
+ logger('Failed to clear local state on remote sign-out', clearError);
555
+ });
556
+ },
557
+ {
558
+ signedOutSocketAuth: async () => {
559
+ // Web (`*.oxy.so`): the first-party `oxy_device` cookie rides the
560
+ // same-site handshake automatically — just connect.
561
+ if (isWebBrowser()) return true;
562
+ // Native (no cookie jar): present the shared device token if we have one.
563
+ try {
564
+ return (await authStore.loadDeviceToken()) ?? false;
565
+ } catch {
566
+ return false;
567
+ }
568
+ },
569
+ onSessionAppeared: () => {
570
+ void runColdBootRef.current?.();
571
+ },
572
+ },
573
+ );
520
574
  }
521
575
  const { client: sessionClient, host: sessionClientHost } = sessionClientPairRef.current;
522
576
 
@@ -804,6 +858,61 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
804
858
  [commitSession],
805
859
  );
806
860
 
861
+ // ── Unified account dialog ─────────────────────────────────────────────────
862
+ // The single account-chooser + sign-in surface. Built ONCE per provider mount
863
+ // and bound to the live `oxyServices` + `sessionClient` + this provider's
864
+ // `handleWebSession` commit funnel. `handleWebSession` is threaded through a ref
865
+ // so the controller keeps a STABLE `commitSession` (rebuilding the controller
866
+ // on every commit-identity change would drop its subscription + state).
867
+ const handleWebSessionRef = useRef(handleWebSession);
868
+ handleWebSessionRef.current = handleWebSession;
869
+
870
+ const accountDialogControllerRef = useRef<AccountDialogController | null>(null);
871
+ if (!accountDialogControllerRef.current) {
872
+ accountDialogControllerRef.current = createAccountDialogController({
873
+ oxyServices,
874
+ sessionClient,
875
+ clientId,
876
+ locale: currentLanguage,
877
+ commitSession: (session) => handleWebSessionRef.current(session),
878
+ onSignedIn: () => setAccountDialogOpen(false),
879
+ openUrl: (url) => {
880
+ void Linking.openURL(url);
881
+ },
882
+ });
883
+ }
884
+ const accountDialogController = accountDialogControllerRef.current;
885
+
886
+ const openAccountDialog = useCallback((view?: AccountDialogView): void => {
887
+ accountDialogControllerRef.current?.setView(view ?? 'accounts');
888
+ setAccountDialogOpen(true);
889
+ }, []);
890
+
891
+ const closeAccountDialog = useCallback((): void => {
892
+ accountDialogControllerRef.current?.cancelSignIn();
893
+ setAccountDialogOpen(false);
894
+ }, []);
895
+
896
+ // Start driving the dialog on mount; tear it down on unmount.
897
+ useEffect(() => {
898
+ const controller = accountDialogControllerRef.current;
899
+ controller?.start();
900
+ return () => controller?.destroy();
901
+ }, []);
902
+
903
+ // Expose the live open/close controls to the imperative manager so
904
+ // `showSignInModal()` (and any app-level imperative "sign in" handler) works.
905
+ useEffect(
906
+ () => registerAccountDialogControls({ open: openAccountDialog, close: closeAccountDialog }),
907
+ [openAccountDialog, closeAccountDialog],
908
+ );
909
+
910
+ // Broadcast visibility so `OxySignInButton`'s "Signing in…" affordance stays
911
+ // accurate regardless of what opened or dismissed the dialog.
912
+ useEffect(() => {
913
+ notifyAccountDialogVisibility(accountDialogOpen);
914
+ }, [accountDialogOpen]);
915
+
807
916
  // Keyless password sign-in: username/email + password.
808
917
  const signInWithPassword = useCallback(
809
918
  async (
@@ -908,6 +1017,18 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
908
1017
  },
909
1018
  onSignedOut: () => {
910
1019
  markAuthResolvedRef.current();
1020
+ // Open the signed-out realtime channel so this idle tab joins its
1021
+ // `device:<id>` room (web `oxy_device` cookie / native device token)
1022
+ // and self-acquires when a sibling signs in. Idempotent, best-effort.
1023
+ void sessionClient.start().catch((startError) => {
1024
+ if (__DEV__) {
1025
+ loggerUtil.debug(
1026
+ 'signed-out socket start failed (non-fatal)',
1027
+ { component: 'OxyContext', method: 'runColdBoot' },
1028
+ startError as unknown,
1029
+ );
1030
+ }
1031
+ });
911
1032
  },
912
1033
  onStepError: (id, error) => {
913
1034
  if (__DEV__) {
@@ -931,7 +1052,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
931
1052
  // Backstop: resolve on every exit path so the gate can never hang.
932
1053
  markAuthResolvedRef.current();
933
1054
  }
934
- }, [oxyServices, authStore]);
1055
+ }, [oxyServices, authStore, sessionClient]);
1056
+ // Bridge for `onSessionAppeared` (SessionClient) → re-run the cold boot to
1057
+ // self-acquire when a sibling signs in on this device while this tab is idle.
1058
+ runColdBootRef.current = runColdBoot;
935
1059
 
936
1060
  useEffect(() => {
937
1061
  if (!storage || initialized) {
@@ -1018,6 +1142,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1018
1142
  useEffect(() => {
1019
1143
  if (isAuthenticated && initialized && tokenReady) {
1020
1144
  refreshAccounts();
1145
+ // Reload the dialog's own account graph too, so a session restored OUTSIDE
1146
+ // the dialog (cold boot / password sign-in) surfaces its graph accounts.
1147
+ void accountDialogControllerRef.current?.refresh();
1021
1148
  }
1022
1149
  }, [isAuthenticated, initialized, tokenReady, refreshAccounts]);
1023
1150
 
@@ -1115,6 +1242,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1115
1242
  useFollow: useFollowHook,
1116
1243
  showBottomSheet: showBottomSheetForContext,
1117
1244
  openAvatarPicker,
1245
+ accountDialogController,
1246
+ isAccountDialogOpen: accountDialogOpen,
1247
+ openAccountDialog,
1248
+ closeAccountDialog,
1118
1249
  accounts,
1119
1250
  switchToAccount,
1120
1251
  refreshAccounts,
@@ -1159,6 +1290,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1159
1290
  useFollowHook,
1160
1291
  showBottomSheetForContext,
1161
1292
  openAvatarPicker,
1293
+ accountDialogController,
1294
+ accountDialogOpen,
1295
+ openAccountDialog,
1296
+ closeAccountDialog,
1162
1297
  accounts,
1163
1298
  switchToAccount,
1164
1299
  refreshAccounts,
@@ -1221,6 +1356,10 @@ const LOADING_STATE: OxyContextState = {
1221
1356
  clientId: null,
1222
1357
  oxyServices: LOADING_STATE_OXY_SERVICES,
1223
1358
  openAvatarPicker: () => {},
1359
+ accountDialogController: null,
1360
+ isAccountDialogOpen: false,
1361
+ openAccountDialog: () => {},
1362
+ closeAccountDialog: () => {},
1224
1363
  accounts: [],
1225
1364
  switchToAccount: () => rejectMissingProvider<void>(),
1226
1365
  refreshAccounts: () => rejectMissingProvider<void>(),
@@ -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();