@oxyhq/core 5.5.0 → 6.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 (181) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/HttpService.js +6 -3
  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 +47 -44
  12. package/dist/cjs/mixins/OxyServices.accounts.js +6 -13
  13. package/dist/cjs/mixins/OxyServices.auth.js +66 -201
  14. package/dist/cjs/mixins/OxyServices.authorizedApps.js +38 -0
  15. package/dist/cjs/mixins/OxyServices.deviceBoot.js +119 -0
  16. package/dist/cjs/mixins/index.js +13 -17
  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/utils/accountUtils.js +1 -55
  21. package/dist/cjs/utils/authWebUrl.js +6 -15
  22. package/dist/cjs/utils/fapiAutoDetect.js +16 -64
  23. package/dist/cjs/utils/platform.js +19 -0
  24. package/dist/cjs/utils/ssoBounce.js +15 -362
  25. package/dist/cjs/utils/validationUtils.js +57 -0
  26. package/dist/esm/.tsbuildinfo +1 -1
  27. package/dist/esm/HttpService.js +6 -3
  28. package/dist/esm/OxyServices.base.js +7 -102
  29. package/dist/esm/boot/coldBootV2.js +344 -0
  30. package/dist/esm/boot/deviceBootReturn.js +146 -0
  31. package/dist/esm/crypto/keyManager.js +95 -0
  32. package/dist/esm/i18n/locales/en-US.json +13 -1
  33. package/dist/esm/i18n/locales/es-ES.json +13 -1
  34. package/dist/esm/i18n/locales/locales/en-US.json +13 -1
  35. package/dist/esm/i18n/locales/locales/es-ES.json +13 -1
  36. package/dist/esm/index.js +28 -18
  37. package/dist/esm/mixins/OxyServices.accounts.js +6 -13
  38. package/dist/esm/mixins/OxyServices.auth.js +66 -201
  39. package/dist/esm/mixins/OxyServices.authorizedApps.js +35 -0
  40. package/dist/esm/mixins/OxyServices.deviceBoot.js +116 -0
  41. package/dist/esm/mixins/index.js +13 -17
  42. package/dist/esm/session/SessionClient.js +40 -1
  43. package/dist/esm/session/authStateStore.js +278 -0
  44. package/dist/esm/session/refresh.js +257 -0
  45. package/dist/esm/utils/accountUtils.js +0 -53
  46. package/dist/esm/utils/authWebUrl.js +6 -14
  47. package/dist/esm/utils/fapiAutoDetect.js +16 -63
  48. package/dist/esm/utils/platform.js +18 -0
  49. package/dist/esm/utils/ssoBounce.js +14 -345
  50. package/dist/esm/utils/validationUtils.js +56 -0
  51. package/dist/types/.tsbuildinfo +1 -1
  52. package/dist/types/HttpService.d.ts +13 -0
  53. package/dist/types/OxyServices.base.d.ts +0 -52
  54. package/dist/types/OxyServices.d.ts +0 -25
  55. package/dist/types/boot/coldBootV2.d.ts +76 -0
  56. package/dist/types/boot/deviceBootReturn.d.ts +83 -0
  57. package/dist/types/crypto/keyManager.d.ts +21 -0
  58. package/dist/types/index.d.ts +15 -21
  59. package/dist/types/mixins/OxyServices.accounts.d.ts +0 -2
  60. package/dist/types/mixins/OxyServices.analytics.d.ts +0 -2
  61. package/dist/types/mixins/OxyServices.appData.d.ts +0 -2
  62. package/dist/types/mixins/OxyServices.assets.d.ts +0 -2
  63. package/dist/types/mixins/OxyServices.auth.d.ts +35 -77
  64. package/dist/types/mixins/{OxyServices.redirect.d.ts → OxyServices.authorizedApps.d.ts} +35 -33
  65. package/dist/types/mixins/OxyServices.civic.d.ts +0 -2
  66. package/dist/types/mixins/OxyServices.connectedApps.d.ts +0 -2
  67. package/dist/types/mixins/OxyServices.contacts.d.ts +0 -2
  68. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +110 -0
  69. package/dist/types/mixins/OxyServices.devices.d.ts +0 -2
  70. package/dist/types/mixins/OxyServices.features.d.ts +0 -2
  71. package/dist/types/mixins/OxyServices.identity.d.ts +0 -2
  72. package/dist/types/mixins/OxyServices.language.d.ts +0 -2
  73. package/dist/types/mixins/OxyServices.links.d.ts +0 -2
  74. package/dist/types/mixins/OxyServices.location.d.ts +0 -2
  75. package/dist/types/mixins/OxyServices.nodes.d.ts +0 -2
  76. package/dist/types/mixins/OxyServices.payment.d.ts +0 -2
  77. package/dist/types/mixins/OxyServices.privacy.d.ts +0 -2
  78. package/dist/types/mixins/OxyServices.reputation.d.ts +0 -2
  79. package/dist/types/mixins/OxyServices.security.d.ts +0 -2
  80. package/dist/types/mixins/OxyServices.topics.d.ts +0 -2
  81. package/dist/types/mixins/OxyServices.user.d.ts +0 -2
  82. package/dist/types/mixins/OxyServices.utility.d.ts +0 -2
  83. package/dist/types/mixins/index.d.ts +6 -9
  84. package/dist/types/models/interfaces.d.ts +0 -67
  85. package/dist/types/session/SessionClient.d.ts +25 -0
  86. package/dist/types/session/authStateStore.d.ts +119 -0
  87. package/dist/types/session/refresh.d.ts +93 -0
  88. package/dist/types/utils/accountUtils.d.ts +0 -14
  89. package/dist/types/utils/authWebUrl.d.ts +6 -12
  90. package/dist/types/utils/fapiAutoDetect.d.ts +15 -38
  91. package/dist/types/utils/platform.d.ts +14 -0
  92. package/dist/types/utils/ssoBounce.d.ts +14 -280
  93. package/dist/types/utils/validationUtils.d.ts +15 -0
  94. package/package.json +2 -2
  95. package/src/HttpService.ts +19 -3
  96. package/src/OxyServices.base.ts +7 -112
  97. package/src/OxyServices.ts +0 -38
  98. package/src/boot/__tests__/coldBootV2.test.ts +317 -0
  99. package/src/boot/__tests__/deviceBootReturn.test.ts +158 -0
  100. package/src/boot/coldBootV2.ts +426 -0
  101. package/src/boot/deviceBootReturn.ts +195 -0
  102. package/src/crypto/__tests__/sharedDeviceToken.test.ts +24 -0
  103. package/src/crypto/keyManager.ts +101 -0
  104. package/src/i18n/locales/en-US.json +13 -1
  105. package/src/i18n/locales/es-ES.json +13 -1
  106. package/src/index.ts +74 -65
  107. package/src/mixins/OxyServices.accounts.ts +6 -13
  108. package/src/mixins/OxyServices.auth.ts +78 -253
  109. package/src/mixins/OxyServices.authorizedApps.ts +75 -0
  110. package/src/mixins/OxyServices.deviceBoot.ts +146 -0
  111. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +107 -0
  112. package/src/mixins/__tests__/accounts.test.ts +17 -44
  113. package/src/mixins/__tests__/authorizedApps.test.ts +63 -0
  114. package/src/mixins/__tests__/passwordSignIn.test.ts +91 -0
  115. package/src/mixins/index.ts +16 -22
  116. package/src/models/interfaces.ts +0 -79
  117. package/src/session/SessionClient.ts +52 -1
  118. package/src/session/__tests__/SessionClient.additive.test.ts +92 -0
  119. package/src/session/__tests__/SessionClient.rest.test.ts +25 -0
  120. package/src/session/__tests__/SessionClient.state.test.ts +18 -5
  121. package/src/session/__tests__/authStateStore.test.ts +209 -0
  122. package/src/session/__tests__/refresh.test.ts +256 -0
  123. package/src/session/authStateStore.ts +335 -0
  124. package/src/session/refresh.ts +334 -0
  125. package/src/utils/__tests__/authWebUrl.test.ts +5 -29
  126. package/src/utils/__tests__/fapiAutoDetect.test.ts +5 -126
  127. package/src/utils/__tests__/validationUtils.test.ts +30 -0
  128. package/src/utils/accountUtils.ts +0 -62
  129. package/src/utils/authWebUrl.ts +6 -15
  130. package/src/utils/fapiAutoDetect.ts +16 -60
  131. package/src/utils/platform.ts +21 -0
  132. package/src/utils/ssoBounce.ts +14 -393
  133. package/src/utils/validationUtils.ts +62 -0
  134. package/dist/cjs/AuthManager.js +0 -1110
  135. package/dist/cjs/AuthManagerTypes.js +0 -13
  136. package/dist/cjs/CrossDomainAuth.js +0 -206
  137. package/dist/cjs/mixins/OxyServices.fedcm.js +0 -823
  138. package/dist/cjs/mixins/OxyServices.redirect.js +0 -95
  139. package/dist/cjs/mixins/OxyServices.silent.js +0 -204
  140. package/dist/cjs/mixins/OxyServices.sso.js +0 -208
  141. package/dist/cjs/utils/ssoEstablish.js +0 -110
  142. package/dist/cjs/utils/ssoReturn.js +0 -275
  143. package/dist/esm/AuthManager.js +0 -1105
  144. package/dist/esm/AuthManagerTypes.js +0 -12
  145. package/dist/esm/CrossDomainAuth.js +0 -201
  146. package/dist/esm/mixins/OxyServices.fedcm.js +0 -821
  147. package/dist/esm/mixins/OxyServices.redirect.js +0 -92
  148. package/dist/esm/mixins/OxyServices.silent.js +0 -202
  149. package/dist/esm/mixins/OxyServices.sso.js +0 -204
  150. package/dist/esm/utils/ssoEstablish.js +0 -107
  151. package/dist/esm/utils/ssoReturn.js +0 -271
  152. package/dist/types/AuthManager.d.ts +0 -380
  153. package/dist/types/AuthManagerTypes.d.ts +0 -81
  154. package/dist/types/CrossDomainAuth.d.ts +0 -164
  155. package/dist/types/mixins/OxyServices.fedcm.d.ts +0 -331
  156. package/dist/types/mixins/OxyServices.silent.d.ts +0 -132
  157. package/dist/types/mixins/OxyServices.sso.d.ts +0 -138
  158. package/dist/types/utils/ssoEstablish.d.ts +0 -85
  159. package/dist/types/utils/ssoReturn.d.ts +0 -156
  160. package/src/AuthManager.ts +0 -1269
  161. package/src/AuthManagerTypes.ts +0 -86
  162. package/src/CrossDomainAuth.ts +0 -243
  163. package/src/__tests__/authManager.cookiePath.test.ts +0 -390
  164. package/src/__tests__/authManager.security.test.ts +0 -377
  165. package/src/__tests__/crossDomainAuth.test.ts +0 -116
  166. package/src/__tests__/establishDeviceRefreshSlot.test.ts +0 -221
  167. package/src/mixins/OxyServices.fedcm.ts +0 -1026
  168. package/src/mixins/OxyServices.redirect.ts +0 -122
  169. package/src/mixins/OxyServices.silent.ts +0 -272
  170. package/src/mixins/OxyServices.sso.ts +0 -261
  171. package/src/mixins/__tests__/constructorAuthWebUrl.test.ts +0 -85
  172. package/src/mixins/__tests__/fedcm.test.ts +0 -667
  173. package/src/mixins/__tests__/sessionBaseUrl.test.ts +0 -61
  174. package/src/mixins/__tests__/silent.test.ts +0 -102
  175. package/src/mixins/__tests__/sso.test.ts +0 -228
  176. package/src/utils/__tests__/consumeSsoReturn.test.ts +0 -816
  177. package/src/utils/__tests__/ssoBounce.test.ts +0 -219
  178. package/src/utils/__tests__/ssoEstablish.test.ts +0 -204
  179. package/src/utils/__tests__/ssoReturn.test.ts +0 -276
  180. package/src/utils/ssoEstablish.ts +0 -174
  181. package/src/utils/ssoReturn.ts +0 -389
@@ -1,11 +1,12 @@
1
1
  /**
2
- * `resolveCentralAuthUrl` / `CENTRAL_AUTH_URL` — central IdP resolution.
2
+ * Central IdP apex/origin constants.
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` was removed in the device-first cutover (the SDK no
5
+ * longer resolves an IdP host). The `CENTRAL_IDP_APEX` / `CENTRAL_AUTH_URL`
6
+ * constants survive because the IdP worker imports them to brand assertions.
6
7
  */
7
8
 
8
- import { CENTRAL_AUTH_URL, CENTRAL_IDP_APEX, resolveCentralAuthUrl } from '../authWebUrl';
9
+ import { CENTRAL_AUTH_URL, CENTRAL_IDP_APEX } from '../authWebUrl';
9
10
 
10
11
  describe('CENTRAL_IDP_APEX', () => {
11
12
  it('is the central IdP registrable apex', () => {
@@ -23,28 +24,3 @@ describe('CENTRAL_AUTH_URL', () => {
23
24
  expect(CENTRAL_AUTH_URL).toBe(`https://auth.${CENTRAL_IDP_APEX}`);
24
25
  });
25
26
  });
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
- });
@@ -1,133 +1,12 @@
1
1
  /**
2
- * Auto-detect of `auth.<rp-apex>` from `window.location` for the Clerk-style
3
- * multi-domain FAPI setup. The IdP backend independently derives `iss` and
4
- * the FedCM manifest from the request host, so the only contract the SDK
5
- * needs to honour is: build URLs against the same host the page is on,
6
- * one subdomain over.
2
+ * `registrableApex` the eTLD+1 host kernel (Public Suffix List aware).
7
3
  *
8
- * Ported verbatim from packages/auth-sdk/__tests__/utils/fapiAutoDetect.test.ts
9
- * plus Public Suffix List regression cases that are unique to the core
10
- * implementation's registrable-apex guard.
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.
11
7
  */
12
8
 
13
- import { autoDetectAuthWebUrl, registrableApex } from '../fapiAutoDetect';
14
-
15
- function loc(hostname: string, protocol = 'https:'): Pick<Location, 'hostname' | 'protocol'> {
16
- return { hostname, protocol };
17
- }
18
-
19
- describe('autoDetectAuthWebUrl', () => {
20
- describe('returns auth.<apex> for public hostnames', () => {
21
- it('derives from the apex itself', () => {
22
- expect(autoDetectAuthWebUrl(loc('mention.earth'))).toBe('https://auth.mention.earth');
23
- });
24
-
25
- it('strips one leading subdomain', () => {
26
- expect(autoDetectAuthWebUrl(loc('www.mention.earth'))).toBe('https://auth.mention.earth');
27
- expect(autoDetectAuthWebUrl(loc('app.alia.onl'))).toBe('https://auth.alia.onl');
28
- });
29
-
30
- it('strips multiple leading subdomains down to the last two labels', () => {
31
- // The heuristic is "last two labels" — fine for our use case because
32
- // the IdP itself validates the request host. Deeply-nested hostnames
33
- // resolve to the trailing two-label apex.
34
- expect(autoDetectAuthWebUrl(loc('deep.app.homiio.com'))).toBe('https://auth.homiio.com');
35
- });
36
-
37
- it('honours the request protocol so dev http stays http', () => {
38
- expect(autoDetectAuthWebUrl(loc('staging.example.test', 'http:'))).toBe(
39
- 'http://auth.example.test'
40
- );
41
- });
42
- });
43
-
44
- describe('returns current origin when already on the IdP', () => {
45
- it('keeps everything same-origin instead of hopping to a sibling IdP', () => {
46
- expect(autoDetectAuthWebUrl(loc('auth.mention.earth'))).toBe('https://auth.mention.earth');
47
- expect(autoDetectAuthWebUrl(loc('auth.oxy.so'))).toBe('https://auth.oxy.so');
48
- });
49
- });
50
-
51
- describe('returns undefined where auto-detect would be wrong', () => {
52
- it('skips localhost and 127.0.0.1 (dev)', () => {
53
- expect(autoDetectAuthWebUrl(loc('localhost', 'http:'))).toBeUndefined();
54
- expect(autoDetectAuthWebUrl(loc('127.0.0.1', 'http:'))).toBeUndefined();
55
- });
56
-
57
- it('skips IPv4 literals', () => {
58
- expect(autoDetectAuthWebUrl(loc('192.168.1.10'))).toBeUndefined();
59
- expect(autoDetectAuthWebUrl(loc('10.0.0.1'))).toBeUndefined();
60
- });
61
-
62
- it('skips IPv6 literals (bracketed)', () => {
63
- expect(autoDetectAuthWebUrl(loc('[::1]'))).toBeUndefined();
64
- });
65
-
66
- it('skips single-label hostnames', () => {
67
- expect(autoDetectAuthWebUrl(loc('intranet'))).toBeUndefined();
68
- });
69
-
70
- it('skips unknown protocols', () => {
71
- expect(autoDetectAuthWebUrl({ hostname: 'mention.earth', protocol: 'file:' })).toBeUndefined();
72
- expect(autoDetectAuthWebUrl({ hostname: 'mention.earth', protocol: 'ftp:' })).toBeUndefined();
73
- });
74
-
75
- it('skips empty/missing hostnames', () => {
76
- expect(autoDetectAuthWebUrl(loc(''))).toBeUndefined();
77
- });
78
-
79
- it('returns undefined when no location is available (SSR / non-browser)', () => {
80
- expect(autoDetectAuthWebUrl(undefined)).toBeUndefined();
81
- });
82
- });
83
-
84
- describe('uses the Public Suffix List for registrable apex detection', () => {
85
- it('derives from the registrable domain under multi-part public suffixes', () => {
86
- expect(autoDetectAuthWebUrl(loc('foo.co.uk'))).toBe('https://auth.foo.co.uk');
87
- expect(autoDetectAuthWebUrl(loc('shop.com.au'))).toBe('https://auth.shop.com.au');
88
- expect(autoDetectAuthWebUrl(loc('shop.victim.com.tr'))).toBe('https://auth.victim.com.tr');
89
- });
90
-
91
- it('derives from the registrable domain under private hosted suffixes', () => {
92
- expect(autoDetectAuthWebUrl(loc('honest.github.io'))).toBe('https://auth.honest.github.io');
93
- expect(autoDetectAuthWebUrl(loc('app.victim.pages.dev'))).toBe(
94
- 'https://auth.victim.pages.dev'
95
- );
96
- expect(autoDetectAuthWebUrl(loc('site.victim.netlify.app'))).toBe(
97
- 'https://auth.victim.netlify.app'
98
- );
99
- });
100
-
101
- it('returns undefined for bare public and private suffixes', () => {
102
- expect(autoDetectAuthWebUrl(loc('co.uk'))).toBeUndefined();
103
- expect(autoDetectAuthWebUrl(loc('github.io'))).toBeUndefined();
104
- expect(autoDetectAuthWebUrl(loc('pages.dev'))).toBeUndefined();
105
- expect(autoDetectAuthWebUrl(loc('netlify.app'))).toBeUndefined();
106
- });
107
- });
108
-
109
- // Regression guard: the refactor to delegate host handling to `registrableApex`
110
- // must not change any of the pre-existing return values.
111
- describe('regression — unchanged values after registrableApex extraction', () => {
112
- const cases: Array<[string, string | undefined]> = [
113
- ['mention.earth', 'https://auth.mention.earth'],
114
- ['www.mention.earth', 'https://auth.mention.earth'],
115
- ['deep.app.homiio.com', 'https://auth.homiio.com'],
116
- ['auth.oxy.so', 'https://auth.oxy.so'],
117
- ['auth.mention.earth', 'https://auth.mention.earth'],
118
- ['localhost', undefined],
119
- ['192.168.1.10', undefined],
120
- ['[::1]', undefined],
121
- ['intranet', undefined],
122
- ['', undefined],
123
- ['foo.co.uk', 'https://auth.foo.co.uk'],
124
- ];
125
- it.each(cases)('autoDetectAuthWebUrl(%s) === %s', (hostname, expected) => {
126
- const protocol = hostname === 'localhost' || hostname === '[::1]' ? 'http:' : 'https:';
127
- expect(autoDetectAuthWebUrl(loc(hostname, protocol))).toBe(expected);
128
- });
129
- });
130
- });
9
+ import { registrableApex } from '../fapiAutoDetect';
131
10
 
132
11
  describe('registrableApex', () => {
133
12
  it('returns the eTLD+1 for a normal two-label host', () => {
@@ -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;
@@ -178,67 +177,6 @@ export const createQuickAccount = (
178
177
  };
179
178
  };
180
179
 
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
180
  /**
243
181
  * Return the account's preferred Bloom color preset, or `null` if it has no
244
182
  * preference. Centralises the `color ?? null` normalisation so consumers can
@@ -12,9 +12,11 @@
12
12
  * `window`/`location`, and has no side effects. It is the single source of
13
13
  * truth for the central IdP origin so call sites never hardcode the literal.
14
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.
15
+ * LEGACY(old-sdk): the client resolver (`resolveCentralAuthUrl`) was removed in
16
+ * the device-first cutover; `CENTRAL_IDP_APEX` / `CENTRAL_AUTH_URL` survive ONLY
17
+ * because the lista-B IdP worker imports them to brand assertions. Deletable
18
+ * once Homiio/Allo/Alia/Syra are bumped off the old SDK AND CloudWatch
19
+ * `/oxy/ecs` shows the `/sso*` + `/fedcm/*` routes quiet — F-final sweep.
18
20
  */
19
21
 
20
22
  /**
@@ -31,17 +33,6 @@ export const CENTRAL_IDP_APEX = 'oxy.so';
31
33
  /**
32
34
  * The canonical central Identity Provider origin for the Oxy ecosystem.
33
35
  * No trailing slash. Derived from {@link CENTRAL_IDP_APEX} so the apex and the
34
- * full origin never drift apart.
36
+ * full origin never drift apart. The IdP worker imports it to brand assertions.
35
37
  */
36
38
  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
- }
@@ -1,37 +1,17 @@
1
1
  /**
2
- * Auto-detect the FAPI (IdP) URL from the current browser hostname.
3
- *
4
- * This is the canonical cross-domain IdP-resolution primitive for the Oxy
5
- * ecosystem. Both candidate cross-domain SSO designs derive `auth.<rp-apex>`
6
- * through this helper; do not fork it.
7
- *
8
- * Clerk-style multi-domain SSO depends on the IdP being reachable on a
9
- * subdomain of the RP's own apex (e.g. `auth.mention.earth` CNAMEd to the
10
- * central Oxy IdP). That way every FedCM endpoint, the session cookie,
11
- * and any redirect target are same-site with the RP the only way
12
- * to get first-party cookies in Safari ITP and Firefox Total Cookie
13
- * Protection.
14
- *
15
- * This helper computes `https://auth.<rp-apex>` from
16
- * `window.location.hostname` so a consuming app doesn't have to pass
17
- * `authWebUrl` explicitly. Returns `undefined` for environments where
18
- * auto-detection would be wrong:
19
- *
20
- * - SSR / non-browser (no `window`).
21
- * - `localhost`, `127.0.0.1`, IPv4/IPv6 literals.
22
- * - Hostnames with fewer than two labels.
23
- * - Hostnames where a registrable domain cannot be determined from the
24
- * Public Suffix List, including private hosted suffixes such as
25
- * `github.io`, `pages.dev`, and `netlify.app`.
26
- *
27
- * When the page is already loaded ON the IdP itself (`auth.<anything>`),
28
- * the helper returns the current origin so the SDK keeps everything
29
- * same-origin instead of hopping to a different IdP host.
30
- *
31
- * The IdP backend independently derives `iss`, `provider_urls`, and the
32
- * `fedcm.json` icon URLs from the request host
33
- * (`packages/auth/server/index.ts`), so an honest CNAME pair is all that
34
- * is required for end-to-end FedCM correctness — no per-RP config.
2
+ * Registrable-apex (eTLD+1) host kernel.
3
+ *
4
+ * The client FAPI auto-detection helper (`autoDetectAuthWebUrl`) was removed in
5
+ * the device-first cutover the SDK no longer derives a per-apex `auth.<rp>`
6
+ * IdP host. What survives is the pure registrable-domain kernel, still used by
7
+ * the api SSO surface, `fedcm.service`, `deviceAuth` (same-apex trust checks),
8
+ * and the IdP worker (all lista B / server-side). `@oxyhq/core/server`
9
+ * re-exports it for the api.
10
+ *
11
+ * LEGACY(old-sdk): `registrableApex` survives ONLY for the lista-B server/IdP
12
+ * SSO surface. Deletable once Homiio/Allo/Alia/Syra are bumped off the old SDK
13
+ * AND CloudWatch `/oxy/ecs` shows the `/sso*` + `/fedcm/*` routes quiet — the
14
+ * F-final sweep should remove this file then.
35
15
  */
36
16
 
37
17
  import { getDomain } from 'tldts';
@@ -40,10 +20,8 @@ import { getDomain } from 'tldts';
40
20
  * Compute the bare registrable apex (eTLD+1) of a hostname using the Public
41
21
  * Suffix List, including private hosted suffixes.
42
22
  *
43
- * This is the pure host-handling kernel shared by {@link autoDetectAuthWebUrl}
44
- * and the IdP worker it performs NO protocol handling, NO `auth.` prefixing,
45
- * and builds NO URL. It only answers "what is the registrable domain of this
46
- * host, or is that undefinable?".
23
+ * Performs NO protocol handling and builds NO URL — it only answers "what is
24
+ * the registrable domain of this host, or is that undefinable?".
47
25
  *
48
26
  * Returns `null` (apex undefinable) for:
49
27
  * - empty input;
@@ -64,28 +42,6 @@ export function registrableApex(hostname: string): string | null {
64
42
  if (host.startsWith('[') || host.includes(':')) return null;
65
43
  // The Public Suffix List (private suffixes included) is the source of truth
66
44
  // for what an attacker can register. A multi-part public suffix like `co.uk`
67
- // or a hosted suffix like `github.io` returns no registrable domain, so
68
- // deriving `auth.<apex>` against it is impossible — `getDomain` returns null.
45
+ // or a hosted suffix like `github.io` returns no registrable domain.
69
46
  return getDomain(host, { allowPrivateDomains: true });
70
47
  }
71
-
72
- export function autoDetectAuthWebUrl(
73
- location: Pick<Location, 'hostname' | 'protocol'> | undefined =
74
- typeof window !== 'undefined' ? window.location : undefined
75
- ): string | undefined {
76
- if (!location) return undefined;
77
- const { hostname, protocol } = location;
78
- if (!hostname) return undefined;
79
- if (protocol !== 'https:' && protocol !== 'http:') return undefined;
80
- if (hostname === 'localhost' || hostname === '127.0.0.1') return undefined;
81
- if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) return undefined;
82
- if (hostname.startsWith('[')) return undefined;
83
- // Already ON the IdP — keep everything same-origin instead of hopping to a
84
- // sibling host.
85
- if (hostname.startsWith('auth.')) {
86
- return `${protocol}//${hostname}`;
87
- }
88
- const apex = registrableApex(hostname);
89
- if (apex === null) return undefined;
90
- return `${protocol}//auth.${apex}`;
91
- }
@@ -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
+