@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
@@ -0,0 +1,318 @@
1
+ /**
2
+ * The platform half of the shared native DeviceSession credential.
3
+ *
4
+ * `@oxyhq/core` owns the rules (`sharedDeviceCredential.ts`: when to adopt, when
5
+ * to publish, and above all that an unreadable slot is never an empty one). This
6
+ * module owns only where the bytes live, which is genuinely different per
7
+ * platform:
8
+ *
9
+ * - **iOS** — one `expo-secure-store` item in the approved Keychain Access Group
10
+ * `group.so.oxy.shared`, under its OWN `keychainService`
11
+ * ({@link IOS_DEVICE_SESSION_KEYCHAIN_SERVICE}). Every identity slot uses a
12
+ * different service (`oxy_identity`, `oxy_identity_backup`,
13
+ * `oxy_identity_mnemonic`) and the legacy cross-app identity keypair uses the
14
+ * same group with NO service at all — so this item cannot alias any of them.
15
+ * That separation is the point of the whole phase: the group-shared identity
16
+ * key and the group-shared device-session credential are different secrets
17
+ * with different blast radii and must not be conflated.
18
+ * - **Android** — the signature-protected `OxyDeviceSession` broker (its own
19
+ * EncryptedSharedPreferences file, its own ContentProvider, its own
20
+ * `signature`-level permission). It carries a session credential; it can NOT
21
+ * reach the identity keypair, which lives in a different file behind a
22
+ * different provider.
23
+ * - **web / anything else** — no shared slot. Each origin is its own device by
24
+ * design, so the factory returns `null` and the cold boot simply omits the
25
+ * lane.
26
+ *
27
+ * ## What is written here, and what is not
28
+ *
29
+ * Only `{deviceId, deviceSecret}`. No access token, no account id, no user id,
30
+ * and never any identity key material.
31
+ *
32
+ * ## Accessibility
33
+ *
34
+ * `WHEN_UNLOCKED_THIS_DEVICE_ONLY`: a device credential must not travel to
35
+ * another handset in an iCloud/encrypted backup restore — the restored device is
36
+ * a different device and has to establish its own session. It also keeps the item
37
+ * out of reach while the phone is locked, which is why a read can legitimately
38
+ * fail and why the `unavailable` verdict exists at all.
39
+ */
40
+ import { Platform } from 'react-native';
41
+ import { requireOptionalNativeModule } from 'expo-modules-core';
42
+ import {
43
+ normalizeSharedDeviceSessionRead,
44
+ type SharedDeviceCredential,
45
+ type SharedDeviceCredentialRead,
46
+ type SharedDeviceCredentialStore,
47
+ } from '@oxyhq/core';
48
+
49
+ /**
50
+ * The iOS Keychain Access Group every official Oxy app declares in its
51
+ * entitlements. Shared with the identity slots — the group is the app-group
52
+ * boundary, not the secret's identity.
53
+ */
54
+ export const IOS_KEYCHAIN_ACCESS_GROUP = 'group.so.oxy.shared';
55
+
56
+ /**
57
+ * The iOS `keychainService` for the DEVICE SESSION credential. Deliberately
58
+ * distinct from every identity and recovery service; do not reuse one of those
59
+ * here, and do not drop it (an item written with no service shares the legacy
60
+ * identity slot's namespace inside the group).
61
+ */
62
+ export const IOS_DEVICE_SESSION_KEYCHAIN_SERVICE = 'oxy_device_session';
63
+
64
+ /** The single key holding the JSON-encoded `{deviceId, deviceSecret}` pair. */
65
+ export const SHARED_DEVICE_SESSION_STORAGE_KEY = 'oxy_shared_device_session_v1';
66
+
67
+ // Variable indirection so Metro's static analyzer never traces expo-secure-store
68
+ // into the web bundle; the module is native-only and optional.
69
+ const SECURE_STORE_MODULE = 'expo-secure-store';
70
+
71
+ /**
72
+ * The subset of `expo-secure-store` this module needs. Unlike the adapter in
73
+ * `nativeSecureStorage.ts` it must pass OPTIONS — the access group and the
74
+ * dedicated service are the entire mechanism here, so an options-less wrapper
75
+ * would silently write a package-private item that no sibling app can see.
76
+ */
77
+ interface KeychainGroupStore {
78
+ getItemAsync(key: string, options?: object): Promise<string | null>;
79
+ setItemAsync(key: string, value: string, options?: object): Promise<void>;
80
+ deleteItemAsync(key: string, options?: object): Promise<void>;
81
+ readonly WHEN_UNLOCKED_THIS_DEVICE_ONLY: number;
82
+ }
83
+
84
+ let keychainStorePromise: Promise<KeychainGroupStore | null> | null = null;
85
+
86
+ function loadKeychainGroupStore(): Promise<KeychainGroupStore | null> {
87
+ if (!keychainStorePromise) {
88
+ const moduleName = SECURE_STORE_MODULE;
89
+ keychainStorePromise = import(moduleName)
90
+ .then((mod: Partial<KeychainGroupStore>) => {
91
+ if (
92
+ typeof mod.getItemAsync === 'function' &&
93
+ typeof mod.setItemAsync === 'function' &&
94
+ typeof mod.deleteItemAsync === 'function' &&
95
+ typeof mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY === 'number'
96
+ ) {
97
+ return {
98
+ getItemAsync: mod.getItemAsync.bind(mod),
99
+ setItemAsync: mod.setItemAsync.bind(mod),
100
+ deleteItemAsync: mod.deleteItemAsync.bind(mod),
101
+ WHEN_UNLOCKED_THIS_DEVICE_ONLY: mod.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
102
+ } satisfies KeychainGroupStore;
103
+ }
104
+ return null;
105
+ })
106
+ .catch(() => null);
107
+ }
108
+ return keychainStorePromise;
109
+ }
110
+
111
+ /** Parse the stored JSON into a credential, or `null` for anything malformed. */
112
+ function parseStoredCredential(raw: string | null): SharedDeviceCredential | null {
113
+ if (!raw) {
114
+ return null;
115
+ }
116
+ let parsed: unknown;
117
+ try {
118
+ parsed = JSON.parse(raw);
119
+ } catch {
120
+ return null;
121
+ }
122
+ if (!parsed || typeof parsed !== 'object') {
123
+ return null;
124
+ }
125
+ const candidate = parsed as Record<string, unknown>;
126
+ if (
127
+ typeof candidate.deviceId !== 'string' ||
128
+ candidate.deviceId.length === 0 ||
129
+ typeof candidate.deviceSecret !== 'string' ||
130
+ candidate.deviceSecret.length === 0
131
+ ) {
132
+ return null;
133
+ }
134
+ return { deviceId: candidate.deviceId, deviceSecret: candidate.deviceSecret };
135
+ }
136
+
137
+ /**
138
+ * The iOS store: one keychain item in the shared access group.
139
+ *
140
+ * A THROWN read is `unavailable`, a `null` read is `absent`. That mapping is the
141
+ * whole safety contract — `expo-secure-store` returns `null` for a genuinely
142
+ * missing item and throws when the keychain itself refuses, and collapsing the
143
+ * two is what would let a locked phone look like a device that never had a
144
+ * session.
145
+ */
146
+ function createKeychainGroupCredentialStore(): SharedDeviceCredentialStore {
147
+ const readOptions = {
148
+ keychainService: IOS_DEVICE_SESSION_KEYCHAIN_SERVICE,
149
+ keychainAccessGroup: IOS_KEYCHAIN_ACCESS_GROUP,
150
+ };
151
+
152
+ return {
153
+ read: async (): Promise<SharedDeviceCredentialRead> => {
154
+ const store = await loadKeychainGroupStore();
155
+ if (!store) {
156
+ return { state: 'unsupported' };
157
+ }
158
+ let raw: string | null;
159
+ try {
160
+ raw = await store.getItemAsync(SHARED_DEVICE_SESSION_STORAGE_KEY, readOptions);
161
+ } catch (error) {
162
+ return { state: 'unavailable', cause: error };
163
+ }
164
+ if (raw === null) {
165
+ return { state: 'absent' };
166
+ }
167
+ const credential = parseStoredCredential(raw);
168
+ if (!credential) {
169
+ // Something IS stored and we cannot read it. Reporting `absent` would
170
+ // authorise overwriting whatever a newer build put there.
171
+ return {
172
+ state: 'unavailable',
173
+ cause: new Error('shared device session item is present but not parseable'),
174
+ };
175
+ }
176
+ return { state: 'present', credential };
177
+ },
178
+
179
+ publish: async (credential): Promise<boolean> => {
180
+ const store = await loadKeychainGroupStore();
181
+ if (!store) {
182
+ return false;
183
+ }
184
+ const value = JSON.stringify(credential);
185
+ try {
186
+ await store.setItemAsync(SHARED_DEVICE_SESSION_STORAGE_KEY, value, {
187
+ ...readOptions,
188
+ keychainAccessible: store.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
189
+ });
190
+ // Read-back verify. A keychain write can resolve without the item having
191
+ // landed under the access group (a missing entitlement is the usual
192
+ // cause), and a phantom credential would send a fresh install into a mint
193
+ // that can never succeed.
194
+ return (await store.getItemAsync(SHARED_DEVICE_SESSION_STORAGE_KEY, readOptions)) === value;
195
+ } catch {
196
+ return false;
197
+ }
198
+ },
199
+
200
+ clear: async (): Promise<void> => {
201
+ const store = await loadKeychainGroupStore();
202
+ if (!store) {
203
+ return;
204
+ }
205
+ try {
206
+ await store.deleteItemAsync(SHARED_DEVICE_SESSION_STORAGE_KEY, readOptions);
207
+ } catch {
208
+ // Best-effort: the slot is a join point, not a source of truth.
209
+ }
210
+ },
211
+ };
212
+ }
213
+
214
+ /**
215
+ * The Android broker's JS surface. `read` returns an UNTRUSTED payload that
216
+ * {@link normalizeSharedDeviceSessionRead} narrows; `write` reports whether the
217
+ * native read-back confirmed the bytes.
218
+ */
219
+ interface OxyDeviceSessionNativeModule {
220
+ read(): Promise<unknown>;
221
+ write(deviceId: string, deviceSecret: string): Promise<boolean>;
222
+ clear(): Promise<void>;
223
+ }
224
+
225
+ let brokerModule: OxyDeviceSessionNativeModule | null | undefined;
226
+
227
+ /**
228
+ * Resolve the `OxyDeviceSession` broker, or `null` when it is not autolinked.
229
+ *
230
+ * `requireOptionalNativeModule` (a static import Metro always resolves) rather
231
+ * than a dynamic `import(moduleName)`: the latter compiles to `require(variable)`
232
+ * in the CJS build, which Metro cannot resolve inside a consuming app — it
233
+ * silently returns `null` there, which is exactly how the cross-app identity
234
+ * bridge broke once. Same reasoning as `backgroundSession.ts` next door.
235
+ */
236
+ function loadBrokerModule(): OxyDeviceSessionNativeModule | null {
237
+ if (brokerModule === undefined) {
238
+ const native = requireOptionalNativeModule<Partial<OxyDeviceSessionNativeModule>>('OxyDeviceSession');
239
+ brokerModule =
240
+ native && typeof native.read === 'function' && typeof native.write === 'function' && typeof native.clear === 'function'
241
+ ? {
242
+ read: native.read.bind(native),
243
+ write: native.write.bind(native),
244
+ clear: native.clear.bind(native),
245
+ }
246
+ : null;
247
+ }
248
+ return brokerModule;
249
+ }
250
+
251
+ /**
252
+ * The Android store: the signature-protected `OxyDeviceSession` broker.
253
+ *
254
+ * The broker already answers with an explicit status, so the only work here is
255
+ * narrowing an UNTRUSTED payload — `normalizeSharedDeviceSessionRead` resolves
256
+ * anything it does not recognise to `unavailable`, never `absent`.
257
+ */
258
+ function createBrokerCredentialStore(): SharedDeviceCredentialStore {
259
+ return {
260
+ read: async (): Promise<SharedDeviceCredentialRead> => {
261
+ const broker = loadBrokerModule();
262
+ if (!broker) {
263
+ return { state: 'unsupported' };
264
+ }
265
+ try {
266
+ return normalizeSharedDeviceSessionRead(await broker.read());
267
+ } catch (error) {
268
+ return { state: 'unavailable', cause: error };
269
+ }
270
+ },
271
+
272
+ publish: async (credential): Promise<boolean> => {
273
+ const broker = loadBrokerModule();
274
+ if (!broker) {
275
+ return false;
276
+ }
277
+ try {
278
+ // The broker read-back-verifies natively and returns the verdict.
279
+ return await broker.write(credential.deviceId, credential.deviceSecret);
280
+ } catch {
281
+ return false;
282
+ }
283
+ },
284
+
285
+ clear: async (): Promise<void> => {
286
+ const broker = loadBrokerModule();
287
+ if (!broker) {
288
+ return;
289
+ }
290
+ try {
291
+ await broker.clear();
292
+ } catch {
293
+ // Best-effort — see the iOS store.
294
+ }
295
+ },
296
+ };
297
+ }
298
+
299
+ /**
300
+ * The shared DeviceSession credential slot for this runtime, or `null` when
301
+ * there is none.
302
+ *
303
+ * The branch is on the PLATFORM, not on which module happens to load, because
304
+ * `expo-secure-store` loads perfectly well on Android — it would just quietly
305
+ * ignore `keychainAccessGroup` and read a package-private item, which looks
306
+ * exactly like an empty shared slot. This is the same iOS-vs-Android split
307
+ * `KeyManager` makes for the shared identity, for the same reason.
308
+ *
309
+ * Any other native platform gets the broker, which answers `unsupported` when
310
+ * the module is not linked — so the lane is simply off, which is the safe
311
+ * direction.
312
+ */
313
+ export function createPlatformSharedDeviceCredentialStore(): SharedDeviceCredentialStore | null {
314
+ if (Platform.OS === 'web') {
315
+ return null;
316
+ }
317
+ return Platform.OS === 'ios' ? createKeychainGroupCredentialStore() : createBrokerCredentialStore();
318
+ }
@@ -1,63 +1,191 @@
1
- import { create } from 'zustand';
2
- import { createLogger, normalizeUserIdentity } from '@oxyhq/core';
1
+ import { useCallback, useRef, useSyncExternalStore } from 'react';
3
2
  import type { User } from '@oxyhq/core';
3
+ import type { OxyRuntime } from '../runtime';
4
4
 
5
- const log = createLogger('AuthStore');
6
-
5
+ /**
6
+ * The signed-in user, as a zustand-shaped read/write surface.
7
+ *
8
+ * It used to BE a zustand store, and that made it a second owner of the
9
+ * session: the provider wrote `user`/`isAuthenticated` here, `SessionClient`
10
+ * wrote the same facts into its device state, and nothing reconciled them —
11
+ * when they disagreed, which one was right was not answerable. It is now a
12
+ * projection of the ONE `OxyRuntime` (ADR 0004): reads come off the runtime
13
+ * snapshot and writes go into the runtime, so there is nothing left to drift.
14
+ *
15
+ * The zustand SHAPE is kept deliberately — `useAuthStore(selector)`,
16
+ * `.getState()`, `.setState()`, `.subscribe()` — because a dozen call sites
17
+ * across this package and the Commons app read the user out of band, from
18
+ * mutation success handlers and cache helpers that are not inside a render.
19
+ * Rewriting them is Phase 8's cut, not this one's.
20
+ *
21
+ * `fetchUser` and `lastUserFetch` are GONE rather than projected: they were a
22
+ * five-minute profile cache with zero readers anywhere in the monorepo, and the
23
+ * Query cache has owned that job since long before this change.
24
+ */
7
25
  export interface AuthState {
8
26
  user: User | null;
9
27
  isAuthenticated: boolean;
10
28
  isLoading: boolean;
11
29
  error: string | null;
12
- lastUserFetch: number | null; // Timestamp of last user fetch for caching
13
-
30
+
14
31
  loginSuccess: (user: User) => void;
15
32
  loginFailure: (error: string) => void;
16
33
  logout: () => void;
17
- fetchUser: (oxyServices: { getCurrentUser: () => Promise<User> }, forceRefresh?: boolean) => Promise<void>;
18
- setUser: (user: User) => void; // Direct user setter for caching
34
+ /** Replace the signed-in profile after a write (a rename, a new avatar). */
35
+ setUser: (user: User) => void;
19
36
  }
20
37
 
21
- export const useAuthStore = create<AuthState>((set: (state: Partial<AuthState>) => void, get: () => AuthState) => ({
38
+ /** The subset of `AuthState` a caller may push back in through `setState`. */
39
+ export type AuthStatePatch = Partial<Pick<AuthState, 'user' | 'isLoading' | 'error'>>;
40
+
41
+ let boundRuntime: OxyRuntime | null = null;
42
+ let unbindRuntime: (() => void) | null = null;
43
+ const listeners = new Set<() => void>();
44
+
45
+ const loginSuccess = (user: User): void => {
46
+ boundRuntime?.setAccount(user);
47
+ };
48
+
49
+ const loginFailure = (error: string): void => {
50
+ boundRuntime?.batch(() => {
51
+ boundRuntime?.setLoading(false);
52
+ boundRuntime?.setError(error);
53
+ });
54
+ };
55
+
56
+ const logout = (): void => {
57
+ boundRuntime?.clearSession();
58
+ };
59
+
60
+ const setUser = (user: User): void => {
61
+ boundRuntime?.setAccount(user);
62
+ };
63
+
64
+ const SIGNED_OUT: AuthState = Object.freeze({
22
65
  user: null,
23
66
  isAuthenticated: false,
24
67
  isLoading: false,
25
68
  error: null,
26
- lastUserFetch: null,
27
-
28
- loginSuccess: (user: User) => set({
29
- isLoading: false,
30
- isAuthenticated: true,
31
- user: normalizeUserIdentity(user),
32
- lastUserFetch: Date.now(),
33
- }),
34
- loginFailure: (error: string) => set({ isLoading: false, error }),
35
- logout: () => set({
36
- user: null,
37
- isAuthenticated: false,
38
- lastUserFetch: null,
39
- }),
40
- setUser: (user: User) => set({ user: normalizeUserIdentity(user), lastUserFetch: Date.now() }),
41
- fetchUser: async (oxyServices, forceRefresh = false) => {
42
- const state = get();
43
- const now = Date.now();
44
- const cacheAge = state.lastUserFetch ? now - state.lastUserFetch : Number.POSITIVE_INFINITY;
45
- const cacheValid = cacheAge < 5 * 60 * 1000; // 5 minutes cache
46
-
47
- // Use cached data if available and not forcing refresh
48
- if (!forceRefresh && state.user && cacheValid) {
49
- log.info('Using cached user data', { cacheAge });
69
+ loginSuccess,
70
+ loginFailure,
71
+ logout,
72
+ setUser,
73
+ });
74
+
75
+ let state: AuthState = SIGNED_OUT;
76
+
77
+ function project(runtime: OxyRuntime | null): AuthState {
78
+ if (runtime === null) {
79
+ return SIGNED_OUT;
80
+ }
81
+ const snapshot = runtime.getSnapshot();
82
+ return Object.freeze({
83
+ user: snapshot.account,
84
+ isAuthenticated: snapshot.account !== null,
85
+ isLoading: snapshot.isLoading,
86
+ error: snapshot.error?.message ?? null,
87
+ loginSuccess,
88
+ loginFailure,
89
+ logout,
90
+ setUser,
91
+ });
92
+ }
93
+
94
+ function republish(): void {
95
+ const next = project(boundRuntime);
96
+ if (
97
+ next.user === state.user &&
98
+ next.isAuthenticated === state.isAuthenticated &&
99
+ next.isLoading === state.isLoading &&
100
+ next.error === state.error
101
+ ) {
102
+ return;
103
+ }
104
+ state = next;
105
+ for (const listener of [...listeners]) {
106
+ listener();
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Point this surface at the provider's runtime. Called once by
112
+ * `OxyRuntimeProvider`; the returned function detaches it again on unmount.
113
+ *
114
+ * With nothing bound, reads answer signed-out and writes are dropped. That is
115
+ * the honest answer for the two places it happens — the SSR shim, and a unit
116
+ * test poking the store with no provider mounted — and it is why binding is an
117
+ * explicit call rather than a lazily-created default runtime, which is exactly
118
+ * the fabricated-forever-loading runtime ADR 0004 deleted.
119
+ */
120
+ export function bindAuthStoreToRuntime(runtime: OxyRuntime): () => void {
121
+ unbindRuntime?.();
122
+ boundRuntime = runtime;
123
+ unbindRuntime = runtime.subscribe(republish);
124
+ republish();
125
+ return () => {
126
+ if (boundRuntime !== runtime) {
50
127
  return;
51
128
  }
129
+ unbindRuntime?.();
130
+ unbindRuntime = null;
131
+ boundRuntime = null;
132
+ republish();
133
+ };
134
+ }
52
135
 
53
- set({ isLoading: true, error: null });
54
- try {
55
- const user = normalizeUserIdentity(await oxyServices.getCurrentUser());
56
- set({ user, isLoading: false, isAuthenticated: true, lastUserFetch: now });
57
- } catch (error) {
58
- const errorMessage = error instanceof Error ? error.message : 'Failed to fetch user';
59
- log.error('Error fetching user', error);
60
- set({ error: errorMessage, isLoading: false });
136
+ const getState = (): AuthState => state;
137
+
138
+ const setState = (patch: AuthStatePatch): void => {
139
+ boundRuntime?.batch(() => {
140
+ if (patch.isLoading !== undefined) {
141
+ boundRuntime?.setLoading(patch.isLoading);
142
+ }
143
+ if (patch.error !== undefined) {
144
+ boundRuntime?.setError(patch.error);
145
+ }
146
+ if (patch.user !== undefined) {
147
+ if (patch.user === null) {
148
+ boundRuntime?.clearSession();
149
+ } else {
150
+ boundRuntime?.setAccount(patch.user);
151
+ }
61
152
  }
62
- },
63
- }));
153
+ });
154
+ };
155
+
156
+ const subscribe = (listener: () => void): (() => void) => {
157
+ listeners.add(listener);
158
+ return () => {
159
+ listeners.delete(listener);
160
+ };
161
+ };
162
+
163
+ function useAuthStoreHook<T>(selector: (state: AuthState) => T): T {
164
+ // The selection is cached against the state object it came from, so a
165
+ // selector returning a fresh object cannot make `useSyncExternalStore` see a
166
+ // new value on every call and loop.
167
+ const cache = useRef<{ state: AuthState; value: T } | null>(null);
168
+ const getSelection = useCallback((): T => {
169
+ const cached = cache.current;
170
+ if (cached !== null && cached.state === state) {
171
+ return cached.value;
172
+ }
173
+ const value = selector(state);
174
+ cache.current = { state, value };
175
+ return value;
176
+ }, [selector]);
177
+ return useSyncExternalStore(subscribe, getSelection, getSelection);
178
+ }
179
+
180
+ /**
181
+ * Read the signed-in user. Zustand-shaped, projected from the runtime.
182
+ *
183
+ * A selector that builds an object must be wrapped in `useShallow` (as every
184
+ * call site here already does) — otherwise a structurally-equal result reads as
185
+ * a change on every publish.
186
+ */
187
+ export const useAuthStore = Object.assign(useAuthStoreHook, {
188
+ getState,
189
+ setState,
190
+ subscribe,
191
+ });
@@ -23,7 +23,7 @@
23
23
  */
24
24
 
25
25
  import { create } from 'zustand';
26
- import type { FollowApplicationMode, FollowStatus } from '@oxyhq/contracts';
26
+ import type { FollowApplicationMode, FollowRecord, FollowStatus } from '@oxyhq/contracts';
27
27
 
28
28
  /** The status of one target, or `undefined` when it has never been read. */
29
29
  type StatusMap = Record<string, FollowStatus | undefined>;
@@ -104,6 +104,29 @@ export function isCompleteFollowStatus(status: FollowStatus): boolean {
104
104
  return true;
105
105
  }
106
106
 
107
+ /** Convert one list row into the status shape the follow-target store expects. */
108
+ export function followRecordToStatus(record: FollowRecord): FollowStatus {
109
+ return {
110
+ relationshipId: record.relationshipId,
111
+ globalState: record.globalState,
112
+ applicationMode: record.applicationMode,
113
+ effectiveState: record.effectiveState,
114
+ ...(record.expiresAt ? { expiresAt: record.expiresAt } : {}),
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Bulk-convert a follow list page into a status map keyed by target id — the
120
+ * input shape for `useFollowTargetStore.getState().seed(...)`.
121
+ */
122
+ export function followRecordsToStatusMap(records: FollowRecord[]): Record<string, FollowStatus> {
123
+ const entries: Record<string, FollowStatus> = {};
124
+ for (const record of records) {
125
+ entries[record.target.id] = followRecordToStatus(record);
126
+ }
127
+ return entries;
128
+ }
129
+
107
130
  /**
108
131
  * Apply a mode change to a cached status without a round trip.
109
132
  *
@@ -143,4 +143,30 @@ export interface OxyProviderProps {
143
143
  * @default false
144
144
  */
145
145
  backgroundSession?: boolean;
146
+ /**
147
+ * Whether this origin/app may persist the durable device credential
148
+ * (`deviceId` + `deviceSecret`) at all.
149
+ *
150
+ * `'persistent'` (the default, and every app's behaviour) stores it — web
151
+ * localStorage per origin, SecureStore per app on native — so the SDK can
152
+ * re-mint an access token on the next cold boot.
153
+ *
154
+ * `'ephemeral'` stores NOTHING durable: the session lives in memory for the
155
+ * lifetime of the page and is gone on reload. It exists for ONE caller,
156
+ * `auth.oxy.so` with the browser hub enabled (issue #937 Phase 5, ADR 0003),
157
+ * where the durable credential for the browser profile is the server-side
158
+ * DeviceSession behind the `__Host-oxy-device` handle. Two durable
159
+ * credentials for one origin is the dual authority that phase exists to
160
+ * remove — revoking the hub while a localStorage secret still mints would
161
+ * make a sign-out look like it worked — so the way to let the hub be
162
+ * authoritative is to stop persisting the other one, not to consult it
163
+ * second.
164
+ *
165
+ * Do NOT reach for this to make an app "more private". A relying-party
166
+ * origin set to `'ephemeral'` simply signs the user out on every reload,
167
+ * because nothing else on that origin remembers the device.
168
+ *
169
+ * @default 'persistent'
170
+ */
171
+ deviceCredentialStorage?: 'persistent' | 'ephemeral';
146
172
  }
@@ -1,7 +1,6 @@
1
1
  import { authenticatedApiCall } from '@oxyhq/core';
2
2
  import type { OxyServices, User } from '@oxyhq/core';
3
3
  import type { UserProfileUpdate } from '@oxyhq/contracts';
4
- import { useAccountStore } from '../stores/accountStore';
5
4
  import { useAuthStore } from '../stores/authStore';
6
5
  import type { QueryClient } from '@tanstack/react-query';
7
6
  import { queryKeys, invalidateUserQueries, invalidateAccountQueries } from '../hooks/queries/queryKeys';
@@ -11,28 +10,8 @@ import {
11
10
  } from '../hooks/queries/userCache';
12
11
 
13
12
  /**
14
- * Refreshes avatar in accountStore with cache-busted URL to force image reload.
15
- *
16
- * @param sessionId - The session ID for the account to update
17
- * @param avatarFileId - The new avatar file ID
18
- * @param oxyServices - OxyServices instance to generate download URL
19
- */
20
- export function refreshAvatarInStore(
21
- sessionId: string,
22
- avatarFileId: string,
23
- oxyServices: OxyServices
24
- ): void {
25
- const { updateAccount } = useAccountStore.getState();
26
- const cacheBustedUrl = `${oxyServices.getFileDownloadUrl(avatarFileId, 'thumb')}?t=${Date.now()}`;
27
- updateAccount(sessionId, {
28
- avatar: avatarFileId,
29
- avatarUrl: cacheBustedUrl,
30
- });
31
- }
32
-
33
- /**
34
- * Updates user profile with avatar and handles all side effects (query invalidation, accountStore update).
35
- * This function can be used from within OxyContext provider without requiring useOxy hook.
13
+ * Updates user profile with avatar and handles all side effects (cache writes,
14
+ * query invalidation). Usable from inside the provider without `useOxy`.
36
15
  *
37
16
  * @param updates - Profile updates including avatar
38
17
  * @param oxyServices - OxyServices instance
@@ -69,20 +48,6 @@ export async function updateProfileWithAvatar(
69
48
  cleared: cleared.length > 0 ? cleared : undefined,
70
49
  });
71
50
 
72
- // If avatar was updated, refresh accountStore with a cache-busted URL. An
73
- // EMPTY avatar is a removal: clear both fields so the switcher falls back to
74
- // initials instead of pointing at a download URL for the empty file id.
75
- if (typeof updates.avatar === 'string' && activeSessionId) {
76
- if (updates.avatar) {
77
- refreshAvatarInStore(activeSessionId, updates.avatar, oxyServices);
78
- } else {
79
- useAccountStore.getState().updateAccount(activeSessionId, {
80
- avatar: undefined,
81
- avatarUrl: undefined,
82
- });
83
- }
84
- }
85
-
86
51
  // Invalidate all related queries to refresh everywhere
87
52
  invalidateUserQueries(queryClient);
88
53
  invalidateAccountQueries(queryClient);