@oxyhq/services 22.12.4 → 22.13.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 (446) hide show
  1. package/README.md +16 -46
  2. package/lib/commonjs/index.js +18 -12
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/ui/boot/runProviderColdBoot.js +51 -9
  5. package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
  6. package/lib/commonjs/ui/client.js +0 -13
  7. package/lib/commonjs/ui/client.js.map +1 -1
  8. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +27 -4
  9. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
  10. package/lib/commonjs/ui/components/OxyAuthChooser.js +72 -1123
  11. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  12. package/lib/commonjs/ui/components/OxyConsentScreen.js +50 -12
  13. package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -1
  14. package/lib/commonjs/ui/components/OxyProvider.js +4 -4
  15. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  16. package/lib/commonjs/ui/components/OxySignInButton.js +74 -40
  17. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  18. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +527 -0
  19. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -0
  20. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +90 -0
  21. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -0
  22. package/lib/commonjs/ui/components/authChooser/SignInRequestView.js +144 -0
  23. package/lib/commonjs/ui/components/authChooser/SignInRequestView.js.map +1 -0
  24. package/lib/commonjs/ui/components/authChooser/SignUpView.js +188 -0
  25. package/lib/commonjs/ui/components/authChooser/SignUpView.js.map +1 -0
  26. package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js +63 -0
  27. package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js.map +1 -0
  28. package/lib/commonjs/ui/components/authChooser/primitives.js +169 -0
  29. package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -0
  30. package/lib/commonjs/ui/components/authChooser/requestSurfaces.js +149 -0
  31. package/lib/commonjs/ui/components/authChooser/requestSurfaces.js.map +1 -0
  32. package/lib/commonjs/ui/components/authChooser/signInProgress.js +54 -0
  33. package/lib/commonjs/ui/components/authChooser/signInProgress.js.map +1 -0
  34. package/lib/commonjs/ui/components/{oxyAuthChooserStyles.js → authChooser/styles.js} +19 -1
  35. package/lib/commonjs/ui/components/authChooser/styles.js.map +1 -0
  36. package/lib/commonjs/ui/components/authChooser/types.js +59 -0
  37. package/lib/commonjs/ui/components/authChooser/types.js.map +1 -0
  38. package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js +50 -0
  39. package/lib/commonjs/ui/components/authChooser/useUsernameAvailability.js.map +1 -0
  40. package/lib/commonjs/ui/context/OxyContext.js +205 -30
  41. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  42. package/lib/commonjs/ui/context/commitSessionFlow.js +46 -0
  43. package/lib/commonjs/ui/context/commitSessionFlow.js.map +1 -1
  44. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +15 -2
  45. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  46. package/lib/commonjs/ui/context/useOxyAccountGraph.js +13 -5
  47. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  48. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +6 -1
  49. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
  50. package/lib/commonjs/ui/index.js +0 -13
  51. package/lib/commonjs/ui/index.js.map +1 -1
  52. package/lib/commonjs/ui/oauth/browserAuthTransport.js +170 -0
  53. package/lib/commonjs/ui/oauth/browserAuthTransport.js.map +1 -0
  54. package/lib/commonjs/ui/oauth/completeOAuthCode.js +78 -0
  55. package/lib/commonjs/ui/oauth/completeOAuthCode.js.map +1 -0
  56. package/lib/commonjs/ui/oauth/legacyRedirectLanes.js +48 -0
  57. package/lib/commonjs/ui/oauth/legacyRedirectLanes.js.map +1 -0
  58. package/lib/commonjs/ui/oauth/oauthHandshake.js +53 -0
  59. package/lib/commonjs/ui/oauth/oauthHandshake.js.map +1 -0
  60. package/lib/commonjs/ui/oauth/oauthPopup.js +276 -0
  61. package/lib/commonjs/ui/oauth/oauthPopup.js.map +1 -0
  62. package/lib/commonjs/ui/oauth/oauthPopupMessages.js +141 -0
  63. package/lib/commonjs/ui/oauth/oauthPopupMessages.js.map +1 -0
  64. package/lib/commonjs/ui/oauth/types.js +2 -0
  65. package/lib/commonjs/ui/oauth/types.js.map +1 -0
  66. package/lib/commonjs/ui/server.js +2 -5
  67. package/lib/commonjs/ui/server.js.map +1 -1
  68. package/lib/commonjs/ui/session/authStore.js +6 -75
  69. package/lib/commonjs/ui/session/authStore.js.map +1 -1
  70. package/lib/commonjs/ui/session/createSessionClient.js +11 -3
  71. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  72. package/lib/commonjs/ui/session/identityBinding.js +95 -0
  73. package/lib/commonjs/ui/session/identityBinding.js.map +1 -0
  74. package/lib/commonjs/ui/session/index.js +13 -0
  75. package/lib/commonjs/ui/session/index.js.map +1 -1
  76. package/lib/commonjs/ui/session/nativeSecureStorage.js +90 -0
  77. package/lib/commonjs/ui/session/nativeSecureStorage.js.map +1 -0
  78. package/lib/commonjs/ui/session/tokenTransport.js +11 -1
  79. package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
  80. package/lib/commonjs/ui/utils/crossOriginRestore.js +5 -4
  81. package/lib/commonjs/ui/utils/crossOriginRestore.js.map +1 -1
  82. package/lib/commonjs/ui/utils/deliveryPlatform.js +58 -0
  83. package/lib/commonjs/ui/utils/deliveryPlatform.js.map +1 -0
  84. package/lib/commonjs/ui/utils/oauthReturn.js +72 -46
  85. package/lib/commonjs/ui/utils/oauthReturn.js.map +1 -1
  86. package/lib/module/index.js +9 -4
  87. package/lib/module/index.js.map +1 -1
  88. package/lib/module/ui/boot/runProviderColdBoot.js +51 -9
  89. package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
  90. package/lib/module/ui/client.js +0 -1
  91. package/lib/module/ui/client.js.map +1 -1
  92. package/lib/module/ui/components/OxyAccountDialogScreen.js +27 -4
  93. package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
  94. package/lib/module/ui/components/OxyAuthChooser.js +76 -1128
  95. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  96. package/lib/module/ui/components/OxyConsentScreen.js +50 -12
  97. package/lib/module/ui/components/OxyConsentScreen.js.map +1 -1
  98. package/lib/module/ui/components/OxyProvider.js +4 -4
  99. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  100. package/lib/module/ui/components/OxySignInButton.js +76 -42
  101. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  102. package/lib/module/ui/components/authChooser/AccountsMenuView.js +523 -0
  103. package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -0
  104. package/lib/module/ui/components/authChooser/SignInEntryView.js +85 -0
  105. package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -0
  106. package/lib/module/ui/components/authChooser/SignInRequestView.js +139 -0
  107. package/lib/module/ui/components/authChooser/SignInRequestView.js.map +1 -0
  108. package/lib/module/ui/components/authChooser/SignUpView.js +184 -0
  109. package/lib/module/ui/components/authChooser/SignUpView.js.map +1 -0
  110. package/lib/module/ui/components/authChooser/TroubleDisclosure.js +59 -0
  111. package/lib/module/ui/components/authChooser/TroubleDisclosure.js.map +1 -0
  112. package/lib/module/ui/components/authChooser/primitives.js +163 -0
  113. package/lib/module/ui/components/authChooser/primitives.js.map +1 -0
  114. package/lib/module/ui/components/authChooser/requestSurfaces.js +139 -0
  115. package/lib/module/ui/components/authChooser/requestSurfaces.js.map +1 -0
  116. package/lib/module/ui/components/authChooser/signInProgress.js +50 -0
  117. package/lib/module/ui/components/authChooser/signInProgress.js.map +1 -0
  118. package/lib/module/ui/components/{oxyAuthChooserStyles.js → authChooser/styles.js} +19 -1
  119. package/lib/module/ui/components/authChooser/styles.js.map +1 -0
  120. package/lib/module/ui/components/authChooser/types.js +55 -0
  121. package/lib/module/ui/components/authChooser/types.js.map +1 -0
  122. package/lib/module/ui/components/authChooser/useUsernameAvailability.js +46 -0
  123. package/lib/module/ui/components/authChooser/useUsernameAvailability.js.map +1 -0
  124. package/lib/module/ui/context/OxyContext.js +208 -33
  125. package/lib/module/ui/context/OxyContext.js.map +1 -1
  126. package/lib/module/ui/context/commitSessionFlow.js +45 -0
  127. package/lib/module/ui/context/commitSessionFlow.js.map +1 -1
  128. package/lib/module/ui/context/hooks/useAuthOperations.js +15 -2
  129. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  130. package/lib/module/ui/context/useOxyAccountGraph.js +13 -5
  131. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  132. package/lib/module/ui/hooks/useSwitchableAccounts.js +6 -1
  133. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
  134. package/lib/module/ui/index.js +0 -1
  135. package/lib/module/ui/index.js.map +1 -1
  136. package/lib/module/ui/oauth/browserAuthTransport.js +167 -0
  137. package/lib/module/ui/oauth/browserAuthTransport.js.map +1 -0
  138. package/lib/module/ui/oauth/completeOAuthCode.js +75 -0
  139. package/lib/module/ui/oauth/completeOAuthCode.js.map +1 -0
  140. package/lib/module/ui/oauth/legacyRedirectLanes.js +44 -0
  141. package/lib/module/ui/oauth/legacyRedirectLanes.js.map +1 -0
  142. package/lib/module/ui/oauth/oauthHandshake.js +50 -0
  143. package/lib/module/ui/oauth/oauthHandshake.js.map +1 -0
  144. package/lib/module/ui/oauth/oauthPopup.js +268 -0
  145. package/lib/module/ui/oauth/oauthPopup.js.map +1 -0
  146. package/lib/module/ui/oauth/oauthPopupMessages.js +135 -0
  147. package/lib/module/ui/oauth/oauthPopupMessages.js.map +1 -0
  148. package/lib/module/ui/oauth/types.js +2 -0
  149. package/lib/module/ui/oauth/types.js.map +1 -0
  150. package/lib/module/ui/server.js +0 -2
  151. package/lib/module/ui/server.js.map +1 -1
  152. package/lib/module/ui/session/authStore.js +5 -75
  153. package/lib/module/ui/session/authStore.js.map +1 -1
  154. package/lib/module/ui/session/createSessionClient.js +11 -3
  155. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  156. package/lib/module/ui/session/identityBinding.js +89 -0
  157. package/lib/module/ui/session/identityBinding.js.map +1 -0
  158. package/lib/module/ui/session/index.js +5 -2
  159. package/lib/module/ui/session/index.js.map +1 -1
  160. package/lib/module/ui/session/nativeSecureStorage.js +88 -0
  161. package/lib/module/ui/session/nativeSecureStorage.js.map +1 -0
  162. package/lib/module/ui/session/tokenTransport.js +11 -1
  163. package/lib/module/ui/session/tokenTransport.js.map +1 -1
  164. package/lib/module/ui/utils/crossOriginRestore.js +6 -5
  165. package/lib/module/ui/utils/crossOriginRestore.js.map +1 -1
  166. package/lib/module/ui/utils/deliveryPlatform.js +55 -0
  167. package/lib/module/ui/utils/deliveryPlatform.js.map +1 -0
  168. package/lib/module/ui/utils/oauthReturn.js +73 -47
  169. package/lib/module/ui/utils/oauthReturn.js.map +1 -1
  170. package/lib/typescript/commonjs/index.d.ts +4 -1
  171. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  172. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +34 -2
  173. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  174. package/lib/typescript/commonjs/ui/client.d.ts +0 -1
  175. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  176. package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  177. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +21 -29
  178. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts.map +1 -1
  180. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  181. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts +17 -12
  182. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  183. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts +38 -0
  184. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -0
  185. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +32 -0
  186. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -0
  187. package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts +34 -0
  188. package/lib/typescript/commonjs/ui/components/authChooser/SignInRequestView.d.ts.map +1 -0
  189. package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts +34 -0
  190. package/lib/typescript/commonjs/ui/components/authChooser/SignUpView.d.ts.map +1 -0
  191. package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts +43 -0
  192. package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -0
  193. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +55 -0
  194. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -0
  195. package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts +54 -0
  196. package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts.map +1 -0
  197. package/lib/typescript/commonjs/ui/components/authChooser/signInProgress.d.ts +26 -0
  198. package/lib/typescript/commonjs/ui/components/authChooser/signInProgress.d.ts.map +1 -0
  199. package/lib/typescript/{module/ui/components/oxyAuthChooserStyles.d.ts → commonjs/ui/components/authChooser/styles.d.ts} +19 -1
  200. package/lib/typescript/commonjs/ui/components/authChooser/styles.d.ts.map +1 -0
  201. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +85 -0
  202. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -0
  203. package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts +16 -0
  204. package/lib/typescript/commonjs/ui/components/authChooser/useUsernameAvailability.d.ts.map +1 -0
  205. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  206. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts +40 -0
  207. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts.map +1 -1
  208. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +11 -2
  209. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  210. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +44 -1
  211. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  212. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +7 -1
  213. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  214. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  215. package/lib/typescript/commonjs/ui/index.d.ts +0 -1
  216. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  217. package/lib/typescript/commonjs/ui/oauth/browserAuthTransport.d.ts +56 -0
  218. package/lib/typescript/commonjs/ui/oauth/browserAuthTransport.d.ts.map +1 -0
  219. package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts +49 -0
  220. package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts.map +1 -0
  221. package/lib/typescript/commonjs/ui/oauth/legacyRedirectLanes.d.ts +40 -0
  222. package/lib/typescript/commonjs/ui/oauth/legacyRedirectLanes.d.ts.map +1 -0
  223. package/lib/typescript/commonjs/ui/oauth/oauthHandshake.d.ts +50 -0
  224. package/lib/typescript/commonjs/ui/oauth/oauthHandshake.d.ts.map +1 -0
  225. package/lib/typescript/commonjs/ui/oauth/oauthPopup.d.ts +83 -0
  226. package/lib/typescript/commonjs/ui/oauth/oauthPopup.d.ts.map +1 -0
  227. package/lib/typescript/commonjs/ui/oauth/oauthPopupMessages.d.ts +67 -0
  228. package/lib/typescript/commonjs/ui/oauth/oauthPopupMessages.d.ts.map +1 -0
  229. package/lib/typescript/commonjs/ui/oauth/types.d.ts +176 -0
  230. package/lib/typescript/commonjs/ui/oauth/types.d.ts.map +1 -0
  231. package/lib/typescript/commonjs/ui/server.d.ts +0 -2
  232. package/lib/typescript/commonjs/ui/server.d.ts.map +1 -1
  233. package/lib/typescript/commonjs/ui/session/authStore.d.ts +3 -7
  234. package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
  235. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +8 -1
  236. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  237. package/lib/typescript/commonjs/ui/session/identityBinding.d.ts +66 -0
  238. package/lib/typescript/commonjs/ui/session/identityBinding.d.ts.map +1 -0
  239. package/lib/typescript/commonjs/ui/session/index.d.ts +6 -2
  240. package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -1
  241. package/lib/typescript/commonjs/ui/session/nativeSecureStorage.d.ts +25 -0
  242. package/lib/typescript/commonjs/ui/session/nativeSecureStorage.d.ts.map +1 -0
  243. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +8 -1
  244. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
  245. package/lib/typescript/commonjs/ui/types/navigation.d.ts +44 -0
  246. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  247. package/lib/typescript/commonjs/ui/utils/crossOriginRestore.d.ts.map +1 -1
  248. package/lib/typescript/commonjs/ui/utils/deliveryPlatform.d.ts +33 -0
  249. package/lib/typescript/commonjs/ui/utils/deliveryPlatform.d.ts.map +1 -0
  250. package/lib/typescript/commonjs/ui/utils/oauthReturn.d.ts +30 -16
  251. package/lib/typescript/commonjs/ui/utils/oauthReturn.d.ts.map +1 -1
  252. package/lib/typescript/module/index.d.ts +4 -1
  253. package/lib/typescript/module/index.d.ts.map +1 -1
  254. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +34 -2
  255. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  256. package/lib/typescript/module/ui/client.d.ts +0 -1
  257. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  258. package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  259. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +21 -29
  260. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  261. package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts.map +1 -1
  262. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  263. package/lib/typescript/module/ui/components/OxySignInButton.d.ts +17 -12
  264. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  265. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts +38 -0
  266. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -0
  267. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +32 -0
  268. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -0
  269. package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts +34 -0
  270. package/lib/typescript/module/ui/components/authChooser/SignInRequestView.d.ts.map +1 -0
  271. package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts +34 -0
  272. package/lib/typescript/module/ui/components/authChooser/SignUpView.d.ts.map +1 -0
  273. package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts +43 -0
  274. package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -0
  275. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +55 -0
  276. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -0
  277. package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts +54 -0
  278. package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts.map +1 -0
  279. package/lib/typescript/module/ui/components/authChooser/signInProgress.d.ts +26 -0
  280. package/lib/typescript/module/ui/components/authChooser/signInProgress.d.ts.map +1 -0
  281. package/lib/typescript/{commonjs/ui/components/oxyAuthChooserStyles.d.ts → module/ui/components/authChooser/styles.d.ts} +19 -1
  282. package/lib/typescript/module/ui/components/authChooser/styles.d.ts.map +1 -0
  283. package/lib/typescript/module/ui/components/authChooser/types.d.ts +85 -0
  284. package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -0
  285. package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts +16 -0
  286. package/lib/typescript/module/ui/components/authChooser/useUsernameAvailability.d.ts.map +1 -0
  287. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  288. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts +40 -0
  289. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts.map +1 -1
  290. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +11 -2
  291. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  292. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +44 -1
  293. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  294. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +7 -1
  295. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  296. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
  297. package/lib/typescript/module/ui/index.d.ts +0 -1
  298. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  299. package/lib/typescript/module/ui/oauth/browserAuthTransport.d.ts +56 -0
  300. package/lib/typescript/module/ui/oauth/browserAuthTransport.d.ts.map +1 -0
  301. package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts +49 -0
  302. package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts.map +1 -0
  303. package/lib/typescript/module/ui/oauth/legacyRedirectLanes.d.ts +40 -0
  304. package/lib/typescript/module/ui/oauth/legacyRedirectLanes.d.ts.map +1 -0
  305. package/lib/typescript/module/ui/oauth/oauthHandshake.d.ts +50 -0
  306. package/lib/typescript/module/ui/oauth/oauthHandshake.d.ts.map +1 -0
  307. package/lib/typescript/module/ui/oauth/oauthPopup.d.ts +83 -0
  308. package/lib/typescript/module/ui/oauth/oauthPopup.d.ts.map +1 -0
  309. package/lib/typescript/module/ui/oauth/oauthPopupMessages.d.ts +67 -0
  310. package/lib/typescript/module/ui/oauth/oauthPopupMessages.d.ts.map +1 -0
  311. package/lib/typescript/module/ui/oauth/types.d.ts +176 -0
  312. package/lib/typescript/module/ui/oauth/types.d.ts.map +1 -0
  313. package/lib/typescript/module/ui/server.d.ts +0 -2
  314. package/lib/typescript/module/ui/server.d.ts.map +1 -1
  315. package/lib/typescript/module/ui/session/authStore.d.ts +3 -7
  316. package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
  317. package/lib/typescript/module/ui/session/createSessionClient.d.ts +8 -1
  318. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  319. package/lib/typescript/module/ui/session/identityBinding.d.ts +66 -0
  320. package/lib/typescript/module/ui/session/identityBinding.d.ts.map +1 -0
  321. package/lib/typescript/module/ui/session/index.d.ts +6 -2
  322. package/lib/typescript/module/ui/session/index.d.ts.map +1 -1
  323. package/lib/typescript/module/ui/session/nativeSecureStorage.d.ts +25 -0
  324. package/lib/typescript/module/ui/session/nativeSecureStorage.d.ts.map +1 -0
  325. package/lib/typescript/module/ui/session/tokenTransport.d.ts +8 -1
  326. package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
  327. package/lib/typescript/module/ui/types/navigation.d.ts +44 -0
  328. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  329. package/lib/typescript/module/ui/utils/crossOriginRestore.d.ts.map +1 -1
  330. package/lib/typescript/module/ui/utils/deliveryPlatform.d.ts +33 -0
  331. package/lib/typescript/module/ui/utils/deliveryPlatform.d.ts.map +1 -0
  332. package/lib/typescript/module/ui/utils/oauthReturn.d.ts +30 -16
  333. package/lib/typescript/module/ui/utils/oauthReturn.d.ts.map +1 -1
  334. package/package.json +6 -8
  335. package/src/index.ts +19 -5
  336. package/src/ui/boot/__tests__/coldBootWebAuthMode.test.ts +281 -0
  337. package/src/ui/boot/runProviderColdBoot.ts +83 -13
  338. package/src/ui/client.ts +0 -1
  339. package/src/ui/components/OxyAccountDialogScreen.tsx +33 -4
  340. package/src/ui/components/OxyAuthChooser.tsx +100 -1230
  341. package/src/ui/components/OxyConsentScreen.tsx +56 -13
  342. package/src/ui/components/OxyProvider.tsx +4 -4
  343. package/src/ui/components/OxySignInButton.tsx +119 -78
  344. package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +27 -0
  345. package/src/ui/components/authChooser/AccountsMenuView.tsx +569 -0
  346. package/src/ui/components/authChooser/SignInEntryView.tsx +115 -0
  347. package/src/ui/components/authChooser/SignInRequestView.tsx +163 -0
  348. package/src/ui/components/authChooser/SignUpView.tsx +174 -0
  349. package/src/ui/components/authChooser/TroubleDisclosure.tsx +81 -0
  350. package/src/ui/components/authChooser/primitives.tsx +144 -0
  351. package/src/ui/components/authChooser/requestSurfaces.tsx +116 -0
  352. package/src/ui/components/authChooser/signInProgress.ts +54 -0
  353. package/src/ui/components/{oxyAuthChooserStyles.ts → authChooser/styles.ts} +18 -0
  354. package/src/ui/components/authChooser/types.ts +101 -0
  355. package/src/ui/components/authChooser/useUsernameAvailability.ts +60 -0
  356. package/src/ui/context/OxyContext.tsx +226 -24
  357. package/src/ui/context/__tests__/commitSessionFlow.test.ts +102 -0
  358. package/src/ui/context/commitSessionFlow.ts +64 -0
  359. package/src/ui/context/hooks/useAuthOperations.ts +26 -1
  360. package/src/ui/context/oxyContextTypes.ts +45 -1
  361. package/src/ui/context/useOxyAccountGraph.ts +19 -5
  362. package/src/ui/hooks/useSwitchableAccounts.ts +12 -1
  363. package/src/ui/index.ts +0 -1
  364. package/src/ui/oauth/__tests__/browserAuthTransport.test.ts +335 -0
  365. package/src/ui/oauth/__tests__/completeOAuthCode.test.ts +142 -0
  366. package/src/ui/oauth/__tests__/oauthPopup.test.ts +352 -0
  367. package/src/ui/oauth/__tests__/oauthPopupMessages.test.ts +189 -0
  368. package/src/ui/oauth/__tests__/sharedCompletionPath.test.ts +174 -0
  369. package/src/ui/oauth/browserAuthTransport.ts +193 -0
  370. package/src/ui/oauth/completeOAuthCode.ts +93 -0
  371. package/src/ui/oauth/legacyRedirectLanes.ts +43 -0
  372. package/src/ui/oauth/oauthHandshake.ts +74 -0
  373. package/src/ui/oauth/oauthPopup.ts +277 -0
  374. package/src/ui/oauth/oauthPopupMessages.ts +136 -0
  375. package/src/ui/oauth/types.ts +150 -0
  376. package/src/ui/server.ts +0 -2
  377. package/src/ui/session/__tests__/createSessionClient.test.ts +26 -0
  378. package/src/ui/session/__tests__/tokenTransport.test.ts +34 -0
  379. package/src/ui/session/authStore.ts +5 -89
  380. package/src/ui/session/createSessionClient.ts +15 -2
  381. package/src/ui/session/identityBinding.ts +116 -0
  382. package/src/ui/session/index.ts +6 -2
  383. package/src/ui/session/nativeSecureStorage.ts +98 -0
  384. package/src/ui/session/tokenTransport.ts +14 -1
  385. package/src/ui/types/navigation.ts +44 -1
  386. package/src/ui/utils/__tests__/crossOriginRestore.test.ts +17 -0
  387. package/src/ui/utils/crossOriginRestore.ts +9 -13
  388. package/src/ui/utils/deliveryPlatform.ts +65 -0
  389. package/src/ui/utils/oauthReturn.ts +79 -65
  390. package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
  391. package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
  392. package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
  393. package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
  394. package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
  395. package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
  396. package/lib/commonjs/assets/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
  397. package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
  398. package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
  399. package/lib/commonjs/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
  400. package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
  401. package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
  402. package/lib/commonjs/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
  403. package/lib/commonjs/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
  404. package/lib/commonjs/ui/components/FontLoader.js +0 -35
  405. package/lib/commonjs/ui/components/FontLoader.js.map +0 -1
  406. package/lib/commonjs/ui/components/FontLoader.native.js +0 -88
  407. package/lib/commonjs/ui/components/FontLoader.native.js.map +0 -1
  408. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +0 -1
  409. package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
  410. package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
  411. package/lib/module/assets/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
  412. package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
  413. package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
  414. package/lib/module/assets/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
  415. package/lib/module/assets/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
  416. package/lib/module/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
  417. package/lib/module/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
  418. package/lib/module/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
  419. package/lib/module/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
  420. package/lib/module/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
  421. package/lib/module/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
  422. package/lib/module/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
  423. package/lib/module/ui/components/FontLoader.js +0 -29
  424. package/lib/module/ui/components/FontLoader.js.map +0 -1
  425. package/lib/module/ui/components/FontLoader.native.js +0 -83
  426. package/lib/module/ui/components/FontLoader.native.js.map +0 -1
  427. package/lib/module/ui/components/oxyAuthChooserStyles.js.map +0 -1
  428. package/lib/typescript/commonjs/ui/components/FontLoader.d.ts +0 -24
  429. package/lib/typescript/commonjs/ui/components/FontLoader.d.ts.map +0 -1
  430. package/lib/typescript/commonjs/ui/components/FontLoader.native.d.ts +0 -13
  431. package/lib/typescript/commonjs/ui/components/FontLoader.native.d.ts.map +0 -1
  432. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +0 -1
  433. package/lib/typescript/module/ui/components/FontLoader.d.ts +0 -24
  434. package/lib/typescript/module/ui/components/FontLoader.d.ts.map +0 -1
  435. package/lib/typescript/module/ui/components/FontLoader.native.d.ts +0 -13
  436. package/lib/typescript/module/ui/components/FontLoader.native.d.ts.map +0 -1
  437. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +0 -1
  438. package/src/assets/fonts/Inter/Inter_18pt-Black.ttf +0 -0
  439. package/src/assets/fonts/Inter/Inter_18pt-Bold.ttf +0 -0
  440. package/src/assets/fonts/Inter/Inter_18pt-ExtraBold.ttf +0 -0
  441. package/src/assets/fonts/Inter/Inter_18pt-Light.ttf +0 -0
  442. package/src/assets/fonts/Inter/Inter_18pt-Medium.ttf +0 -0
  443. package/src/assets/fonts/Inter/Inter_18pt-Regular.ttf +0 -0
  444. package/src/assets/fonts/Inter/Inter_18pt-SemiBold.ttf +0 -0
  445. package/src/ui/components/FontLoader.native.tsx +0 -88
  446. package/src/ui/components/FontLoader.tsx +0 -27
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Popup lifecycle for the web OAuth transport.
3
+ *
4
+ * Owns the window itself — opening it, navigating it, watching it, and tearing
5
+ * every listener and timer down — and nothing else: message validation lives in
6
+ * `oauthPopupMessages.ts`, and the code exchange in `completeOAuthCode.ts`.
7
+ *
8
+ * Three browser realities shape this module:
9
+ *
10
+ * - **Gesture attribution.** `window.open` only succeeds while the user's click
11
+ * is still being handled. Anything awaited first (resolving the application,
12
+ * generating PKCE) loses it and the popup is silently blocked. So the window
13
+ * is opened EMPTY up front and navigated once the authorize URL exists —
14
+ * the same shape as `passkeyHubPopup.ts`.
15
+ * - **No close event.** A cross-origin popup fires nothing when the user
16
+ * dismisses it; polling `closed` is the only mechanism (as in core's
17
+ * `AccountDialogController.watchPopup`).
18
+ * - **No cross-origin reads.** The popup's URL, title, and document are all
19
+ * opaque. The authorization result arrives ONLY as a `postMessage`.
20
+ */
21
+
22
+ import { canonicalizeOAuthRedirectUri, logger } from '@oxyhq/core';
23
+ import { readOAuthPopupMessage } from './oauthPopupMessages';
24
+ import type { OAuthPopupHandle, OAuthPopupOutcome } from './types';
25
+
26
+ /**
27
+ * Window name for the sign-in popup. Naming the target means a second press
28
+ * REUSES the same window instead of stacking a new one behind the first.
29
+ */
30
+ export const OXY_OAUTH_POPUP_WINDOW_NAME = 'oxy-oauth';
31
+
32
+ /** Chrome-free window roughly sized for the IdP's sign-in / QR surface. */
33
+ const POPUP_FEATURES = 'popup=yes,width=460,height=700,menubar=no,toolbar=no,location=no,status=no';
34
+
35
+ /**
36
+ * How long a popup sign-in may stay unanswered (ms).
37
+ *
38
+ * Generous on purpose: the IdP surface can involve scanning a QR and approving
39
+ * on a phone, so this tracks the authorize handle's own ~5-minute lifetime
40
+ * rather than a UI-scale timeout. It is a backstop against a wedged popup, not
41
+ * a pace-setter — a user who gives up closes the window and gets the faster
42
+ * `closed` outcome.
43
+ */
44
+ export const DEFAULT_OAUTH_POPUP_TIMEOUT_MS = 300_000;
45
+
46
+ /** How often the popup's `closed` flag is sampled (ms). */
47
+ const CLOSE_POLL_INTERVAL_MS = 1000;
48
+
49
+ /**
50
+ * Grace period (ms) after `closed` flips before reporting cancellation.
51
+ *
52
+ * The IdP posts its result and THEN closes itself. Both land as separate tasks,
53
+ * so a poll tick that happens to fall between them would otherwise turn a
54
+ * successful sign-in into a "user cancelled". One short grace window lets the
55
+ * already-queued message win.
56
+ */
57
+ const CLOSE_GRACE_MS = 400;
58
+
59
+ /** The `message` half of the DOM host, absent on native and during SSR. */
60
+ interface MessageEventHost {
61
+ addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
62
+ removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
63
+ }
64
+
65
+ /** The `open` half of the DOM host, absent on native and during SSR. */
66
+ interface PopupOpenerHost {
67
+ open?: (url: string, target: string, features: string) => OAuthPopupHandle | null;
68
+ }
69
+
70
+ function messageEventHost(): MessageEventHost | null {
71
+ const win = (globalThis as { window?: Partial<MessageEventHost> }).window;
72
+ if (!win || typeof win.addEventListener !== 'function' || typeof win.removeEventListener !== 'function') {
73
+ return null;
74
+ }
75
+ return win as MessageEventHost;
76
+ }
77
+
78
+ /**
79
+ * Open a new, EMPTY sign-in popup.
80
+ *
81
+ * MUST be called SYNCHRONOUSLY from a user-gesture handler, before any `await`.
82
+ * Returns `null` when the browser blocked it, when `window.open` threw, and on
83
+ * hosts with no DOM (native / SSR) — never throws, because "no popup" is an
84
+ * ordinary outcome the caller answers by falling back to a redirect.
85
+ */
86
+ export function openOAuthPopup(): OAuthPopupHandle | null {
87
+ const win = (globalThis as { window?: PopupOpenerHost }).window;
88
+ if (!win || typeof win.open !== 'function') return null;
89
+ try {
90
+ return win.open('', OXY_OAUTH_POPUP_WINDOW_NAME, POPUP_FEATURES) ?? null;
91
+ } catch (error) {
92
+ logger.warn('Could not open the OAuth sign-in popup', { component: 'oauthPopup' }, error);
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Point an already-open popup at the authorize URL. Returns `false` when the
99
+ * assignment threw (a window closed between opening and navigating), so the
100
+ * caller can fall back to a redirect instead of waiting on a dead window.
101
+ */
102
+ export function navigateOAuthPopup(popup: OAuthPopupHandle, url: string): boolean {
103
+ try {
104
+ popup.location.href = url;
105
+ return true;
106
+ } catch (error) {
107
+ logger.warn('Could not navigate the OAuth sign-in popup', { component: 'oauthPopup' }, error);
108
+ return false;
109
+ }
110
+ }
111
+
112
+ /** Close the popup if it is still open. Idempotent, null-safe, never throws. */
113
+ export function closeOAuthPopup(popup: OAuthPopupHandle | null): void {
114
+ if (!popup || popup.closed) return;
115
+ try {
116
+ popup.close();
117
+ } catch (error) {
118
+ logger.debug('OAuth popup close failed', { component: 'oauthPopup' }, error);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * When the IdP falls back to a top-level redirect inside the popup (no usable
124
+ * `window.opener`), a same-origin `redirect_uri` is readable from the opener.
125
+ * Cross-origin redirects remain opaque and are not handled here.
126
+ */
127
+ function readSameOriginRedirectOutcome(
128
+ popup: OAuthPopupHandle,
129
+ redirectUri: string,
130
+ expectedState: string,
131
+ ): OAuthPopupOutcome | null {
132
+ try {
133
+ const href = popup.location.href;
134
+ if (!href) return null;
135
+
136
+ const popupUrl = new URL(href);
137
+ const expected = new URL(canonicalizeOAuthRedirectUri(redirectUri));
138
+ if (popupUrl.origin !== expected.origin) return null;
139
+ if (popupUrl.pathname !== expected.pathname) return null;
140
+
141
+ const oauthError = popupUrl.searchParams.get('error');
142
+ if (oauthError) {
143
+ const errorDescription = popupUrl.searchParams.get('error_description') ?? undefined;
144
+ return {
145
+ kind: 'oauth-error',
146
+ error: oauthError,
147
+ ...(errorDescription ? { errorDescription } : {}),
148
+ };
149
+ }
150
+
151
+ const code = popupUrl.searchParams.get('code');
152
+ if (!code) return null;
153
+
154
+ const state = popupUrl.searchParams.get('state');
155
+ if (!state || state !== expectedState) return { kind: 'state-mismatch' };
156
+ return { kind: 'code', code, state };
157
+ } catch {
158
+ // Cross-origin navigation — the popup URL is opaque to the opener.
159
+ return null;
160
+ }
161
+ }
162
+
163
+ /** Inputs for {@link awaitOAuthPopupResult}. */
164
+ export interface AwaitOAuthPopupOptions {
165
+ /** The exact window reference returned by {@link openOAuthPopup}. */
166
+ popup: OAuthPopupHandle;
167
+ /** Exact origin the IdP will post from (`new URL(authorizeUrl).origin`). */
168
+ expectedOrigin: string;
169
+ /** The CSRF `state` sent on the authorize request. */
170
+ expectedState: string;
171
+ /**
172
+ * EXACT registered redirect URI for this attempt. When the IdP cannot
173
+ * `postMessage` (no usable opener) it falls back to redirecting the popup;
174
+ * if that lands on the same origin, the result is read from the popup URL.
175
+ */
176
+ redirectUri: string;
177
+ /** @default DEFAULT_OAUTH_POPUP_TIMEOUT_MS */
178
+ timeoutMs?: number;
179
+ }
180
+
181
+ /**
182
+ * Wait for the popup to deliver an authorization result.
183
+ *
184
+ * Settles EXACTLY once, with a typed outcome for every path — code, IdP error,
185
+ * state mismatch, user-closed, timeout — and removes its listener, its close
186
+ * poll, and both timers before resolving, whatever the outcome. Messages that
187
+ * arrive after settling reach nothing, so a duplicate delivery is inert.
188
+ *
189
+ * Never rejects: closing the window and running out of time are expected
190
+ * outcomes, not exceptions.
191
+ */
192
+ export function awaitOAuthPopupResult(
193
+ options: AwaitOAuthPopupOptions,
194
+ ): Promise<OAuthPopupOutcome> {
195
+ const {
196
+ popup,
197
+ expectedOrigin,
198
+ expectedState,
199
+ redirectUri,
200
+ timeoutMs = DEFAULT_OAUTH_POPUP_TIMEOUT_MS,
201
+ } = options;
202
+
203
+ const host = messageEventHost();
204
+ if (!host) return Promise.resolve({ kind: 'unsupported' });
205
+
206
+ return new Promise<OAuthPopupOutcome>((resolve) => {
207
+ let settled = false;
208
+ let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
209
+ let closeGraceTimer: ReturnType<typeof setTimeout> | null = null;
210
+ let closePoll: ReturnType<typeof setInterval> | null = null;
211
+
212
+ const onMessage = (event: MessageEvent): void => {
213
+ const verdict = readOAuthPopupMessage(event, { expectedOrigin, expectedState, popup });
214
+ switch (verdict.kind) {
215
+ case 'ignore':
216
+ return;
217
+ case 'code':
218
+ settle({ kind: 'code', code: verdict.code, state: verdict.state });
219
+ return;
220
+ case 'oauth-error':
221
+ settle({
222
+ kind: 'oauth-error',
223
+ error: verdict.error,
224
+ ...(verdict.errorDescription ? { errorDescription: verdict.errorDescription } : {}),
225
+ });
226
+ return;
227
+ case 'state-mismatch':
228
+ settle({ kind: 'state-mismatch' });
229
+ return;
230
+ }
231
+ };
232
+
233
+ const cleanup = (): void => {
234
+ host.removeEventListener('message', onMessage);
235
+ if (timeoutTimer !== null) {
236
+ clearTimeout(timeoutTimer);
237
+ timeoutTimer = null;
238
+ }
239
+ if (closeGraceTimer !== null) {
240
+ clearTimeout(closeGraceTimer);
241
+ closeGraceTimer = null;
242
+ }
243
+ if (closePoll !== null) {
244
+ clearInterval(closePoll);
245
+ closePoll = null;
246
+ }
247
+ };
248
+
249
+ function settle(outcome: OAuthPopupOutcome): void {
250
+ if (settled) return;
251
+ settled = true;
252
+ cleanup();
253
+ resolve(outcome);
254
+ }
255
+
256
+ host.addEventListener('message', onMessage);
257
+
258
+ timeoutTimer = setTimeout(() => settle({ kind: 'timed-out' }), timeoutMs);
259
+
260
+ closePoll = setInterval(() => {
261
+ const redirectOutcome = readSameOriginRedirectOutcome(popup, redirectUri, expectedState);
262
+ if (redirectOutcome) {
263
+ settle(redirectOutcome);
264
+ return;
265
+ }
266
+
267
+ if (!popup.closed) return;
268
+ // Stop sampling immediately: the window is gone and only the in-flight
269
+ // message (if any) can still change the outcome.
270
+ if (closePoll !== null) {
271
+ clearInterval(closePoll);
272
+ closePoll = null;
273
+ }
274
+ closeGraceTimer = setTimeout(() => settle({ kind: 'closed' }), CLOSE_GRACE_MS);
275
+ }, CLOSE_POLL_INTERVAL_MS);
276
+ });
277
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Runtime validation for the popup OAuth `postMessage` channel.
3
+ *
4
+ * Everything arriving on `window`'s `message` event is untrusted: any frame on
5
+ * the page, any other popup, and any browser extension can post to us. This
6
+ * module is the ONLY place that decides whether an incoming event is a genuine
7
+ * authorization response, and it answers with a value — never a thrown error.
8
+ *
9
+ * A message is accepted only when ALL of these hold:
10
+ * 1. `event.origin` is EXACTLY the authorize endpoint's origin,
11
+ * 2. `event.source` is the exact `Window` reference we opened,
12
+ * 3. `event.data` matches one of the two frozen message shapes,
13
+ * 4. the `state` it carries matches the one this RP sent.
14
+ *
15
+ * Failing (1)–(3) is indistinguishable from unrelated page traffic, so the
16
+ * message is IGNORED and the flow keeps waiting. Failing (4) means our own
17
+ * popup answered a different request than the one in flight, which is terminal:
18
+ * it is reported as a mismatch rather than left to hang until the timeout.
19
+ */
20
+
21
+ import { logger } from '@oxyhq/core';
22
+ import type { OAuthPopupHandle, OxyOAuthMessage } from './types';
23
+
24
+ /** `postMessage` discriminator for a successful authorization. */
25
+ export const OXY_OAUTH_CODE_MESSAGE_TYPE = 'oxy:oauth:code';
26
+
27
+ /** `postMessage` discriminator for a typed OAuth failure. */
28
+ export const OXY_OAUTH_ERROR_MESSAGE_TYPE = 'oxy:oauth:error';
29
+
30
+ /** Verdict for one incoming `message` event. */
31
+ export type OAuthPopupMessageVerdict =
32
+ /** Not ours (wrong origin/source) or not a valid message — keep waiting. */
33
+ | { kind: 'ignore' }
34
+ | { kind: 'code'; code: string; state: string }
35
+ | { kind: 'oauth-error'; error: string; errorDescription?: string }
36
+ /** Well-formed, from our popup, but bound to a different authorize request. */
37
+ | { kind: 'state-mismatch' };
38
+
39
+ /** What {@link readOAuthPopupMessage} validates an event against. */
40
+ export interface OAuthPopupMessageContext {
41
+ /** Exact origin of the authorize endpoint (`new URL(authorizeUrl).origin`). */
42
+ expectedOrigin: string;
43
+ /** The CSRF `state` this RP sent on the authorize request. */
44
+ expectedState: string;
45
+ /** The exact popup reference `window.open()` returned. */
46
+ popup: OAuthPopupHandle;
47
+ }
48
+
49
+ function isNonEmptyString(value: unknown): value is string {
50
+ return typeof value === 'string' && value.length > 0;
51
+ }
52
+
53
+ /**
54
+ * Reference equality across two `unknown` values.
55
+ *
56
+ * `event.source` is a `WindowProxy` and {@link OAuthPopupHandle} is a narrow
57
+ * structural type, so comparing them directly is a type error. Widening both to
58
+ * `unknown` keeps the check a plain identity comparison — which is exactly the
59
+ * security property we want — without a cast.
60
+ */
61
+ function isSameWindowReference(a: unknown, b: unknown): boolean {
62
+ return a === b;
63
+ }
64
+
65
+ /**
66
+ * Parse untrusted `event.data` into one of the two frozen message shapes, or
67
+ * `null` when it is anything else (including `null`, primitives, arrays, and
68
+ * objects with missing/wrong-typed fields).
69
+ *
70
+ * Unknown extra properties are tolerated but never read: only the fields below
71
+ * are ever surfaced, so a hostile sender cannot smuggle values past this point.
72
+ */
73
+ export function parseOAuthPopupMessage(data: unknown): OxyOAuthMessage | null {
74
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) return null;
75
+ const record = data as Record<string, unknown>;
76
+
77
+ if (record.type === OXY_OAUTH_CODE_MESSAGE_TYPE) {
78
+ if (!isNonEmptyString(record.code) || !isNonEmptyString(record.state)) return null;
79
+ return { type: OXY_OAUTH_CODE_MESSAGE_TYPE, code: record.code, state: record.state };
80
+ }
81
+
82
+ if (record.type === OXY_OAUTH_ERROR_MESSAGE_TYPE) {
83
+ if (!isNonEmptyString(record.error)) return null;
84
+ return {
85
+ type: OXY_OAUTH_ERROR_MESSAGE_TYPE,
86
+ error: record.error,
87
+ ...(isNonEmptyString(record.errorDescription)
88
+ ? { errorDescription: record.errorDescription }
89
+ : {}),
90
+ ...(isNonEmptyString(record.state) ? { state: record.state } : {}),
91
+ };
92
+ }
93
+
94
+ return null;
95
+ }
96
+
97
+ /**
98
+ * Classify one `message` event against the in-flight authorize request.
99
+ *
100
+ * Never throws. See the module header for the acceptance rules.
101
+ */
102
+ export function readOAuthPopupMessage(
103
+ event: MessageEvent,
104
+ context: OAuthPopupMessageContext,
105
+ ): OAuthPopupMessageVerdict {
106
+ if (event.origin !== context.expectedOrigin) return { kind: 'ignore' };
107
+ if (!isSameWindowReference(event.source, context.popup)) return { kind: 'ignore' };
108
+
109
+ const message = parseOAuthPopupMessage(event.data);
110
+ if (!message) {
111
+ // Origin and source matched, so this came from the IdP window we opened —
112
+ // a shape we cannot read is worth a breadcrumb, unlike ordinary page noise.
113
+ logger.debug(
114
+ 'Ignored an unrecognised message from the OAuth popup',
115
+ { component: 'oauthPopupMessages' },
116
+ );
117
+ return { kind: 'ignore' };
118
+ }
119
+
120
+ if (message.type === OXY_OAUTH_CODE_MESSAGE_TYPE) {
121
+ if (message.state !== context.expectedState) return { kind: 'state-mismatch' };
122
+ return { kind: 'code', code: message.code, state: message.state };
123
+ }
124
+
125
+ // An error MAY carry `state` (the IdP omits it when the request never got far
126
+ // enough to bind one). When present it must match: a foreign error must not be
127
+ // able to end an unrelated in-flight sign-in.
128
+ if (message.state !== undefined && message.state !== context.expectedState) {
129
+ return { kind: 'state-mismatch' };
130
+ }
131
+ return {
132
+ kind: 'oauth-error',
133
+ error: message.error,
134
+ ...(message.errorDescription ? { errorDescription: message.errorDescription } : {}),
135
+ };
136
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Typed contracts shared by the browser "Sign in with Oxy" OAuth transports.
3
+ *
4
+ * Two transports deliver the same authorization code to the same completion
5
+ * path (see `completeOAuthCode.ts`):
6
+ *
7
+ * - `redirect` — the historical full-page hand-off to `auth.oxy.so/authorize`.
8
+ * The RP tab navigates away and comes back to its registered `redirect_uri`
9
+ * with `?code=…&state=…`; cold boot finishes the exchange.
10
+ * - `popup` — a small `auth.oxy.so` window opened from the user's click. The RP
11
+ * stays mounted with its route and unsaved state intact; the IdP delivers the
12
+ * result to `window.opener` via `postMessage` and closes itself.
13
+ *
14
+ * Only the authorization code, the CSRF `state`, and a typed OAuth error ever
15
+ * cross the `postMessage` channel. Access tokens, device secrets, and the PKCE
16
+ * `code_verifier` NEVER do — the opener keeps the verifier and performs the code
17
+ * exchange itself.
18
+ */
19
+
20
+ /** How a web relying party hands the user to the IdP for third-party sign-in. */
21
+ export type WebAuthMode = 'popup' | 'redirect';
22
+
23
+ /**
24
+ * Minimal structural handle over a popup `Window` — exactly what the popup
25
+ * transport needs: navigate it once the authorize URL is built, detect the user
26
+ * closing it (`closed`, polled — there is no cross-origin close event), and
27
+ * close it programmatically on completion. Satisfied directly by the value
28
+ * `window.open()` returns.
29
+ *
30
+ * Deliberately NOT `Window`: the transport must never read cross-origin
31
+ * properties off the popup, and a narrow handle makes that impossible to do by
32
+ * accident (and trivial to fake in tests).
33
+ */
34
+ export interface OAuthPopupHandle {
35
+ readonly closed: boolean;
36
+ close(): void;
37
+ location: { href: string };
38
+ }
39
+
40
+ /**
41
+ * The RP-side OAuth handshake for one authorize request: the CSRF `state` sent
42
+ * to the IdP and the PKCE `code_verifier` replayed on the token exchange.
43
+ *
44
+ * In `redirect` mode it is persisted to `sessionStorage` across the full-page
45
+ * navigation; in `popup` mode it never leaves memory.
46
+ */
47
+ export interface OAuthHandshake {
48
+ /** Opaque CSRF token echoed back by the IdP and validated before any exchange. */
49
+ state: string;
50
+ /** The PKCE secret. Never crosses `postMessage`, a URL, or storage in popup mode. */
51
+ codeVerifier: string;
52
+ }
53
+
54
+ /** Successful authorization delivered by the IdP over `postMessage`. */
55
+ export interface OxyOAuthCodeMessage {
56
+ type: 'oxy:oauth:code';
57
+ code: string;
58
+ state: string;
59
+ }
60
+
61
+ /** Typed OAuth failure delivered by the IdP over `postMessage`. */
62
+ export interface OxyOAuthErrorMessage {
63
+ type: 'oxy:oauth:error';
64
+ error: string;
65
+ errorDescription?: string;
66
+ state?: string;
67
+ }
68
+
69
+ /** The ONLY two message shapes that may cross the popup channel. */
70
+ export type OxyOAuthMessage = OxyOAuthCodeMessage | OxyOAuthErrorMessage;
71
+
72
+ /** Session material handed to the provider's commit funnel after an exchange. */
73
+ export interface OAuthSessionCommitInput {
74
+ sessionId: string;
75
+ accessToken?: string;
76
+ deviceId?: string;
77
+ deviceSecret?: string;
78
+ userId: string;
79
+ expiresAt?: string;
80
+ user?: { id: string; username?: string; avatar?: string };
81
+ }
82
+
83
+ /**
84
+ * Terminal outcome of one popup sign-in attempt. Every expected path — the user
85
+ * closing the window, a timeout, an IdP error — is a value, never a thrown
86
+ * exception.
87
+ */
88
+ export type OAuthPopupOutcome =
89
+ /** The IdP delivered an authorization code whose `state` matched. */
90
+ | { kind: 'code'; code: string; state: string }
91
+ /** The IdP delivered a typed OAuth error (`access_denied`, `login_required`, …). */
92
+ | { kind: 'oauth-error'; error: string; errorDescription?: string }
93
+ /** A well-formed message from our popup carried the wrong `state`. */
94
+ | { kind: 'state-mismatch' }
95
+ /** The user dismissed the popup without completing sign-in. */
96
+ | { kind: 'closed' }
97
+ /** No result arrived within the allotted window. */
98
+ | { kind: 'timed-out' }
99
+ /** No DOM message host (SSR / native) — the popup transport does not apply. */
100
+ | { kind: 'unsupported' };
101
+
102
+ /** Why the shared completion path could not produce a session. */
103
+ export type OAuthCompletionFailure = 'state-mismatch' | 'exchange-failed';
104
+
105
+ /** Result of the ONE completion path both transports run. */
106
+ export type OAuthCompletionResult =
107
+ | { ok: true }
108
+ | { ok: false; reason: OAuthCompletionFailure };
109
+
110
+ /** Why the transport fell back to a full-page redirect. */
111
+ export type WebOAuthRedirectReason =
112
+ /** `webAuthMode: 'redirect'` — the configured transport. */
113
+ | 'redirect-mode'
114
+ /** The browser refused `window.open` (blocker, or no gesture attribution). */
115
+ | 'popup-blocked'
116
+ /** The popup opened but could not be navigated to the authorize URL. */
117
+ | 'popup-navigation-failed';
118
+
119
+ /** Why a web sign-in attempt failed outright. */
120
+ export type WebOAuthFailureReason =
121
+ /** The handshake could not be persisted for the redirect return (no/blocked storage). */
122
+ | 'handshake-storage'
123
+ /** The `state` returned by the IdP did not match the one we sent. */
124
+ | 'state-mismatch'
125
+ /** `POST /auth/oauth/token` or the session commit failed. */
126
+ | 'exchange-failed'
127
+ /** The IdP reported a typed OAuth error. */
128
+ | 'idp-error';
129
+
130
+ /** Why a web sign-in attempt was not applicable at all. */
131
+ export type WebOAuthUnsupportedReason =
132
+ /** Not a browser (native / SSR) — native uses `openAuthorizeUrlNative`. */
133
+ | 'no-browser'
134
+ /** `sessionMode: 'identity'` has no third-party OAuth lane by construction. */
135
+ | 'identity-bound'
136
+ /** No `clientId` is configured on the provider. */
137
+ | 'missing-client-id';
138
+
139
+ /** Terminal, typed outcome of a web third-party sign-in attempt. */
140
+ export type WebOAuthSignInResult =
141
+ /** A session was committed; the RP is authenticated without a reload. */
142
+ | { status: 'signed-in' }
143
+ /** The tab is navigating to the IdP; nothing else will resolve in this document. */
144
+ | { status: 'redirecting'; via: WebOAuthRedirectReason }
145
+ /** The user dismissed the popup. */
146
+ | { status: 'cancelled' }
147
+ /** No result arrived within the allotted window. */
148
+ | { status: 'timed-out' }
149
+ | { status: 'failed'; reason: WebOAuthFailureReason; description?: string }
150
+ | { status: 'unsupported'; reason: WebOAuthUnsupportedReason };
package/src/ui/server.ts CHANGED
@@ -20,8 +20,6 @@ export const LogoIcon = noopComponent;
20
20
  export const LogoText = noopComponent;
21
21
  export const FollowButton = noopComponent;
22
22
  export const OxyPayButton = noopComponent;
23
- export const FontLoader = noopComponent;
24
- export const setupFonts = async (): Promise<boolean> => true;
25
23
 
26
24
  // Context
27
25
  export const useOxy = noopHook;
@@ -83,4 +83,30 @@ describe('createSessionClient', () => {
83
83
  expect(ioMock).toHaveBeenCalledTimes(1);
84
84
  client.stop();
85
85
  });
86
+
87
+ test('threads the pinned-account resolver through: a pinned registerAndActivate only ADDS', async () => {
88
+ // `sessionMode: 'identity'`. The pin reaching `SessionClient` is observable
89
+ // here: a pinned client's own session is minted for the pinned account
90
+ // explicitly, so registering it must never SWITCH the shared device session
91
+ // (which would re-elect the active account under every other app on it).
92
+ const oxy = fakeOxy();
93
+
94
+ const { client } = createSessionClient(oxy as never, undefined, () => 'pinned-account');
95
+ await client.registerAndActivate('pinned-account');
96
+
97
+ const paths = oxy.makeRequest.mock.calls.map((call) => call[1]);
98
+ expect(paths).toContain('/session/device/add');
99
+ expect(paths).not.toContain('/session/device/switch');
100
+ });
101
+
102
+ test('a resolver reporting no pin (account mode) leaves registerAndActivate switching', async () => {
103
+ const oxy = fakeOxy();
104
+
105
+ const { client } = createSessionClient(oxy as never, undefined, () => null);
106
+ await client.registerAndActivate('u1');
107
+
108
+ const paths = oxy.makeRequest.mock.calls.map((call) => call[1]);
109
+ expect(paths).toContain('/session/device/add');
110
+ expect(paths).toContain('/session/device/switch');
111
+ });
86
112
  });
@@ -106,6 +106,40 @@ describe('createTokenTransport', () => {
106
106
  await expect(Promise.all([first, second])).resolves.toEqual([undefined, undefined]);
107
107
  });
108
108
 
109
+ test('never mints while identity-bound, even when the device switched to another account', async () => {
110
+ // `sessionMode: 'identity'`: the bearer belongs to the PINNED account and is
111
+ // minted for it by the cold boot / re-mint lane. Converging it on
112
+ // `state.activeAccountId` — here a DIFFERENT account another app switched the
113
+ // device to — is exactly the drift the mode prevents, so the transport must
114
+ // short-circuit even though the bearer/active-account check disagrees.
115
+ const refreshAccessToken = jest.fn(async () => 'minted-token');
116
+ const oxy = fakeOxy('pinned-account', refreshAccessToken);
117
+ const transport = createTokenTransport(oxy as never, () => 'pinned-account');
118
+
119
+ await transport.ensureActiveToken({
120
+ ...state,
121
+ accounts: [
122
+ { accountId: 'pinned-account', sessionId: 'sess-pinned', authuser: 0 },
123
+ { accountId: 'a1', sessionId: 'sess-a1', authuser: 1 },
124
+ ],
125
+ activeAccountId: 'a1',
126
+ });
127
+
128
+ expect(refreshAccessToken).not.toHaveBeenCalled();
129
+ expect(oxy.getCurrentUserId).not.toHaveBeenCalled();
130
+ });
131
+
132
+ test('a resolver reporting no pin (every account-mode provider) behaves exactly as an absent one', async () => {
133
+ const refreshAccessToken = jest.fn(async () => 'minted-token');
134
+ const oxy = fakeOxy('previous-account', refreshAccessToken);
135
+ const transport = createTokenTransport(oxy as never, () => null);
136
+
137
+ await transport.ensureActiveToken(state);
138
+
139
+ expect(refreshAccessToken).toHaveBeenCalledTimes(1);
140
+ expect(refreshAccessToken).toHaveBeenCalledWith('preflight');
141
+ });
142
+
109
143
  test('treats a throwing bearer-account check as a mismatch and still mints', async () => {
110
144
  const refreshAccessToken = jest.fn(async () => 'minted-token');
111
145
  const oxy = fakeOxy(null, refreshAccessToken);