@oxyhq/services 27.1.3 → 29.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 (387) hide show
  1. package/LICENSE +202 -661
  2. package/NOTICE +16 -0
  3. package/README.md +67 -2
  4. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionModule.kt +178 -0
  5. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionProvider.kt +99 -0
  6. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionStore.kt +115 -0
  7. package/expo-module.config.json +1 -0
  8. package/lib/commonjs/index.js +78 -16
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/ui/boot/runProviderColdBoot.js +7 -0
  11. package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
  12. package/lib/commonjs/ui/client.js +12 -7
  13. package/lib/commonjs/ui/client.js.map +1 -1
  14. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +10 -27
  15. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
  16. package/lib/commonjs/ui/components/OxyAuthChooser.js +132 -44
  17. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  18. package/lib/commonjs/ui/components/OxyProvider.js +4 -2
  19. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  20. package/lib/commonjs/ui/components/SurfaceScreen.js +31 -3
  21. package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
  22. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +209 -84
  23. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  24. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +25 -7
  25. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -1
  26. package/lib/commonjs/ui/components/authChooser/primitives.js +35 -22
  27. package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -1
  28. package/lib/commonjs/ui/components/authChooser/signInProgress.js +5 -0
  29. package/lib/commonjs/ui/components/authChooser/signInProgress.js.map +1 -1
  30. package/lib/commonjs/ui/components/authChooser/types.js +11 -0
  31. package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
  32. package/lib/commonjs/ui/context/OxyContext.js +257 -306
  33. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  34. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +27 -37
  35. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  36. package/lib/commonjs/ui/context/oxyContextHelpers.js +0 -29
  37. package/lib/commonjs/ui/context/oxyContextHelpers.js.map +1 -1
  38. package/lib/commonjs/ui/context/useOxyAccountGraph.js +6 -17
  39. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  40. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js +42 -0
  41. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js.map +1 -0
  42. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +0 -19
  43. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  44. package/lib/commonjs/ui/hooks/useDeviceSwitcher.js +57 -0
  45. package/lib/commonjs/ui/hooks/useDeviceSwitcher.js.map +1 -0
  46. package/lib/commonjs/ui/hooks/useI18n.js +22 -3
  47. package/lib/commonjs/ui/hooks/useI18n.js.map +1 -1
  48. package/lib/commonjs/ui/hooks/useSessionManagement.js +46 -153
  49. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  50. package/lib/commonjs/ui/index.js +12 -7
  51. package/lib/commonjs/ui/index.js.map +1 -1
  52. package/lib/commonjs/ui/navigation/routes.js +85 -73
  53. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  54. package/lib/commonjs/ui/navigation/surfaces.js +8 -7
  55. package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
  56. package/lib/commonjs/ui/runtime/createOxyRuntime.js +433 -0
  57. package/lib/commonjs/ui/runtime/createOxyRuntime.js.map +1 -0
  58. package/lib/commonjs/ui/runtime/index.js +63 -0
  59. package/lib/commonjs/ui/runtime/index.js.map +1 -0
  60. package/lib/commonjs/ui/runtime/runtimeContext.js +83 -0
  61. package/lib/commonjs/ui/runtime/runtimeContext.js.map +1 -0
  62. package/lib/commonjs/ui/runtime/types.js +6 -0
  63. package/lib/commonjs/ui/runtime/types.js.map +1 -0
  64. package/lib/commonjs/ui/runtime/useRuntimeSnapshot.js +57 -0
  65. package/lib/commonjs/ui/runtime/useRuntimeSnapshot.js.map +1 -0
  66. package/lib/commonjs/ui/screens/AccountMembersScreen.js +3 -4
  67. package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -1
  68. package/lib/commonjs/ui/screens/CreateAccountScreen.js +3 -12
  69. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
  70. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js +6 -5
  71. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  72. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js +4 -4
  73. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  74. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js +1 -1
  75. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js +4 -2
  76. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js.map +1 -1
  77. package/lib/commonjs/ui/server.js +1 -2
  78. package/lib/commonjs/ui/server.js.map +1 -1
  79. package/lib/commonjs/ui/session/authStore.js +45 -9
  80. package/lib/commonjs/ui/session/authStore.js.map +1 -1
  81. package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js +306 -0
  82. package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js.map +1 -0
  83. package/lib/commonjs/ui/stores/authStore.js +154 -55
  84. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  85. package/lib/commonjs/ui/stores/followTargetStore.js +27 -0
  86. package/lib/commonjs/ui/stores/followTargetStore.js.map +1 -1
  87. package/lib/commonjs/ui/utils/avatarUtils.js +2 -36
  88. package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
  89. package/lib/module/index.js +20 -9
  90. package/lib/module/index.js.map +1 -1
  91. package/lib/module/ui/boot/runProviderColdBoot.js +7 -0
  92. package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
  93. package/lib/module/ui/client.js +1 -2
  94. package/lib/module/ui/client.js.map +1 -1
  95. package/lib/module/ui/components/OxyAccountDialogScreen.js +10 -27
  96. package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
  97. package/lib/module/ui/components/OxyAuthChooser.js +134 -44
  98. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  99. package/lib/module/ui/components/OxyProvider.js +5 -3
  100. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  101. package/lib/module/ui/components/SurfaceScreen.js +33 -5
  102. package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
  103. package/lib/module/ui/components/authChooser/AccountsMenuView.js +210 -85
  104. package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  105. package/lib/module/ui/components/authChooser/SignInEntryView.js +25 -7
  106. package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -1
  107. package/lib/module/ui/components/authChooser/primitives.js +35 -22
  108. package/lib/module/ui/components/authChooser/primitives.js.map +1 -1
  109. package/lib/module/ui/components/authChooser/signInProgress.js +5 -0
  110. package/lib/module/ui/components/authChooser/signInProgress.js.map +1 -1
  111. package/lib/module/ui/components/authChooser/types.js +11 -0
  112. package/lib/module/ui/components/authChooser/types.js.map +1 -1
  113. package/lib/module/ui/context/OxyContext.js +251 -303
  114. package/lib/module/ui/context/OxyContext.js.map +1 -1
  115. package/lib/module/ui/context/hooks/useAuthOperations.js +27 -37
  116. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  117. package/lib/module/ui/context/oxyContextHelpers.js +0 -28
  118. package/lib/module/ui/context/oxyContextHelpers.js.map +1 -1
  119. package/lib/module/ui/context/useOxyAccountGraph.js +6 -17
  120. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  121. package/lib/module/ui/hooks/accountDialogSnapshot.js +38 -0
  122. package/lib/module/ui/hooks/accountDialogSnapshot.js.map +1 -0
  123. package/lib/module/ui/hooks/mutations/useAccountMutations.js +0 -19
  124. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  125. package/lib/module/ui/hooks/useDeviceSwitcher.js +53 -0
  126. package/lib/module/ui/hooks/useDeviceSwitcher.js.map +1 -0
  127. package/lib/module/ui/hooks/useI18n.js +24 -4
  128. package/lib/module/ui/hooks/useI18n.js.map +1 -1
  129. package/lib/module/ui/hooks/useSessionManagement.js +47 -155
  130. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  131. package/lib/module/ui/index.js +1 -2
  132. package/lib/module/ui/index.js.map +1 -1
  133. package/lib/module/ui/navigation/routes.js +84 -72
  134. package/lib/module/ui/navigation/routes.js.map +1 -1
  135. package/lib/module/ui/navigation/surfaces.js +6 -6
  136. package/lib/module/ui/navigation/surfaces.js.map +1 -1
  137. package/lib/module/ui/runtime/createOxyRuntime.js +430 -0
  138. package/lib/module/ui/runtime/createOxyRuntime.js.map +1 -0
  139. package/lib/module/ui/runtime/index.js +15 -0
  140. package/lib/module/ui/runtime/index.js.map +1 -0
  141. package/lib/module/ui/runtime/runtimeContext.js +75 -0
  142. package/lib/module/ui/runtime/runtimeContext.js.map +1 -0
  143. package/lib/module/ui/runtime/types.js +4 -0
  144. package/lib/module/ui/runtime/types.js.map +1 -0
  145. package/lib/module/ui/runtime/useRuntimeSnapshot.js +52 -0
  146. package/lib/module/ui/runtime/useRuntimeSnapshot.js.map +1 -0
  147. package/lib/module/ui/screens/AccountMembersScreen.js +4 -5
  148. package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -1
  149. package/lib/module/ui/screens/CreateAccountScreen.js +4 -13
  150. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
  151. package/lib/module/ui/screens/trust/TrustCenterScreen.js +6 -5
  152. package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  153. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js +5 -5
  154. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  155. package/lib/module/ui/screens/trust/TrustRewardsScreen.js +1 -1
  156. package/lib/module/ui/screens/trust/TrustRulesScreen.js +4 -2
  157. package/lib/module/ui/screens/trust/TrustRulesScreen.js.map +1 -1
  158. package/lib/module/ui/server.js +0 -1
  159. package/lib/module/ui/server.js.map +1 -1
  160. package/lib/module/ui/session/authStore.js +46 -11
  161. package/lib/module/ui/session/authStore.js.map +1 -1
  162. package/lib/module/ui/session/sharedDeviceCredentialStore.js +302 -0
  163. package/lib/module/ui/session/sharedDeviceCredentialStore.js.map +1 -0
  164. package/lib/module/ui/stores/authStore.js +154 -55
  165. package/lib/module/ui/stores/authStore.js.map +1 -1
  166. package/lib/module/ui/stores/followTargetStore.js +25 -0
  167. package/lib/module/ui/stores/followTargetStore.js.map +1 -1
  168. package/lib/module/ui/utils/avatarUtils.js +2 -35
  169. package/lib/module/ui/utils/avatarUtils.js.map +1 -1
  170. package/lib/typescript/commonjs/index.d.ts +6 -4
  171. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  172. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/ui/client.d.ts +1 -2
  174. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  176. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  177. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  178. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts +11 -4
  180. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  181. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +3 -1
  182. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  183. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +18 -6
  184. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -1
  185. package/lib/typescript/commonjs/ui/components/authChooser/signInProgress.d.ts.map +1 -1
  186. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +33 -1
  187. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
  188. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +35 -4
  189. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  190. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +8 -12
  191. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  192. package/lib/typescript/commonjs/ui/context/oxyContextHelpers.d.ts +0 -2
  193. package/lib/typescript/commonjs/ui/context/oxyContextHelpers.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +9 -1
  195. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  196. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +1 -3
  197. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  198. package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts +15 -0
  199. package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts.map +1 -0
  200. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  201. package/lib/typescript/commonjs/ui/hooks/useDeviceSwitcher.d.ts +49 -0
  202. package/lib/typescript/commonjs/ui/hooks/useDeviceSwitcher.d.ts.map +1 -0
  203. package/lib/typescript/commonjs/ui/hooks/useI18n.d.ts +13 -0
  204. package/lib/typescript/commonjs/ui/hooks/useI18n.d.ts.map +1 -1
  205. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts +23 -18
  206. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
  207. package/lib/typescript/commonjs/ui/index.d.ts +1 -2
  208. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  209. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +15 -1
  210. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  211. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
  212. package/lib/typescript/commonjs/ui/runtime/createOxyRuntime.d.ts +144 -0
  213. package/lib/typescript/commonjs/ui/runtime/createOxyRuntime.d.ts.map +1 -0
  214. package/lib/typescript/commonjs/ui/runtime/index.d.ts +16 -0
  215. package/lib/typescript/commonjs/ui/runtime/index.d.ts.map +1 -0
  216. package/lib/typescript/commonjs/ui/runtime/runtimeContext.d.ts +43 -0
  217. package/lib/typescript/commonjs/ui/runtime/runtimeContext.d.ts.map +1 -0
  218. package/lib/typescript/commonjs/ui/runtime/types.d.ts +89 -0
  219. package/lib/typescript/commonjs/ui/runtime/types.d.ts.map +1 -0
  220. package/lib/typescript/commonjs/ui/runtime/useRuntimeSnapshot.d.ts +23 -0
  221. package/lib/typescript/commonjs/ui/runtime/useRuntimeSnapshot.d.ts.map +1 -0
  222. package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts.map +1 -1
  223. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  224. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  225. package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -1
  226. package/lib/typescript/commonjs/ui/server.d.ts +0 -1
  227. package/lib/typescript/commonjs/ui/server.d.ts.map +1 -1
  228. package/lib/typescript/commonjs/ui/session/authStore.d.ts +60 -9
  229. package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
  230. package/lib/typescript/commonjs/ui/session/sharedDeviceCredentialStore.d.ts +32 -0
  231. package/lib/typescript/commonjs/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -0
  232. package/lib/typescript/commonjs/ui/stores/authStore.d.ts +49 -5
  233. package/lib/typescript/commonjs/ui/stores/authStore.d.ts.map +1 -1
  234. package/lib/typescript/commonjs/ui/stores/followTargetStore.d.ts +8 -1
  235. package/lib/typescript/commonjs/ui/stores/followTargetStore.d.ts.map +1 -1
  236. package/lib/typescript/commonjs/ui/types/navigation.d.ts +26 -0
  237. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  238. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts +2 -10
  239. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
  240. package/lib/typescript/module/index.d.ts +6 -4
  241. package/lib/typescript/module/index.d.ts.map +1 -1
  242. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  243. package/lib/typescript/module/ui/client.d.ts +1 -2
  244. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  245. package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  246. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  247. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  248. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -1
  249. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts +11 -4
  250. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  251. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +3 -1
  252. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  253. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +18 -6
  254. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -1
  255. package/lib/typescript/module/ui/components/authChooser/signInProgress.d.ts.map +1 -1
  256. package/lib/typescript/module/ui/components/authChooser/types.d.ts +33 -1
  257. package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
  258. package/lib/typescript/module/ui/context/OxyContext.d.ts +35 -4
  259. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  260. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +8 -12
  261. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  262. package/lib/typescript/module/ui/context/oxyContextHelpers.d.ts +0 -2
  263. package/lib/typescript/module/ui/context/oxyContextHelpers.d.ts.map +1 -1
  264. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +9 -1
  265. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  266. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +1 -3
  267. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  268. package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts +15 -0
  269. package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts.map +1 -0
  270. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  271. package/lib/typescript/module/ui/hooks/useDeviceSwitcher.d.ts +49 -0
  272. package/lib/typescript/module/ui/hooks/useDeviceSwitcher.d.ts.map +1 -0
  273. package/lib/typescript/module/ui/hooks/useI18n.d.ts +13 -0
  274. package/lib/typescript/module/ui/hooks/useI18n.d.ts.map +1 -1
  275. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts +23 -18
  276. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
  277. package/lib/typescript/module/ui/index.d.ts +1 -2
  278. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  279. package/lib/typescript/module/ui/navigation/routes.d.ts +15 -1
  280. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  281. package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
  282. package/lib/typescript/module/ui/runtime/createOxyRuntime.d.ts +144 -0
  283. package/lib/typescript/module/ui/runtime/createOxyRuntime.d.ts.map +1 -0
  284. package/lib/typescript/module/ui/runtime/index.d.ts +16 -0
  285. package/lib/typescript/module/ui/runtime/index.d.ts.map +1 -0
  286. package/lib/typescript/module/ui/runtime/runtimeContext.d.ts +43 -0
  287. package/lib/typescript/module/ui/runtime/runtimeContext.d.ts.map +1 -0
  288. package/lib/typescript/module/ui/runtime/types.d.ts +89 -0
  289. package/lib/typescript/module/ui/runtime/types.d.ts.map +1 -0
  290. package/lib/typescript/module/ui/runtime/useRuntimeSnapshot.d.ts +23 -0
  291. package/lib/typescript/module/ui/runtime/useRuntimeSnapshot.d.ts.map +1 -0
  292. package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts.map +1 -1
  293. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  294. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  295. package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -1
  296. package/lib/typescript/module/ui/server.d.ts +0 -1
  297. package/lib/typescript/module/ui/server.d.ts.map +1 -1
  298. package/lib/typescript/module/ui/session/authStore.d.ts +60 -9
  299. package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
  300. package/lib/typescript/module/ui/session/sharedDeviceCredentialStore.d.ts +32 -0
  301. package/lib/typescript/module/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -0
  302. package/lib/typescript/module/ui/stores/authStore.d.ts +49 -5
  303. package/lib/typescript/module/ui/stores/authStore.d.ts.map +1 -1
  304. package/lib/typescript/module/ui/stores/followTargetStore.d.ts +8 -1
  305. package/lib/typescript/module/ui/stores/followTargetStore.d.ts.map +1 -1
  306. package/lib/typescript/module/ui/types/navigation.d.ts +26 -0
  307. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  308. package/lib/typescript/module/ui/utils/avatarUtils.d.ts +2 -10
  309. package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
  310. package/package.json +8 -8
  311. package/plugins/withSharedDeviceSessionProvider.js +102 -0
  312. package/plugins/withSharedDeviceSessionReader.js +57 -0
  313. package/src/__tests__/esmSourcePurity.test.ts +61 -0
  314. package/src/index.ts +36 -9
  315. package/src/ui/boot/runProviderColdBoot.ts +7 -0
  316. package/src/ui/client.ts +1 -2
  317. package/src/ui/components/OxyAccountDialogScreen.tsx +10 -29
  318. package/src/ui/components/OxyAuthChooser.tsx +149 -50
  319. package/src/ui/components/OxyProvider.tsx +7 -5
  320. package/src/ui/components/SurfaceScreen.tsx +28 -4
  321. package/src/ui/components/__tests__/followTargetButton.test.ts +39 -0
  322. package/src/ui/components/authChooser/AccountsMenuView.tsx +246 -96
  323. package/src/ui/components/authChooser/SignInEntryView.tsx +31 -8
  324. package/src/ui/components/authChooser/primitives.tsx +37 -23
  325. package/src/ui/components/authChooser/signInProgress.ts +4 -0
  326. package/src/ui/components/authChooser/types.ts +34 -1
  327. package/src/ui/context/OxyContext.tsx +269 -324
  328. package/src/ui/context/hooks/useAuthOperations.ts +32 -35
  329. package/src/ui/context/oxyContextHelpers.ts +0 -30
  330. package/src/ui/context/oxyContextTypes.ts +9 -1
  331. package/src/ui/context/useOxyAccountGraph.ts +5 -19
  332. package/src/ui/hooks/accountDialogSnapshot.ts +37 -0
  333. package/src/ui/hooks/mutations/useAccountMutations.ts +0 -19
  334. package/src/ui/hooks/queries/__tests__/userCache.test.ts +12 -1
  335. package/src/ui/hooks/useDeviceSwitcher.ts +110 -0
  336. package/src/ui/hooks/useI18n.ts +23 -3
  337. package/src/ui/hooks/useSessionManagement.ts +62 -211
  338. package/src/ui/index.ts +1 -2
  339. package/src/ui/navigation/routes.ts +85 -75
  340. package/src/ui/navigation/surfaces.ts +6 -9
  341. package/src/ui/runtime/createOxyRuntime.ts +623 -0
  342. package/src/ui/runtime/index.ts +34 -0
  343. package/src/ui/runtime/runtimeContext.tsx +111 -0
  344. package/src/ui/runtime/types.ts +110 -0
  345. package/src/ui/runtime/useRuntimeSnapshot.ts +52 -0
  346. package/src/ui/screens/AccountMembersScreen.tsx +6 -5
  347. package/src/ui/screens/CreateAccountScreen.tsx +3 -16
  348. package/src/ui/screens/trust/TrustCenterScreen.tsx +7 -7
  349. package/src/ui/screens/trust/TrustLeaderboardScreen.tsx +4 -5
  350. package/src/ui/screens/trust/TrustRewardsScreen.tsx +1 -1
  351. package/src/ui/screens/trust/TrustRulesScreen.tsx +3 -2
  352. package/src/ui/server.ts +0 -1
  353. package/src/ui/session/__tests__/authStore.test.ts +90 -0
  354. package/src/ui/session/__tests__/sharedDeviceCredentialStore.test.ts +289 -0
  355. package/src/ui/session/authStore.ts +78 -9
  356. package/src/ui/session/sharedDeviceCredentialStore.ts +318 -0
  357. package/src/ui/stores/authStore.ts +171 -43
  358. package/src/ui/stores/followTargetStore.ts +24 -1
  359. package/src/ui/types/navigation.ts +26 -0
  360. package/src/ui/utils/avatarUtils.ts +2 -37
  361. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +0 -64
  362. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +0 -1
  363. package/lib/commonjs/ui/screens/trust/trustTier.js +0 -23
  364. package/lib/commonjs/ui/screens/trust/trustTier.js.map +0 -1
  365. package/lib/commonjs/ui/stores/accountStore.js +0 -221
  366. package/lib/commonjs/ui/stores/accountStore.js.map +0 -1
  367. package/lib/module/ui/hooks/useSwitchableAccounts.js +0 -60
  368. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +0 -1
  369. package/lib/module/ui/screens/trust/trustTier.js +0 -19
  370. package/lib/module/ui/screens/trust/trustTier.js.map +0 -1
  371. package/lib/module/ui/stores/accountStore.js +0 -213
  372. package/lib/module/ui/stores/accountStore.js.map +0 -1
  373. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +0 -23
  374. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +0 -1
  375. package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts +0 -9
  376. package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts.map +0 -1
  377. package/lib/typescript/commonjs/ui/stores/accountStore.d.ts +0 -27
  378. package/lib/typescript/commonjs/ui/stores/accountStore.d.ts.map +0 -1
  379. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +0 -23
  380. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +0 -1
  381. package/lib/typescript/module/ui/screens/trust/trustTier.d.ts +0 -9
  382. package/lib/typescript/module/ui/screens/trust/trustTier.d.ts.map +0 -1
  383. package/lib/typescript/module/ui/stores/accountStore.d.ts +0 -27
  384. package/lib/typescript/module/ui/stores/accountStore.d.ts.map +0 -1
  385. package/src/ui/hooks/useSwitchableAccounts.ts +0 -75
  386. package/src/ui/screens/trust/trustTier.ts +0 -20
  387. package/src/ui/stores/accountStore.ts +0 -264
@@ -42,8 +42,7 @@ import { isWebBrowser } from '../utils/isWebBrowser';
42
42
  import { resolveDeliveryPlatform } from '../utils/deliveryPlatform';
43
43
  import { runProviderColdBoot } from '../boot/runProviderColdBoot';
44
44
  import { loadPersistedDeviceCredential } from '../utils/deviceCredential';
45
- import { useAuthStore, type AuthState } from '../stores/authStore';
46
- import { useShallow } from 'zustand/react/shallow';
45
+ import { bindAuthStoreToRuntime } from '../stores/authStore';
47
46
  import { useLanguageManagement } from '../hooks/useLanguageManagement';
48
47
  import { useSessionManagement } from '../hooks/useSessionManagement';
49
48
  import { useAuthOperations, clearPersistedAuthSafe } from './hooks/useAuthOperations';
@@ -60,7 +59,15 @@ import {
60
59
  import { useQueryClient, onlineManager } from '@tanstack/react-query';
61
60
  import { clearQueryCache } from '../hooks/queryClient';
62
61
  import { useAvatarPicker } from '../hooks/useAvatarPicker';
63
- import { useAccountStore } from '../stores/accountStore';
62
+ import { resetSessionScopedStores } from '../stores/resetSessionScopedStores';
63
+ import { ASSET_DOWNLOAD_URLS_QUERY_KEY } from '../hooks/useResolvedFileUrls';
64
+ import {
65
+ createOxyRuntime,
66
+ OxyRuntimeHandleProvider,
67
+ useRuntimeSnapshot,
68
+ type OxyRuntime,
69
+ type SubjectTransition,
70
+ } from '../runtime';
64
71
  import {
65
72
  createSessionClient,
66
73
  createIdentitySessionBinding,
@@ -75,10 +82,14 @@ import {
75
82
  } from '../session';
76
83
  import type {
77
84
  OxyContextState,
78
- OxyContextProviderProps,
85
+ OxyRuntimeProviderProps,
79
86
  CommitInput,
80
87
  } from './oxyContextTypes';
81
- import { DEFAULT_SESSION_VALIDITY_MS, loadUseFollowHook } from './oxyContextHelpers';
88
+ import { DEFAULT_SESSION_VALIDITY_MS } from './oxyContextHelpers';
89
+ // `useFollow` imports this module back, so the binding must only be READ inside
90
+ // the provider body — at render time, once every module has evaluated. It is
91
+ // never dereferenced at module scope here.
92
+ import { useFollow } from '../hooks/useFollow';
82
93
  import { commitDeviceSetAndResolve } from './commitSessionFlow';
83
94
  import { runPasskeyLogin, runPasskeyRegister, runPasskeyAdd } from './passkeyFlow';
84
95
  import {
@@ -89,11 +100,22 @@ import {
89
100
  import { queryKeys } from '../hooks/queries/queryKeys';
90
101
  import { useOxyAccountGraph } from './useOxyAccountGraph';
91
102
 
92
- export type { OxyContextState, OxyContextProviderProps } from './oxyContextTypes';
103
+ export type { OxyContextState, OxyRuntimeProviderProps } from './oxyContextTypes';
93
104
 
94
- const OxyContext = createContext<OxyContextState | null>(null);
105
+ const OxyRuntimeContext = createContext<OxyContextState | null>(null);
95
106
 
96
- export const OxyProvider: React.FC<OxyContextProviderProps> = ({
107
+ /**
108
+ * The SDK's internal runtime provider — the session/account state machine that
109
+ * backs `useOxy()`.
110
+ *
111
+ * This is NOT the component consumers mount. The one public composition root is
112
+ * `OxyProvider` (`../components/OxyProvider`), which mounts this alongside the
113
+ * QueryClient, Bloom's dialog/surface/toast hosts, safe areas, the gesture root
114
+ * and the keyboard provider. Naming both of them `OxyProvider` (as this file
115
+ * used to) made every error message, stack frame and doc reference ambiguous —
116
+ * see ADR 0004.
117
+ */
118
+ export const OxyRuntimeProvider: React.FC<OxyRuntimeProviderProps> = ({
97
119
  children,
98
120
  oxyServices: providedOxyServices,
99
121
  baseURL,
@@ -105,6 +127,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
105
127
  sessionMode = 'account',
106
128
  webAuthMode = 'popup',
107
129
  backgroundSession = false,
130
+ deviceCredentialStorage = 'persistent',
108
131
  onAuthStateChange,
109
132
  onError,
110
133
  }) => {
@@ -117,17 +140,24 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
117
140
  // link builder at the central auth host; there is no cold-boot redirect.
118
141
  oxyServicesRef.current = new OxyServices({ baseURL, authWebUrl, authRedirectUri });
119
142
  } else {
120
- throw new Error('Either oxyServices or baseURL must be provided to OxyContextProvider');
143
+ throw new Error('Either oxyServices or baseURL must be provided to OxyProvider');
121
144
  }
122
145
  }
123
146
  const oxyServices = oxyServicesRef.current;
124
147
 
125
- // The device-first persisted auth-state store (per-origin zero-cookie device
126
- // credential on web; SecureStore session blob on native). Built ONCE per
127
- // provider mount.
148
+ // The device-first persisted auth-state store (per-origin device credential on
149
+ // web; SecureStore session blob on native). Built ONCE per provider mount.
150
+ //
151
+ // `deviceCredentialStorage` is mount-time configuration for the same reason
152
+ // `sessionMode` is: it decides where the durable credential lives, and letting
153
+ // it change mid-flight would mean a session that started ephemeral could begin
154
+ // writing one.
128
155
  const authStoreRef = useRef<AuthStateStore | null>(null);
129
156
  if (!authStoreRef.current) {
130
- authStoreRef.current = createPlatformAuthStateStore();
157
+ authStoreRef.current = createPlatformAuthStateStore({
158
+ sessionMode,
159
+ storage: deviceCredentialStorage,
160
+ });
131
161
  }
132
162
  const authStore = authStoreRef.current;
133
163
 
@@ -148,37 +178,96 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
148
178
  const identity = identityRef.current;
149
179
  const isIdentityBound = identity !== null;
150
180
 
181
+ const logger = useCallback((message: string, err?: unknown) => {
182
+ if (__DEV__) {
183
+ loggerUtil.warn(message, { component: 'OxyContext' }, err);
184
+ }
185
+ }, []);
186
+
187
+ // Server-authoritative device session client. Built ONCE per `oxyServices`
188
+ // instance. Device-scoped sockets connect with deviceId+deviceSecret when no
189
+ // bearer is planted yet, so cross-origin apps sync via `session_state`.
190
+ const sessionClientPairRef = useRef<ReturnType<typeof createSessionClient> | null>(null);
191
+ if (!sessionClientPairRef.current) {
192
+ sessionClientPairRef.current = createSessionClient(
193
+ oxyServices,
194
+ (origin) => {
195
+ // Erase the DURABLE device credential only on a `request`-origin verdict
196
+ // (a REST sign-out / revocation). A `push`-origin empty state is a socket
197
+ // broadcast that may be a transient reconnect artifact — clearing the
198
+ // credential on it would strand the user signed out with no way back, so
199
+ // we clear only the local UI session and keep the credential (a dead one
200
+ // re-mints to `no_active_session` and resolves signed-out on next boot).
201
+ if (origin === 'request') {
202
+ clearPersistedAuthSafe(authStore, logger);
203
+ }
204
+ void clearSessionStateRef.current().catch((clearError) => {
205
+ logger('Failed to clear local state on remote sign-out', clearError);
206
+ });
207
+ },
208
+ // `null` for every account-mode provider — identical to omitting the
209
+ // option — and the pinned account id once an identity-bound provider has
210
+ // resolved its pin. Read through the ref so the resolver stays stable for
211
+ // the client's lifetime while still seeing later resolutions.
212
+ () => identityRef.current?.getPinnedAccountId() ?? null,
213
+ );
214
+ }
215
+ const { client: sessionClient, host: sessionClientHost } = sessionClientPairRef.current;
216
+
217
+ // ── The runtime ──────────────────────────────────────────────────────────
218
+ // One owner for the session facts (ADR 0004), built ONCE and never rebuilt,
219
+ // so the context value below can hand out a reference that never changes.
220
+ //
221
+ // Its three callbacks reach forward through refs because the collaborators
222
+ // they need — the storage-backed teardown, the account-graph refresh — are
223
+ // hooks declared after it. Latching the runtime here rather than after them
224
+ // is deliberate: a runtime rebuilt mid-flight would drop its `SessionClient`
225
+ // subscription and every subscriber with it.
226
+ const clearSessionStateRef = useRef<() => Promise<void>>(() => Promise.resolve());
227
+ const saveSessionIdsRef = useRef<(sessionIds: string[]) => void>(() => undefined);
228
+ const subjectChangeRef = useRef<(transition: SubjectTransition) => void>(() => undefined);
229
+ const reestablishRef = useRef<(binding: IdentitySessionBinding, revision: number) => void>(
230
+ () => undefined,
231
+ );
232
+
233
+ const runtimeRef = useRef<OxyRuntime | null>(null);
234
+ if (!runtimeRef.current) {
235
+ runtimeRef.current = createOxyRuntime({
236
+ oxyServices,
237
+ sessionClient,
238
+ sessionClientHost,
239
+ identity,
240
+ onSubjectChange: (transition) => subjectChangeRef.current(transition),
241
+ onDeviceEmpty: () => clearSessionStateRef.current(),
242
+ onSessionsProjected: (sessionIds) => saveSessionIdsRef.current(sessionIds),
243
+ onIdentityUnbound: (binding, revision) => reestablishRef.current(binding, revision),
244
+ logger,
245
+ });
246
+ }
247
+ const runtime = runtimeRef.current;
248
+
249
+ const snapshot = useRuntimeSnapshot(runtime);
151
250
  const {
152
- user,
153
- isAuthenticated,
251
+ account: user,
252
+ sessions,
253
+ activeSessionId,
154
254
  isLoading,
155
- error,
156
- loginSuccess,
157
- loginFailure,
158
- logoutStore,
159
- } = useAuthStore(
160
- useShallow((state: AuthState) => ({
161
- user: state.user,
162
- isAuthenticated: state.isAuthenticated,
163
- isLoading: state.isLoading,
164
- error: state.error,
165
- loginSuccess: state.loginSuccess,
166
- loginFailure: state.loginFailure,
167
- logoutStore: state.logout,
168
- })),
169
- );
255
+ tokenReady,
256
+ hasAccessToken,
257
+ authResolved,
258
+ } = snapshot;
259
+ const isAuthenticated = user !== null;
260
+ const error = snapshot.error?.message ?? null;
170
261
 
171
- const [tokenReady, setTokenReady] = useState(true);
172
- const [hasAccessToken, setHasAccessToken] = useState(() => Boolean(oxyServices.getAccessToken()));
173
- const [authResolved, setAuthResolved] = useState(false);
174
- const authResolvedRef = useRef(false);
175
- const userRef = useRef<User | null>(user);
176
- const isAuthenticatedRef = useRef(isAuthenticated);
177
- userRef.current = user;
178
- isAuthenticatedRef.current = isAuthenticated;
179
262
  const [initialized, setInitialized] = useState(false);
180
263
  const [accountDialogOpen, setAccountDialogOpen] = useState(false);
181
- const setAuthState = useAuthStore.setState;
264
+
265
+ // Project the runtime onto `useAuthStore`, which a dozen out-of-band callers
266
+ // (mutation success handlers, cache helpers, the Commons app) still read the
267
+ // signed-in user through. It is a projection, not a second store.
268
+ useEffect(() => bindAuthStoreToRuntime(runtime), [runtime]);
269
+
270
+ useEffect(() => runtime.start(), [runtime]);
182
271
 
183
272
  // Keep the shared `oxyClient` singleton's token store in lockstep with the
184
273
  // session owned by THIS provider's instance (many apps build API clients
@@ -199,12 +288,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
199
288
  return oxyServices.onTokensChanged(applyToSingleton);
200
289
  }, [oxyServices]);
201
290
 
202
- const logger = useCallback((message: string, err?: unknown) => {
203
- if (__DEV__) {
204
- loggerUtil.warn(message, { component: 'OxyContext' }, err);
205
- }
206
- }, []);
207
-
208
291
  const storageKeys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
209
292
 
210
293
  const clientId = useMemo(() => {
@@ -238,6 +321,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
238
321
  storageReady.resolve(storageInstance);
239
322
  if (mounted) {
240
323
  setStorage(storageInstance);
324
+ runtime.setStorageReady(true);
241
325
  }
242
326
  })
243
327
  .catch((err) => {
@@ -249,7 +333,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
249
333
  return () => {
250
334
  mounted = false;
251
335
  };
252
- }, [logger, onError, storageReady]);
336
+ }, [logger, onError, runtime, storageReady]);
253
337
 
254
338
  const {
255
339
  currentLanguage,
@@ -269,83 +353,42 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
269
353
  const queryClient = useQueryClient();
270
354
 
271
355
  const {
272
- sessions,
273
- activeSessionId,
274
- setActiveSessionId,
275
- updateSessions,
356
+ saveSessionIds,
276
357
  switchSession,
277
358
  clearSessionState,
278
359
  saveActiveSessionId,
279
360
  } = useSessionManagement({
280
361
  oxyServices,
362
+ runtime,
281
363
  storage,
282
364
  storageKeyPrefix,
283
- loginSuccess,
284
- logoutStore,
285
365
  onAuthStateChange,
286
366
  onError,
287
- setAuthError: (message) => setAuthState({ error: message }),
367
+ setAuthError: (message) => runtime.setError(message),
288
368
  logger,
289
- setTokenReady,
290
369
  queryClient,
291
370
  });
292
371
 
293
- // Refs so callbacks below can invoke the latest session-management functions
294
- // without widening dependency arrays.
295
- const switchSessionRef = useRef(switchSession);
296
- switchSessionRef.current = switchSession;
297
- const clearSessionStateRef = useRef(clearSessionState);
298
372
  clearSessionStateRef.current = clearSessionState;
299
- const setActiveSessionIdRef = useRef(setActiveSessionId);
300
- setActiveSessionIdRef.current = setActiveSessionId;
301
- const loginSuccessRef = useRef(loginSuccess);
302
- loginSuccessRef.current = loginSuccess;
373
+ saveSessionIdsRef.current = saveSessionIds;
374
+
303
375
  const onAuthStateChangeRef = useRef(onAuthStateChange);
304
376
  onAuthStateChangeRef.current = onAuthStateChange;
305
377
 
306
- // Flip the auth-resolution gate (`authResolved` + `tokenReady`) the moment a
307
- // session commits or the boot concludes signed out. Idempotent + monotonic.
378
+ // Flip the auth-resolution gate the moment a session commits or the boot
379
+ // concludes signed out. Idempotent + monotonic, enforced by the runtime.
308
380
  const markAuthResolved = useCallback(() => {
309
- if (authResolvedRef.current) {
310
- return;
311
- }
312
- authResolvedRef.current = true;
313
- setTokenReady(true);
314
- setAuthResolved(true);
315
- }, []);
381
+ runtime.markAuthResolved();
382
+ }, [runtime]);
316
383
  const markAuthResolvedRef = useRef(markAuthResolved);
317
384
  markAuthResolvedRef.current = markAuthResolved;
318
385
 
319
- // Server-authoritative device session client. Built ONCE per `oxyServices`
320
- // instance. Device-scoped sockets connect with deviceId+deviceSecret when no
321
- // bearer is planted yet, so cross-origin apps sync via `session_state` after
322
- // the one-shot join redirect.
323
- const sessionClientPairRef = useRef<ReturnType<typeof createSessionClient> | null>(null);
324
- if (!sessionClientPairRef.current) {
325
- sessionClientPairRef.current = createSessionClient(
326
- oxyServices,
327
- (origin) => {
328
- // Erase the DURABLE device credential only on a `request`-origin verdict
329
- // (a REST sign-out / revocation). A `push`-origin empty state is a socket
330
- // broadcast that may be a transient reconnect artifact — clearing the
331
- // credential on it would strand the user signed out with no way back, so
332
- // we clear only the local UI session and keep the credential (a dead one
333
- // re-mints to `no_active_session` and resolves signed-out on next boot).
334
- if (origin === 'request') {
335
- clearPersistedAuthSafe(authStore, logger);
336
- }
337
- void clearSessionStateRef.current().catch((clearError) => {
338
- logger('Failed to clear local state on remote sign-out', clearError);
339
- });
340
- },
341
- // `null` for every account-mode provider — identical to omitting the
342
- // option — and the pinned account id once an identity-bound provider has
343
- // resolved its pin. Read through the ref so the resolver stays stable for
344
- // the client's lifetime while still seeing later resolutions.
345
- () => identityRef.current?.getPinnedAccountId() ?? null,
346
- );
347
- }
348
- const { client: sessionClient, host: sessionClientHost } = sessionClientPairRef.current;
386
+ const setTokenReady = useCallback(
387
+ (ready: boolean) => {
388
+ runtime.setTokenReady(ready);
389
+ },
390
+ [runtime],
391
+ );
349
392
 
350
393
  // Re-establishment lane for an identity-bound provider whose pin is missing or
351
394
  // whose pinned account is no longer part of this device's session set (another
@@ -396,78 +439,16 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
396
439
  },
397
440
  [oxyServices, authStore, sessionClient, logger],
398
441
  );
399
-
400
- // Projects `client.getState()` onto the exposed `sessions`/`activeSessionId`/
401
- // `user`. Sole authority for both locally-initiated mutations (switch/logout)
402
- // AND remotely-pushed `session_state` over the `device:<deviceId>` socket.
403
- const syncFromClient = useCallback(async (): Promise<void> => {
404
- const state = sessionClient.getState();
405
- if (state === null) {
406
- return;
407
- }
408
- if (state.accounts.length === 0) {
409
- // Clear the LOCAL UI session on any applied empty state, but NEVER the
410
- // durable device credential here: `syncFromClient` runs for socket-pushed
411
- // (possibly transient) states too, and wiping the credential on one would
412
- // strand the user signed out. The credential wipe is gated on a
413
- // `request`-origin verdict in `onUnauthenticated` above.
414
- sessionClientHost.setCurrentAccountId(null);
415
- await clearSessionState();
416
- return;
417
- }
418
- // Last-write-wins guard: capture the revision this projection is for, then
419
- // after the async profile fetch bail if a fresher state has landed.
420
- const capturedRevision = state.revision;
421
- // Resolve the pin BEFORE the profile fetch (memoised: one storage + keychain
422
- // read per boot) so every projection below is bound on the FIRST pass over a
423
- // freshly applied state. Deferring it would let a sibling app's switch render
424
- // once as this client's user before the pin corrected it. `null` for every
425
- // account-mode provider, where each projection falls back to the device's
426
- // active account exactly as before.
427
- const pinnedAccountId = identity ? await identity.ensurePinnedAccountId() : null;
428
- const ids = accountIdsOf(state);
429
- let users: User[] = [];
430
- try {
431
- users = ids.length > 0 ? await oxyServices.getUsersByIds(ids) : [];
432
- } catch (fetchError) {
433
- logger('Failed to resolve account profiles during syncFromClient', fetchError);
434
- return;
435
- }
436
- const latest = sessionClient.getState();
437
- if (!latest || latest.revision !== capturedRevision) {
438
- return;
439
- }
440
- if (identity && (pinnedAccountId === null || activeSessionIdOf(latest, pinnedAccountId) === null)) {
441
- // Either no verified pin, or the pinned account left this device's session
442
- // set. Projecting anything here would mean projecting SOMEBODY ELSE's
443
- // account, so leave the current projection untouched and re-derive the
444
- // session from the local identity key instead.
445
- reestablishIdentitySession(identity, latest.revision);
446
- return;
447
- }
448
- const usersById = new Map(users.map((resolvedUser) => [resolvedUser.id, resolvedUser]));
449
- updateSessions(deviceStateToClientSessions(latest, usersById, pinnedAccountId));
450
- setActiveSessionId(activeSessionIdOf(latest, pinnedAccountId));
451
- const activeUser = activeUserOf(latest, usersById, pinnedAccountId);
452
- if (activeUser) {
453
- loginSuccess(activeUser);
454
- }
455
- // The host's current account feeds the socket handshake and the bearer's
456
- // token-account comparisons, so it tracks the PIN — never the account another
457
- // app switched the device to.
458
- sessionClientHost.setCurrentAccountId(pinnedAccountId ?? latest.activeAccountId);
459
- }, [
460
- identity,
461
- oxyServices,
462
- reestablishIdentitySession,
463
- sessionClient,
464
- sessionClientHost,
465
- updateSessions,
466
- setActiveSessionId,
467
- loginSuccess,
468
- clearSessionState,
469
- logger,
470
- ]);
442
+ reestablishRef.current = reestablishIdentitySession;
443
+
444
+ /**
445
+ * Project the server-authoritative device state onto the runtime.
446
+ *
447
+ * The projection itself lives in the runtime; this is the awaitable handle
448
+ * the commit/logout/reconnect paths already had. It is stable, because the
449
+ * runtime is.
450
+ */
451
+ const syncFromClient = useCallback((): Promise<void> => runtime.reconcileFromClient(), [runtime]);
471
452
  const syncFromClientRef = useRef(syncFromClient);
472
453
  syncFromClientRef.current = syncFromClient;
473
454
 
@@ -476,19 +457,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
476
457
  sessionClientHost.setDeviceCredential(cred);
477
458
  }, [authStore, sessionClientHost]);
478
459
 
479
- useEffect(() => {
480
- return sessionClient.subscribe(() => {
481
- void syncFromClient();
482
- });
483
- }, [sessionClient, syncFromClient]);
484
-
485
460
  const { signIn, logout, logoutAll } = useAuthOperations({
486
461
  oxyServices,
487
462
  store: authStore,
488
463
  storage,
489
- activeSessionId,
490
- setActiveSessionId,
491
- updateSessions,
464
+ runtime,
492
465
  saveActiveSessionId,
493
466
  clearSessionState,
494
467
  switchSession,
@@ -496,10 +469,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
496
469
  syncFromClient,
497
470
  onAuthStateChange,
498
471
  onError,
499
- loginSuccess,
500
- loginFailure,
501
- logoutStore,
502
- setAuthState,
503
472
  logger,
504
473
  ...(identity
505
474
  ? {
@@ -531,7 +500,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
531
500
  // Explicit FULL wipe: also drop the persisted device credential so a reload
532
501
  // finds nothing to restore.
533
502
  await authStore.clear();
534
- useAccountStore.getState().reset();
535
503
  oxyServices.clearCache();
536
504
  }, [queryClient, storage, clearSessionState, authStore, logger, oxyServices]);
537
505
 
@@ -543,7 +511,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
543
511
  logger,
544
512
  });
545
513
 
546
- const useFollowHook = loadUseFollowHook();
547
514
 
548
515
  // Token-change side effects: an invalidated bearer (HttpService clears tokens
549
516
  // on an unrecoverable 401 and emits `null`) must locally sign out an
@@ -554,16 +521,16 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
554
521
  const clearingInvalidTokenRef = useRef(false);
555
522
  useEffect(() => {
556
523
  const handleTokenChange = (accessToken: string | null) => {
557
- setHasAccessToken(Boolean(accessToken));
524
+ runtime.setHasAccessToken(Boolean(accessToken));
558
525
  if (accessToken) {
559
- setTokenReady(true);
526
+ runtime.setTokenReady(true);
560
527
  if (sessionClient.getState()?.accounts.length) {
561
528
  void syncFromClientRef.current();
562
529
  }
563
530
  return;
564
531
  }
565
- if (userRef.current || isAuthenticatedRef.current) {
566
- setTokenReady(false);
532
+ if (runtime.getSnapshot().account !== null) {
533
+ runtime.setTokenReady(false);
567
534
  if (clearingInvalidTokenRef.current) {
568
535
  return;
569
536
  }
@@ -574,19 +541,19 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
574
541
  })
575
542
  .finally(() => {
576
543
  clearingInvalidTokenRef.current = false;
577
- if (authResolvedRef.current) {
578
- setTokenReady(true);
544
+ if (runtime.getSnapshot().authResolved) {
545
+ runtime.setTokenReady(true);
579
546
  }
580
547
  });
581
548
  return;
582
549
  }
583
- if (authResolvedRef.current) {
584
- setTokenReady(true);
550
+ if (runtime.getSnapshot().authResolved) {
551
+ runtime.setTokenReady(true);
585
552
  }
586
553
  };
587
554
  handleTokenChange(oxyServices.getAccessToken());
588
555
  return oxyServices.onTokensChanged(handleTokenChange);
589
- }, [logger, oxyServices, sessionClient]);
556
+ }, [logger, oxyServices, runtime, sessionClient]);
590
557
 
591
558
  // Unified in-session refresh (SDK-owned; every RP inherits it). Installs the
592
559
  // ONE core refresh handler (re-mint from the persisted zero-cookie device
@@ -644,23 +611,28 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
644
611
  }
645
612
 
646
613
  // Fast local mirror so the UI updates before the server round-trip; the
647
- // SessionClient projection below overwrites it with server truth.
614
+ // SessionClient projection below overwrites it with server truth. Batched
615
+ // so the mirror and the active id land in ONE transition — unbatched, a
616
+ // subscriber woken between them would see the new session list still
617
+ // pointing at the previous active id.
648
618
  if (input.userId) {
649
619
  const now = new Date();
650
- updateSessions(
651
- [
652
- {
653
- sessionId: input.sessionId,
654
- deviceId: input.deviceId ?? '',
655
- expiresAt: input.expiresAt || new Date(now.getTime() + DEFAULT_SESSION_VALIDITY_MS).toISOString(),
656
- lastActive: now.toISOString(),
657
- userId: input.userId,
658
- isCurrent: true,
659
- },
660
- ],
661
- { merge: true },
662
- );
663
- setActiveSessionId(input.sessionId);
620
+ runtime.batch(() => {
621
+ runtime.mergeSessions(
622
+ [
623
+ {
624
+ sessionId: input.sessionId,
625
+ deviceId: input.deviceId ?? '',
626
+ expiresAt: input.expiresAt || new Date(now.getTime() + DEFAULT_SESSION_VALIDITY_MS).toISOString(),
627
+ lastActive: now.toISOString(),
628
+ userId: input.userId,
629
+ isCurrent: true,
630
+ },
631
+ ],
632
+ { merge: true },
633
+ );
634
+ runtime.setActiveSessionId(input.sessionId);
635
+ });
664
636
  }
665
637
 
666
638
  // Register into the device set, hydrate the full user, and flip the
@@ -680,12 +652,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
680
652
  startSocket: () => sessionClient.start(),
681
653
  syncFromClient,
682
654
  getCurrentUser: () => oxyServices.getCurrentUser(),
683
- loginSuccess,
655
+ loginSuccess: (fullUser) => runtime.setAccount(fullUser),
684
656
  onAuthStateChange: onAuthStateChangeRef.current,
685
657
  markAuthResolved: markAuthResolvedRef.current,
686
658
  });
687
659
  },
688
- [oxyServices, authStore, updateSessions, setActiveSessionId, sessionClient, sessionClientHost, syncFromClient, loginSuccess, logger],
660
+ [oxyServices, authStore, runtime, sessionClient, sessionClientHost, syncFromClient, logger],
689
661
  );
690
662
  const commitSessionRef = useRef(commitSession);
691
663
  commitSessionRef.current = commitSession;
@@ -713,31 +685,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
713
685
  [commitSession],
714
686
  );
715
687
 
716
- // Commit a minted graph-SWITCH session through the same funnel as
717
- // `handleWebSession`. Nothing extra to do IN-PLACE: the switch already
718
- // propagates across tabs/apps via the server's `session_state` socket
719
- // broadcast.
720
- const commitSwitchedSession = useCallback(
721
- async (session: SessionLoginResponse): Promise<void> => {
722
- if (!session?.user || !session?.sessionId || !session.accessToken) {
723
- throw new Error('Session response did not include a usable session');
724
- }
725
- await commitSession(
726
- {
727
- sessionId: session.sessionId,
728
- accessToken: session.accessToken,
729
- deviceSecret: session.deviceSecret,
730
- deviceId: session.deviceId,
731
- expiresAt: session.expiresAt,
732
- userId: session.user.id,
733
- user: session.user,
734
- },
735
- { activate: true },
736
- );
737
- },
738
- [commitSession],
739
- );
740
-
741
688
  // ── Web third-party OAuth sign-in ──────────────────────────────────────────
742
689
  // The shared operation `OxySignInButton` (and any RP-owned button) delegates
743
690
  // to, so no consumer ever writes popup listeners or a token exchange. The
@@ -762,13 +709,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
762
709
  // ── Unified account dialog ─────────────────────────────────────────────────
763
710
  // The single account-chooser + sign-in surface. Built ONCE per provider mount
764
711
  // and bound to the live `oxyServices` + `sessionClient` + this provider's
765
- // commit funnels. Both funnels are threaded through refs so the controller
766
- // keeps STABLE commit callbacks (rebuilding the controller on every
767
- // commit-identity change would drop its subscription + state).
712
+ // sign-in commit funnel, threaded through a ref so the controller keeps a
713
+ // STABLE commit callback (rebuilding the controller on every commit-identity
714
+ // change would drop its subscription + state).
768
715
  const handleWebSessionRef = useRef(handleWebSession);
769
716
  handleWebSessionRef.current = handleWebSession;
770
- const commitSwitchedSessionRef = useRef(commitSwitchedSession);
771
- commitSwitchedSessionRef.current = commitSwitchedSession;
772
717
 
773
718
  // The live AccountDialog surface (a stacked Bloom surface), while open — the
774
719
  // SINGLE source of truth for "is the account dialog open". Held so
@@ -811,12 +756,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
811
756
  oxyServices,
812
757
  sessionClient,
813
758
  clientId,
814
- locale: currentLanguage,
815
759
  // Same statically-injected `io` as the SessionClient: gives the QR flow an
816
760
  // instant `/auth-session` `auth_update` wake instead of a slow poll.
817
761
  socketFactory: io,
818
762
  commitSession: (session) => handleWebSessionRef.current(session),
819
- commitSwitchedSession: (session) => commitSwitchedSessionRef.current(session),
820
763
  onSignedIn: () => {
821
764
  // Close the dialog: pop the morphed frame back to its host surface, or
822
765
  // dismiss the detached surface (whose settle flips `accountDialogOpen`).
@@ -1044,6 +987,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1044
987
  identity,
1045
988
  syncDeviceCredentialToHost,
1046
989
  sessionClient,
990
+ setTokenReady,
1047
991
  ]);
1048
992
 
1049
993
  useEffect(() => {
@@ -1147,13 +1091,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1147
1091
  }
1148
1092
  await sessionClient.switchAccount(targetAccountId);
1149
1093
  await syncFromClient();
1150
- const activeUser = useAuthStore.getState().user;
1094
+ const activeUser = runtime.getSnapshot().account;
1151
1095
  if (!activeUser) {
1152
1096
  throw new Error('Active account profile could not be resolved after switch');
1153
1097
  }
1154
1098
  return activeUser;
1155
1099
  },
1156
- [isIdentityBound, sessionClient, syncFromClient],
1100
+ [isIdentityBound, runtime, sessionClient, syncFromClient],
1157
1101
  );
1158
1102
 
1159
1103
  // Thin passthroughs to the platform-agnostic KeyManager. NOTE: both now THROW
@@ -1189,11 +1133,43 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1189
1133
  sessionClient,
1190
1134
  syncFromClient,
1191
1135
  commitSession,
1192
- queryClient,
1193
1136
  accountDialogControllerRef,
1194
1137
  clearSessionStateRef,
1195
1138
  });
1196
1139
 
1140
+ // The account-scoped cache reset, run by the runtime BETWEEN committing the
1141
+ // new subject's bearer and waking anybody (ADR 0002). It used to trail the two
1142
+ // branches of `switchToAccount`, which meant a switch pushed over the socket
1143
+ // by another app on this device got none of it: the follow stores, the scoped
1144
+ // media URLs and every warm query stayed on the previous subject until
1145
+ // something happened to refetch.
1146
+ //
1147
+ // An initial sign-in is deliberately exempt. There is nothing account-scoped
1148
+ // cached before the first account, so invalidating everything there would only
1149
+ // buy a refetch storm at first paint.
1150
+ const handleSubjectChange = useCallback(
1151
+ ({ previous, next }: SubjectTransition): void => {
1152
+ if (previous === null) {
1153
+ return;
1154
+ }
1155
+ resetSessionScopedStores();
1156
+ if (next === null) {
1157
+ // A sign-out: `clearSessionState` owns the rest of the teardown, and
1158
+ // invalidating a cache we are about to clear would only refetch as the
1159
+ // bearer disappears.
1160
+ return;
1161
+ }
1162
+ // Scoped media tokens are per-viewer — drop any cached URLs immediately so
1163
+ // `keepPreviousData`-style placeholders cannot flash the prior account's
1164
+ // private thumbnails while the new bearer mint lands.
1165
+ queryClient.removeQueries({ queryKey: [ASSET_DOWNLOAD_URLS_QUERY_KEY] });
1166
+ queryClient.invalidateQueries();
1167
+ void refreshAccounts();
1168
+ },
1169
+ [queryClient, refreshAccounts],
1170
+ );
1171
+ subjectChangeRef.current = handleSubjectChange;
1172
+
1197
1173
  const canUsePrivateApi = authResolved && isAuthenticated && tokenReady && hasAccessToken;
1198
1174
  const isPrivateApiPending = !authResolved || (isAuthenticated && (!tokenReady || !hasAccessToken));
1199
1175
 
@@ -1221,7 +1197,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1221
1197
  canUsePrivateApi,
1222
1198
  isPrivateApiPending,
1223
1199
  isAuthResolved: authResolved,
1224
- isStorageReady: storage !== null,
1200
+ isStorageReady: snapshot.storageReady,
1225
1201
  sessionMode: isIdentityBound ? 'identity' : 'account',
1226
1202
  webAuthMode,
1227
1203
  error,
@@ -1255,7 +1231,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1255
1231
  clientId,
1256
1232
  oxyServices,
1257
1233
  sessionClient,
1258
- useFollow: useFollowHook,
1234
+ useFollow,
1259
1235
  showBottomSheet: showBottomSheetForContext,
1260
1236
  openAvatarPicker,
1261
1237
  accountDialogController,
@@ -1278,7 +1254,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1278
1254
  canUsePrivateApi,
1279
1255
  isPrivateApiPending,
1280
1256
  authResolved,
1281
- storage,
1257
+ snapshot.storageReady,
1282
1258
  isIdentityBound,
1283
1259
  webAuthMode,
1284
1260
  error,
@@ -1311,7 +1287,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1311
1287
  clientId,
1312
1288
  oxyServices,
1313
1289
  sessionClient,
1314
- useFollowHook,
1315
1290
  showBottomSheetForContext,
1316
1291
  openAvatarPicker,
1317
1292
  accountDialogController,
@@ -1325,83 +1300,53 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1325
1300
  ],
1326
1301
  );
1327
1302
 
1328
- return <OxyContext.Provider value={contextValue}>{children}</OxyContext.Provider>;
1303
+ // Two contexts, deliberately. The inner one carries the wide compatibility
1304
+ // value every `useOxy()` consumer reads, rebuilt whenever any of its members
1305
+ // moves. The outer one carries the runtime reference, which never changes
1306
+ // identity — so `useActiveAccount()` and friends re-render on the fact they
1307
+ // selected and nothing else. Phase 8 retires the inner one.
1308
+ return (
1309
+ <OxyRuntimeHandleProvider value={runtime}>
1310
+ <OxyRuntimeContext.Provider value={contextValue}>{children}</OxyRuntimeContext.Provider>
1311
+ </OxyRuntimeHandleProvider>
1312
+ );
1329
1313
  };
1330
1314
 
1331
- export const OxyContextProvider = OxyProvider;
1315
+ const PROVIDER_MISSING_ERROR_MESSAGE =
1316
+ 'useOxy() was called outside <OxyProvider>. Mount <OxyProvider clientId="…"> above this component, ' +
1317
+ 'or use useOptionalOxy() if this component legitimately renders both inside and outside the provider.';
1318
+
1319
+ export class OxyProviderMissingError extends Error {
1320
+ readonly code = 'oxy_provider_missing';
1321
+
1322
+ constructor() {
1323
+ super(PROVIDER_MISSING_ERROR_MESSAGE);
1324
+ this.name = 'OxyProviderMissingError';
1325
+ }
1326
+ }
1332
1327
 
1333
1328
  /**
1334
- * Loading-state stub used when `useOxy()` is called outside an OxyProvider.
1335
- * All async methods reject with a clear error so misuse is caught early.
1329
+ * The base runtime hook. THROWS when no `<OxyProvider>` is mounted.
1330
+ *
1331
+ * It used to return a fabricated forever-loading runtime — `isLoading: true`,
1332
+ * `isPrivateApiPending: true`, every method a rejecting no-op — which turned a
1333
+ * missing provider into a UI that spins forever with nothing in the console.
1334
+ * Failing here names the mistake at the exact component that made it (ADR 0004).
1335
+ *
1336
+ * A component that legitimately renders both inside and outside the provider
1337
+ * uses {@link useOptionalOxy} instead.
1336
1338
  */
1337
- const PROVIDER_MISSING_ERROR_MESSAGE =
1338
- 'OxyProvider is not mounted. Wrap your app in <OxyProvider> before calling useOxy() methods.';
1339
-
1340
- const rejectMissingProvider = <T,>(): Promise<T> =>
1341
- Promise.reject(new Error(PROVIDER_MISSING_ERROR_MESSAGE));
1342
-
1343
- const LOADING_STATE_OXY_SERVICES = new OxyServices({ baseURL: 'about:blank' });
1344
-
1345
- const LOADING_STATE: OxyContextState = {
1346
- user: null,
1347
- sessions: [],
1348
- activeSessionId: null,
1349
- isAuthenticated: false,
1350
- isLoading: true,
1351
- isTokenReady: false,
1352
- hasAccessToken: false,
1353
- canUsePrivateApi: false,
1354
- isPrivateApiPending: true,
1355
- isAuthResolved: false,
1356
- isStorageReady: false,
1357
- sessionMode: 'account',
1358
- webAuthMode: 'popup',
1359
- error: null,
1360
- currentLanguage: 'en-US',
1361
- currentLanguages: [],
1362
- currentLanguageMetadata: null,
1363
- currentLanguageName: 'English (United States)',
1364
- currentNativeLanguageName: 'English (United States)',
1365
- hasIdentity: () => Promise.resolve(false),
1366
- getPublicKey: () => Promise.resolve(null),
1367
- signIn: () => rejectMissingProvider<User>(),
1368
- signInWithPasskey: () => rejectMissingProvider<void>(),
1369
- registerWithPasskey: () => rejectMissingProvider<void>(),
1370
- addPasskey: () => rejectMissingProvider<void>(),
1371
- removePasskey: () => rejectMissingProvider<void>(),
1372
- revokeSuspiciousSignIn: () => rejectMissingProvider<void>(),
1373
- handleWebSession: () => rejectMissingProvider<void>(),
1374
- startWebOAuthSignIn: () => rejectMissingProvider<WebOAuthSignInResult>(),
1375
- logout: () => rejectMissingProvider<void>(),
1376
- logoutAll: () => rejectMissingProvider<void>(),
1377
- switchSession: () => rejectMissingProvider<User>(),
1378
- removeSession: () => rejectMissingProvider<void>(),
1379
- refreshSessions: () => rejectMissingProvider<void>(),
1380
- setLanguage: () => rejectMissingProvider<void>(),
1381
- getDeviceSessions: () => Promise.resolve([]),
1382
- logoutAllDeviceSessions: () => rejectMissingProvider<void>(),
1383
- updateDeviceName: () => rejectMissingProvider<void>(),
1384
- clearSessionState: () => rejectMissingProvider<void>(),
1385
- clearAllAccountData: () => rejectMissingProvider<void>(),
1386
- storageKeyPrefix: 'oxy_session',
1387
- clientId: null,
1388
- oxyServices: LOADING_STATE_OXY_SERVICES,
1389
- sessionClient: null,
1390
- openAvatarPicker: () => {},
1391
- accountDialogController: null,
1392
- isAccountDialogOpen: false,
1393
- openAccountDialog: () => {},
1394
- closeAccountDialog: () => {},
1395
- accounts: [],
1396
- switchToAccount: () => rejectMissingProvider<void>(),
1397
- refreshAccounts: () => rejectMissingProvider<void>(),
1398
- createAccount: () => rejectMissingProvider<import('@oxyhq/core').AccountNode>(),
1399
- };
1400
-
1401
1339
  export const useOxy = (): OxyContextState => {
1402
- const context = useContext(OxyContext);
1340
+ const context = useContext(OxyRuntimeContext);
1403
1341
  if (!context) {
1404
- return LOADING_STATE;
1342
+ throw new OxyProviderMissingError();
1405
1343
  }
1406
1344
  return context;
1407
1345
  };
1346
+
1347
+ /**
1348
+ * `useOxy()` for the rare component that renders both inside and outside the
1349
+ * provider — returns `null` instead of throwing. Callers must handle `null`;
1350
+ * there is no fabricated runtime to fall back on.
1351
+ */
1352
+ export const useOptionalOxy = (): OxyContextState | null => useContext(OxyRuntimeContext);