@oxyhq/services 28.0.0 → 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 (324) hide show
  1. package/NOTICE +10 -9
  2. package/README.md +65 -2
  3. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionModule.kt +178 -0
  4. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionProvider.kt +99 -0
  5. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionStore.kt +115 -0
  6. package/expo-module.config.json +1 -0
  7. package/lib/commonjs/index.js +60 -16
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/ui/boot/runProviderColdBoot.js +7 -0
  10. package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
  11. package/lib/commonjs/ui/client.js +12 -7
  12. package/lib/commonjs/ui/client.js.map +1 -1
  13. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +10 -27
  14. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
  15. package/lib/commonjs/ui/components/OxyAuthChooser.js +132 -44
  16. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  17. package/lib/commonjs/ui/components/OxyProvider.js +4 -2
  18. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  19. package/lib/commonjs/ui/components/SurfaceScreen.js +31 -3
  20. package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
  21. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +209 -84
  22. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  23. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +25 -7
  24. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -1
  25. package/lib/commonjs/ui/components/authChooser/primitives.js +35 -22
  26. package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -1
  27. package/lib/commonjs/ui/components/authChooser/types.js +11 -0
  28. package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
  29. package/lib/commonjs/ui/context/OxyContext.js +257 -306
  30. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  31. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +27 -37
  32. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  33. package/lib/commonjs/ui/context/oxyContextHelpers.js +0 -29
  34. package/lib/commonjs/ui/context/oxyContextHelpers.js.map +1 -1
  35. package/lib/commonjs/ui/context/useOxyAccountGraph.js +6 -17
  36. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  37. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js +42 -0
  38. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js.map +1 -0
  39. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +0 -19
  40. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  41. package/lib/commonjs/ui/hooks/useDeviceSwitcher.js +57 -0
  42. package/lib/commonjs/ui/hooks/useDeviceSwitcher.js.map +1 -0
  43. package/lib/commonjs/ui/hooks/useI18n.js +22 -3
  44. package/lib/commonjs/ui/hooks/useI18n.js.map +1 -1
  45. package/lib/commonjs/ui/hooks/useSessionManagement.js +46 -153
  46. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  47. package/lib/commonjs/ui/index.js +12 -7
  48. package/lib/commonjs/ui/index.js.map +1 -1
  49. package/lib/commonjs/ui/navigation/routes.js +85 -73
  50. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  51. package/lib/commonjs/ui/navigation/surfaces.js +8 -7
  52. package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
  53. package/lib/commonjs/ui/runtime/createOxyRuntime.js +433 -0
  54. package/lib/commonjs/ui/runtime/createOxyRuntime.js.map +1 -0
  55. package/lib/commonjs/ui/runtime/index.js +63 -0
  56. package/lib/commonjs/ui/runtime/index.js.map +1 -0
  57. package/lib/commonjs/ui/runtime/runtimeContext.js +83 -0
  58. package/lib/commonjs/ui/runtime/runtimeContext.js.map +1 -0
  59. package/lib/commonjs/ui/runtime/types.js +6 -0
  60. package/lib/commonjs/ui/runtime/types.js.map +1 -0
  61. package/lib/commonjs/ui/runtime/useRuntimeSnapshot.js +57 -0
  62. package/lib/commonjs/ui/runtime/useRuntimeSnapshot.js.map +1 -0
  63. package/lib/commonjs/ui/server.js +1 -2
  64. package/lib/commonjs/ui/server.js.map +1 -1
  65. package/lib/commonjs/ui/session/authStore.js +45 -9
  66. package/lib/commonjs/ui/session/authStore.js.map +1 -1
  67. package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js +306 -0
  68. package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js.map +1 -0
  69. package/lib/commonjs/ui/stores/authStore.js +154 -55
  70. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  71. package/lib/commonjs/ui/utils/avatarUtils.js +2 -36
  72. package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
  73. package/lib/module/index.js +16 -8
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/ui/boot/runProviderColdBoot.js +7 -0
  76. package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
  77. package/lib/module/ui/client.js +1 -2
  78. package/lib/module/ui/client.js.map +1 -1
  79. package/lib/module/ui/components/OxyAccountDialogScreen.js +10 -27
  80. package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
  81. package/lib/module/ui/components/OxyAuthChooser.js +134 -44
  82. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  83. package/lib/module/ui/components/OxyProvider.js +5 -3
  84. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  85. package/lib/module/ui/components/SurfaceScreen.js +33 -5
  86. package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
  87. package/lib/module/ui/components/authChooser/AccountsMenuView.js +210 -85
  88. package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  89. package/lib/module/ui/components/authChooser/SignInEntryView.js +25 -7
  90. package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -1
  91. package/lib/module/ui/components/authChooser/primitives.js +35 -22
  92. package/lib/module/ui/components/authChooser/primitives.js.map +1 -1
  93. package/lib/module/ui/components/authChooser/types.js +11 -0
  94. package/lib/module/ui/components/authChooser/types.js.map +1 -1
  95. package/lib/module/ui/context/OxyContext.js +251 -303
  96. package/lib/module/ui/context/OxyContext.js.map +1 -1
  97. package/lib/module/ui/context/hooks/useAuthOperations.js +27 -37
  98. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  99. package/lib/module/ui/context/oxyContextHelpers.js +0 -28
  100. package/lib/module/ui/context/oxyContextHelpers.js.map +1 -1
  101. package/lib/module/ui/context/useOxyAccountGraph.js +6 -17
  102. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  103. package/lib/module/ui/hooks/accountDialogSnapshot.js +38 -0
  104. package/lib/module/ui/hooks/accountDialogSnapshot.js.map +1 -0
  105. package/lib/module/ui/hooks/mutations/useAccountMutations.js +0 -19
  106. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  107. package/lib/module/ui/hooks/useDeviceSwitcher.js +53 -0
  108. package/lib/module/ui/hooks/useDeviceSwitcher.js.map +1 -0
  109. package/lib/module/ui/hooks/useI18n.js +24 -4
  110. package/lib/module/ui/hooks/useI18n.js.map +1 -1
  111. package/lib/module/ui/hooks/useSessionManagement.js +47 -155
  112. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  113. package/lib/module/ui/index.js +1 -2
  114. package/lib/module/ui/index.js.map +1 -1
  115. package/lib/module/ui/navigation/routes.js +84 -72
  116. package/lib/module/ui/navigation/routes.js.map +1 -1
  117. package/lib/module/ui/navigation/surfaces.js +6 -6
  118. package/lib/module/ui/navigation/surfaces.js.map +1 -1
  119. package/lib/module/ui/runtime/createOxyRuntime.js +430 -0
  120. package/lib/module/ui/runtime/createOxyRuntime.js.map +1 -0
  121. package/lib/module/ui/runtime/index.js +15 -0
  122. package/lib/module/ui/runtime/index.js.map +1 -0
  123. package/lib/module/ui/runtime/runtimeContext.js +75 -0
  124. package/lib/module/ui/runtime/runtimeContext.js.map +1 -0
  125. package/lib/module/ui/runtime/types.js +4 -0
  126. package/lib/module/ui/runtime/types.js.map +1 -0
  127. package/lib/module/ui/runtime/useRuntimeSnapshot.js +52 -0
  128. package/lib/module/ui/runtime/useRuntimeSnapshot.js.map +1 -0
  129. package/lib/module/ui/server.js +0 -1
  130. package/lib/module/ui/server.js.map +1 -1
  131. package/lib/module/ui/session/authStore.js +46 -11
  132. package/lib/module/ui/session/authStore.js.map +1 -1
  133. package/lib/module/ui/session/sharedDeviceCredentialStore.js +302 -0
  134. package/lib/module/ui/session/sharedDeviceCredentialStore.js.map +1 -0
  135. package/lib/module/ui/stores/authStore.js +154 -55
  136. package/lib/module/ui/stores/authStore.js.map +1 -1
  137. package/lib/module/ui/utils/avatarUtils.js +2 -35
  138. package/lib/module/ui/utils/avatarUtils.js.map +1 -1
  139. package/lib/typescript/commonjs/index.d.ts +5 -3
  140. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/ui/client.d.ts +1 -2
  143. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  144. package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  146. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  147. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -1
  148. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts +11 -4
  149. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +3 -1
  151. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +18 -6
  153. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -1
  154. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +33 -1
  155. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
  156. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +35 -4
  157. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  158. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +8 -12
  159. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  160. package/lib/typescript/commonjs/ui/context/oxyContextHelpers.d.ts +0 -2
  161. package/lib/typescript/commonjs/ui/context/oxyContextHelpers.d.ts.map +1 -1
  162. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +9 -1
  163. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  164. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +1 -3
  165. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  166. package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts +15 -0
  167. package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts.map +1 -0
  168. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/ui/hooks/useDeviceSwitcher.d.ts +49 -0
  170. package/lib/typescript/commonjs/ui/hooks/useDeviceSwitcher.d.ts.map +1 -0
  171. package/lib/typescript/commonjs/ui/hooks/useI18n.d.ts +13 -0
  172. package/lib/typescript/commonjs/ui/hooks/useI18n.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts +23 -18
  174. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/ui/index.d.ts +1 -2
  176. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  177. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +15 -1
  178. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
  180. package/lib/typescript/commonjs/ui/runtime/createOxyRuntime.d.ts +144 -0
  181. package/lib/typescript/commonjs/ui/runtime/createOxyRuntime.d.ts.map +1 -0
  182. package/lib/typescript/commonjs/ui/runtime/index.d.ts +16 -0
  183. package/lib/typescript/commonjs/ui/runtime/index.d.ts.map +1 -0
  184. package/lib/typescript/commonjs/ui/runtime/runtimeContext.d.ts +43 -0
  185. package/lib/typescript/commonjs/ui/runtime/runtimeContext.d.ts.map +1 -0
  186. package/lib/typescript/commonjs/ui/runtime/types.d.ts +89 -0
  187. package/lib/typescript/commonjs/ui/runtime/types.d.ts.map +1 -0
  188. package/lib/typescript/commonjs/ui/runtime/useRuntimeSnapshot.d.ts +23 -0
  189. package/lib/typescript/commonjs/ui/runtime/useRuntimeSnapshot.d.ts.map +1 -0
  190. package/lib/typescript/commonjs/ui/server.d.ts +0 -1
  191. package/lib/typescript/commonjs/ui/server.d.ts.map +1 -1
  192. package/lib/typescript/commonjs/ui/session/authStore.d.ts +60 -9
  193. package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/ui/session/sharedDeviceCredentialStore.d.ts +32 -0
  195. package/lib/typescript/commonjs/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -0
  196. package/lib/typescript/commonjs/ui/stores/authStore.d.ts +49 -5
  197. package/lib/typescript/commonjs/ui/stores/authStore.d.ts.map +1 -1
  198. package/lib/typescript/commonjs/ui/types/navigation.d.ts +26 -0
  199. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  200. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts +2 -10
  201. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
  202. package/lib/typescript/module/index.d.ts +5 -3
  203. package/lib/typescript/module/index.d.ts.map +1 -1
  204. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  205. package/lib/typescript/module/ui/client.d.ts +1 -2
  206. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  207. package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  208. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  209. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  210. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -1
  211. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts +11 -4
  212. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  213. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +3 -1
  214. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  215. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +18 -6
  216. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -1
  217. package/lib/typescript/module/ui/components/authChooser/types.d.ts +33 -1
  218. package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
  219. package/lib/typescript/module/ui/context/OxyContext.d.ts +35 -4
  220. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  221. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +8 -12
  222. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  223. package/lib/typescript/module/ui/context/oxyContextHelpers.d.ts +0 -2
  224. package/lib/typescript/module/ui/context/oxyContextHelpers.d.ts.map +1 -1
  225. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +9 -1
  226. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  227. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +1 -3
  228. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  229. package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts +15 -0
  230. package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts.map +1 -0
  231. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  232. package/lib/typescript/module/ui/hooks/useDeviceSwitcher.d.ts +49 -0
  233. package/lib/typescript/module/ui/hooks/useDeviceSwitcher.d.ts.map +1 -0
  234. package/lib/typescript/module/ui/hooks/useI18n.d.ts +13 -0
  235. package/lib/typescript/module/ui/hooks/useI18n.d.ts.map +1 -1
  236. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts +23 -18
  237. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
  238. package/lib/typescript/module/ui/index.d.ts +1 -2
  239. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  240. package/lib/typescript/module/ui/navigation/routes.d.ts +15 -1
  241. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  242. package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
  243. package/lib/typescript/module/ui/runtime/createOxyRuntime.d.ts +144 -0
  244. package/lib/typescript/module/ui/runtime/createOxyRuntime.d.ts.map +1 -0
  245. package/lib/typescript/module/ui/runtime/index.d.ts +16 -0
  246. package/lib/typescript/module/ui/runtime/index.d.ts.map +1 -0
  247. package/lib/typescript/module/ui/runtime/runtimeContext.d.ts +43 -0
  248. package/lib/typescript/module/ui/runtime/runtimeContext.d.ts.map +1 -0
  249. package/lib/typescript/module/ui/runtime/types.d.ts +89 -0
  250. package/lib/typescript/module/ui/runtime/types.d.ts.map +1 -0
  251. package/lib/typescript/module/ui/runtime/useRuntimeSnapshot.d.ts +23 -0
  252. package/lib/typescript/module/ui/runtime/useRuntimeSnapshot.d.ts.map +1 -0
  253. package/lib/typescript/module/ui/server.d.ts +0 -1
  254. package/lib/typescript/module/ui/server.d.ts.map +1 -1
  255. package/lib/typescript/module/ui/session/authStore.d.ts +60 -9
  256. package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
  257. package/lib/typescript/module/ui/session/sharedDeviceCredentialStore.d.ts +32 -0
  258. package/lib/typescript/module/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -0
  259. package/lib/typescript/module/ui/stores/authStore.d.ts +49 -5
  260. package/lib/typescript/module/ui/stores/authStore.d.ts.map +1 -1
  261. package/lib/typescript/module/ui/types/navigation.d.ts +26 -0
  262. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  263. package/lib/typescript/module/ui/utils/avatarUtils.d.ts +2 -10
  264. package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
  265. package/package.json +6 -7
  266. package/plugins/withSharedDeviceSessionProvider.js +102 -0
  267. package/plugins/withSharedDeviceSessionReader.js +57 -0
  268. package/src/__tests__/esmSourcePurity.test.ts +61 -0
  269. package/src/index.ts +31 -9
  270. package/src/ui/boot/runProviderColdBoot.ts +7 -0
  271. package/src/ui/client.ts +1 -2
  272. package/src/ui/components/OxyAccountDialogScreen.tsx +10 -29
  273. package/src/ui/components/OxyAuthChooser.tsx +149 -50
  274. package/src/ui/components/OxyProvider.tsx +7 -5
  275. package/src/ui/components/SurfaceScreen.tsx +28 -4
  276. package/src/ui/components/authChooser/AccountsMenuView.tsx +246 -96
  277. package/src/ui/components/authChooser/SignInEntryView.tsx +31 -8
  278. package/src/ui/components/authChooser/primitives.tsx +37 -23
  279. package/src/ui/components/authChooser/types.ts +34 -1
  280. package/src/ui/context/OxyContext.tsx +269 -324
  281. package/src/ui/context/hooks/useAuthOperations.ts +32 -35
  282. package/src/ui/context/oxyContextHelpers.ts +0 -30
  283. package/src/ui/context/oxyContextTypes.ts +9 -1
  284. package/src/ui/context/useOxyAccountGraph.ts +5 -19
  285. package/src/ui/hooks/accountDialogSnapshot.ts +37 -0
  286. package/src/ui/hooks/mutations/useAccountMutations.ts +0 -19
  287. package/src/ui/hooks/queries/__tests__/userCache.test.ts +12 -1
  288. package/src/ui/hooks/useDeviceSwitcher.ts +110 -0
  289. package/src/ui/hooks/useI18n.ts +23 -3
  290. package/src/ui/hooks/useSessionManagement.ts +62 -211
  291. package/src/ui/index.ts +1 -2
  292. package/src/ui/navigation/routes.ts +85 -75
  293. package/src/ui/navigation/surfaces.ts +6 -9
  294. package/src/ui/runtime/createOxyRuntime.ts +623 -0
  295. package/src/ui/runtime/index.ts +34 -0
  296. package/src/ui/runtime/runtimeContext.tsx +111 -0
  297. package/src/ui/runtime/types.ts +110 -0
  298. package/src/ui/runtime/useRuntimeSnapshot.ts +52 -0
  299. package/src/ui/server.ts +0 -1
  300. package/src/ui/session/__tests__/authStore.test.ts +90 -0
  301. package/src/ui/session/__tests__/sharedDeviceCredentialStore.test.ts +289 -0
  302. package/src/ui/session/authStore.ts +78 -9
  303. package/src/ui/session/sharedDeviceCredentialStore.ts +318 -0
  304. package/src/ui/stores/authStore.ts +171 -43
  305. package/src/ui/types/navigation.ts +26 -0
  306. package/src/ui/utils/avatarUtils.ts +2 -37
  307. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +0 -64
  308. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +0 -1
  309. package/lib/commonjs/ui/stores/accountStore.js +0 -221
  310. package/lib/commonjs/ui/stores/accountStore.js.map +0 -1
  311. package/lib/module/ui/hooks/useSwitchableAccounts.js +0 -60
  312. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +0 -1
  313. package/lib/module/ui/stores/accountStore.js +0 -213
  314. package/lib/module/ui/stores/accountStore.js.map +0 -1
  315. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +0 -23
  316. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +0 -1
  317. package/lib/typescript/commonjs/ui/stores/accountStore.d.ts +0 -27
  318. package/lib/typescript/commonjs/ui/stores/accountStore.d.ts.map +0 -1
  319. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +0 -23
  320. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +0 -1
  321. package/lib/typescript/module/ui/stores/accountStore.d.ts +0 -27
  322. package/lib/typescript/module/ui/stores/accountStore.d.ts.map +0 -1
  323. package/src/ui/hooks/useSwitchableAccounts.ts +0 -75
  324. package/src/ui/stores/accountStore.ts +0 -264
package/NOTICE CHANGED
@@ -1,15 +1,16 @@
1
- =============================================================================
1
+ ============================================================================
2
2
  NOTICE
3
3
  @oxyhq/services
4
- Copyright (c) 2025-present The Oxy Collective Inc
5
- =============================================================================
4
+ Copyright (c) 2025-present The Oxy Collective, Inc.
5
+ ============================================================================
6
6
 
7
- This product is licensed under the Apache License, Version 2.0.
8
- See the LICENSE file in this directory.
7
+ This product is licensed under the Apache License, Version 2.0. See the
8
+ LICENSE file in this directory.
9
9
 
10
- -----------------------------------------------------------------------------
10
+ ----------------------------------------------------------------------------
11
11
  THIRD PARTY COMPONENTS
12
- -----------------------------------------------------------------------------
12
+ ----------------------------------------------------------------------------
13
13
 
14
- This package contains no third party source. Its dependencies are resolved and
15
- installed by the package manager and remain governed by their own licenses.
14
+ This package contains no third party source. Its dependencies are resolved
15
+ and installed by the package manager and remain governed by their own
16
+ licenses.
package/README.md CHANGED
@@ -50,6 +50,69 @@ This package (`@oxyhq/services`) is the **single UI SDK** for every React surfac
50
50
 
51
51
  See [PLATFORM_GUIDE.md](./PLATFORM_GUIDE.md) for the complete architecture guide.
52
52
 
53
+ ### This package does not declare `sideEffects: false`
54
+
55
+ It used to, and the declaration was **wrong**. Three modules in the build have
56
+ top-level side effects, one of them the entry itself:
57
+
58
+ | Module | Effect |
59
+ |---|---|
60
+ | `lib/module/index.js` | `setPlatformOS(Platform.OS)` — configures `@oxyhq/core` |
61
+ | `lib/module/ui/navigation/surfaceBackBridge.js` | `installEscapeHandler()` — installs a global key handler |
62
+ | `lib/module/ui/components/SettingsIcon.js` | assigns `displayName` |
63
+
64
+ Removing a false declaration would be worth doing on its own. It also fixed a
65
+ production outage, and that is the part worth remembering, because the symptom
66
+ looked nothing like a `package.json` field.
67
+
68
+ `auth.oxy.so/authorize` rendered a blank page with React error #130 ("element
69
+ type is undefined") for every request that reached `OxySignInRequestSurface`,
70
+ which blocked every OIDC login into Allo ([#784]). Source and dev server were
71
+ fine; only the production bundle was broken. Two facts combined:
72
+
73
+ 1. **The UI graph was one large import cycle.** `ui/context/OxyContext` reaches
74
+ `ui/navigation/bottomSheetManager` → `navigation/routes`, `routes` reached
75
+ every screen (through `require()` calls it made for Metro's benefit), and
76
+ every screen reaches back into the context — a 67-module strongly connected
77
+ component that included `OxySignInRequestSurface`. A bundler cannot
78
+ concatenate a cycle, so it emits each member as a lazily-initialized wrapper
79
+ whose exported bindings are `undefined` until the initializer runs. Modules
80
+ outside the cycle, `OxyConsentScreen` among them, are emitted inline and were
81
+ never affected — which is exactly why only some screens broke.
82
+ 2. **`sideEffects: false` told the bundler that running those initializers was
83
+ unobservable**, so rolldown-vite omitted the initializer call at the
84
+ consuming import site in `packages/auth` while still emitting the reference.
85
+ The binding was therefore never assigned, and React was handed `undefined`.
86
+
87
+ That cycle was the deeper defect, and it has since been cut down. `routes.ts`
88
+ registers screens as `lazy(() => import(...))` rather than `require()`, so the
89
+ registry no longer drags all 40 screens into the context's component; the two
90
+ remaining `require()` sites became static imports read at call time. Measured on
91
+ the built ESM output, the largest strongly connected component went from **67
92
+ modules to 2**, and `OxySignInRequestSurface` is no longer inside any cycle.
93
+ What is left is two mutual pairs — `surfaces` ↔ `SurfaceScreen` and
94
+ `OxyContext` ↔ `useFollow` — each of which only ever reads the other at call or
95
+ render time.
96
+
97
+ Cycles still exist, so do not re-add `sideEffects` in any form. A narrowed,
98
+ truthful list would leave every component still in a cycle exactly as exposed,
99
+ because none of *them* has a top-level side effect either. The cost of not
100
+ declaring it was measured at the time on the IdP's own production bundle, which
101
+ is the heaviest consumer of this package on the web: **5,483.86 kB → 5,489.99 kB
102
+ raw (+0.11%)** and **1,457.31 kB → 1,458.66 kB gzip (+0.09%)**.
103
+
104
+ **No module in this package's source may call `require()`.** The build is ESM,
105
+ and a surviving `require()` puts bundlers back into CommonJS interop — the same
106
+ lazily-initialized wrappers, reachable the same silent way. To defer a read, use
107
+ a thunk over a static import; to defer a *load*, use `import()`.
108
+ `src/__tests__/esmSourcePurity.test.ts` enforces this.
109
+
110
+ `packages/auth/lib/__tests__/authorize-surface-bundle.test.ts` builds the IdP's
111
+ real production bundle and renders the surface out of it, so a regression fails
112
+ in CI with React's own message instead of on auth.oxy.so.
113
+
114
+ [#784]: https://github.com/OxyHQ/OxyHQServices/issues/784
115
+
53
116
  ## Installation
54
117
 
55
118
  ```bash
@@ -618,7 +681,7 @@ const metadata = getLanguageMetadata('es-ES');
618
681
 
619
682
  ### Common Issues
620
683
 
621
- #### 1. "useOxy must be used within an OxyContextProvider"
684
+ #### 1. "useOxy() was called outside <OxyProvider>"
622
685
 
623
686
  **Solution**: Wrap your app with `OxyProvider`
624
687
 
@@ -785,6 +848,6 @@ Comprehensive documentation is available in the `/docs` directory:
785
848
 
786
849
  ## License
787
850
 
788
- This package is licensed under the Apache License, Version 2.0, (c) The Oxy Collective Inc. See the [LICENSE](LICENSE) and [NOTICE](NOTICE) files for details.
851
+ This package is licensed under the Apache License, Version 2.0, (c) The Oxy Collective, Inc. See the [LICENSE](LICENSE) and [NOTICE](NOTICE) files for details.
789
852
 
790
853
  Versions published before `28.0.0` were AGPL-3.0-only and stay that way; a licence change binds future versions only.
@@ -0,0 +1,178 @@
1
+ package so.oxy.devicesession
2
+
3
+ import android.content.Context
4
+ import android.net.Uri
5
+ import android.os.Bundle
6
+ import expo.modules.kotlin.exception.Exceptions
7
+ import expo.modules.kotlin.modules.Module
8
+ import expo.modules.kotlin.modules.ModuleDefinition
9
+
10
+ /**
11
+ * JS bridge for the shared DeviceSession credential.
12
+ *
13
+ * - `read` resolves the credential this DEVICE holds, from the sibling providers
14
+ * first and this app's own store second (see [readShared] for why that order).
15
+ * - `write` publishes into this app's own store only, and reports whether a
16
+ * read-back confirmed it.
17
+ * - `clear` drops this app's copy.
18
+ *
19
+ * ## `read` returns a STATUS, not a nullable value
20
+ *
21
+ * The three answers "here it is", "there is none" and "I could not tell" are
22
+ * genuinely different, and only the second one authorises the JS side to seed the
23
+ * slot. A nullable return would merge the last two, and the merged value reads as
24
+ * "fresh device" — which is exactly how a locked or broken keystore ends up
25
+ * overwriting a live session. So every path here reports its status explicitly,
26
+ * and never degrades a failure into an absence.
27
+ *
28
+ * Return shapes:
29
+ * ```
30
+ * { "status": "present", "deviceId": "...", "deviceSecret": "..." }
31
+ * { "status": "absent" }
32
+ * { "status": "unavailable", "reason": "<ExceptionClassName>" }
33
+ * ```
34
+ *
35
+ * Plain scalars in, a plain `Map` out — never an Expo `Record`. This package
36
+ * ships as SOURCE that each consuming app compiles, so the annotation processing
37
+ * a `Record` needs to become introspectable does not necessarily run in the
38
+ * consumer's build; the one `Record` this package ever had failed to convert on a
39
+ * real device and stored nothing, silently. See the long note in
40
+ * `so.oxy.session.OxyBackgroundSessionModule` before reaching for one here.
41
+ */
42
+ class OxyDeviceSessionModule : Module() {
43
+ private val context: Context
44
+ get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
45
+
46
+ override fun definition() = ModuleDefinition {
47
+ Name("OxyDeviceSession")
48
+
49
+ AsyncFunction("read") {
50
+ readShared()
51
+ }
52
+
53
+ AsyncFunction("write") { deviceId: String, deviceSecret: String ->
54
+ if (deviceId.isEmpty() || deviceSecret.isEmpty()) {
55
+ return@AsyncFunction false
56
+ }
57
+ OxyDeviceSessionStore.write(context, deviceId, deviceSecret)
58
+ }
59
+
60
+ AsyncFunction("clear") {
61
+ OxyDeviceSessionStore.clear(context)
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Providers FIRST, this app's own store second.
67
+ *
68
+ * Under `android:sharedUserId="so.oxy.shared"` every Oxy app sees one data
69
+ * directory, so for a UID member the local file IS the group's file and the
70
+ * order changes nothing. It matters for a same-signature app OUTSIDE the UID:
71
+ * there the local file is a private mirror of whatever that app itself last
72
+ * wrote, so reading it first would shadow the group's real credential forever.
73
+ *
74
+ * `unavailable` is sticky across the whole sweep: if ANY source could not be
75
+ * read and none produced a credential, the answer is `unavailable`, not
76
+ * `absent`. The pessimistic merge is the point — the optimistic one authorises
77
+ * a write.
78
+ */
79
+ private fun readShared(): Map<String, String> {
80
+ var unavailableReason: String? = null
81
+
82
+ for (authority in PROVIDER_AUTHORITIES) {
83
+ when (val read = callProvider(authority)) {
84
+ is DeviceSessionRead.Present -> return present(read)
85
+ is DeviceSessionRead.Unavailable -> unavailableReason = unavailableReason ?: read.reason
86
+ // Absent, or no provider there at all (not installed, refused, threw) —
87
+ // neither is evidence about the other sources, so keep looking.
88
+ else -> Unit
89
+ }
90
+ }
91
+
92
+ when (val local = OxyDeviceSessionStore.read(context)) {
93
+ is DeviceSessionRead.Present -> return present(local)
94
+ is DeviceSessionRead.Unavailable -> unavailableReason = unavailableReason ?: local.reason
95
+ is DeviceSessionRead.Absent -> Unit
96
+ }
97
+
98
+ val reason = unavailableReason
99
+ return if (reason != null) {
100
+ mapOf(
101
+ OxyDeviceSessionProvider.KEY_STATUS to OxyDeviceSessionProvider.STATUS_UNAVAILABLE,
102
+ OxyDeviceSessionProvider.KEY_REASON to reason,
103
+ )
104
+ } else {
105
+ mapOf(OxyDeviceSessionProvider.KEY_STATUS to OxyDeviceSessionProvider.STATUS_ABSENT)
106
+ }
107
+ }
108
+
109
+ private fun present(read: DeviceSessionRead.Present): Map<String, String> = mapOf(
110
+ OxyDeviceSessionProvider.KEY_STATUS to OxyDeviceSessionProvider.STATUS_PRESENT,
111
+ OxyDeviceSessionStore.KEY_DEVICE_ID to read.deviceId,
112
+ OxyDeviceSessionStore.KEY_DEVICE_SECRET to read.deviceSecret,
113
+ )
114
+
115
+ /**
116
+ * Call one sibling provider. `null` means "nothing to learn from this one" —
117
+ * the app is not installed, package visibility hid it, the permission was
118
+ * refused, or the call threw. A provider that answered but could not read its
119
+ * own store returns [DeviceSessionRead.Unavailable], which the sweep keeps.
120
+ */
121
+ private fun callProvider(authority: String): DeviceSessionRead? = runCatching {
122
+ val uri = Uri.parse("content://$authority")
123
+ val bundle: Bundle = context.contentResolver.call(
124
+ uri,
125
+ OxyDeviceSessionProvider.METHOD_READ,
126
+ null,
127
+ null,
128
+ ) ?: return@runCatching null
129
+
130
+ when (bundle.getString(OxyDeviceSessionProvider.KEY_STATUS)) {
131
+ OxyDeviceSessionProvider.STATUS_PRESENT -> {
132
+ val deviceId = bundle.getString(OxyDeviceSessionStore.KEY_DEVICE_ID)
133
+ val deviceSecret = bundle.getString(OxyDeviceSessionStore.KEY_DEVICE_SECRET)
134
+ if (deviceId.isNullOrEmpty() || deviceSecret.isNullOrEmpty()) {
135
+ // A `present` verdict with an incomplete payload is a broken peer, not
136
+ // an empty device.
137
+ DeviceSessionRead.Unavailable("IncompleteProviderPayload")
138
+ } else {
139
+ DeviceSessionRead.Present(deviceId, deviceSecret)
140
+ }
141
+ }
142
+ OxyDeviceSessionProvider.STATUS_ABSENT -> DeviceSessionRead.Absent
143
+ OxyDeviceSessionProvider.STATUS_UNAVAILABLE ->
144
+ DeviceSessionRead.Unavailable(
145
+ bundle.getString(OxyDeviceSessionProvider.KEY_REASON) ?: "PeerUnavailable",
146
+ )
147
+ // An older sibling that predates this protocol. It said something we do not
148
+ // understand, so we have learned nothing — never read that as "absent".
149
+ else -> DeviceSessionRead.Unavailable("UnrecognisedProviderStatus")
150
+ }
151
+ }.getOrNull()
152
+
153
+ companion object {
154
+ /**
155
+ * Sibling authorities to sweep, in order. Each official Oxy Android app hosts
156
+ * the provider at `${applicationId}.devicesession` via the
157
+ * `withSharedDeviceSession` config plugin, and both the prod and `.dev`
158
+ * variants are listed so a developer build can join a device too.
159
+ *
160
+ * MAINTAINED LIST, and the constraint on it is specific: every entry must be
161
+ * an app inside the `so.oxy.shared` UID. UID members all serve ONE file, so
162
+ * the sweep is deterministic no matter which of them answers first. Adding an
163
+ * app that is same-signature but NOT in the UID would put a second,
164
+ * independent copy of "the shared credential" into the sweep and make the
165
+ * winner depend on list order.
166
+ *
167
+ * The same authorities must also be added to the `<queries>` block in
168
+ * `withSharedDeviceSession.js`, or Android 11+ package-visibility filtering
169
+ * hides them from `ContentResolver.call` and the sweep silently finds nothing.
170
+ */
171
+ private val PROVIDER_AUTHORITIES = listOf(
172
+ "so.oxy.accounts.devicesession",
173
+ "so.oxy.accounts.dev.devicesession",
174
+ "so.oxy.commons.devicesession",
175
+ "so.oxy.commons.dev.devicesession",
176
+ )
177
+ }
178
+ }
@@ -0,0 +1,99 @@
1
+ package so.oxy.devicesession
2
+
3
+ import android.content.ContentProvider
4
+ import android.content.ContentValues
5
+ import android.content.Context
6
+ import android.content.pm.PackageManager
7
+ import android.database.Cursor
8
+ import android.net.Uri
9
+ import android.os.Bundle
10
+
11
+ /**
12
+ * Cross-process READ surface for the shared DeviceSession credential, declared
13
+ * per-app by the `withSharedDeviceSession` config plugin at authority
14
+ * `${applicationId}.devicesession` behind a `signature`-level permission.
15
+ *
16
+ * The signature check below is belt-and-suspenders on top of that permission:
17
+ * even if the manifest gate were ever misconfigured, a differently-signed caller
18
+ * still gets nothing. Same-signature is the whole trust boundary — an
19
+ * incorrectly-signed app cannot read this slot, and there is no other way in.
20
+ *
21
+ * READ-ONLY across the process boundary, deliberately. A caller may join the
22
+ * device's session; it may not seed or overwrite another app's. Writes happen
23
+ * only in-process via [OxyDeviceSessionModule], against this app's own store.
24
+ *
25
+ * What crosses the boundary is a session credential — never the identity
26
+ * keypair, which lives behind `so.oxy.identity.OxyIdentityProvider` under a
27
+ * different permission. That separation is the point.
28
+ *
29
+ * All standard CRUD operations are no-ops; this provider exists solely for the
30
+ * `call()` channel.
31
+ */
32
+ class OxyDeviceSessionProvider : ContentProvider() {
33
+ override fun onCreate(): Boolean = true
34
+
35
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
36
+ if (method != METHOD_READ) return null
37
+ val ctx = context ?: return null
38
+ if (!callerSignatureMatches(ctx)) return null
39
+
40
+ return when (val read = OxyDeviceSessionStore.read(ctx)) {
41
+ is DeviceSessionRead.Present -> Bundle().apply {
42
+ putString(KEY_STATUS, STATUS_PRESENT)
43
+ putString(OxyDeviceSessionStore.KEY_DEVICE_ID, read.deviceId)
44
+ putString(OxyDeviceSessionStore.KEY_DEVICE_SECRET, read.deviceSecret)
45
+ }
46
+ is DeviceSessionRead.Absent -> Bundle().apply { putString(KEY_STATUS, STATUS_ABSENT) }
47
+ // Reported, not swallowed. A null here would be indistinguishable from
48
+ // "this app has no credential", and the caller would go on to treat the
49
+ // device as fresh.
50
+ is DeviceSessionRead.Unavailable -> Bundle().apply {
51
+ putString(KEY_STATUS, STATUS_UNAVAILABLE)
52
+ putString(KEY_REASON, read.reason)
53
+ }
54
+ }
55
+ }
56
+
57
+ /**
58
+ * True when the calling package shares this app's signing certificate. Uses
59
+ * `checkSignatures` (deprecated but still the simplest correct cross-package
60
+ * signature comparison; returns SIGNATURE_MATCH only for same-cert apps).
61
+ */
62
+ private fun callerSignatureMatches(ctx: Context): Boolean {
63
+ val caller = callingPackage ?: return false
64
+ return runCatching {
65
+ @Suppress("DEPRECATION")
66
+ ctx.packageManager.checkSignatures(caller, ctx.packageName) == PackageManager.SIGNATURE_MATCH
67
+ }.getOrDefault(false)
68
+ }
69
+
70
+ override fun query(
71
+ uri: Uri,
72
+ projection: Array<out String>?,
73
+ selection: String?,
74
+ selectionArgs: Array<out String>?,
75
+ sortOrder: String?
76
+ ): Cursor? = null
77
+
78
+ override fun getType(uri: Uri): String? = null
79
+
80
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
81
+
82
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
83
+
84
+ override fun update(
85
+ uri: Uri,
86
+ values: ContentValues?,
87
+ selection: String?,
88
+ selectionArgs: Array<out String>?
89
+ ): Int = 0
90
+
91
+ companion object {
92
+ const val METHOD_READ = "read"
93
+ const val KEY_STATUS = "status"
94
+ const val KEY_REASON = "reason"
95
+ const val STATUS_PRESENT = "present"
96
+ const val STATUS_ABSENT = "absent"
97
+ const val STATUS_UNAVAILABLE = "unavailable"
98
+ }
99
+ }
@@ -0,0 +1,115 @@
1
+ package so.oxy.devicesession
2
+
3
+ import android.content.Context
4
+ import android.content.SharedPreferences
5
+ import so.oxy.storage.OxyEncryptedPrefs
6
+ import so.oxy.storage.RecoveryPolicy
7
+
8
+ /**
9
+ * What a read of the shared slot found.
10
+ *
11
+ * THREE outcomes, and the difference between the last two is the entire safety
12
+ * property of this file. An EMPTY slot authorises a write (seed the device
13
+ * session); a slot that could not be READ must authorise nothing, because it may
14
+ * hold a live session belonging to someone still signed in. Collapsing them —
15
+ * which a plain nullable return would do — is how a locked or broken keystore
16
+ * gets mistaken for a fresh device.
17
+ */
18
+ internal sealed interface DeviceSessionRead {
19
+ data class Present(val deviceId: String, val deviceSecret: String) : DeviceSessionRead
20
+ object Absent : DeviceSessionRead
21
+ /** [reason] is an exception CLASS NAME only — never a message, never a value. */
22
+ data class Unavailable(val reason: String) : DeviceSessionRead
23
+ }
24
+
25
+ /**
26
+ * The cross-app DeviceSession credential — `deviceId` + `deviceSecret`, and
27
+ * nothing else.
28
+ *
29
+ * This is NOT the identity keypair. `so.oxy.identity.OxyIdentityStore` holds the
30
+ * self-custody private key that signs identity approvals and cannot be
31
+ * re-created; this file holds an ordinary session credential the server can
32
+ * revoke and any signed-in app can re-publish. They are separate files behind
33
+ * separate providers with separate permissions precisely so an app that only
34
+ * needs a session is never handed the key.
35
+ *
36
+ * Under `android:sharedUserId="so.oxy.shared"` every Oxy app sees ONE data
37
+ * directory, so this file is literally shared between them — that is the primary
38
+ * transport, and [OxyDeviceSessionProvider] extends the same bytes to a
39
+ * same-signature app outside the UID.
40
+ */
41
+ internal object OxyDeviceSessionStore {
42
+ /**
43
+ * Global (not package-suffixed) on purpose — the opposite choice from
44
+ * `oxy_background_session`, and for the opposite reason. That store is
45
+ * per-app precisely so one app cannot touch another's widget credential. This
46
+ * one EXISTS to be one value shared by every app in the UID; scoping it per
47
+ * package would give each app its own copy and defeat the whole point.
48
+ */
49
+ const val PREFS_NAME = "oxy_shared_device_session"
50
+ const val KEY_DEVICE_ID = "deviceId"
51
+ const val KEY_DEVICE_SECRET = "deviceSecret"
52
+
53
+ /**
54
+ * [RecoveryPolicy.RebuildFileOnly] — this store must NEVER escalate to a
55
+ * UID-shared master-key reset.
56
+ *
57
+ * What it holds is DERIVED: every signed-in app re-publishes the credential
58
+ * from its own durable copy, so losing this file costs at most one interactive
59
+ * sign-in on a device that has no other Oxy app installed. A master-key reset
60
+ * would wipe every other Oxy store sharing the alias — including the
61
+ * self-custody identity keypair, which is IRREPLACEABLE. Trading someone's
62
+ * unrecoverable identity to save a credential we can simply re-publish is never
63
+ * the right trade, so the escalation is not merely discouraged here, it is
64
+ * unreachable.
65
+ *
66
+ * Consequence worth stating: a stage-1 heal WIPES this file. That surfaces as
67
+ * `Absent`, which is honest — after the wipe the slot really is empty, and the
68
+ * next successful mint in any app re-seeds it.
69
+ */
70
+ private fun prefs(context: Context): SharedPreferences =
71
+ OxyEncryptedPrefs.open(context, PREFS_NAME, RecoveryPolicy.RebuildFileOnly)
72
+
73
+ /**
74
+ * Read the credential. A thrown open (the keyset is unreadable and the policy
75
+ * above refuses to escalate) is reported as [DeviceSessionRead.Unavailable] —
76
+ * never as absent.
77
+ */
78
+ fun read(context: Context): DeviceSessionRead =
79
+ runCatching {
80
+ val p = prefs(context)
81
+ val deviceId = p.getString(KEY_DEVICE_ID, null)
82
+ val deviceSecret = p.getString(KEY_DEVICE_SECRET, null)
83
+ if (deviceId.isNullOrEmpty() || deviceSecret.isNullOrEmpty()) {
84
+ DeviceSessionRead.Absent
85
+ } else {
86
+ DeviceSessionRead.Present(deviceId, deviceSecret)
87
+ }
88
+ }.getOrElse { DeviceSessionRead.Unavailable(it.javaClass.simpleName) }
89
+
90
+ /**
91
+ * Replace the credential, returning whether a read-back confirmed it landed.
92
+ *
93
+ * The read-back is the contract JS relies on: a fresh install adopts whatever
94
+ * is in this slot, so publishing a value that did not actually persist would
95
+ * send it into a mint that can never succeed.
96
+ *
97
+ * `commit()` (synchronous) so a cross-process reader firing immediately after
98
+ * the write sees the flushed value.
99
+ */
100
+ fun write(context: Context, deviceId: String, deviceSecret: String): Boolean =
101
+ runCatching {
102
+ val p = prefs(context)
103
+ p.edit()
104
+ .putString(KEY_DEVICE_ID, deviceId)
105
+ .putString(KEY_DEVICE_SECRET, deviceSecret)
106
+ .commit()
107
+ p.getString(KEY_DEVICE_ID, null) == deviceId &&
108
+ p.getString(KEY_DEVICE_SECRET, null) == deviceSecret
109
+ }.getOrDefault(false)
110
+
111
+ /** Drop the shared credential. Best-effort; safe to call when already empty. */
112
+ fun clear(context: Context) {
113
+ runCatching { prefs(context).edit().clear().commit() }
114
+ }
115
+ }
@@ -6,6 +6,7 @@
6
6
  "android": {
7
7
  "modules": [
8
8
  "so.oxy.identity.OxyIdentityModule",
9
+ "so.oxy.devicesession.OxyDeviceSessionModule",
9
10
  "so.oxy.session.OxyBackgroundSessionModule"
10
11
  ]
11
12
  }
@@ -129,6 +129,18 @@ Object.defineProperty(exports, "OxyProvider", {
129
129
  return _OxyProvider.default;
130
130
  }
131
131
  });
132
+ Object.defineProperty(exports, "OxyProviderMissingError", {
133
+ enumerable: true,
134
+ get: function () {
135
+ return _OxyContext.OxyProviderMissingError;
136
+ }
137
+ });
138
+ Object.defineProperty(exports, "OxyRuntimeMissingError", {
139
+ enumerable: true,
140
+ get: function () {
141
+ return _index.OxyRuntimeMissingError;
142
+ }
143
+ });
132
144
  Object.defineProperty(exports, "OxySignInButton", {
133
145
  enumerable: true,
134
146
  get: function () {
@@ -393,10 +405,10 @@ Object.defineProperty(exports, "useAccountStorageUsage", {
393
405
  return _useServicesQueries.useAccountStorageUsage;
394
406
  }
395
407
  });
396
- Object.defineProperty(exports, "useAccountStore", {
408
+ Object.defineProperty(exports, "useActiveAccount", {
397
409
  enumerable: true,
398
410
  get: function () {
399
- return _accountStore.useAccountStore;
411
+ return _index.useActiveAccount;
400
412
  }
401
413
  });
402
414
  Object.defineProperty(exports, "useAsset", {
@@ -483,12 +495,24 @@ Object.defineProperty(exports, "useCurrentUser", {
483
495
  return _useAccountQueries.useCurrentUser;
484
496
  }
485
497
  });
498
+ Object.defineProperty(exports, "useDeviceDirectory", {
499
+ enumerable: true,
500
+ get: function () {
501
+ return _index.useDeviceDirectory;
502
+ }
503
+ });
486
504
  Object.defineProperty(exports, "useDeviceSessions", {
487
505
  enumerable: true,
488
506
  get: function () {
489
507
  return _useServicesQueries.useDeviceSessions;
490
508
  }
491
509
  });
510
+ Object.defineProperty(exports, "useDeviceSwitcher", {
511
+ enumerable: true,
512
+ get: function () {
513
+ return _useDeviceSwitcher.useDeviceSwitcher;
514
+ }
515
+ });
492
516
  Object.defineProperty(exports, "useFileDownloadUrl", {
493
517
  enumerable: true,
494
518
  get: function () {
@@ -561,6 +585,12 @@ Object.defineProperty(exports, "useOnlineStatus", {
561
585
  return _useOnlineStatus.useOnlineStatus;
562
586
  }
563
587
  });
588
+ Object.defineProperty(exports, "useOptionalOxy", {
589
+ enumerable: true,
590
+ get: function () {
591
+ return _OxyContext.useOptionalOxy;
592
+ }
593
+ });
564
594
  Object.defineProperty(exports, "useOxy", {
565
595
  enumerable: true,
566
596
  get: function () {
@@ -573,6 +603,18 @@ Object.defineProperty(exports, "useOxyEvent", {
573
603
  return _useOxyEvent.useOxyEvent;
574
604
  }
575
605
  });
606
+ Object.defineProperty(exports, "useOxyRuntime", {
607
+ enumerable: true,
608
+ get: function () {
609
+ return _index.useOxyRuntime;
610
+ }
611
+ });
612
+ Object.defineProperty(exports, "useOxySnapshot", {
613
+ enumerable: true,
614
+ get: function () {
615
+ return _index.useOxySnapshot;
616
+ }
617
+ });
576
618
  Object.defineProperty(exports, "usePrivacySettings", {
577
619
  enumerable: true,
578
620
  get: function () {
@@ -645,12 +687,6 @@ Object.defineProperty(exports, "useSwitchSession", {
645
687
  return _useServicesMutations.useSwitchSession;
646
688
  }
647
689
  });
648
- Object.defineProperty(exports, "useSwitchableAccounts", {
649
- enumerable: true,
650
- get: function () {
651
- return _useSwitchableAccounts.useSwitchableAccounts;
652
- }
653
- });
654
690
  Object.defineProperty(exports, "useUpdateAccountSettings", {
655
691
  enumerable: true,
656
692
  get: function () {
@@ -775,10 +811,10 @@ var _core = require("@oxyhq/core");
775
811
  var _reactNative = require("react-native");
776
812
  var _OxyProvider = _interopRequireDefault(require("./ui/components/OxyProvider.js"));
777
813
  var _OxyContext = require("./ui/context/OxyContext.js");
814
+ var _index = require("./ui/runtime/index.js");
778
815
  var _useAuth = require("./ui/hooks/useAuth.js");
779
816
  var _identityBinding = require("./ui/session/identityBinding.js");
780
817
  var _authStore = require("./ui/stores/authStore.js");
781
- var _accountStore = require("./ui/stores/accountStore.js");
782
818
  var _assetStore = require("./ui/stores/assetStore.js");
783
819
  var _useAssets = require("./ui/hooks/useAssets.js");
784
820
  var _useFileDownloadUrl = require("./ui/hooks/useFileDownloadUrl.js");
@@ -816,7 +852,7 @@ var _LogoText = require("./ui/components/logo/LogoText.js");
816
852
  var _ProfileButton = _interopRequireDefault(require("./ui/components/ProfileButton.js"));
817
853
  var _OxyAuthChooser = _interopRequireDefault(require("./ui/components/OxyAuthChooser.js"));
818
854
  var _OxySignInRequestSurface = require("./ui/components/OxySignInRequestSurface.js");
819
- var _useSwitchableAccounts = require("./ui/hooks/useSwitchableAccounts.js");
855
+ var _useDeviceSwitcher = require("./ui/hooks/useDeviceSwitcher.js");
820
856
  var _ProfileScreen = _interopRequireDefault(require("./ui/screens/ProfileScreen.js"));
821
857
  var _ManageAccountScreen = _interopRequireDefault(require("./ui/screens/ManageAccountScreen.js"));
822
858
  var _NotificationsScreen = _interopRequireDefault(require("./ui/screens/NotificationsScreen.js"));
@@ -862,6 +898,14 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
862
898
  // Provider + auth context
863
899
  // ---------------------------------------------------------------------------
864
900
 
901
+ // `useOxy()` THROWS when no provider is mounted. `useOptionalOxy()` returns
902
+ // `null` instead, for the rare component that renders on both sides of the
903
+ // provider boundary; there is no fabricated forever-loading runtime.
904
+
905
+ // The runtime surface (ADR 0004). These subscribe to the ONE runtime with a
906
+ // selector, so — unlike `useOxy()`, whose value is rebuilt by any of its fifty
907
+ // members moving — a locale change or a dialog opening does not re-render them.
908
+
865
909
  // Thrown by `switchToAccount` / `switchSession` while `OxyProvider` runs with
866
910
  // `sessionMode="identity"` — an identity-bound app authenticates as the owner of
867
911
  // the local identity key and cannot switch accounts.
@@ -968,12 +1012,12 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
968
1012
  // no controller: `route` + `progress` + `qrPayload` + the failure flags are the
969
1013
  // whole contract, and none of them is a secret credential.
970
1014
 
971
- // Unified switchable-accounts hook — the single source of everything the user
972
- // can switch into: device sign-ins AND linked graph accounts (owned orgs +
973
- // shared-with-you), deduped by account id and hydrated with real
974
- // name/email/avatar/color. Backed by the shared `AccountDialogController` in
975
- // `@oxyhq/core`. Every switch routes through `useOxy().switchToAccount`.
976
- // The `SwitchableAccount` type lives in `@oxyhq/core` — import it from there.
1015
+ // The device switcher — the server's own directory (ADR 0002) of who is signed
1016
+ // in here and what each of them may act as, grouped by PERSON, with the two
1017
+ // removals an account id cannot name. Backed by the shared
1018
+ // `AccountDialogController` in `@oxyhq/core`; selecting a row activates a
1019
+ // `contextId`. `DevicePrincipalGroup` and `DeviceContext` live in `@oxyhq/core`
1020
+ // — import them from there.
977
1021
 
978
1022
  // Unified "Manage your Oxy Account" screen (the caller's own personal account)
979
1023