@oxy.so/core 1.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 (673) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +221 -0
  4. package/dist/cjs/.tsbuildinfo +1 -0
  5. package/dist/cjs/HttpService.js +1270 -0
  6. package/dist/cjs/OxyServices.base.js +390 -0
  7. package/dist/cjs/OxyServices.errors.js +95 -0
  8. package/dist/cjs/OxyServices.js +72 -0
  9. package/dist/cjs/boot/sessionColdBoot.js +395 -0
  10. package/dist/cjs/constants/version.js +16 -0
  11. package/dist/cjs/crypto/aead.js +79 -0
  12. package/dist/cjs/crypto/ecdh.js +35 -0
  13. package/dist/cjs/crypto/identityMarker.js +255 -0
  14. package/dist/cjs/crypto/kdf.js +39 -0
  15. package/dist/cjs/crypto/keyManager.js +2234 -0
  16. package/dist/cjs/crypto/polyfill.js +131 -0
  17. package/dist/cjs/crypto/recoveryPhrase.js +242 -0
  18. package/dist/cjs/crypto/signatureService.js +273 -0
  19. package/dist/cjs/i18n/accountCategoryLabels.js +44 -0
  20. package/dist/cjs/i18n/accountRoleLabels.js +27 -0
  21. package/dist/cjs/i18n/index.js +90 -0
  22. package/dist/cjs/i18n/locales/ar-SA.json +282 -0
  23. package/dist/cjs/i18n/locales/ca-ES.json +282 -0
  24. package/dist/cjs/i18n/locales/de-DE.json +282 -0
  25. package/dist/cjs/i18n/locales/en-US.json +2161 -0
  26. package/dist/cjs/i18n/locales/es-ES.json +2161 -0
  27. package/dist/cjs/i18n/locales/fr-FR.json +282 -0
  28. package/dist/cjs/i18n/locales/it-IT.json +282 -0
  29. package/dist/cjs/i18n/locales/ja-JP.json +282 -0
  30. package/dist/cjs/i18n/locales/ko-KR.json +282 -0
  31. package/dist/cjs/i18n/locales/locales/ar-SA.json +282 -0
  32. package/dist/cjs/i18n/locales/locales/ca-ES.json +282 -0
  33. package/dist/cjs/i18n/locales/locales/de-DE.json +282 -0
  34. package/dist/cjs/i18n/locales/locales/en-US.json +2161 -0
  35. package/dist/cjs/i18n/locales/locales/es-ES.json +2161 -0
  36. package/dist/cjs/i18n/locales/locales/fr-FR.json +282 -0
  37. package/dist/cjs/i18n/locales/locales/it-IT.json +282 -0
  38. package/dist/cjs/i18n/locales/locales/ja-JP.json +282 -0
  39. package/dist/cjs/i18n/locales/locales/ko-KR.json +282 -0
  40. package/dist/cjs/i18n/locales/locales/pt-PT.json +282 -0
  41. package/dist/cjs/i18n/locales/locales/zh-CN.json +282 -0
  42. package/dist/cjs/i18n/locales/pt-PT.json +282 -0
  43. package/dist/cjs/i18n/locales/zh-CN.json +282 -0
  44. package/dist/cjs/i18n/reputationCategoryLabels.js +20 -0
  45. package/dist/cjs/i18n/trustTierLabels.js +19 -0
  46. package/dist/cjs/index.js +498 -0
  47. package/dist/cjs/inference/OxyInferenceClient.js +544 -0
  48. package/dist/cjs/logger/index.js +149 -0
  49. package/dist/cjs/mixins/OxyServices.accounts.js +555 -0
  50. package/dist/cjs/mixins/OxyServices.analytics.js +49 -0
  51. package/dist/cjs/mixins/OxyServices.appData.js +108 -0
  52. package/dist/cjs/mixins/OxyServices.assets.js +764 -0
  53. package/dist/cjs/mixins/OxyServices.auth.js +1254 -0
  54. package/dist/cjs/mixins/OxyServices.chains.js +73 -0
  55. package/dist/cjs/mixins/OxyServices.civic.js +611 -0
  56. package/dist/cjs/mixins/OxyServices.connectedApps.js +78 -0
  57. package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
  58. package/dist/cjs/mixins/OxyServices.deviceBoot.js +191 -0
  59. package/dist/cjs/mixins/OxyServices.deviceTransfer.js +314 -0
  60. package/dist/cjs/mixins/OxyServices.devices.js +116 -0
  61. package/dist/cjs/mixins/OxyServices.features.js +335 -0
  62. package/dist/cjs/mixins/OxyServices.followGraph.js +221 -0
  63. package/dist/cjs/mixins/OxyServices.identity.js +461 -0
  64. package/dist/cjs/mixins/OxyServices.identityBackup.js +172 -0
  65. package/dist/cjs/mixins/OxyServices.inference.js +59 -0
  66. package/dist/cjs/mixins/OxyServices.language.js +125 -0
  67. package/dist/cjs/mixins/OxyServices.links.js +72 -0
  68. package/dist/cjs/mixins/OxyServices.location.js +43 -0
  69. package/dist/cjs/mixins/OxyServices.nodes.js +175 -0
  70. package/dist/cjs/mixins/OxyServices.notifications.js +63 -0
  71. package/dist/cjs/mixins/OxyServices.payment.js +158 -0
  72. package/dist/cjs/mixins/OxyServices.privacy.js +199 -0
  73. package/dist/cjs/mixins/OxyServices.reputation.js +289 -0
  74. package/dist/cjs/mixins/OxyServices.security.js +89 -0
  75. package/dist/cjs/mixins/OxyServices.store.js +266 -0
  76. package/dist/cjs/mixins/OxyServices.topics.js +137 -0
  77. package/dist/cjs/mixins/OxyServices.user.js +902 -0
  78. package/dist/cjs/mixins/OxyServices.utility.js +1098 -0
  79. package/dist/cjs/mixins/index.js +135 -0
  80. package/dist/cjs/mixins/mixinHelpers.js +53 -0
  81. package/dist/cjs/models/Topic.js +16 -0
  82. package/dist/cjs/models/interfaces.js +20 -0
  83. package/dist/cjs/models/session.js +2 -0
  84. package/dist/cjs/server/auth.js +167 -0
  85. package/dist/cjs/server/capabilityTicket.js +192 -0
  86. package/dist/cjs/server/cors.js +225 -0
  87. package/dist/cjs/server/index.js +97 -0
  88. package/dist/cjs/server/rateLimit.js +207 -0
  89. package/dist/cjs/server/safeFetch.js +458 -0
  90. package/dist/cjs/server/securityHeaders.js +377 -0
  91. package/dist/cjs/server/userInvalidation.js +156 -0
  92. package/dist/cjs/server/verifySecret.js +50 -0
  93. package/dist/cjs/session/SessionClient.js +865 -0
  94. package/dist/cjs/session/accountDelegationAccess.js +48 -0
  95. package/dist/cjs/session/accountDialogController.js +1131 -0
  96. package/dist/cjs/session/accountSwitchTargets.js +81 -0
  97. package/dist/cjs/session/authStateStore.js +404 -0
  98. package/dist/cjs/session/createSessionClient.js +32 -0
  99. package/dist/cjs/session/deviceDirectory.js +143 -0
  100. package/dist/cjs/session/deviceSwitcherRows.js +76 -0
  101. package/dist/cjs/session/identityPin.js +256 -0
  102. package/dist/cjs/session/identitySession.js +135 -0
  103. package/dist/cjs/session/projectSessionState.js +121 -0
  104. package/dist/cjs/session/refresh.js +398 -0
  105. package/dist/cjs/session/sessionClientHost.js +34 -0
  106. package/dist/cjs/session/sharedDeviceCredential.js +247 -0
  107. package/dist/cjs/session/socketLoader.js +55 -0
  108. package/dist/cjs/shared/utils/colorUtils.js +153 -0
  109. package/dist/cjs/shared/utils/errorUtils.js +183 -0
  110. package/dist/cjs/shared/utils/index.js +49 -0
  111. package/dist/cjs/shared/utils/networkUtils.js +183 -0
  112. package/dist/cjs/shared/utils/themeUtils.js +106 -0
  113. package/dist/cjs/utils/accountCacheSweep.js +80 -0
  114. package/dist/cjs/utils/accountUtils.js +140 -0
  115. package/dist/cjs/utils/activityId.js +6 -0
  116. package/dist/cjs/utils/apiUtils.js +91 -0
  117. package/dist/cjs/utils/asyncUtils.js +222 -0
  118. package/dist/cjs/utils/authHelpers.js +120 -0
  119. package/dist/cjs/utils/authWebUrl.js +19 -0
  120. package/dist/cjs/utils/avatarUtils.js +29 -0
  121. package/dist/cjs/utils/cache.js +235 -0
  122. package/dist/cjs/utils/cacheKey.js +85 -0
  123. package/dist/cjs/utils/coldBoot.js +122 -0
  124. package/dist/cjs/utils/commonsApproval.js +31 -0
  125. package/dist/cjs/utils/commonsDelivery.js +59 -0
  126. package/dist/cjs/utils/deviceManager.js +174 -0
  127. package/dist/cjs/utils/displayNamePolicyRanges.generated.js +60 -0
  128. package/dist/cjs/utils/edgeRegion.js +6 -0
  129. package/dist/cjs/utils/errorUtils.js +243 -0
  130. package/dist/cjs/utils/identityCacheSweep.js +97 -0
  131. package/dist/cjs/utils/languageUtils.js +224 -0
  132. package/dist/cjs/utils/oauthPkce.js +311 -0
  133. package/dist/cjs/utils/officialOrigins.js +64 -0
  134. package/dist/cjs/utils/oxyServiceEnvironment.js +19 -0
  135. package/dist/cjs/utils/platform.js +119 -0
  136. package/dist/cjs/utils/profileLinks.js +63 -0
  137. package/dist/cjs/utils/redactUrl.js +29 -0
  138. package/dist/cjs/utils/registrableApex.js +45 -0
  139. package/dist/cjs/utils/requestUtils.js +209 -0
  140. package/dist/cjs/utils/sessionUtils.js +181 -0
  141. package/dist/cjs/utils/textNormalization.js +158 -0
  142. package/dist/cjs/utils/userHandle.js +37 -0
  143. package/dist/cjs/utils/userIdentity.js +50 -0
  144. package/dist/cjs/utils/validationUtils.js +370 -0
  145. package/dist/cjs/utils/webauthnOrigin.js +51 -0
  146. package/dist/esm/.tsbuildinfo +1 -0
  147. package/dist/esm/HttpService.js +1266 -0
  148. package/dist/esm/OxyServices.base.js +386 -0
  149. package/dist/esm/OxyServices.errors.js +88 -0
  150. package/dist/esm/OxyServices.js +66 -0
  151. package/dist/esm/boot/sessionColdBoot.js +392 -0
  152. package/dist/esm/constants/version.js +13 -0
  153. package/dist/esm/crypto/aead.js +74 -0
  154. package/dist/esm/crypto/ecdh.js +32 -0
  155. package/dist/esm/crypto/identityMarker.js +248 -0
  156. package/dist/esm/crypto/kdf.js +36 -0
  157. package/dist/esm/crypto/keyManager.js +2227 -0
  158. package/dist/esm/crypto/polyfill.js +129 -0
  159. package/dist/esm/crypto/recoveryPhrase.js +238 -0
  160. package/dist/esm/crypto/signatureService.js +269 -0
  161. package/dist/esm/i18n/accountCategoryLabels.js +37 -0
  162. package/dist/esm/i18n/accountRoleLabels.js +20 -0
  163. package/dist/esm/i18n/index.js +83 -0
  164. package/dist/esm/i18n/locales/ar-SA.json +282 -0
  165. package/dist/esm/i18n/locales/ca-ES.json +282 -0
  166. package/dist/esm/i18n/locales/de-DE.json +282 -0
  167. package/dist/esm/i18n/locales/en-US.json +2161 -0
  168. package/dist/esm/i18n/locales/es-ES.json +2161 -0
  169. package/dist/esm/i18n/locales/fr-FR.json +282 -0
  170. package/dist/esm/i18n/locales/it-IT.json +282 -0
  171. package/dist/esm/i18n/locales/ja-JP.json +282 -0
  172. package/dist/esm/i18n/locales/ko-KR.json +282 -0
  173. package/dist/esm/i18n/locales/locales/ar-SA.json +282 -0
  174. package/dist/esm/i18n/locales/locales/ca-ES.json +282 -0
  175. package/dist/esm/i18n/locales/locales/de-DE.json +282 -0
  176. package/dist/esm/i18n/locales/locales/en-US.json +2161 -0
  177. package/dist/esm/i18n/locales/locales/es-ES.json +2161 -0
  178. package/dist/esm/i18n/locales/locales/fr-FR.json +282 -0
  179. package/dist/esm/i18n/locales/locales/it-IT.json +282 -0
  180. package/dist/esm/i18n/locales/locales/ja-JP.json +282 -0
  181. package/dist/esm/i18n/locales/locales/ko-KR.json +282 -0
  182. package/dist/esm/i18n/locales/locales/pt-PT.json +282 -0
  183. package/dist/esm/i18n/locales/locales/zh-CN.json +282 -0
  184. package/dist/esm/i18n/locales/pt-PT.json +282 -0
  185. package/dist/esm/i18n/locales/zh-CN.json +282 -0
  186. package/dist/esm/i18n/reputationCategoryLabels.js +13 -0
  187. package/dist/esm/i18n/trustTierLabels.js +12 -0
  188. package/dist/esm/index.js +254 -0
  189. package/dist/esm/inference/OxyInferenceClient.js +538 -0
  190. package/dist/esm/logger/index.js +140 -0
  191. package/dist/esm/mixins/OxyServices.accounts.js +543 -0
  192. package/dist/esm/mixins/OxyServices.analytics.js +46 -0
  193. package/dist/esm/mixins/OxyServices.appData.js +103 -0
  194. package/dist/esm/mixins/OxyServices.assets.js +761 -0
  195. package/dist/esm/mixins/OxyServices.auth.js +1247 -0
  196. package/dist/esm/mixins/OxyServices.chains.js +70 -0
  197. package/dist/esm/mixins/OxyServices.civic.js +605 -0
  198. package/dist/esm/mixins/OxyServices.connectedApps.js +75 -0
  199. package/dist/esm/mixins/OxyServices.contacts.js +47 -0
  200. package/dist/esm/mixins/OxyServices.deviceBoot.js +186 -0
  201. package/dist/esm/mixins/OxyServices.deviceTransfer.js +311 -0
  202. package/dist/esm/mixins/OxyServices.devices.js +113 -0
  203. package/dist/esm/mixins/OxyServices.features.js +332 -0
  204. package/dist/esm/mixins/OxyServices.followGraph.js +218 -0
  205. package/dist/esm/mixins/OxyServices.identity.js +457 -0
  206. package/dist/esm/mixins/OxyServices.identityBackup.js +169 -0
  207. package/dist/esm/mixins/OxyServices.inference.js +56 -0
  208. package/dist/esm/mixins/OxyServices.language.js +122 -0
  209. package/dist/esm/mixins/OxyServices.links.js +69 -0
  210. package/dist/esm/mixins/OxyServices.location.js +40 -0
  211. package/dist/esm/mixins/OxyServices.nodes.js +172 -0
  212. package/dist/esm/mixins/OxyServices.notifications.js +60 -0
  213. package/dist/esm/mixins/OxyServices.payment.js +155 -0
  214. package/dist/esm/mixins/OxyServices.privacy.js +196 -0
  215. package/dist/esm/mixins/OxyServices.reputation.js +286 -0
  216. package/dist/esm/mixins/OxyServices.security.js +86 -0
  217. package/dist/esm/mixins/OxyServices.store.js +263 -0
  218. package/dist/esm/mixins/OxyServices.topics.js +134 -0
  219. package/dist/esm/mixins/OxyServices.user.js +899 -0
  220. package/dist/esm/mixins/OxyServices.utility.js +1095 -0
  221. package/dist/esm/mixins/index.js +132 -0
  222. package/dist/esm/mixins/mixinHelpers.js +48 -0
  223. package/dist/esm/models/Topic.js +13 -0
  224. package/dist/esm/models/interfaces.js +17 -0
  225. package/dist/esm/models/session.js +1 -0
  226. package/dist/esm/server/auth.js +154 -0
  227. package/dist/esm/server/capabilityTicket.js +183 -0
  228. package/dist/esm/server/cors.js +220 -0
  229. package/dist/esm/server/index.js +47 -0
  230. package/dist/esm/server/rateLimit.js +201 -0
  231. package/dist/esm/server/safeFetch.js +447 -0
  232. package/dist/esm/server/securityHeaders.js +364 -0
  233. package/dist/esm/server/userInvalidation.js +152 -0
  234. package/dist/esm/server/verifySecret.js +47 -0
  235. package/dist/esm/session/SessionClient.js +861 -0
  236. package/dist/esm/session/accountDelegationAccess.js +45 -0
  237. package/dist/esm/session/accountDialogController.js +1126 -0
  238. package/dist/esm/session/accountSwitchTargets.js +77 -0
  239. package/dist/esm/session/authStateStore.js +398 -0
  240. package/dist/esm/session/createSessionClient.js +29 -0
  241. package/dist/esm/session/deviceDirectory.js +135 -0
  242. package/dist/esm/session/deviceSwitcherRows.js +72 -0
  243. package/dist/esm/session/identityPin.js +249 -0
  244. package/dist/esm/session/identitySession.js +131 -0
  245. package/dist/esm/session/projectSessionState.js +114 -0
  246. package/dist/esm/session/refresh.js +390 -0
  247. package/dist/esm/session/sessionClientHost.js +31 -0
  248. package/dist/esm/session/sharedDeviceCredential.js +239 -0
  249. package/dist/esm/session/socketLoader.js +19 -0
  250. package/dist/esm/shared/utils/colorUtils.js +143 -0
  251. package/dist/esm/shared/utils/errorUtils.js +170 -0
  252. package/dist/esm/shared/utils/index.js +15 -0
  253. package/dist/esm/shared/utils/networkUtils.js +173 -0
  254. package/dist/esm/shared/utils/themeUtils.js +98 -0
  255. package/dist/esm/utils/accountCacheSweep.js +75 -0
  256. package/dist/esm/utils/accountUtils.js +131 -0
  257. package/dist/esm/utils/activityId.js +2 -0
  258. package/dist/esm/utils/apiUtils.js +84 -0
  259. package/dist/esm/utils/asyncUtils.js +207 -0
  260. package/dist/esm/utils/authHelpers.js +111 -0
  261. package/dist/esm/utils/authWebUrl.js +16 -0
  262. package/dist/esm/utils/avatarUtils.js +26 -0
  263. package/dist/esm/utils/cache.js +227 -0
  264. package/dist/esm/utils/cacheKey.js +80 -0
  265. package/dist/esm/utils/coldBoot.js +119 -0
  266. package/dist/esm/utils/commonsApproval.js +27 -0
  267. package/dist/esm/utils/commonsDelivery.js +54 -0
  268. package/dist/esm/utils/deviceManager.js +170 -0
  269. package/dist/esm/utils/displayNamePolicyRanges.generated.js +57 -0
  270. package/dist/esm/utils/edgeRegion.js +2 -0
  271. package/dist/esm/utils/errorUtils.js +232 -0
  272. package/dist/esm/utils/identityCacheSweep.js +92 -0
  273. package/dist/esm/utils/languageUtils.js +212 -0
  274. package/dist/esm/utils/oauthPkce.js +297 -0
  275. package/dist/esm/utils/officialOrigins.js +59 -0
  276. package/dist/esm/utils/oxyServiceEnvironment.js +16 -0
  277. package/dist/esm/utils/platform.js +110 -0
  278. package/dist/esm/utils/profileLinks.js +60 -0
  279. package/dist/esm/utils/redactUrl.js +26 -0
  280. package/dist/esm/utils/registrableApex.js +42 -0
  281. package/dist/esm/utils/requestUtils.js +203 -0
  282. package/dist/esm/utils/sessionUtils.js +171 -0
  283. package/dist/esm/utils/textNormalization.js +154 -0
  284. package/dist/esm/utils/userHandle.js +33 -0
  285. package/dist/esm/utils/userIdentity.js +45 -0
  286. package/dist/esm/utils/validationUtils.js +350 -0
  287. package/dist/esm/utils/webauthnOrigin.js +48 -0
  288. package/dist/types/.tsbuildinfo +1 -0
  289. package/dist/types/HttpService.d.ts +413 -0
  290. package/dist/types/OxyServices.base.d.ts +187 -0
  291. package/dist/types/OxyServices.d.ts +139 -0
  292. package/dist/types/OxyServices.errors.d.ts +84 -0
  293. package/dist/types/boot/sessionColdBoot.d.ts +140 -0
  294. package/dist/types/constants/version.d.ts +13 -0
  295. package/dist/types/crypto/aead.d.ts +56 -0
  296. package/dist/types/crypto/ecdh.d.ts +29 -0
  297. package/dist/types/crypto/identityMarker.d.ts +94 -0
  298. package/dist/types/crypto/kdf.d.ts +25 -0
  299. package/dist/types/crypto/keyManager.d.ts +582 -0
  300. package/dist/types/crypto/polyfill.d.ts +25 -0
  301. package/dist/types/crypto/recoveryPhrase.d.ts +173 -0
  302. package/dist/types/crypto/signatureService.d.ts +136 -0
  303. package/dist/types/i18n/accountCategoryLabels.d.ts +34 -0
  304. package/dist/types/i18n/accountRoleLabels.d.ts +10 -0
  305. package/dist/types/i18n/index.d.ts +3 -0
  306. package/dist/types/i18n/reputationCategoryLabels.d.ts +10 -0
  307. package/dist/types/i18n/trustTierLabels.d.ts +9 -0
  308. package/dist/types/index.d.ts +145 -0
  309. package/dist/types/inference/OxyInferenceClient.d.ts +342 -0
  310. package/dist/types/logger/index.d.ts +104 -0
  311. package/dist/types/mixins/OxyServices.accounts.d.ts +868 -0
  312. package/dist/types/mixins/OxyServices.analytics.d.ts +72 -0
  313. package/dist/types/mixins/OxyServices.appData.d.ts +108 -0
  314. package/dist/types/mixins/OxyServices.assets.d.ts +329 -0
  315. package/dist/types/mixins/OxyServices.auth.d.ts +908 -0
  316. package/dist/types/mixins/OxyServices.chains.d.ts +156 -0
  317. package/dist/types/mixins/OxyServices.civic.d.ts +510 -0
  318. package/dist/types/mixins/OxyServices.connectedApps.d.ts +169 -0
  319. package/dist/types/mixins/OxyServices.contacts.d.ts +100 -0
  320. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +185 -0
  321. package/dist/types/mixins/OxyServices.deviceTransfer.d.ts +149 -0
  322. package/dist/types/mixins/OxyServices.devices.d.ts +103 -0
  323. package/dist/types/mixins/OxyServices.features.d.ts +250 -0
  324. package/dist/types/mixins/OxyServices.followGraph.d.ts +223 -0
  325. package/dist/types/mixins/OxyServices.identity.d.ts +351 -0
  326. package/dist/types/mixins/OxyServices.identityBackup.d.ts +129 -0
  327. package/dist/types/mixins/OxyServices.inference.d.ts +95 -0
  328. package/dist/types/mixins/OxyServices.language.d.ts +87 -0
  329. package/dist/types/mixins/OxyServices.links.d.ts +104 -0
  330. package/dist/types/mixins/OxyServices.location.d.ts +70 -0
  331. package/dist/types/mixins/OxyServices.nodes.d.ts +198 -0
  332. package/dist/types/mixins/OxyServices.notifications.d.ts +143 -0
  333. package/dist/types/mixins/OxyServices.payment.d.ts +117 -0
  334. package/dist/types/mixins/OxyServices.privacy.d.ts +146 -0
  335. package/dist/types/mixins/OxyServices.reputation.d.ts +202 -0
  336. package/dist/types/mixins/OxyServices.security.d.ts +84 -0
  337. package/dist/types/mixins/OxyServices.store.d.ts +334 -0
  338. package/dist/types/mixins/OxyServices.topics.d.ts +110 -0
  339. package/dist/types/mixins/OxyServices.user.d.ts +558 -0
  340. package/dist/types/mixins/OxyServices.utility.d.ts +393 -0
  341. package/dist/types/mixins/index.d.ts +87 -0
  342. package/dist/types/mixins/mixinHelpers.d.ts +31 -0
  343. package/dist/types/models/Topic.d.ts +42 -0
  344. package/dist/types/models/interfaces.d.ts +675 -0
  345. package/dist/types/models/session.d.ts +51 -0
  346. package/dist/types/server/auth.d.ts +130 -0
  347. package/dist/types/server/capabilityTicket.d.ts +41 -0
  348. package/dist/types/server/cors.d.ts +98 -0
  349. package/dist/types/server/index.d.ts +35 -0
  350. package/dist/types/server/rateLimit.d.ts +40 -0
  351. package/dist/types/server/safeFetch.d.ts +137 -0
  352. package/dist/types/server/securityHeaders.d.ts +221 -0
  353. package/dist/types/server/userInvalidation.d.ts +114 -0
  354. package/dist/types/server/verifySecret.d.ts +29 -0
  355. package/dist/types/session/SessionClient.d.ts +418 -0
  356. package/dist/types/session/accountDelegationAccess.d.ts +40 -0
  357. package/dist/types/session/accountDialogController.d.ts +648 -0
  358. package/dist/types/session/accountSwitchTargets.d.ts +70 -0
  359. package/dist/types/session/authStateStore.d.ts +153 -0
  360. package/dist/types/session/createSessionClient.d.ts +30 -0
  361. package/dist/types/session/deviceDirectory.d.ts +182 -0
  362. package/dist/types/session/deviceSwitcherRows.d.ts +92 -0
  363. package/dist/types/session/identityPin.d.ts +87 -0
  364. package/dist/types/session/identitySession.d.ts +105 -0
  365. package/dist/types/session/projectSessionState.d.ts +74 -0
  366. package/dist/types/session/refresh.d.ts +179 -0
  367. package/dist/types/session/sessionClientHost.d.ts +18 -0
  368. package/dist/types/session/sharedDeviceCredential.d.ts +202 -0
  369. package/dist/types/session/socketLoader.d.ts +11 -0
  370. package/dist/types/shared/utils/colorUtils.d.ts +104 -0
  371. package/dist/types/shared/utils/errorUtils.d.ts +97 -0
  372. package/dist/types/shared/utils/index.d.ts +13 -0
  373. package/dist/types/shared/utils/networkUtils.d.ts +139 -0
  374. package/dist/types/shared/utils/themeUtils.d.ts +90 -0
  375. package/dist/types/utils/accountCacheSweep.d.ts +75 -0
  376. package/dist/types/utils/accountUtils.d.ts +100 -0
  377. package/dist/types/utils/activityId.d.ts +2 -0
  378. package/dist/types/utils/apiUtils.d.ts +95 -0
  379. package/dist/types/utils/asyncUtils.d.ts +62 -0
  380. package/dist/types/utils/authHelpers.d.ts +58 -0
  381. package/dist/types/utils/authWebUrl.d.ts +16 -0
  382. package/dist/types/utils/avatarUtils.d.ts +16 -0
  383. package/dist/types/utils/cache.d.ts +130 -0
  384. package/dist/types/utils/cacheKey.d.ts +66 -0
  385. package/dist/types/utils/coldBoot.d.ts +131 -0
  386. package/dist/types/utils/commonsApproval.d.ts +13 -0
  387. package/dist/types/utils/commonsDelivery.d.ts +105 -0
  388. package/dist/types/utils/deviceManager.d.ts +65 -0
  389. package/dist/types/utils/displayNamePolicyRanges.generated.d.ts +57 -0
  390. package/dist/types/utils/edgeRegion.d.ts +2 -0
  391. package/dist/types/utils/errorUtils.d.ts +125 -0
  392. package/dist/types/utils/identityCacheSweep.d.ts +80 -0
  393. package/dist/types/utils/languageUtils.d.ts +100 -0
  394. package/dist/types/utils/oauthPkce.d.ts +157 -0
  395. package/dist/types/utils/officialOrigins.d.ts +13 -0
  396. package/dist/types/utils/oxyServiceEnvironment.d.ts +17 -0
  397. package/dist/types/utils/platform.d.ts +49 -0
  398. package/dist/types/utils/profileLinks.d.ts +44 -0
  399. package/dist/types/utils/redactUrl.d.ts +17 -0
  400. package/dist/types/utils/registrableApex.d.ts +27 -0
  401. package/dist/types/utils/requestUtils.d.ts +123 -0
  402. package/dist/types/utils/sessionUtils.d.ts +54 -0
  403. package/dist/types/utils/textNormalization.d.ts +103 -0
  404. package/dist/types/utils/userHandle.d.ts +26 -0
  405. package/dist/types/utils/userIdentity.d.ts +21 -0
  406. package/dist/types/utils/validationUtils.d.ts +268 -0
  407. package/dist/types/utils/webauthnOrigin.d.ts +32 -0
  408. package/package.json +168 -0
  409. package/src/HttpService.ts +1553 -0
  410. package/src/OxyServices.base.ts +478 -0
  411. package/src/OxyServices.errors.ts +122 -0
  412. package/src/OxyServices.ts +172 -0
  413. package/src/__tests__/authSocket.test.ts +96 -0
  414. package/src/__tests__/httpServiceActivityId.test.ts +49 -0
  415. package/src/__tests__/httpServiceAuthSelfAwait.test.ts +108 -0
  416. package/src/__tests__/httpServiceCache.test.ts +318 -0
  417. package/src/__tests__/httpServiceCsrf.test.ts +235 -0
  418. package/src/__tests__/httpServiceFormEncoded.test.ts +142 -0
  419. package/src/__tests__/httpServiceQueueDeadlock.test.ts +128 -0
  420. package/src/__tests__/httpServiceUnwrapEnvelope.test.ts +115 -0
  421. package/src/__tests__/inSessionRefresh.test.ts +260 -0
  422. package/src/__tests__/linkedClient.test.ts +370 -0
  423. package/src/__tests__/parseHttpErrorBody.test.ts +116 -0
  424. package/src/__tests__/serverValueImportsDeclared.test.ts +120 -0
  425. package/src/__tests__/userIdentity.test.ts +143 -0
  426. package/src/boot/__tests__/sessionColdBoot.identity.test.ts +393 -0
  427. package/src/boot/__tests__/sessionColdBoot.sharedDevice.test.ts +325 -0
  428. package/src/boot/__tests__/sessionColdBoot.test.ts +570 -0
  429. package/src/boot/sessionColdBoot.ts +529 -0
  430. package/src/constants/version.ts +15 -0
  431. package/src/crypto/__tests__/backupMaterial.test.ts +86 -0
  432. package/src/crypto/__tests__/cryptoPrimitives.test.ts +226 -0
  433. package/src/crypto/__tests__/identityMocks.ts +125 -0
  434. package/src/crypto/__tests__/keyManager.atomicity.test.ts +272 -0
  435. package/src/crypto/__tests__/keyManager.cacheSafety.test.ts +175 -0
  436. package/src/crypto/__tests__/keyManager.identityStatus.test.ts +217 -0
  437. package/src/crypto/__tests__/keyManager.recoveryLadder.test.ts +179 -0
  438. package/src/crypto/__tests__/keyManager.recoveryMnemonic.test.ts +138 -0
  439. package/src/crypto/__tests__/keyManager.storageMigration.test.ts +227 -0
  440. package/src/crypto/__tests__/keyManager.test.ts +329 -0
  441. package/src/crypto/__tests__/polyfill.test.ts +109 -0
  442. package/src/crypto/__tests__/recoveryPhrase.test.ts +61 -0
  443. package/src/crypto/__tests__/scopedSeed.test.ts +126 -0
  444. package/src/crypto/__tests__/signChallengeShared.test.ts +63 -0
  445. package/src/crypto/__tests__/signedRecord.test.ts +230 -0
  446. package/src/crypto/aead.ts +97 -0
  447. package/src/crypto/ecdh.ts +37 -0
  448. package/src/crypto/identityMarker.ts +291 -0
  449. package/src/crypto/kdf.ts +43 -0
  450. package/src/crypto/keyManager.ts +2607 -0
  451. package/src/crypto/polyfill.ts +143 -0
  452. package/src/crypto/recoveryPhrase.ts +337 -0
  453. package/src/crypto/signatureService.ts +350 -0
  454. package/src/i18n/__tests__/accountCategoryLabels.test.ts +62 -0
  455. package/src/i18n/__tests__/accountRoleLabels.test.ts +54 -0
  456. package/src/i18n/__tests__/reputationCategoryLabels.test.ts +56 -0
  457. package/src/i18n/__tests__/trustTierLabels.test.ts +47 -0
  458. package/src/i18n/accountCategoryLabels.ts +44 -0
  459. package/src/i18n/accountRoleLabels.ts +26 -0
  460. package/src/i18n/index.ts +88 -0
  461. package/src/i18n/locales/ar-SA.json +282 -0
  462. package/src/i18n/locales/ca-ES.json +282 -0
  463. package/src/i18n/locales/de-DE.json +282 -0
  464. package/src/i18n/locales/en-US.json +2161 -0
  465. package/src/i18n/locales/es-ES.json +2161 -0
  466. package/src/i18n/locales/fr-FR.json +282 -0
  467. package/src/i18n/locales/it-IT.json +282 -0
  468. package/src/i18n/locales/ja-JP.json +282 -0
  469. package/src/i18n/locales/ko-KR.json +282 -0
  470. package/src/i18n/locales/pt-PT.json +282 -0
  471. package/src/i18n/locales/zh-CN.json +282 -0
  472. package/src/i18n/reputationCategoryLabels.ts +20 -0
  473. package/src/i18n/trustTierLabels.ts +18 -0
  474. package/src/index.ts +881 -0
  475. package/src/inference/OxyInferenceClient.ts +888 -0
  476. package/src/inference/__tests__/OxyInferenceClient.test.ts +834 -0
  477. package/src/logger/__tests__/logger.test.ts +207 -0
  478. package/src/logger/index.ts +217 -0
  479. package/src/mixins/OxyServices.accounts.ts +1330 -0
  480. package/src/mixins/OxyServices.analytics.ts +53 -0
  481. package/src/mixins/OxyServices.appData.ts +158 -0
  482. package/src/mixins/OxyServices.assets.ts +861 -0
  483. package/src/mixins/OxyServices.auth.ts +1858 -0
  484. package/src/mixins/OxyServices.chains.ts +134 -0
  485. package/src/mixins/OxyServices.civic.ts +945 -0
  486. package/src/mixins/OxyServices.connectedApps.ts +174 -0
  487. package/src/mixins/OxyServices.contacts.ts +73 -0
  488. package/src/mixins/OxyServices.deviceBoot.ts +210 -0
  489. package/src/mixins/OxyServices.deviceTransfer.ts +393 -0
  490. package/src/mixins/OxyServices.devices.ts +120 -0
  491. package/src/mixins/OxyServices.features.ts +454 -0
  492. package/src/mixins/OxyServices.followGraph.ts +293 -0
  493. package/src/mixins/OxyServices.identity.ts +705 -0
  494. package/src/mixins/OxyServices.identityBackup.ts +250 -0
  495. package/src/mixins/OxyServices.inference.ts +57 -0
  496. package/src/mixins/OxyServices.language.ts +144 -0
  497. package/src/mixins/OxyServices.links.ts +107 -0
  498. package/src/mixins/OxyServices.location.ts +46 -0
  499. package/src/mixins/OxyServices.nodes.ts +337 -0
  500. package/src/mixins/OxyServices.notifications.ts +142 -0
  501. package/src/mixins/OxyServices.payment.ts +163 -0
  502. package/src/mixins/OxyServices.privacy.ts +219 -0
  503. package/src/mixins/OxyServices.reputation.ts +436 -0
  504. package/src/mixins/OxyServices.security.ts +110 -0
  505. package/src/mixins/OxyServices.store.ts +585 -0
  506. package/src/mixins/OxyServices.topics.ts +169 -0
  507. package/src/mixins/OxyServices.user.ts +1137 -0
  508. package/src/mixins/OxyServices.utility.ts +1370 -0
  509. package/src/mixins/__tests__/OxyServices.civic.test.ts +1095 -0
  510. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +124 -0
  511. package/src/mixins/__tests__/OxyServices.deviceTransfer.test.ts +271 -0
  512. package/src/mixins/__tests__/OxyServices.identity.test.ts +376 -0
  513. package/src/mixins/__tests__/OxyServices.links.test.ts +154 -0
  514. package/src/mixins/__tests__/OxyServices.nodes.test.ts +341 -0
  515. package/src/mixins/__tests__/OxyServices.rotateKey.test.ts +276 -0
  516. package/src/mixins/__tests__/OxyServices.serviceAssetMetadata.test.ts +192 -0
  517. package/src/mixins/__tests__/OxyServices.serviceAssetMetadataBySha.test.ts +135 -0
  518. package/src/mixins/__tests__/accounts.test.ts +727 -0
  519. package/src/mixins/__tests__/appData.test.ts +203 -0
  520. package/src/mixins/__tests__/assetCredentials.test.ts +47 -0
  521. package/src/mixins/__tests__/assetUpload.test.ts +191 -0
  522. package/src/mixins/__tests__/backgroundCredential.httpIntegration.test.ts +135 -0
  523. package/src/mixins/__tests__/chains.test.ts +113 -0
  524. package/src/mixins/__tests__/commonsSignIn.test.ts +1144 -0
  525. package/src/mixins/__tests__/connectedApps.test.ts +123 -0
  526. package/src/mixins/__tests__/deviceTokenMintPinned.test.ts +113 -0
  527. package/src/mixins/__tests__/discoveryErrorHandling.test.ts +266 -0
  528. package/src/mixins/__tests__/followCacheInvalidation.test.ts +173 -0
  529. package/src/mixins/__tests__/followGraph.test.ts +147 -0
  530. package/src/mixins/__tests__/followGraphPagination.test.ts +271 -0
  531. package/src/mixins/__tests__/getFileDownloadUrl.test.ts +346 -0
  532. package/src/mixins/__tests__/getFollowStatuses.test.ts +95 -0
  533. package/src/mixins/__tests__/getUsersByIds.test.ts +149 -0
  534. package/src/mixins/__tests__/identityBackup.test.ts +315 -0
  535. package/src/mixins/__tests__/identityWriteCacheInvalidation.test.ts +407 -0
  536. package/src/mixins/__tests__/inferenceFactory.test.ts +58 -0
  537. package/src/mixins/__tests__/onTokensChanged.test.ts +129 -0
  538. package/src/mixins/__tests__/preSessionSkipAuth.test.ts +284 -0
  539. package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +155 -0
  540. package/src/mixins/__tests__/reputation.test.ts +522 -0
  541. package/src/mixins/__tests__/serviceAuth.test.ts +832 -0
  542. package/src/mixins/__tests__/serviceTokenJwks.test.ts +208 -0
  543. package/src/mixins/__tests__/store.test.ts +304 -0
  544. package/src/mixins/__tests__/topics.test.ts +156 -0
  545. package/src/mixins/__tests__/userReadCacheBypass.test.ts +129 -0
  546. package/src/mixins/__tests__/userTokenAuth.test.ts +746 -0
  547. package/src/mixins/__tests__/verifyChallenge.test.ts +130 -0
  548. package/src/mixins/__tests__/verifyServiceActingAs.test.ts +167 -0
  549. package/src/mixins/__tests__/webauthnAuth.test.ts +205 -0
  550. package/src/mixins/index.ts +201 -0
  551. package/src/mixins/mixinHelpers.ts +69 -0
  552. package/src/models/Topic.ts +46 -0
  553. package/src/models/interfaces.ts +786 -0
  554. package/src/models/session.ts +55 -0
  555. package/src/server/__tests__/auth.test.ts +78 -0
  556. package/src/server/__tests__/capabilityTicket.test.ts +111 -0
  557. package/src/server/__tests__/cors.socket.test.ts +225 -0
  558. package/src/server/__tests__/cors.test.ts +158 -0
  559. package/src/server/__tests__/rateLimit.test.ts +257 -0
  560. package/src/server/__tests__/safeFetch.test.ts +232 -0
  561. package/src/server/__tests__/securityHeaders.test.ts +348 -0
  562. package/src/server/__tests__/serviceTokenAttribution.test.ts +396 -0
  563. package/src/server/__tests__/userInvalidation.test.ts +203 -0
  564. package/src/server/__tests__/verifySecret.test.ts +40 -0
  565. package/src/server/auth.ts +271 -0
  566. package/src/server/capabilityTicket.ts +258 -0
  567. package/src/server/cors.ts +277 -0
  568. package/src/server/index.ts +136 -0
  569. package/src/server/rateLimit.ts +313 -0
  570. package/src/server/safeFetch.ts +586 -0
  571. package/src/server/securityHeaders.ts +467 -0
  572. package/src/server/userInvalidation.ts +199 -0
  573. package/src/server/verifySecret.ts +52 -0
  574. package/src/session/SessionClient.ts +1001 -0
  575. package/src/session/__tests__/SessionClient.additive.test.ts +153 -0
  576. package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +114 -0
  577. package/src/session/__tests__/SessionClient.diagnostics.test.ts +91 -0
  578. package/src/session/__tests__/SessionClient.directory.test.ts +688 -0
  579. package/src/session/__tests__/SessionClient.httpIntegration.test.ts +65 -0
  580. package/src/session/__tests__/SessionClient.identityPin.test.ts +175 -0
  581. package/src/session/__tests__/SessionClient.rest.test.ts +119 -0
  582. package/src/session/__tests__/SessionClient.serverEvents.test.ts +72 -0
  583. package/src/session/__tests__/SessionClient.socket.test.ts +202 -0
  584. package/src/session/__tests__/SessionClient.socketFactory.test.ts +81 -0
  585. package/src/session/__tests__/SessionClient.state.test.ts +78 -0
  586. package/src/session/__tests__/SessionClient.switchTokenOrder.test.ts +170 -0
  587. package/src/session/__tests__/accountDelegationAccess.test.ts +104 -0
  588. package/src/session/__tests__/accountDialogController.test.ts +1969 -0
  589. package/src/session/__tests__/accountDialogShape.test.ts +118 -0
  590. package/src/session/__tests__/accountSwitchTargets.test.ts +137 -0
  591. package/src/session/__tests__/authStateStore.test.ts +451 -0
  592. package/src/session/__tests__/deviceDirectory.test.ts +422 -0
  593. package/src/session/__tests__/deviceSwitcherRows.test.ts +223 -0
  594. package/src/session/__tests__/identityPin.test.ts +131 -0
  595. package/src/session/__tests__/identitySession.test.ts +194 -0
  596. package/src/session/__tests__/projectSessionState.test.ts +105 -0
  597. package/src/session/__tests__/refresh.identityPin.test.ts +307 -0
  598. package/src/session/__tests__/refresh.test.ts +443 -0
  599. package/src/session/__tests__/sessionIntegration.test.ts +209 -0
  600. package/src/session/__tests__/sharedDeviceCredential.test.ts +300 -0
  601. package/src/session/accountDelegationAccess.ts +72 -0
  602. package/src/session/accountDialogController.ts +1494 -0
  603. package/src/session/accountSwitchTargets.ts +93 -0
  604. package/src/session/authStateStore.ts +502 -0
  605. package/src/session/createSessionClient.ts +39 -0
  606. package/src/session/deviceDirectory.ts +269 -0
  607. package/src/session/deviceSwitcherRows.ts +145 -0
  608. package/src/session/identityPin.ts +310 -0
  609. package/src/session/identitySession.ts +206 -0
  610. package/src/session/projectSessionState.ts +137 -0
  611. package/src/session/refresh.ts +517 -0
  612. package/src/session/sessionClientHost.ts +39 -0
  613. package/src/session/sharedDeviceCredential.ts +349 -0
  614. package/src/session/socketLoader.ts +30 -0
  615. package/src/shared/utils/colorUtils.ts +155 -0
  616. package/src/shared/utils/errorUtils.ts +181 -0
  617. package/src/shared/utils/index.ts +59 -0
  618. package/src/shared/utils/networkUtils.ts +248 -0
  619. package/src/shared/utils/themeUtils.ts +115 -0
  620. package/src/types/buffer.d.ts +97 -0
  621. package/src/types/expo-crypto.d.ts +16 -0
  622. package/src/types/expo-secure-store.d.ts +17 -0
  623. package/src/utils/__tests__/accountUtils.test.ts +151 -0
  624. package/src/utils/__tests__/activityId.test.ts +45 -0
  625. package/src/utils/__tests__/asyncUtils.test.ts +187 -0
  626. package/src/utils/__tests__/authWebUrl.test.ts +15 -0
  627. package/src/utils/__tests__/cacheKey.test.ts +0 -0
  628. package/src/utils/__tests__/coldBoot.test.ts +496 -0
  629. package/src/utils/__tests__/commonsApproval.test.ts +60 -0
  630. package/src/utils/__tests__/identityCacheSweep.test.ts +151 -0
  631. package/src/utils/__tests__/languageUtils.test.ts +219 -0
  632. package/src/utils/__tests__/oauthPkce.test.ts +272 -0
  633. package/src/utils/__tests__/officialOrigins.test.ts +31 -0
  634. package/src/utils/__tests__/oxyServiceEnvironment.test.ts +7 -0
  635. package/src/utils/__tests__/profileLinks.test.ts +180 -0
  636. package/src/utils/__tests__/redactUrl.test.ts +33 -0
  637. package/src/utils/__tests__/registrableApex.test.ts +62 -0
  638. package/src/utils/__tests__/textNormalization.test.ts +205 -0
  639. package/src/utils/__tests__/validationUtils.test.ts +703 -0
  640. package/src/utils/__tests__/webauthnOrigin.test.ts +83 -0
  641. package/src/utils/accountCacheSweep.ts +93 -0
  642. package/src/utils/accountUtils.ts +194 -0
  643. package/src/utils/activityId.ts +2 -0
  644. package/src/utils/apiUtils.ts +137 -0
  645. package/src/utils/asyncUtils.ts +280 -0
  646. package/src/utils/authHelpers.ts +148 -0
  647. package/src/utils/authWebUrl.ts +17 -0
  648. package/src/utils/avatarUtils.ts +39 -0
  649. package/src/utils/cache.ts +273 -0
  650. package/src/utils/cacheKey.ts +93 -0
  651. package/src/utils/coldBoot.ts +218 -0
  652. package/src/utils/commonsApproval.ts +39 -0
  653. package/src/utils/commonsDelivery.ts +132 -0
  654. package/src/utils/deviceManager.ts +203 -0
  655. package/src/utils/displayNamePolicyRanges.generated.ts +69 -0
  656. package/src/utils/edgeRegion.ts +2 -0
  657. package/src/utils/errorUtils.ts +352 -0
  658. package/src/utils/identityCacheSweep.ts +104 -0
  659. package/src/utils/languageUtils.ts +256 -0
  660. package/src/utils/oauthPkce.ts +392 -0
  661. package/src/utils/officialOrigins.ts +62 -0
  662. package/src/utils/oxyServiceEnvironment.ts +17 -0
  663. package/src/utils/platform.ts +139 -0
  664. package/src/utils/profileLinks.ts +91 -0
  665. package/src/utils/redactUrl.ts +28 -0
  666. package/src/utils/registrableApex.ts +41 -0
  667. package/src/utils/requestUtils.ts +237 -0
  668. package/src/utils/sessionUtils.ts +206 -0
  669. package/src/utils/textNormalization.ts +162 -0
  670. package/src/utils/userHandle.ts +49 -0
  671. package/src/utils/userIdentity.ts +60 -0
  672. package/src/utils/validationUtils.ts +390 -0
  673. package/src/utils/webauthnOrigin.ts +52 -0
@@ -0,0 +1,443 @@
1
+ import type { OxyServices } from '../../OxyServices';
2
+ import type { DeviceTokenMintResponse } from '@oxy.so/contracts';
3
+ import type { SessionLoginResponse } from '../../models/session';
4
+ import {
5
+ refreshPersistedSession,
6
+ startTokenRefreshScheduler,
7
+ type DeviceSecretMintOutcome,
8
+ } from '../refresh';
9
+ import {
10
+ createMemoryAuthStateStore,
11
+ type AuthStateStore,
12
+ type PersistedAuthState,
13
+ } from '../authStateStore';
14
+
15
+ /**
16
+ * A real, per-client device-secret mint single-flight matching
17
+ * `HttpService.runSingleFlightDeviceSecretMint`: concurrent callers await the
18
+ * SAME in-flight mint and all receive its result; a fresh call after it settles
19
+ * starts a new one.
20
+ */
21
+ function makeMintSingleFlight(): (mint: () => Promise<DeviceSecretMintOutcome>) => Promise<DeviceSecretMintOutcome> {
22
+ let inFlight: Promise<DeviceSecretMintOutcome> | null = null;
23
+ return (mint) => {
24
+ if (!inFlight) {
25
+ inFlight = mint().finally(() => {
26
+ inFlight = null;
27
+ });
28
+ }
29
+ return inFlight;
30
+ };
31
+ }
32
+
33
+ /** The persisted zero-cookie mint credential the refresh reads. */
34
+ const STORED: PersistedAuthState = {
35
+ sessionId: 'sess-old',
36
+ userId: 'user-1',
37
+ deviceId: 'dev-mint',
38
+ deviceSecret: 'ds-secret-orig',
39
+ };
40
+
41
+ /** A successful `POST /session/device/token` mint (rotates the secret + active account). */
42
+ const MINT: DeviceTokenMintResponse = {
43
+ accessToken: 'access-new',
44
+ expiresAt: '2030-01-01T00:00:00.000Z',
45
+ nextDeviceSecret: 'ds-next-secret',
46
+ state: {
47
+ deviceId: 'dev-mint',
48
+ accounts: [{ accountId: 'user-1', sessionId: 'sess-new', authuser: 0 }],
49
+ activeAccountId: 'user-1',
50
+ revision: 4,
51
+ updatedAt: 1_700_000_000_000,
52
+ },
53
+ };
54
+
55
+ interface RefreshMockOverrides {
56
+ mintFromDeviceSecret?: OxyServices['mintFromDeviceSecret'];
57
+ signInWithSharedIdentity?: OxyServices['signInWithSharedIdentity'];
58
+ }
59
+
60
+ function makeOxy(overrides: RefreshMockOverrides = {}): { oxy: OxyServices; setTokens: jest.Mock } {
61
+ const setTokens = jest.fn();
62
+ const oxy = {
63
+ setTokens,
64
+ mintFromDeviceSecret: overrides.mintFromDeviceSecret ?? (async () => MINT),
65
+ signInWithSharedIdentity: overrides.signInWithSharedIdentity ?? (async () => null),
66
+ // The rotating mint runs under the client's process-wide single-flight; the
67
+ // arm reaches for it via `oxy.httpService.runSingleFlightDeviceSecretMint`.
68
+ httpService: { runSingleFlightDeviceSecretMint: makeMintSingleFlight() },
69
+ } as unknown as OxyServices;
70
+ return { oxy, setTokens };
71
+ }
72
+
73
+ describe('refreshPersistedSession — arm 1 (device-secret mint)', () => {
74
+ it('mints, plants, persists the rotated secret + active account, and returns the token', async () => {
75
+ const store = createMemoryAuthStateStore();
76
+ await store.save(STORED);
77
+ const { oxy, setTokens } = makeOxy();
78
+
79
+ const token = await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: false });
80
+
81
+ expect(token).toBe('access-new');
82
+ expect(setTokens).toHaveBeenCalledWith('access-new');
83
+ expect(await store.load()).toEqual({
84
+ sessionId: 'sess-new',
85
+ userId: 'user-1',
86
+ deviceId: 'dev-mint',
87
+ deviceSecret: 'ds-next-secret',
88
+ accessToken: 'access-new',
89
+ expiresAt: '2030-01-01T00:00:00.000Z',
90
+ });
91
+ });
92
+
93
+ it('presents the persisted deviceId + deviceSecret to the mint', async () => {
94
+ const store = createMemoryAuthStateStore();
95
+ await store.save(STORED);
96
+ const mintFromDeviceSecret = jest.fn(async () => MINT);
97
+ const { oxy } = makeOxy({ mintFromDeviceSecret });
98
+
99
+ await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: false });
100
+
101
+ expect(mintFromDeviceSecret).toHaveBeenCalledWith('dev-mint', 'ds-secret-orig');
102
+ });
103
+
104
+ it('drops only the secret (keeps deviceId) on a 401 and falls to shared-key when native', async () => {
105
+ const store = createMemoryAuthStateStore();
106
+ await store.save(STORED);
107
+ const SHARED: SessionLoginResponse = {
108
+ sessionId: 'sess-shared',
109
+ deviceId: 'dev-1',
110
+ expiresAt: '2030-01-01T00:00:00.000Z',
111
+ user: { id: 'user-1', username: 'u', name: {}, avatar: undefined },
112
+ accessToken: 'access-shared',
113
+ };
114
+ const signInWithSharedIdentity = jest.fn(async () => SHARED);
115
+ const { oxy } = makeOxy({
116
+ mintFromDeviceSecret: async () => {
117
+ throw Object.assign(new Error('invalid_device_secret'), { status: 401 });
118
+ },
119
+ signInWithSharedIdentity,
120
+ });
121
+
122
+ const token = await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: true });
123
+
124
+ expect(token).toBe('access-shared');
125
+ expect(signInWithSharedIdentity).toHaveBeenCalledTimes(1);
126
+ const persisted = await store.load();
127
+ expect(persisted?.deviceSecret).toBeUndefined();
128
+ expect(persisted?.deviceId).toBe('dev-mint');
129
+ });
130
+
131
+ it('clears the store on a 401 when there is no shared-key fallback (web)', async () => {
132
+ const store = createMemoryAuthStateStore();
133
+ await store.save(STORED);
134
+ const { oxy } = makeOxy({
135
+ mintFromDeviceSecret: async () => {
136
+ throw Object.assign(new Error('no_active_session'), { status: 401 });
137
+ },
138
+ });
139
+
140
+ const token = await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: false });
141
+
142
+ expect(token).toBeNull();
143
+ expect(await store.load()).toBeNull();
144
+ });
145
+
146
+ it('KEEPS the store and returns null on a transient (500 / network) error', async () => {
147
+ const store = createMemoryAuthStateStore();
148
+ await store.save(STORED);
149
+ const signInWithSharedIdentity = jest.fn(async () => null);
150
+ const { oxy } = makeOxy({
151
+ mintFromDeviceSecret: async () => {
152
+ throw Object.assign(new Error('server'), { status: 500 });
153
+ },
154
+ signInWithSharedIdentity,
155
+ });
156
+
157
+ // A transient failure must NOT drop the credential nor fall through to shared-key.
158
+ expect(await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: true })).toBeNull();
159
+ expect(await store.load()).toEqual(STORED);
160
+ expect(signInWithSharedIdentity).not.toHaveBeenCalled();
161
+ });
162
+
163
+ it('KEEPS the store on an UNRECOGNIZED 401 (proxy / middleware / deploy-window) — never wipes the credential on an ambiguous 401', async () => {
164
+ const store = createMemoryAuthStateStore();
165
+ await store.save(STORED);
166
+ const signInWithSharedIdentity = jest.fn(async () => null);
167
+ const { oxy } = makeOxy({
168
+ mintFromDeviceSecret: async () => {
169
+ // A 401 whose body is NEITHER `invalid_device_secret` NOR `no_active_session`
170
+ // — an auth-layer / proxy / starting-instance 401 common during a deploy
171
+ // window. It is NOT proof the secret diverged, so the durable device
172
+ // credential must survive (treated as transient) and a later attempt self-heals.
173
+ throw Object.assign(new Error('Unauthorized'), { status: 401 });
174
+ },
175
+ signInWithSharedIdentity,
176
+ });
177
+
178
+ expect(await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: true })).toBeNull();
179
+ expect(await store.load()).toEqual(STORED);
180
+ expect(signInWithSharedIdentity).not.toHaveBeenCalled();
181
+ });
182
+ });
183
+
184
+ describe('refreshPersistedSession — arm 2 (native shared-key fallback)', () => {
185
+ const SHARED_SESSION: SessionLoginResponse = {
186
+ sessionId: 'sess-shared',
187
+ deviceId: 'dev-1',
188
+ expiresAt: '2030-01-01T00:00:00.000Z',
189
+ user: { id: 'user-1', username: 'u', name: {}, avatar: undefined },
190
+ accessToken: 'access-shared',
191
+ };
192
+
193
+ it('re-mints via shared identity when there is no persisted secret', async () => {
194
+ const store = createMemoryAuthStateStore(); // no stored device secret
195
+ const signInWithSharedIdentity = jest.fn(async () => SHARED_SESSION);
196
+ const { oxy } = makeOxy({ signInWithSharedIdentity });
197
+
198
+ const token = await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: true });
199
+
200
+ expect(token).toBe('access-shared');
201
+ expect(signInWithSharedIdentity).toHaveBeenCalledTimes(1);
202
+ });
203
+
204
+ it('does NOT try shared-key when the fallback is disabled (web)', async () => {
205
+ const store = createMemoryAuthStateStore();
206
+ const signInWithSharedIdentity = jest.fn(async () => SHARED_SESSION);
207
+ const { oxy } = makeOxy({ signInWithSharedIdentity });
208
+
209
+ expect(await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: false })).toBeNull();
210
+ expect(signInWithSharedIdentity).not.toHaveBeenCalled();
211
+ });
212
+
213
+ it('returns null when the shared-key re-mint yields no session', async () => {
214
+ const store = createMemoryAuthStateStore();
215
+ const signInWithSharedIdentity = jest.fn(async () => null);
216
+ const { oxy } = makeOxy({ signInWithSharedIdentity });
217
+
218
+ expect(await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: true })).toBeNull();
219
+ expect(signInWithSharedIdentity).toHaveBeenCalledTimes(1);
220
+ });
221
+
222
+ it('persists the recovered credential from the shared-key re-mint (repopulates the fast lane)', async () => {
223
+ // Bug #3: an in-session shared-key recovery must repopulate the durable
224
+ // device credential, not leave the fast device-secret lane empty.
225
+ const store = createMemoryAuthStateStore(); // no persisted secret → arm 1 skips
226
+ const RECOVERED: SessionLoginResponse = {
227
+ sessionId: 'sess-shared',
228
+ deviceId: 'dev-shared',
229
+ deviceSecret: 'ds-shared-secret',
230
+ expiresAt: '2030-01-01T00:00:00.000Z',
231
+ user: { id: 'user-shared', username: 'u', name: {}, avatar: undefined },
232
+ accessToken: 'access-shared',
233
+ };
234
+ const { oxy } = makeOxy({ signInWithSharedIdentity: jest.fn(async () => RECOVERED) });
235
+
236
+ const token = await refreshPersistedSession({ oxy, store, allowSharedKeyFallback: true });
237
+
238
+ expect(token).toBe('access-shared');
239
+ expect(await store.load()).toEqual({
240
+ sessionId: 'sess-shared',
241
+ userId: 'user-shared',
242
+ deviceId: 'dev-shared',
243
+ deviceSecret: 'ds-shared-secret',
244
+ accessToken: 'access-shared',
245
+ expiresAt: '2030-01-01T00:00:00.000Z',
246
+ });
247
+ });
248
+ });
249
+
250
+ describe('refreshPersistedSession — single-flight (no double-rotation)', () => {
251
+ it('coalesces two concurrent mints into ONE server rotation; the store holds the final current secret', async () => {
252
+ // The server rotates the secret on every mint. Two concurrent lanes must
253
+ // therefore share ONE in-flight mint (one rotation) or the store could
254
+ // converge on a superseded secret.
255
+ const store = createMemoryAuthStateStore();
256
+ await store.save(STORED);
257
+
258
+ let release: (() => void) | null = null;
259
+ const gate = new Promise<void>((resolve) => {
260
+ release = resolve;
261
+ });
262
+ const mintFromDeviceSecret = jest.fn(async (deviceId: string, deviceSecret: string) => {
263
+ // Block until BOTH callers have entered so the single-flight is exercised.
264
+ await gate;
265
+ expect(deviceId).toBe('dev-mint');
266
+ expect(deviceSecret).toBe('ds-secret-orig');
267
+ return MINT;
268
+ });
269
+ const { oxy, setTokens } = makeOxy({ mintFromDeviceSecret });
270
+
271
+ const first = refreshPersistedSession({ oxy, store, allowSharedKeyFallback: false });
272
+ const second = refreshPersistedSession({ oxy, store, allowSharedKeyFallback: false });
273
+ // Both callers entered while the mint is in flight.
274
+ release?.();
275
+ const [t1, t2] = await Promise.all([first, second]);
276
+
277
+ // Exactly one server rotation despite two concurrent callers…
278
+ expect(mintFromDeviceSecret).toHaveBeenCalledTimes(1);
279
+ // …both callers received the same minted token…
280
+ expect(t1).toBe('access-new');
281
+ expect(t2).toBe('access-new');
282
+ // …and the durable store converged on the rotated (current) secret.
283
+ expect((await store.load())?.deviceSecret).toBe('ds-next-secret');
284
+ // The token was planted exactly once (inside the single-flighted mint).
285
+ expect(setTokens).toHaveBeenCalledTimes(1);
286
+ });
287
+ });
288
+
289
+ describe('refreshPersistedSession — durable persist failure is fatal to the mint', () => {
290
+ it('does NOT plant a token when the rotated secret cannot be durably persisted', async () => {
291
+ // Bug #2: a mint that rotated the server secret but could not persist it must
292
+ // not leave the process advertising a session on an unsaved, soon-dead secret.
293
+ const failingStore: AuthStateStore = {
294
+ load: async () => STORED,
295
+ save: async () => false, // durable write did not land
296
+ clear: async () => undefined,
297
+ };
298
+ const mintFromDeviceSecret = jest.fn(async () => MINT);
299
+ const { oxy, setTokens } = makeOxy({ mintFromDeviceSecret });
300
+
301
+ const token = await refreshPersistedSession({
302
+ oxy,
303
+ store: failingStore,
304
+ allowSharedKeyFallback: false,
305
+ });
306
+
307
+ // The mint ran (the server rotated) but the token was NOT planted…
308
+ expect(mintFromDeviceSecret).toHaveBeenCalledTimes(1);
309
+ expect(setTokens).not.toHaveBeenCalled();
310
+ // …and the lane reports failure rather than a healthy session.
311
+ expect(token).toBeNull();
312
+ });
313
+
314
+ it('does NOT fall through to the shared-key arm on a persist failure', async () => {
315
+ const failingStore: AuthStateStore = {
316
+ load: async () => STORED,
317
+ save: async () => false,
318
+ clear: async () => undefined,
319
+ };
320
+ const signInWithSharedIdentity = jest.fn(async () => null);
321
+ const { oxy } = makeOxy({
322
+ mintFromDeviceSecret: async () => MINT,
323
+ signInWithSharedIdentity,
324
+ });
325
+
326
+ await refreshPersistedSession({ oxy, store: failingStore, allowSharedKeyFallback: true });
327
+
328
+ // A storage failure is not a bad-secret signal — the shared-key arm must not run.
329
+ expect(signInWithSharedIdentity).not.toHaveBeenCalled();
330
+ });
331
+ });
332
+
333
+ describe('startTokenRefreshScheduler', () => {
334
+ function makeSchedulerOxy(expiresInSeconds: number | null): {
335
+ oxy: OxyServices;
336
+ refreshAccessToken: jest.Mock;
337
+ } {
338
+ let cleared = false;
339
+ const refreshAccessToken = jest.fn(async () => {
340
+ cleared = true; // stop the reschedule loop after the first fire
341
+ return null;
342
+ });
343
+ const nowSec = Math.floor(Date.now() / 1000);
344
+ const oxy = {
345
+ getAccessToken: () => (cleared ? null : 'tok'),
346
+ getAccessTokenExpiry: () => (expiresInSeconds === null ? null : nowSec + expiresInSeconds),
347
+ onTokensChanged: () => () => undefined,
348
+ httpService: { refreshAccessToken },
349
+ } as unknown as OxyServices;
350
+ return { oxy, refreshAccessToken };
351
+ }
352
+
353
+ afterEach(() => {
354
+ jest.useRealTimers();
355
+ });
356
+
357
+ it('fires a refresh ~60s before expiry', async () => {
358
+ jest.useFakeTimers();
359
+ const { oxy, refreshAccessToken } = makeSchedulerOxy(120);
360
+ const handle = startTokenRefreshScheduler(oxy);
361
+
362
+ expect(refreshAccessToken).not.toHaveBeenCalled();
363
+ await jest.advanceTimersByTimeAsync(60_000);
364
+ expect(refreshAccessToken).toHaveBeenCalledWith('preflight');
365
+
366
+ handle.dispose();
367
+ });
368
+
369
+ it('no-ops cleanly for an opaque token with no exp', () => {
370
+ jest.useFakeTimers();
371
+ const { oxy, refreshAccessToken } = makeSchedulerOxy(null);
372
+ const handle = startTokenRefreshScheduler(oxy);
373
+ jest.advanceTimersByTime(10 * 60_000);
374
+ expect(refreshAccessToken).not.toHaveBeenCalled();
375
+ handle.dispose();
376
+ });
377
+
378
+ it('backs off on repeated failure — bounded attempts, never a zero-delay busy loop', async () => {
379
+ jest.useFakeTimers();
380
+ // Already-expired token + a refresh that ALWAYS fails. The old code floored
381
+ // the delay to 0 and re-armed in the finally block → a tight loop that would
382
+ // exhaust jest's 100k-timer guard. The backoff schedule fires only a handful
383
+ // of times per simulated minute.
384
+ const refreshAccessToken = jest.fn(async () => null);
385
+ const nowSec = Math.floor(Date.now() / 1000);
386
+ const oxy = {
387
+ getAccessToken: () => 'tok',
388
+ getAccessTokenExpiry: () => nowSec - 10,
389
+ onTokensChanged: () => () => undefined,
390
+ httpService: { refreshAccessToken },
391
+ } as unknown as OxyServices;
392
+
393
+ const handle = startTokenRefreshScheduler(oxy);
394
+ await jest.advanceTimersByTimeAsync(60_000);
395
+
396
+ expect(refreshAccessToken.mock.calls.length).toBeGreaterThan(0);
397
+ expect(refreshAccessToken.mock.calls.length).toBeLessThan(10);
398
+ handle.dispose();
399
+ });
400
+
401
+ it('resets the backoff after a successful refresh', async () => {
402
+ jest.useFakeTimers();
403
+ // Expiry is always ~1h from NOW, so a successful refresh genuinely pushes
404
+ // the token out (as a real rotation would). Fail once, then succeed.
405
+ const refreshAccessToken = jest
406
+ .fn<Promise<string | null>, [unknown]>()
407
+ .mockResolvedValueOnce(null)
408
+ .mockResolvedValue('fresh');
409
+ const oxy = {
410
+ getAccessToken: () => 'tok',
411
+ getAccessTokenExpiry: () => Math.floor(Date.now() / 1000) + 3600,
412
+ onTokensChanged: () => () => undefined,
413
+ httpService: { refreshAccessToken },
414
+ } as unknown as OxyServices;
415
+
416
+ const handle = startTokenRefreshScheduler(oxy);
417
+ // First fire ~ (3600-60)s out (fails → 5s backoff), then the backoff fire
418
+ // succeeds and re-arms ~1h out from the (now-fresh) expiry.
419
+ await jest.advanceTimersByTimeAsync((3600 - 60) * 1000);
420
+ await jest.advanceTimersByTimeAsync(5_000);
421
+ const callsAfterSuccess = refreshAccessToken.mock.calls.length;
422
+ expect(callsAfterSuccess).toBe(2); // one failure + one success
423
+ // A further minute must NOT produce a burst — the healthy re-arm is ~1h out.
424
+ await jest.advanceTimersByTimeAsync(60_000);
425
+ expect(refreshAccessToken.mock.calls.length).toBe(callsAfterSuccess);
426
+ handle.dispose();
427
+ });
428
+
429
+ it('calls unref() on its timer so it never holds the event loop', () => {
430
+ const unref = jest.fn();
431
+ const setTimeoutSpy = jest
432
+ .spyOn(global, 'setTimeout')
433
+ .mockReturnValue({ unref } as unknown as ReturnType<typeof setTimeout>);
434
+ const { oxy } = makeSchedulerOxy(120);
435
+
436
+ const handle = startTokenRefreshScheduler(oxy);
437
+ expect(setTimeoutSpy).toHaveBeenCalled();
438
+ expect(unref).toHaveBeenCalled();
439
+
440
+ handle.dispose();
441
+ setTimeoutSpy.mockRestore();
442
+ });
443
+ });
@@ -0,0 +1,209 @@
1
+ import type { DeviceSessionState } from '@oxy.so/contracts';
2
+ import type { User } from '../../models/interfaces';
3
+ import { SessionClient, type TokenTransport } from '../SessionClient';
4
+ import { createSessionClientHost } from '../sessionClientHost';
5
+ import { createSessionClient } from '../createSessionClient';
6
+ import {
7
+ accountIdsOf,
8
+ activeSessionIdOf,
9
+ activeUserOf,
10
+ deviceStateToClientSessions,
11
+ } from '../projectSessionState';
12
+
13
+ function fakeOxy() {
14
+ const listeners = new Set<(t: string | null) => void>();
15
+ return {
16
+ makeRequest: jest.fn().mockResolvedValue({ ok: true }),
17
+ getBaseURL: jest.fn().mockReturnValue('https://api.oxy.so'),
18
+ getAccessToken: jest.fn().mockReturnValue('tok'),
19
+ setTokens: jest.fn(),
20
+ onTokensChanged: jest.fn((l: (t: string | null) => void) => {
21
+ listeners.add(l);
22
+ return () => listeners.delete(l);
23
+ }),
24
+ _emit: (t: string | null) => listeners.forEach((l) => l(t)),
25
+ };
26
+ }
27
+
28
+ describe('createSessionClientHost', () => {
29
+ test('delegates REST + token methods to oxyServices', async () => {
30
+ const oxy = fakeOxy();
31
+ const host = createSessionClientHost(oxy as never);
32
+ await host.makeRequest('GET', '/session/device/state', undefined, { cache: false });
33
+ expect(oxy.makeRequest).toHaveBeenCalledWith('GET', '/session/device/state', undefined, { cache: false });
34
+ expect(host.getBaseURL()).toBe('https://api.oxy.so');
35
+ expect(host.getAccessToken()).toBe('tok');
36
+ host.setTokens('new');
37
+ expect(oxy.setTokens).toHaveBeenCalledWith('new');
38
+ });
39
+
40
+ test('getCurrentAccountId reflects setCurrentAccountId', () => {
41
+ const host = createSessionClientHost(fakeOxy() as never);
42
+ expect(host.getCurrentAccountId()).toBeNull();
43
+ host.setCurrentAccountId('u1');
44
+ expect(host.getCurrentAccountId()).toBe('u1');
45
+ });
46
+
47
+ test('setDeviceCredential reflects on getDeviceCredential', () => {
48
+ const host = createSessionClientHost(fakeOxy() as never);
49
+ expect(host.getDeviceCredential()).toBeNull();
50
+ host.setDeviceCredential({ deviceId: 'd1', deviceSecret: 's1' });
51
+ expect(host.getDeviceCredential()).toEqual({ deviceId: 'd1', deviceSecret: 's1' });
52
+ });
53
+
54
+ test('onTokensChanged forwards to oxyServices and unsubscribes', () => {
55
+ const oxy = fakeOxy();
56
+ const host = createSessionClientHost(oxy as never);
57
+ const cb = jest.fn();
58
+ const unsub = host.onTokensChanged(cb);
59
+ oxy._emit(null);
60
+ expect(cb).toHaveBeenCalledWith(null);
61
+ unsub();
62
+ oxy._emit('x');
63
+ expect(cb).toHaveBeenCalledTimes(1);
64
+ });
65
+ });
66
+
67
+ function makeUser(id: string): User {
68
+ return {
69
+ id,
70
+ publicKey: `pk-${id}`,
71
+ username: `user-${id}`,
72
+ name: {},
73
+ };
74
+ }
75
+
76
+ // DeviceSessionState.updatedAt is an epoch-ms number on the wire (see
77
+ // packages/contracts/src/deviceSession.ts: `updatedAt: z.number()`), not an
78
+ // ISO string.
79
+ const UPDATED_AT_MS = Date.UTC(2026, 6, 1, 0, 0, 0, 0);
80
+ const UPDATED_AT_ISO = new Date(UPDATED_AT_MS).toISOString();
81
+
82
+ const state: DeviceSessionState = {
83
+ deviceId: 'device-1',
84
+ accounts: [
85
+ { accountId: 'a1', sessionId: 'sess-a1', authuser: 0 },
86
+ { accountId: 'a2', sessionId: 'sess-a2', authuser: 1 },
87
+ ],
88
+ activeAccountId: 'a2',
89
+ revision: 1,
90
+ updatedAt: UPDATED_AT_MS,
91
+ };
92
+
93
+ const usersById = new Map<string, User>([
94
+ ['a1', makeUser('a1')],
95
+ ['a2', makeUser('a2')],
96
+ ]);
97
+
98
+ describe('projectSessionState', () => {
99
+ describe('activeSessionIdOf', () => {
100
+ test('returns the active account sessionId', () => {
101
+ expect(activeSessionIdOf(state)).toBe('sess-a2');
102
+ });
103
+
104
+ test('returns null for null state', () => {
105
+ expect(activeSessionIdOf(null)).toBeNull();
106
+ });
107
+
108
+ test('returns null when activeAccountId is null', () => {
109
+ expect(activeSessionIdOf({ ...state, activeAccountId: null })).toBeNull();
110
+ });
111
+ });
112
+
113
+ describe('deviceStateToClientSessions', () => {
114
+ test('maps every account in order with isCurrent + authuser', () => {
115
+ const sessions = deviceStateToClientSessions(state, usersById);
116
+ expect(sessions).toHaveLength(2);
117
+ expect(sessions[0]).toEqual({
118
+ sessionId: 'sess-a1',
119
+ deviceId: 'device-1',
120
+ expiresAt: UPDATED_AT_ISO,
121
+ lastActive: UPDATED_AT_ISO,
122
+ userId: 'a1',
123
+ isCurrent: false,
124
+ authuser: 0,
125
+ });
126
+ expect(sessions[1]).toEqual({
127
+ sessionId: 'sess-a2',
128
+ deviceId: 'device-1',
129
+ expiresAt: UPDATED_AT_ISO,
130
+ lastActive: UPDATED_AT_ISO,
131
+ userId: 'a2',
132
+ isCurrent: true,
133
+ authuser: 1,
134
+ });
135
+ });
136
+
137
+ test('still projects a session for an account absent from usersById', () => {
138
+ const sessions = deviceStateToClientSessions(state, new Map());
139
+ expect(sessions).toHaveLength(2);
140
+ expect(sessions.map((session) => session.userId)).toEqual(['a1', 'a2']);
141
+ });
142
+ });
143
+
144
+ describe('activeUserOf', () => {
145
+ test('returns the active account user', () => {
146
+ expect(activeUserOf(state, usersById)).toEqual(makeUser('a2'));
147
+ });
148
+
149
+ test('returns null for null state', () => {
150
+ expect(activeUserOf(null, usersById)).toBeNull();
151
+ });
152
+
153
+ test('returns null when activeAccountId is null', () => {
154
+ expect(activeUserOf({ ...state, activeAccountId: null }, usersById)).toBeNull();
155
+ });
156
+
157
+ test('returns null when the active account id is absent from usersById', () => {
158
+ expect(activeUserOf(state, new Map())).toBeNull();
159
+ });
160
+ });
161
+
162
+ describe('accountIdsOf', () => {
163
+ test('returns every account id in order', () => {
164
+ expect(accountIdsOf(state)).toEqual(['a1', 'a2']);
165
+ });
166
+
167
+ test('returns [] for null state', () => {
168
+ expect(accountIdsOf(null)).toEqual([]);
169
+ });
170
+ });
171
+ });
172
+
173
+ describe('createSessionClient', () => {
174
+ function fakeTransport(): TokenTransport {
175
+ return { ensureActiveToken: jest.fn().mockResolvedValue(undefined) };
176
+ }
177
+
178
+ test('wires a SessionClient instance backed by the host + injected transport', () => {
179
+ const oxy = fakeOxy();
180
+
181
+ const { client, host } = createSessionClient(oxy as never, fakeTransport());
182
+
183
+ expect(client).toBeInstanceOf(SessionClient);
184
+ expect(typeof client.bootstrap).toBe('function');
185
+ expect(client.getState()).toBeNull();
186
+ expect(typeof host.setCurrentAccountId).toBe('function');
187
+ });
188
+
189
+ test('the returned host reflects setCurrentAccountId', () => {
190
+ const oxy = fakeOxy();
191
+
192
+ const { host } = createSessionClient(oxy as never, fakeTransport());
193
+
194
+ expect(host.getCurrentAccountId()).toBeNull();
195
+ host.setCurrentAccountId('u1');
196
+ expect(host.getCurrentAccountId()).toBe('u1');
197
+ });
198
+
199
+ test('uses the injected transport (not a hard-coded one) when the client bootstraps', async () => {
200
+ const oxy = fakeOxy();
201
+ oxy.makeRequest.mockResolvedValue({ state, activeToken: null });
202
+ const transport = fakeTransport();
203
+
204
+ const { client } = createSessionClient(oxy as never, transport);
205
+ await client.bootstrap();
206
+
207
+ expect(transport.ensureActiveToken).toHaveBeenCalledWith(expect.objectContaining({ revision: state.revision }));
208
+ });
209
+ });