@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
@@ -1,50 +1,15 @@
1
1
  /**
2
- * `resolveCentralAuthUrl` / `CENTRAL_AUTH_URL` — central IdP resolution.
2
+ * Central IdP apex constant.
3
3
  *
4
- * Pure helper: an explicit non-empty value always wins; otherwise the central
5
- * `auth.oxy.so` origin is returned. No DOM, no side effects.
4
+ * `resolveCentralAuthUrl` + `CENTRAL_AUTH_URL` were removed in the device-first /
5
+ * legacy-final cutovers. `CENTRAL_IDP_APEX` survives because live server-side
6
+ * callers (the `@oxyhq/core/server` CORS helper + the IdP worker) import it.
6
7
  */
7
8
 
8
- import { CENTRAL_AUTH_URL, CENTRAL_IDP_APEX, resolveCentralAuthUrl } from '../authWebUrl';
9
+ import { CENTRAL_IDP_APEX } from '../authWebUrl';
9
10
 
10
11
  describe('CENTRAL_IDP_APEX', () => {
11
12
  it('is the central IdP registrable apex', () => {
12
13
  expect(CENTRAL_IDP_APEX).toBe('oxy.so');
13
14
  });
14
15
  });
15
-
16
- describe('CENTRAL_AUTH_URL', () => {
17
- it('is the central IdP origin with no trailing slash', () => {
18
- expect(CENTRAL_AUTH_URL).toBe('https://auth.oxy.so');
19
- expect(CENTRAL_AUTH_URL.endsWith('/')).toBe(false);
20
- });
21
-
22
- it('is derived from CENTRAL_IDP_APEX (apex and origin never drift)', () => {
23
- expect(CENTRAL_AUTH_URL).toBe(`https://auth.${CENTRAL_IDP_APEX}`);
24
- });
25
- });
26
-
27
- describe('resolveCentralAuthUrl', () => {
28
- it('returns the central default when no explicit value is given', () => {
29
- expect(resolveCentralAuthUrl()).toBe(CENTRAL_AUTH_URL);
30
- expect(resolveCentralAuthUrl(undefined)).toBe('https://auth.oxy.so');
31
- });
32
-
33
- it('returns the explicit value when provided (explicit wins)', () => {
34
- expect(resolveCentralAuthUrl('https://auth.mention.earth')).toBe(
35
- 'https://auth.mention.earth',
36
- );
37
- });
38
-
39
- it('does not read any ambient DOM/window state', () => {
40
- // Even with a window installed, the result is purely a function of the arg.
41
- (globalThis as unknown as { window: unknown }).window = {
42
- location: { hostname: 'mention.earth', protocol: 'https:' },
43
- };
44
- try {
45
- expect(resolveCentralAuthUrl()).toBe('https://auth.oxy.so');
46
- } finally {
47
- delete (globalThis as Record<string, unknown>).window;
48
- }
49
- });
50
- });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `registrableApex` — the eTLD+1 host kernel (Public Suffix List aware).
3
+ *
4
+ * The client `autoDetectAuthWebUrl` helper was removed in the device-first
5
+ * cutover; only the registrable-apex kernel survives (used by the api SSO
6
+ * surface + the IdP). These cases lock its Public-Suffix-List behaviour.
7
+ */
8
+
9
+ import { registrableApex } from '../registrableApex';
10
+
11
+ describe('registrableApex', () => {
12
+ it('returns the eTLD+1 for a normal two-label host', () => {
13
+ expect(registrableApex('mention.earth')).toBe('mention.earth');
14
+ });
15
+
16
+ it('strips subdomains down to the trailing two labels', () => {
17
+ expect(registrableApex('www.mention.earth')).toBe('mention.earth');
18
+ expect(registrableApex('deep.app.homiio.com')).toBe('homiio.com');
19
+ });
20
+
21
+ it('lower-cases the input', () => {
22
+ expect(registrableApex('WWW.Mention.EARTH')).toBe('mention.earth');
23
+ });
24
+
25
+ it('returns the eTLD+1 for hosts under multi-part public suffixes', () => {
26
+ expect(registrableApex('foo.co.uk')).toBe('foo.co.uk');
27
+ expect(registrableApex('www.foo.co.uk')).toBe('foo.co.uk');
28
+ expect(registrableApex('shop.victim.com.tr')).toBe('victim.com.tr');
29
+ });
30
+
31
+ it('returns the eTLD+1 for hosts under private hosted suffixes', () => {
32
+ expect(registrableApex('honest.github.io')).toBe('honest.github.io');
33
+ expect(registrableApex('app.victim.pages.dev')).toBe('victim.pages.dev');
34
+ expect(registrableApex('site.victim.netlify.app')).toBe('victim.netlify.app');
35
+ });
36
+
37
+ it('returns null for bare public and private suffixes', () => {
38
+ expect(registrableApex('co.uk')).toBeNull();
39
+ expect(registrableApex('github.io')).toBeNull();
40
+ expect(registrableApex('pages.dev')).toBeNull();
41
+ expect(registrableApex('netlify.app')).toBeNull();
42
+ });
43
+
44
+ it('returns null for IPv4 literals', () => {
45
+ expect(registrableApex('192.168.1.10')).toBeNull();
46
+ expect(registrableApex('10.0.0.1')).toBeNull();
47
+ });
48
+
49
+ it('returns null for IPv6 literals and hosts carrying a port', () => {
50
+ expect(registrableApex('[::1]')).toBeNull();
51
+ expect(registrableApex('mention.earth:3000')).toBeNull();
52
+ });
53
+
54
+ it('returns null for single-label hosts', () => {
55
+ expect(registrableApex('intranet')).toBeNull();
56
+ expect(registrableApex('localhost')).toBeNull();
57
+ });
58
+
59
+ it('returns null for empty input', () => {
60
+ expect(registrableApex('')).toBeNull();
61
+ });
62
+ });
@@ -7,6 +7,7 @@ import {
7
7
  isValidEmail,
8
8
  isValidUsername,
9
9
  isValidPassword,
10
+ isValidDisplayName,
10
11
  isValidUUID,
11
12
  isValidDate,
12
13
  isValidFileSize,
@@ -139,6 +140,35 @@ describe('Validation Utils', () => {
139
140
  });
140
141
  });
141
142
 
143
+ describe('isValidDisplayName', () => {
144
+ it('should return true for clean names (letters, spaces, apostrophe)', () => {
145
+ expect(isValidDisplayName("Renée O'Brien")).toBe(true);
146
+ expect(isValidDisplayName('Ada Lovelace')).toBe(true);
147
+ expect(isValidDisplayName('山田太郎')).toBe(true);
148
+ expect(isValidDisplayName('')).toBe(true); // empty is valid; non-empty enforced elsewhere
149
+ });
150
+
151
+ it('should return false for emoji, symbols, digits, and punctuation', () => {
152
+ expect(isValidDisplayName('nixCraft \u{1f427}')).toBe(false); // penguin emoji
153
+ expect(isValidDisplayName('Agent007')).toBe(false);
154
+ expect(isValidDisplayName('Jean-Luc')).toBe(false);
155
+ expect(isValidDisplayName('J.R.')).toBe(false);
156
+ });
157
+
158
+ it('should return false for control whitespace (tab/newline/CR)', () => {
159
+ // \p{Zs} (space separators only) rejects layout-breaking / multi-line
160
+ // spoofing whitespace that \s would have admitted.
161
+ expect(isValidDisplayName('Ada\tLovelace')).toBe(false);
162
+ expect(isValidDisplayName('Ada\nLovelace')).toBe(false);
163
+ expect(isValidDisplayName('Ada\rLovelace')).toBe(false);
164
+ });
165
+
166
+ it('should return true for Unicode space separators', () => {
167
+ expect(isValidDisplayName('Ada Lovelace')).toBe(true); // NBSP
168
+ expect(isValidDisplayName('山田 太郎')).toBe(true); // ideographic space
169
+ });
170
+ });
171
+
142
172
  describe('isValidUUID', () => {
143
173
  it('should return true for valid UUIDs', () => {
144
174
  expect(isValidUUID('123e4567-e89b-12d3-a456-426614174000')).toBe(true);
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import { translate } from '../i18n';
7
- import type { RefreshAllAccount } from '../models/interfaces';
8
7
 
9
8
  export interface QuickAccount {
10
9
  sessionId: string;
@@ -15,9 +14,8 @@ export interface QuickAccount {
15
14
  avatarUrl?: string;
16
15
  /**
17
16
  * Device-local account slot index, 0..N-1 (Google-style multi-account).
18
- * Mirrors the server's `oxy_rt_${authuser}` cookie slot. Optional so that
19
- * pre-multi-account QuickAccounts (sessionId-only, non-cookie auth on RN)
20
- * remain valid; web flows always populate it after `refreshAllSessions`.
17
+ * Optional so that pre-multi-account QuickAccounts (sessionId-only) remain
18
+ * valid; the device session set populates it where available.
21
19
  */
22
20
  authuser?: number;
23
21
  /**
@@ -178,67 +176,6 @@ export const createQuickAccount = (
178
176
  };
179
177
  };
180
178
 
181
- /**
182
- * Merge a fresh `/auth/refresh-all` snapshot into an existing QuickAccount
183
- * list, preserving any cached fields (`avatarUrl`) for slots that didn't
184
- * change. The fresh response is canonical: the resulting list contains EXACTLY
185
- * the slots present in `fresh`, sorted by `authuser` ascending. Stale stored
186
- * accounts that no longer appear in `fresh` are dropped (the server already
187
- * authoritatively cleared the corresponding cookie).
188
- *
189
- * @param stored Previously persisted QuickAccount list (any order).
190
- * @param fresh Server's authoritative refresh-all response.
191
- * @returns Canonical merged list, sorted by `authuser` asc.
192
- */
193
- export const mergeAccountsFromRefreshAll = (
194
- stored: QuickAccount[] | undefined,
195
- fresh: RefreshAllAccount[],
196
- ): QuickAccount[] => {
197
- const storedByAuthuser = new Map<number, QuickAccount>();
198
- if (stored) {
199
- for (const account of stored) {
200
- if (typeof account.authuser === 'number') {
201
- storedByAuthuser.set(account.authuser, account);
202
- }
203
- }
204
- }
205
-
206
- const merged: QuickAccount[] = fresh.map((entry) => {
207
- const previous = storedByAuthuser.get(entry.authuser);
208
- // Preserve any previously cached identity for a slot that arrives
209
- // without a user shape rather than overwriting it with blanks, and let
210
- // AuthManager's getCurrentUser() hydration refresh it on the next
211
- // snapshot.
212
- const wireUser = entry.user;
213
- const username = wireUser?.username ?? previous?.username ?? '';
214
- const displayName = getAccountDisplayName({
215
- name: wireUser?.name,
216
- username,
217
- });
218
- const avatar = wireUser?.avatar ?? previous?.avatar ?? undefined;
219
- const avatarUrl =
220
- previous && previous.avatar === avatar ? previous.avatarUrl : undefined;
221
- return {
222
- sessionId: entry.sessionId,
223
- userId: wireUser?.id ?? previous?.userId,
224
- username,
225
- displayName,
226
- avatar,
227
- avatarUrl,
228
- authuser: entry.authuser,
229
- color: wireUser?.color ?? previous?.color ?? null,
230
- };
231
- });
232
-
233
- merged.sort((a, b) => {
234
- const aIdx = a.authuser ?? Number.POSITIVE_INFINITY;
235
- const bIdx = b.authuser ?? Number.POSITIVE_INFINITY;
236
- return aIdx - bIdx;
237
- });
238
-
239
- return merged;
240
- };
241
-
242
179
  /**
243
180
  * Return the account's preferred Bloom color preset, or `null` if it has no
244
181
  * preference. Centralises the `color ?? null` normalisation so consumers can
@@ -1,47 +1,17 @@
1
1
  /**
2
- * Central IdP (auth web) URL resolution for cross-domain SSO.
2
+ * Central IdP apex constant.
3
3
  *
4
- * The Oxy ecosystem runs a single, central Identity Provider at
5
- * `auth.oxy.so`. For TRUE central cross-domain SSO (Google/Meta/Clerk style),
6
- * FedCM and the opaque-code SSO bounce always target this one origin it owns
7
- * the host-only `fedcm_session` cookie and the central session store reachable
8
- * via `api.oxy.so`. Relying Parties (mention.earth, homiio.com, alia.onl, …)
9
- * delegate to it rather than standing up a per-apex IdP.
10
- *
11
- * This module is intentionally pure: it performs no DOM access, reads no
12
- * `window`/`location`, and has no side effects. It is the single source of
13
- * truth for the central IdP origin so call sites never hardcode the literal.
14
- *
15
- * Note: this is distinct from `autoDetectAuthWebUrl` (per-apex `auth.<rp-apex>`
16
- * derivation). The central-SSO path deliberately does NOT auto-detect per-apex
17
- * IdPs — it is central only. An explicitly-configured `authWebUrl` still wins.
4
+ * The client SSO/FedCM resolvers (`resolveCentralAuthUrl`, `CENTRAL_AUTH_URL`)
5
+ * were removed in the device-first / legacy-final cutovers. The lone survivor is
6
+ * `CENTRAL_IDP_APEX`, kept because it has a LIVE consumer
7
+ * `@oxyhq/core/server`'s CORS helper (`server/cors.ts`'s `createOxyCors`)
8
+ * auto-allows `*.oxy.so` from it. That CORS use is permanent, so this stays
9
+ * past the SSO/FedCM teardown.
18
10
  */
19
11
 
20
12
  /**
21
13
  * The registrable apex (eTLD+1) of the Oxy ecosystem's central Identity
22
- * Provider. The central IdP is reachable at `auth.${CENTRAL_IDP_APEX}` and the
23
- * ID-token assertion issuer is always `https://auth.${CENTRAL_IDP_APEX}`
24
- * regardless of which per-apex `auth.<rp>` host served a given request.
25
- *
26
- * Kept as a standalone constant so the IdP worker and the SDK derive the same
27
- * literal from one source of truth (the worker imports it to brand assertions).
14
+ * Provider, reachable at `auth.${CENTRAL_IDP_APEX}`. Single source of truth so
15
+ * the CORS helper (and anything else that needs the central apex) never drifts.
28
16
  */
29
17
  export const CENTRAL_IDP_APEX = 'oxy.so';
30
-
31
- /**
32
- * The canonical central Identity Provider origin for the Oxy ecosystem.
33
- * No trailing slash. Derived from {@link CENTRAL_IDP_APEX} so the apex and the
34
- * full origin never drift apart.
35
- */
36
- export const CENTRAL_AUTH_URL = `https://auth.${CENTRAL_IDP_APEX}`;
37
-
38
- /**
39
- * Resolve the central IdP origin, honouring an explicit override.
40
- *
41
- * @param explicit - A caller-supplied auth web URL, or `undefined`/empty to use
42
- * the central default. An explicit non-empty value always wins.
43
- * @returns The explicit value when provided, otherwise {@link CENTRAL_AUTH_URL}.
44
- */
45
- export function resolveCentralAuthUrl(explicit?: string): string {
46
- return explicit ?? CENTRAL_AUTH_URL;
47
- }
@@ -3,19 +3,21 @@
3
3
  * authentication resolution.
4
4
  *
5
5
  * On a fresh page load / app launch the SDK may have several ways to recover an
6
- * existing session (silent FedCM, a persisted refresh token, a cross-domain
7
- * claim, a redirect SSO return, ...). They must be attempted in a deterministic
8
- * order*, and the FIRST one that yields a session wins — every later step is
9
- * skipped. This module encodes exactly that contract and nothing else.
6
+ * existing session (a persisted refresh-token family, a shared-keychain
7
+ * identity, a cross-domain boot-fragment return, ...). They must be attempted
8
+ * in a deterministic order, and the FIRST one that yields a session wins —
9
+ * every later step is skipped. This module encodes exactly that contract and
10
+ * nothing else.
10
11
  *
11
12
  * Design constraints (all enforced):
12
13
  * - PURE: no DOM, no `navigator`, no `window`, no React, no platform globals.
13
14
  * - NO module-level mutable state. Every call to {@link runColdBoot} is fully
14
15
  * self-contained, so it is safe under bundler re-evaluation (e.g. the Metro
15
- * web bundle, which is precisely why the FedCM silent-SSO guard had to live
16
- * in consumers rather than a core singleton).
17
- * - Architecture-agnostic: both candidate cross-domain SSO designs consume
18
- * this same primitive; it knows nothing about HOW a step resolves a session.
16
+ * web bundle the reason any run-once guard for a step must live in the
17
+ * calling consumer, never in a core module-level singleton).
18
+ * - Architecture-agnostic: it knows nothing about HOW a step resolves a
19
+ * session; `runSessionColdBoot` (`boot/coldBootV2.ts`) is the current
20
+ * device-first consumer.
19
21
  *
20
22
  * A step is skipped (without running) when its `enabled` predicate returns
21
23
  * false. Any thrown error — from either `enabled` or `run` — is reported via
@@ -104,15 +106,16 @@ export interface RunColdBootOptions<S> {
104
106
  * fails to settle before the deadline, the runner abandons the await for that
105
107
  * step (reporting it via `onStepDeadline`) and CONTINUES to the next step,
106
108
  * each now racing against an already-expired deadline. This is deliberate:
107
- * the runner keeps iterating so the TERMINAL step (e.g. the `/sso` bounce,
108
- * whose `run()` performs its side effect synchronously before its first
109
- * `await`) still gets to fire. A step that has nothing to contribute after
110
- * the deadline simply doesn't settle and is skipped in turn.
109
+ * the runner keeps iterating so the TERMINAL step (e.g. `coldBootV2`'s
110
+ * `bootstrap-hop`, whose `run()` performs its navigation side effect
111
+ * synchronously before its first `await`) still gets to fire. A step that
112
+ * has nothing to contribute after the deadline simply doesn't settle and is
113
+ * skipped in turn.
111
114
  *
112
115
  * Per-step timeouts inside `run()` remain the first line of defense and
113
116
  * should keep every step well under this budget on a healthy load; this only
114
- * trips when one of them regresses (the production FedCM-silent hang). When
115
- * omitted there is no overall deadline.
117
+ * trips when one of them regresses (a step hanging past its own timeout).
118
+ * When omitted there is no overall deadline.
116
119
  */
117
120
  readonly overallDeadlineMs?: number;
118
121
  /**
@@ -98,3 +98,24 @@ export function setPlatformOS(os: PlatformOS): void {
98
98
  (globalThis as any).__REACT_NATIVE_PLATFORM__ = os;
99
99
  }
100
100
 
101
+ /**
102
+ * True only in a real web browser (a DOM is present), false on React Native
103
+ * and Node/SSR.
104
+ *
105
+ * Native defines a global `window` but no `document`, so the DOM probe — not a
106
+ * bare `window` check — is the reliable discriminator. This is the single
107
+ * source of truth consumed by `@oxyhq/services` and `@oxyhq/auth` (both dropped
108
+ * their local copies), so every consumer shares the exact same predicate.
109
+ *
110
+ * NOTE: this is a live runtime probe (not the cached `getPlatformOS()` verdict)
111
+ * because it must reflect the actual DOM availability at call time in the
112
+ * consumer's bundle.
113
+ */
114
+ export function isWebBrowser(): boolean {
115
+ return (
116
+ typeof window !== 'undefined' &&
117
+ typeof document !== 'undefined' &&
118
+ typeof document.documentElement !== 'undefined'
119
+ );
120
+ }
121
+
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Registrable-apex (eTLD+1) host kernel.
3
+ *
4
+ * The client FAPI auto-detection helper was removed in the device-first cutover
5
+ * (which is why this file is now named for what it actually is, not the old
6
+ * `fapiAutoDetect`). What survives is the pure registrable-domain kernel, still
7
+ * used server-side by the api's device-first same-apex trust checks
8
+ * (`deviceAuth.ts`'s `POST /auth/device/web-session`, via `sameSite.ts`'s
9
+ * `isSameSiteTrustedRequest`) and the `@oxyhq/core/server` CORS/re-export layer.
10
+ *
11
+ * `registrableApex` is NOT legacy — the device-first same-apex check is a live
12
+ * consumer, so this kernel stays regardless of the SSO/FedCM removal.
13
+ */
14
+
15
+ import { getDomain } from 'tldts';
16
+
17
+ /**
18
+ * Compute the bare registrable apex (eTLD+1) of a hostname using the Public
19
+ * Suffix List, including private hosted suffixes.
20
+ *
21
+ * Performs NO protocol handling and builds NO URL — it only answers "what is
22
+ * the registrable domain of this host, or is that undefinable?".
23
+ *
24
+ * Returns `null` (apex undefinable) for:
25
+ * - empty input;
26
+ * - IPv4 literals (`192.168.1.10`);
27
+ * - IPv6 literals or any host carrying a port (`[::1]`, anything with `:`);
28
+ * - single-label hosts (`intranet`, `localhost`);
29
+ * - public suffixes without a registrable label (e.g. `co.uk`, `github.io`).
30
+ *
31
+ * @param hostname - A bare hostname (no scheme), e.g. `www.mention.earth`.
32
+ * @returns The eTLD+1 (`mention.earth`), or `null` when undefinable.
33
+ */
34
+ export function registrableApex(hostname: string): string | null {
35
+ if (!hostname) return null;
36
+ const host = hostname.toLowerCase();
37
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) return null;
38
+ // IPv6 literals are bracketed; any remaining ':' implies a port — neither
39
+ // yields a registrable apex.
40
+ if (host.startsWith('[') || host.includes(':')) return null;
41
+ // The Public Suffix List (private suffixes included) is the source of truth
42
+ // for what an attacker can register. A multi-part public suffix like `co.uk`
43
+ // or a hosted suffix like `github.io` returns no registrable domain.
44
+ return getDomain(host, { allowPrivateDomains: true });
45
+ }