@oxyhq/core 5.5.0 → 7.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 (196) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/HttpService.js +7 -4
  3. package/dist/cjs/OxyServices.base.js +7 -102
  4. package/dist/cjs/boot/coldBootV2.js +350 -0
  5. package/dist/cjs/boot/deviceBootReturn.js +152 -0
  6. package/dist/cjs/crypto/keyManager.js +95 -0
  7. package/dist/cjs/i18n/locales/en-US.json +13 -1
  8. package/dist/cjs/i18n/locales/es-ES.json +13 -1
  9. package/dist/cjs/i18n/locales/locales/en-US.json +13 -1
  10. package/dist/cjs/i18n/locales/locales/es-ES.json +13 -1
  11. package/dist/cjs/index.js +54 -45
  12. package/dist/cjs/mixins/OxyServices.accounts.js +13 -26
  13. package/dist/cjs/mixins/OxyServices.auth.js +66 -201
  14. package/dist/cjs/mixins/OxyServices.deviceBoot.js +119 -0
  15. package/dist/cjs/mixins/index.js +8 -17
  16. package/dist/cjs/server/index.js +2 -2
  17. package/dist/cjs/session/SessionClient.js +40 -1
  18. package/dist/cjs/session/authStateStore.js +284 -0
  19. package/dist/cjs/session/refresh.js +264 -0
  20. package/dist/cjs/shared/utils/debugUtils.js +3 -3
  21. package/dist/cjs/utils/accountUtils.js +1 -55
  22. package/dist/cjs/utils/authWebUrl.js +10 -39
  23. package/dist/cjs/utils/coldBoot.js +10 -8
  24. package/dist/cjs/utils/platform.js +19 -0
  25. package/dist/cjs/utils/registrableApex.js +49 -0
  26. package/dist/cjs/utils/ssoBounce.js +15 -362
  27. package/dist/cjs/utils/validationUtils.js +57 -0
  28. package/dist/esm/.tsbuildinfo +1 -1
  29. package/dist/esm/HttpService.js +7 -4
  30. package/dist/esm/OxyServices.base.js +7 -102
  31. package/dist/esm/boot/coldBootV2.js +344 -0
  32. package/dist/esm/boot/deviceBootReturn.js +146 -0
  33. package/dist/esm/crypto/keyManager.js +95 -0
  34. package/dist/esm/i18n/locales/en-US.json +13 -1
  35. package/dist/esm/i18n/locales/es-ES.json +13 -1
  36. package/dist/esm/i18n/locales/locales/en-US.json +13 -1
  37. package/dist/esm/i18n/locales/locales/es-ES.json +13 -1
  38. package/dist/esm/index.js +34 -17
  39. package/dist/esm/mixins/OxyServices.accounts.js +13 -26
  40. package/dist/esm/mixins/OxyServices.auth.js +66 -201
  41. package/dist/esm/mixins/OxyServices.deviceBoot.js +116 -0
  42. package/dist/esm/mixins/index.js +8 -17
  43. package/dist/esm/server/index.js +1 -1
  44. package/dist/esm/session/SessionClient.js +40 -1
  45. package/dist/esm/session/authStateStore.js +278 -0
  46. package/dist/esm/session/refresh.js +257 -0
  47. package/dist/esm/shared/utils/debugUtils.js +3 -3
  48. package/dist/esm/utils/accountUtils.js +0 -53
  49. package/dist/esm/utils/authWebUrl.js +9 -37
  50. package/dist/esm/utils/coldBoot.js +10 -8
  51. package/dist/esm/utils/platform.js +18 -0
  52. package/dist/esm/utils/registrableApex.js +46 -0
  53. package/dist/esm/utils/ssoBounce.js +14 -345
  54. package/dist/esm/utils/validationUtils.js +56 -0
  55. package/dist/types/.tsbuildinfo +1 -1
  56. package/dist/types/HttpService.d.ts +14 -1
  57. package/dist/types/OxyServices.base.d.ts +0 -52
  58. package/dist/types/OxyServices.d.ts +0 -25
  59. package/dist/types/boot/coldBootV2.d.ts +76 -0
  60. package/dist/types/boot/deviceBootReturn.d.ts +83 -0
  61. package/dist/types/crypto/keyManager.d.ts +21 -0
  62. package/dist/types/index.d.ts +14 -21
  63. package/dist/types/mixins/OxyServices.accounts.d.ts +24 -29
  64. package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
  65. package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
  66. package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
  67. package/dist/types/mixins/OxyServices.auth.d.ts +35 -77
  68. package/dist/types/mixins/OxyServices.civic.d.ts +0 -2
  69. package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
  70. package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
  71. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +110 -0
  72. package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
  73. package/dist/types/mixins/OxyServices.features.d.ts +0 -2
  74. package/dist/types/mixins/OxyServices.identity.d.ts +0 -2
  75. package/dist/types/mixins/OxyServices.language.d.ts +0 -2
  76. package/dist/types/mixins/OxyServices.links.d.ts +0 -2
  77. package/dist/types/mixins/OxyServices.location.d.ts +0 -2
  78. package/dist/types/mixins/OxyServices.nodes.d.ts +0 -2
  79. package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
  80. package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
  81. package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
  82. package/dist/types/mixins/OxyServices.security.d.ts +0 -2
  83. package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
  84. package/dist/types/mixins/OxyServices.user.d.ts +0 -2
  85. package/dist/types/mixins/OxyServices.utility.d.ts +0 -2
  86. package/dist/types/mixins/index.d.ts +5 -9
  87. package/dist/types/models/interfaces.d.ts +0 -67
  88. package/dist/types/models/session.d.ts +4 -5
  89. package/dist/types/server/index.d.ts +1 -1
  90. package/dist/types/session/SessionClient.d.ts +26 -1
  91. package/dist/types/session/authStateStore.d.ts +119 -0
  92. package/dist/types/session/refresh.d.ts +93 -0
  93. package/dist/types/shared/utils/debugUtils.d.ts +3 -3
  94. package/dist/types/utils/accountUtils.d.ts +2 -17
  95. package/dist/types/utils/authWebUrl.d.ts +9 -35
  96. package/dist/types/utils/coldBoot.d.ts +17 -14
  97. package/dist/types/utils/platform.d.ts +14 -0
  98. package/dist/types/utils/registrableApex.d.ts +31 -0
  99. package/dist/types/utils/ssoBounce.d.ts +14 -280
  100. package/dist/types/utils/validationUtils.d.ts +15 -0
  101. package/package.json +2 -2
  102. package/src/HttpService.ts +20 -4
  103. package/src/OxyServices.base.ts +7 -112
  104. package/src/OxyServices.ts +0 -38
  105. package/src/boot/__tests__/coldBootV2.test.ts +317 -0
  106. package/src/boot/__tests__/deviceBootReturn.test.ts +158 -0
  107. package/src/boot/coldBootV2.ts +426 -0
  108. package/src/boot/deviceBootReturn.ts +195 -0
  109. package/src/crypto/__tests__/sharedDeviceToken.test.ts +24 -0
  110. package/src/crypto/keyManager.ts +101 -0
  111. package/src/i18n/locales/en-US.json +13 -1
  112. package/src/i18n/locales/es-ES.json +13 -1
  113. package/src/index.ts +75 -65
  114. package/src/mixins/OxyServices.accounts.ts +27 -39
  115. package/src/mixins/OxyServices.auth.ts +78 -253
  116. package/src/mixins/OxyServices.deviceBoot.ts +146 -0
  117. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +107 -0
  118. package/src/mixins/__tests__/accounts.test.ts +17 -44
  119. package/src/mixins/__tests__/passwordSignIn.test.ts +91 -0
  120. package/src/mixins/index.ts +10 -23
  121. package/src/models/interfaces.ts +0 -79
  122. package/src/models/session.ts +4 -5
  123. package/src/server/index.ts +1 -1
  124. package/src/session/SessionClient.ts +53 -2
  125. package/src/session/__tests__/SessionClient.additive.test.ts +92 -0
  126. package/src/session/__tests__/SessionClient.rest.test.ts +25 -0
  127. package/src/session/__tests__/SessionClient.state.test.ts +18 -5
  128. package/src/session/__tests__/authStateStore.test.ts +209 -0
  129. package/src/session/__tests__/refresh.test.ts +256 -0
  130. package/src/session/authStateStore.ts +335 -0
  131. package/src/session/refresh.ts +334 -0
  132. package/src/shared/utils/debugUtils.ts +3 -3
  133. package/src/utils/__tests__/authWebUrl.test.ts +5 -40
  134. package/src/utils/__tests__/registrableApex.test.ts +62 -0
  135. package/src/utils/__tests__/validationUtils.test.ts +30 -0
  136. package/src/utils/accountUtils.ts +2 -65
  137. package/src/utils/authWebUrl.ts +9 -39
  138. package/src/utils/coldBoot.ts +17 -14
  139. package/src/utils/platform.ts +21 -0
  140. package/src/utils/registrableApex.ts +45 -0
  141. package/src/utils/ssoBounce.ts +14 -393
  142. package/src/utils/validationUtils.ts +62 -0
  143. package/dist/cjs/AuthManager.js +0 -1110
  144. package/dist/cjs/AuthManagerTypes.js +0 -13
  145. package/dist/cjs/CrossDomainAuth.js +0 -206
  146. package/dist/cjs/mixins/OxyServices.fedcm.js +0 -823
  147. package/dist/cjs/mixins/OxyServices.redirect.js +0 -95
  148. package/dist/cjs/mixins/OxyServices.silent.js +0 -204
  149. package/dist/cjs/mixins/OxyServices.sso.js +0 -208
  150. package/dist/cjs/utils/fapiAutoDetect.js +0 -99
  151. package/dist/cjs/utils/ssoEstablish.js +0 -110
  152. package/dist/cjs/utils/ssoReturn.js +0 -275
  153. package/dist/esm/AuthManager.js +0 -1105
  154. package/dist/esm/AuthManagerTypes.js +0 -12
  155. package/dist/esm/CrossDomainAuth.js +0 -201
  156. package/dist/esm/mixins/OxyServices.fedcm.js +0 -821
  157. package/dist/esm/mixins/OxyServices.redirect.js +0 -92
  158. package/dist/esm/mixins/OxyServices.silent.js +0 -202
  159. package/dist/esm/mixins/OxyServices.sso.js +0 -204
  160. package/dist/esm/utils/fapiAutoDetect.js +0 -95
  161. package/dist/esm/utils/ssoEstablish.js +0 -107
  162. package/dist/esm/utils/ssoReturn.js +0 -271
  163. package/dist/types/AuthManager.d.ts +0 -380
  164. package/dist/types/AuthManagerTypes.d.ts +0 -81
  165. package/dist/types/CrossDomainAuth.d.ts +0 -164
  166. package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -331
  167. package/dist/types/mixins/OxyServices.redirect.d.ts +0 -92
  168. package/dist/types/mixins/OxyServices.silent.d.ts +0 -132
  169. package/dist/types/mixins/OxyServices.sso.d.ts +0 -138
  170. package/dist/types/utils/fapiAutoDetect.d.ts +0 -56
  171. package/dist/types/utils/ssoEstablish.d.ts +0 -85
  172. package/dist/types/utils/ssoReturn.d.ts +0 -156
  173. package/src/AuthManager.ts +0 -1269
  174. package/src/AuthManagerTypes.ts +0 -86
  175. package/src/CrossDomainAuth.ts +0 -243
  176. package/src/__tests__/authManager.cookiePath.test.ts +0 -390
  177. package/src/__tests__/authManager.security.test.ts +0 -377
  178. package/src/__tests__/crossDomainAuth.test.ts +0 -116
  179. package/src/__tests__/establishDeviceRefreshSlot.test.ts +0 -221
  180. package/src/mixins/OxyServices.fedcm.ts +0 -1026
  181. package/src/mixins/OxyServices.redirect.ts +0 -122
  182. package/src/mixins/OxyServices.silent.ts +0 -272
  183. package/src/mixins/OxyServices.sso.ts +0 -261
  184. package/src/mixins/__tests__/constructorAuthWebUrl.test.ts +0 -85
  185. package/src/mixins/__tests__/fedcm.test.ts +0 -667
  186. package/src/mixins/__tests__/sessionBaseUrl.test.ts +0 -61
  187. package/src/mixins/__tests__/silent.test.ts +0 -102
  188. package/src/mixins/__tests__/sso.test.ts +0 -228
  189. package/src/utils/__tests__/consumeSsoReturn.test.ts +0 -816
  190. package/src/utils/__tests__/fapiAutoDetect.test.ts +0 -183
  191. package/src/utils/__tests__/ssoBounce.test.ts +0 -219
  192. package/src/utils/__tests__/ssoEstablish.test.ts +0 -204
  193. package/src/utils/__tests__/ssoReturn.test.ts +0 -276
  194. package/src/utils/fapiAutoDetect.ts +0 -91
  195. package/src/utils/ssoEstablish.ts +0 -174
  196. package/src/utils/ssoReturn.ts +0 -389
@@ -91,6 +91,11 @@ const STORAGE_KEYS = {
91
91
  SHARED_PUBLIC_KEY: 'oxy_shared_identity_public_key',
92
92
  SHARED_SESSION_TOKEN: 'oxy_shared_session_token',
93
93
  SHARED_SESSION_ID: 'oxy_shared_session_id',
94
+ // Opaque device-attribution token, shared across all Oxy apps on one device
95
+ // so they all resolve to a SINGLE server-side DeviceSession (device-first
96
+ // session model). Add-only: it never carries session state, only attributes
97
+ // a newly-credentialed session to the shared device set.
98
+ SHARED_DEVICE_TOKEN: 'oxy_shared_device_token',
94
99
  } as const;
95
100
 
96
101
  /**
@@ -643,6 +648,102 @@ export class KeyManager {
643
648
  }
644
649
  }
645
650
 
651
+ /**
652
+ * Store the opaque device-attribution token in the SHARED keychain so every
653
+ * Oxy app on this device reads the SAME token and therefore resolves to one
654
+ * server-side DeviceSession.
655
+ *
656
+ * Mirrors the shared-session storage options exactly (iOS keychain access
657
+ * group `group.so.oxy.shared`; Android shared secure store). Native-only —
658
+ * a no-op on web (the shared keychain does not exist there; web persists its
659
+ * deviceToken in the per-origin {@link AuthStateStore} instead).
660
+ */
661
+ static async setSharedDeviceToken(deviceToken: string): Promise<void> {
662
+ if (isWebPlatform()) {
663
+ return; // Not supported on web
664
+ }
665
+
666
+ try {
667
+ const store = await initSecureStore();
668
+
669
+ if (isIOS()) {
670
+ const opts: OxySecureStoreOptions = {
671
+ keychainAccessible: store.WHEN_UNLOCKED,
672
+ keychainAccessGroup: IOS_KEYCHAIN_GROUP,
673
+ };
674
+ await store.setItemAsync(STORAGE_KEYS.SHARED_DEVICE_TOKEN, deviceToken, opts);
675
+ } else if (isAndroid()) {
676
+ await store.setItemAsync(STORAGE_KEYS.SHARED_DEVICE_TOKEN, deviceToken);
677
+ }
678
+
679
+ if (isDev()) {
680
+ logger.debug('Shared device token stored successfully', { component: 'KeyManager' });
681
+ }
682
+ } catch (error) {
683
+ if (isDev()) {
684
+ logger.error('Failed to store shared device token', error, { component: 'KeyManager' });
685
+ }
686
+ throw error;
687
+ }
688
+ }
689
+
690
+ /**
691
+ * Read the shared device-attribution token, or `null` when none is set (or
692
+ * on web). Mirrors {@link getSharedSession}'s keychain-access-group read.
693
+ */
694
+ static async getSharedDeviceToken(): Promise<string | null> {
695
+ if (isWebPlatform()) {
696
+ return null;
697
+ }
698
+
699
+ try {
700
+ const store = await initSecureStore();
701
+
702
+ if (isIOS()) {
703
+ const opts: OxySecureStoreOptions = { keychainAccessGroup: IOS_KEYCHAIN_GROUP };
704
+ return await store.getItemAsync(STORAGE_KEYS.SHARED_DEVICE_TOKEN, opts);
705
+ }
706
+ if (isAndroid()) {
707
+ return await store.getItemAsync(STORAGE_KEYS.SHARED_DEVICE_TOKEN);
708
+ }
709
+ return null;
710
+ } catch (error) {
711
+ if (isDev()) {
712
+ logger.warn('Failed to get shared device token', { component: 'KeyManager' }, error);
713
+ }
714
+ return null;
715
+ }
716
+ }
717
+
718
+ /**
719
+ * Delete the shared device-attribution token (device signout-all). Best-effort:
720
+ * a keychain failure is logged, not thrown, so it cannot block a sign-out.
721
+ */
722
+ static async clearSharedDeviceToken(): Promise<void> {
723
+ if (isWebPlatform()) {
724
+ return;
725
+ }
726
+
727
+ try {
728
+ const store = await initSecureStore();
729
+
730
+ if (isIOS()) {
731
+ const opts: OxySecureStoreOptions = { keychainAccessGroup: IOS_KEYCHAIN_GROUP };
732
+ await store.deleteItemAsync(STORAGE_KEYS.SHARED_DEVICE_TOKEN, opts);
733
+ } else if (isAndroid()) {
734
+ await store.deleteItemAsync(STORAGE_KEYS.SHARED_DEVICE_TOKEN);
735
+ }
736
+
737
+ if (isDev()) {
738
+ logger.debug('Shared device token cleared successfully', { component: 'KeyManager' });
739
+ }
740
+ } catch (error) {
741
+ if (isDev()) {
742
+ logger.error('Failed to clear shared device token', error, { component: 'KeyManager' });
743
+ }
744
+ }
745
+ }
746
+
646
747
  // ==================== END SHARED IDENTITY METHODS ====================
647
748
 
648
749
  /**
@@ -2,6 +2,13 @@
2
2
  "signin": {
3
3
  "title": "Sign In",
4
4
  "subtitle": "Sign in to continue your journey",
5
+ "chooser": {
6
+ "title": "Choose an account",
7
+ "subtitle": "Continue as an account below, or use another.",
8
+ "signedIn": "Signed in",
9
+ "useAnother": "Use another account",
10
+ "continueAs": "Continue as {{name}}"
11
+ },
5
12
  "addAccountTitle": "Add Another Account",
6
13
  "addAccountSubtitle": "Sign in with another account",
7
14
  "username": {
@@ -602,6 +609,7 @@
602
609
  "editProfile": {
603
610
  "title": "Edit Profile",
604
611
  "subtitle": "Manage your profile and preferences",
612
+ "notSet": "Not set",
605
613
  "sections": {
606
614
  "profilePicture": "Profile Picture",
607
615
  "basicInfo": "Basic Information",
@@ -610,6 +618,9 @@
610
618
  "security": "Security"
611
619
  },
612
620
  "items": {
621
+ "avatar": {
622
+ "subtitle": "Update your profile photo"
623
+ },
613
624
  "displayName": {
614
625
  "title": "Display Name",
615
626
  "subtitle": "This is how your name will appear to others",
@@ -617,7 +628,8 @@
617
628
  "firstName": "First Name",
618
629
  "firstNamePlaceholder": "Enter first name",
619
630
  "lastName": "Last Name",
620
- "lastNamePlaceholder": "Enter last name (optional)"
631
+ "lastNamePlaceholder": "Enter last name (optional)",
632
+ "invalidChars": "Use letters and spaces only"
621
633
  },
622
634
  "username": {
623
635
  "title": "Username",
@@ -2,6 +2,13 @@
2
2
  "signin": {
3
3
  "title": "Iniciar sesión",
4
4
  "subtitle": "Inicia sesión para continuar",
5
+ "chooser": {
6
+ "title": "Elige una cuenta",
7
+ "subtitle": "Continúa con una de tus cuentas o usa otra.",
8
+ "signedIn": "Sesión activa",
9
+ "useAnother": "Usar otra cuenta",
10
+ "continueAs": "Continuar como {{name}}"
11
+ },
5
12
  "addAccountTitle": "Agregar otra cuenta",
6
13
  "addAccountSubtitle": "Inicia sesión con otra cuenta",
7
14
  "username": {
@@ -152,6 +159,7 @@
152
159
  "editProfile": {
153
160
  "title": "Editar perfil",
154
161
  "subtitle": "Gestiona tu perfil y preferencias",
162
+ "notSet": "Sin definir",
155
163
  "sections": {
156
164
  "profilePicture": "Foto de perfil",
157
165
  "basicInfo": "Información básica",
@@ -160,6 +168,9 @@
160
168
  "security": "Seguridad"
161
169
  },
162
170
  "items": {
171
+ "avatar": {
172
+ "subtitle": "Actualiza tu foto de perfil"
173
+ },
163
174
  "displayName": {
164
175
  "title": "Nombre para mostrar",
165
176
  "subtitle": "Así aparecerá tu nombre para los demás",
@@ -167,7 +178,8 @@
167
178
  "firstName": "Nombre",
168
179
  "firstNamePlaceholder": "Introduce tu nombre",
169
180
  "lastName": "Apellido",
170
- "lastNamePlaceholder": "Introduce tu apellido (opcional)"
181
+ "lastNamePlaceholder": "Introduce tu apellido (opcional)",
182
+ "invalidChars": "Usa solo letras y espacios"
171
183
  },
172
184
  "username": {
173
185
  "title": "Usuario",
package/src/index.ts CHANGED
@@ -34,25 +34,6 @@ export type { AuthRefreshReason, AuthRefreshHandler } from './HttpService';
34
34
  // ---------------------------------------------------------------------------
35
35
  // Authentication
36
36
  // ---------------------------------------------------------------------------
37
- export { AuthManager, createAuthManager } from './AuthManager';
38
- export type {
39
- StorageAdapter,
40
- AuthStateChangeCallback,
41
- AuthMethod,
42
- AuthManagerConfig,
43
- } from './AuthManager';
44
- export type {
45
- AuthManagerAccount,
46
- RestoreFromCookiesResult,
47
- RestoreFromCookiesOptions,
48
- SwitchAuthuserResult,
49
- } from './AuthManagerTypes';
50
-
51
- export { CrossDomainAuth, createCrossDomainAuth } from './CrossDomainAuth';
52
- export type { CrossDomainAuthOptions } from './CrossDomainAuth';
53
- export type { FedCMAuthOptions, FedCMConfig, AuthorizedApp } from './mixins/OxyServices.fedcm';
54
- export type { SilentAuthOptions } from './mixins/OxyServices.silent';
55
- export type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
56
37
  export { ServiceCredentialMismatchError } from './mixins/OxyServices.auth';
57
38
  export type { ServiceTokenResponse } from './mixins/OxyServices.auth';
58
39
  // "Sign in with Oxy" — handoff (Workstream C)
@@ -245,16 +226,6 @@ export type {
245
226
  SessionLoginResponse,
246
227
  } from './models/session';
247
228
 
248
- // ---------------------------------------------------------------------------
249
- // Multi-account refresh-all (Google-style)
250
- // ---------------------------------------------------------------------------
251
- export type {
252
- RefreshAllResponse,
253
- RefreshAllAccount,
254
- RefreshAllAccountUser,
255
- RefreshCookieResponse,
256
- } from './models/interfaces';
257
-
258
229
  // ---------------------------------------------------------------------------
259
230
  // Crypto / identity
260
231
  // ---------------------------------------------------------------------------
@@ -367,6 +338,7 @@ export {
367
338
  isNative,
368
339
  isIOS,
369
340
  isAndroid,
341
+ isWebBrowser,
370
342
  } from './utils/platform';
371
343
  export type { PlatformOS } from './utils/platform';
372
344
 
@@ -468,6 +440,7 @@ export {
468
440
  isValidEmail,
469
441
  isValidUsername,
470
442
  isValidPassword,
443
+ isValidDisplayName,
471
444
  isRequiredString,
472
445
  isRequiredNumber,
473
446
  isRequiredBoolean,
@@ -514,48 +487,27 @@ export {
514
487
  getAccountDisplayName,
515
488
  getAccountFallbackHandle,
516
489
  formatPublicKeyHandle,
517
- mergeAccountsFromRefreshAll,
518
490
  getAccountColor,
519
491
  } from './utils/accountUtils';
520
492
  export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
521
493
 
522
494
  // ---------------------------------------------------------------------------
523
- // Cross-domain SSO infrastructure
495
+ // Registrable-domain + central-IdP-apex helpers.
496
+ //
497
+ // The client SSO-bounce / silent-iframe / FedCM machinery was removed in the
498
+ // device-first cutover (wave 2, ecosystem-wide bump complete). `registrableApex`
499
+ // (eTLD+1) and `CENTRAL_IDP_APEX` are still genuinely used: `registrableApex`
500
+ // via the `@oxyhq/core/server` re-export consumed by
501
+ // `packages/api/src/utils/sameSite.ts` for same-site origin checks, and
502
+ // `CENTRAL_IDP_APEX` by `server/cors.ts`'s `createOxyCors` (auto-allows
503
+ // `*.oxy.so`). `SSO_CALLBACK_PATH` has no remaining importer outside this
504
+ // module as of wave 2 — kept exported for now rather than removed here (an
505
+ // export deletion is a logic change, out of scope for a comment sweep); flag
506
+ // for a follow-up dead-export cleanup pass.
524
507
  // ---------------------------------------------------------------------------
525
- export { autoDetectAuthWebUrl, registrableApex } from './utils/fapiAutoDetect';
526
-
527
- // Central cross-domain SSO (opaque single-use code bounce via auth.oxy.so)
528
- export { CENTRAL_AUTH_URL, CENTRAL_IDP_APEX, resolveCentralAuthUrl } from './utils/authWebUrl';
529
- export { parseSsoReturnFragment, consumeSsoReturn } from './utils/ssoReturn';
530
- export type { SsoReturnKind, SsoReturnResult, ConsumeSsoReturnDeps } from './utils/ssoReturn';
531
- export { generateSsoState } from './mixins/OxyServices.sso';
532
-
533
- // Post-claim durable-session establish hop (web device-flow / QR sign-in).
534
- export { establishIdpSessionAfterClaim } from './utils/ssoEstablish';
535
- export type { SsoEstablishClient, EstablishAfterClaimDeps } from './utils/ssoEstablish';
536
-
537
- // SSO bounce — per-origin sessionStorage keys, bounce URL builder, predicates
538
- export {
539
- SSO_CALLBACK_PATH,
540
- SSO_GUARD_TTL_MS,
541
- ssoStateKey,
542
- ssoGuardKey,
543
- ssoDestKey,
544
- ssoNoSessionKey,
545
- ssoAttemptedKey,
546
- ssoPriorSessionKey,
547
- ssoSignedOutKey,
548
- ssoOutcomeKey,
549
- ssoCallbackBootstrapKey,
550
- ssoNavigate,
551
- getSsoCallbackBootstrapScript,
552
- buildSsoBounceUrl,
553
- isCentralIdPOrigin,
554
- guardActive,
555
- silentRestoreSuppressed,
556
- allowSsoBounce,
557
- } from './utils/ssoBounce';
558
- export type { SsoBounceGate } from './utils/ssoBounce';
508
+ export { registrableApex } from './utils/registrableApex';
509
+ export { CENTRAL_IDP_APEX } from './utils/authWebUrl';
510
+ export { SSO_CALLBACK_PATH } from './utils/ssoBounce';
559
511
 
560
512
  export { runColdBoot } from './utils/coldBoot';
561
513
  export type {
@@ -591,6 +543,64 @@ export {
591
543
  accountIdsOf,
592
544
  } from './session/projectSessionState';
593
545
 
546
+ // ---------------------------------------------------------------------------
547
+ // Device-first session machinery (auth centralization, wave 1) — additive.
548
+ // Persisted auth-state store, the unified refresh handler + scheduler, the
549
+ // cold-boot v2 runner, and the device-boot return-fragment consumer. Built ON
550
+ // the existing `runColdBoot` primitive + `SessionClient`; the legacy
551
+ // FedCM/SSO/CrossDomainAuth surface is untouched (cutover happens in F4).
552
+ // ---------------------------------------------------------------------------
553
+ export {
554
+ createWebAuthStateStore,
555
+ createNativeAuthStateStore,
556
+ createMemoryAuthStateStore,
557
+ AUTH_STATE_STORAGE_KEY,
558
+ DEVICE_TOKEN_STORAGE_KEY,
559
+ } from './session/authStateStore';
560
+ export type {
561
+ PersistedAuthState,
562
+ AuthStateStore,
563
+ NativeKeyValueStorage,
564
+ } from './session/authStateStore';
565
+
566
+ export {
567
+ refreshPersistedSession,
568
+ createAuthRefreshHandler,
569
+ installAuthRefreshHandler,
570
+ startTokenRefreshScheduler,
571
+ TOKEN_REFRESH_LEAD_MS,
572
+ } from './session/refresh';
573
+ export type { RefreshDeps, TokenRefreshSchedulerHandle } from './session/refresh';
574
+
575
+ export {
576
+ runSessionColdBoot,
577
+ createBrowserColdBootDom,
578
+ isSameApex,
579
+ BOOT_ATTEMPTED_KEY,
580
+ } from './boot/coldBootV2';
581
+ export type {
582
+ RunSessionColdBootOptions,
583
+ ColdBootDom,
584
+ SignedOutReason,
585
+ } from './boot/coldBootV2';
586
+
587
+ export {
588
+ consumeDeviceBootReturn,
589
+ parseDeviceBootFragment,
590
+ hashHasBootFragment,
591
+ BOOT_FRAGMENT_PARAM,
592
+ BOOT_STATE_SESSION_KEY,
593
+ } from './boot/deviceBootReturn';
594
+ export type {
595
+ DeviceBootSession,
596
+ DeviceBootReturnOutcome,
597
+ ConsumeDeviceBootReturnDeps,
598
+ } from './boot/deviceBootReturn';
599
+
600
+ // The web-session result contract (`WebSessionResult`) is owned by
601
+ // `@oxyhq/contracts` — import it directly from there; `@oxyhq/core` does not
602
+ // re-export contract types.
603
+
594
604
  // API response contracts (request/response Zod schemas + inferred types) live in
595
605
  // `@oxyhq/contracts` — the single source of truth shared by the backend and every
596
606
  // client SDK. Import them directly from `@oxyhq/contracts`; `@oxyhq/core` does NOT
@@ -28,8 +28,9 @@
28
28
  * SWITCHING INTO AN ACCOUNT: `switchToAccount(accountId)` mints a REAL session
29
29
  * for the target account and plants it as the active session — there is no
30
30
  * per-request "acting-as" header. Identity is carried by the session/token, not
31
- * a delegation header, so a switch propagates through reload and `refresh-all`
32
- * exactly like a login.
31
+ * a delegation header, so a switch propagates through reload and cross-domain
32
+ * exactly like a login, via the device-first session model (the server
33
+ * registers the switched session into the operator's device-set directly).
33
34
  */
34
35
  import type { User } from '../models/interfaces';
35
36
  import type { SessionLoginResponse } from '../models/session';
@@ -434,22 +435,22 @@ export interface AccountSuccessResult {
434
435
  /**
435
436
  * Result of {@link OxyServicesAccountsMixin.switchToAccount} — the freshly
436
437
  * minted session for the target account, in the SAME shape the canonical login
437
- * / `claimSessionByToken` responses use (`SessionLoginResponse`), plus the
438
- * device-local refresh-cookie slot index.
438
+ * / `claimSessionByToken` responses use (`SessionLoginResponse`).
439
439
  *
440
440
  * `accessToken` is the first access token for the new session (already planted
441
- * as the active token by `switchToAccount`). The refresh token is NOT in the
442
- * body the server sets it as the httpOnly `oxy_rt_<authuser>` cookie, which
443
- * joins the device multi-account set so the switched session survives reload and
444
- * propagates cross-domain via `/auth/refresh-all`. `user` is the target account.
441
+ * as the active token by `switchToAccount`). The switched session's survival
442
+ * across reload and cross-domain sync is device-first: the server registers
443
+ * it into the operator's `DeviceSession` set directly
444
+ * (`deviceSessionService.addAccount`, broadcast to the device room) there is
445
+ * no client-side refresh-cookie slot to establish. `user` is the target
446
+ * account.
445
447
  */
446
448
  export interface SwitchAccountResult extends SessionLoginResponse {
447
449
  /**
448
- * The device-local refresh-cookie slot index (`oxy_rt_<authuser>`) the server
449
- * assigned to the minted session. Surfaced so the consumer can register the
450
- * new session in its device multi-account set exactly like a login response.
451
- * Absent only when the server could not set the cookie (best-effort — the
452
- * switch itself still succeeds).
450
+ * Legacy device-local refresh-cookie slot index. The current server switch
451
+ * response never sets this field (device-set registration replaced the
452
+ * cookie-slot model) kept optional for backward type-compatibility with
453
+ * any caller still reading it, but always `undefined` in practice.
453
454
  */
454
455
  authuser?: number;
455
456
  }
@@ -515,24 +516,18 @@ export function OxyServicesAccountsMixin<T extends typeof OxyServicesBase>(Base:
515
516
  * target, directly or inherited — else 403; 404 if missing/archived; 403 if
516
517
  * the target is a personal account), then mints a REAL session for the
517
518
  * target account and returns it in the canonical login / `claimSessionByToken`
518
- * shape (`{ sessionId, deviceId, expiresAt, accessToken, user, authuser }`).
519
+ * shape (`{ sessionId, deviceId, expiresAt, accessToken, user }`).
519
520
  *
520
521
  * Unlike the removed `X-Acting-As` delegation header, the returned session
521
522
  * IS the new identity: this plants `accessToken` as the active token —
522
523
  * exactly like `claimSessionByToken` / `verifyChallenge` — so every
523
524
  * subsequent request authenticates as the target account.
524
525
  *
525
- * Joining the device multi-account set (so the switch survives a reload and
526
- * propagates cross-domain via `/auth/refresh-all`) requires a SECOND call, to
527
- * `POST /auth/session`, made here after the token is planted. The switch route
528
- * lives at `/accounts/*`, OUTSIDE the `oxy_rt_<authuser>` cookie's `Path=/auth`
529
- * scope, so the server never sees the device's existing slots from it and
530
- * would clobber slot 0 (destroying the operator's own session). `/auth/session`
531
- * runs where those cookies ARE visible, so the server allocates a NEW slot that
532
- * coexists with the operator's and returns its `authuser`. This step is
533
- * web-only (native multi-account uses stored sessions, not cookies) and
534
- * best-effort — a failure leaves the in-session switch intact; the switched
535
- * account simply won't survive a reload until the cookie is next established.
526
+ * A single call is all that's needed: the server registers the switched
527
+ * session into the operator's `DeviceSession` set directly, inheriting the
528
+ * operator's central `deviceId` so the switch survives a reload and syncs
529
+ * cross-domain via the same device-first session model as a normal login —
530
+ * there is no separate client-side cookie/slot step to make it stick.
536
531
  *
537
532
  * After planting, the SDK's identity-scoped GET cache is fully cleared so
538
533
  * every cached read re-fetches as the new account. (The consuming
@@ -542,7 +537,7 @@ export function OxyServicesAccountsMixin<T extends typeof OxyServicesBase>(Base:
542
537
  * same-user silent refreshes, so the sweep here is explicit.)
543
538
  *
544
539
  * @param accountId - The target account's Mongo `_id`.
545
- * @returns The minted session (planted) plus the device `authuser` slot.
540
+ * @returns The minted session, already planted as the active session.
546
541
  */
547
542
  async switchToAccount(accountId: string): Promise<SwitchAccountResult> {
548
543
  try {
@@ -555,23 +550,16 @@ export function OxyServicesAccountsMixin<T extends typeof OxyServicesBase>(Base:
555
550
 
556
551
  // Plant the freshly minted session as the ACTIVE session, mirroring
557
552
  // `claimSessionByToken` / `verifyChallenge`: the response body carries
558
- // the first access token. The device refresh cookie is established below.
553
+ // the first access token.
559
554
  if (res?.accessToken) {
560
555
  this.setTokens(res.accessToken);
561
556
  }
562
557
 
563
- // Register the switched session in the device's multi-account set by
564
- // establishing its first-party refresh cookie. This MUST be a separate
565
- // call to `POST /auth/session` (the shared `establishDeviceRefreshSlot`
566
- // primitive): the switch route is at `/accounts/*`, outside the
567
- // `oxy_rt_<authuser>` cookie's `Path=/auth` scope, so it can never read
568
- // the device's existing slots and would overwrite slot 0 (destroying the
569
- // operator's own session). `/auth/session` runs where the cookies ARE
570
- // visible, so the server allocates a NEW slot that coexists with the
571
- // operator's and returns its `authuser`. Web-only; best-effort (the helper
572
- // re-plants the rotated access token and returns `null` on native/failure).
573
- const establishedAuthuser = await this.establishDeviceRefreshSlot();
574
- const authuser = establishedAuthuser ?? res.authuser;
558
+ // The switch route now registers the switched session in the device's
559
+ // server-side DeviceSession set directly (device-first), so there is no
560
+ // client-side refresh-cookie slot to establish the `authuser` comes
561
+ // from the switch response (optional-chained to match `res?.accessToken`).
562
+ const authuser = res?.authuser;
575
563
 
576
564
  // Identity changed → drop the entire GET response cache so no entry
577
565
  // personalised for the previous identity is reused. Cache keys are