@oxyhq/core 3.4.0 → 3.4.2

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 (174) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/AuthManager.js +91 -319
  3. package/dist/cjs/CrossDomainAuth.js +19 -106
  4. package/dist/cjs/HttpService.js +49 -73
  5. package/dist/cjs/OxyServices.base.js +2 -2
  6. package/dist/cjs/i18n/index.js +7 -1
  7. package/dist/cjs/i18n/locales/ar-SA.json +18 -2
  8. package/dist/cjs/i18n/locales/ca-ES.json +18 -2
  9. package/dist/cjs/i18n/locales/de-DE.json +18 -2
  10. package/dist/cjs/i18n/locales/en-US.json +16 -2
  11. package/dist/cjs/i18n/locales/es-ES.json +16 -2
  12. package/dist/cjs/i18n/locales/fr-FR.json +18 -2
  13. package/dist/cjs/i18n/locales/it-IT.json +18 -2
  14. package/dist/cjs/i18n/locales/ja-JP.json +18 -2
  15. package/dist/cjs/i18n/locales/ko-KR.json +18 -2
  16. package/dist/cjs/i18n/locales/locales/ar-SA.json +18 -2
  17. package/dist/cjs/i18n/locales/locales/ca-ES.json +18 -2
  18. package/dist/cjs/i18n/locales/locales/de-DE.json +18 -2
  19. package/dist/cjs/i18n/locales/locales/en-US.json +17 -3
  20. package/dist/cjs/i18n/locales/locales/es-ES.json +16 -2
  21. package/dist/cjs/i18n/locales/locales/fr-FR.json +18 -2
  22. package/dist/cjs/i18n/locales/locales/it-IT.json +18 -2
  23. package/dist/cjs/i18n/locales/locales/ja-JP.json +18 -2
  24. package/dist/cjs/i18n/locales/locales/ko-KR.json +18 -2
  25. package/dist/cjs/i18n/locales/locales/pt-PT.json +18 -2
  26. package/dist/cjs/i18n/locales/locales/zh-CN.json +18 -2
  27. package/dist/cjs/i18n/locales/pt-PT.json +18 -2
  28. package/dist/cjs/i18n/locales/zh-CN.json +18 -2
  29. package/dist/cjs/mixins/OxyServices.auth.js +20 -63
  30. package/dist/cjs/mixins/OxyServices.fedcm.js +10 -12
  31. package/dist/cjs/mixins/OxyServices.popup.js +50 -299
  32. package/dist/cjs/mixins/OxyServices.redirect.js +84 -348
  33. package/dist/cjs/mixins/OxyServices.silent.js +204 -0
  34. package/dist/cjs/mixins/OxyServices.sso.js +4 -5
  35. package/dist/cjs/mixins/OxyServices.utility.js +6 -15
  36. package/dist/cjs/mixins/index.js +5 -6
  37. package/dist/cjs/server/index.js +21 -0
  38. package/dist/cjs/server/rateLimit.js +77 -0
  39. package/dist/cjs/shared/utils/debugUtils.js +1 -1
  40. package/dist/cjs/utils/accountUtils.js +4 -4
  41. package/dist/cjs/utils/authHelpers.js +21 -15
  42. package/dist/cjs/utils/coldBoot.js +3 -3
  43. package/dist/cjs/utils/fapiAutoDetect.js +1 -1
  44. package/dist/esm/.tsbuildinfo +1 -1
  45. package/dist/esm/AuthManager.js +91 -319
  46. package/dist/esm/CrossDomainAuth.js +19 -106
  47. package/dist/esm/HttpService.js +49 -73
  48. package/dist/esm/OxyServices.base.js +2 -2
  49. package/dist/esm/i18n/index.js +7 -1
  50. package/dist/esm/i18n/locales/ar-SA.json +18 -2
  51. package/dist/esm/i18n/locales/ca-ES.json +18 -2
  52. package/dist/esm/i18n/locales/de-DE.json +18 -2
  53. package/dist/esm/i18n/locales/en-US.json +16 -2
  54. package/dist/esm/i18n/locales/es-ES.json +16 -2
  55. package/dist/esm/i18n/locales/fr-FR.json +18 -2
  56. package/dist/esm/i18n/locales/it-IT.json +18 -2
  57. package/dist/esm/i18n/locales/ja-JP.json +18 -2
  58. package/dist/esm/i18n/locales/ko-KR.json +18 -2
  59. package/dist/esm/i18n/locales/locales/ar-SA.json +18 -2
  60. package/dist/esm/i18n/locales/locales/ca-ES.json +18 -2
  61. package/dist/esm/i18n/locales/locales/de-DE.json +18 -2
  62. package/dist/esm/i18n/locales/locales/en-US.json +17 -3
  63. package/dist/esm/i18n/locales/locales/es-ES.json +16 -2
  64. package/dist/esm/i18n/locales/locales/fr-FR.json +18 -2
  65. package/dist/esm/i18n/locales/locales/it-IT.json +18 -2
  66. package/dist/esm/i18n/locales/locales/ja-JP.json +18 -2
  67. package/dist/esm/i18n/locales/locales/ko-KR.json +18 -2
  68. package/dist/esm/i18n/locales/locales/pt-PT.json +18 -2
  69. package/dist/esm/i18n/locales/locales/zh-CN.json +18 -2
  70. package/dist/esm/i18n/locales/pt-PT.json +18 -2
  71. package/dist/esm/i18n/locales/zh-CN.json +18 -2
  72. package/dist/esm/mixins/OxyServices.auth.js +20 -63
  73. package/dist/esm/mixins/OxyServices.fedcm.js +10 -12
  74. package/dist/esm/mixins/OxyServices.popup.js +52 -301
  75. package/dist/esm/mixins/OxyServices.redirect.js +84 -349
  76. package/dist/esm/mixins/OxyServices.silent.js +202 -0
  77. package/dist/esm/mixins/OxyServices.sso.js +4 -5
  78. package/dist/esm/mixins/OxyServices.utility.js +6 -15
  79. package/dist/esm/mixins/index.js +5 -6
  80. package/dist/esm/server/index.js +17 -0
  81. package/dist/esm/server/rateLimit.js +71 -0
  82. package/dist/esm/shared/utils/debugUtils.js +1 -1
  83. package/dist/esm/utils/accountUtils.js +4 -4
  84. package/dist/esm/utils/authHelpers.js +21 -15
  85. package/dist/esm/utils/coldBoot.js +3 -3
  86. package/dist/esm/utils/fapiAutoDetect.js +1 -1
  87. package/dist/types/.tsbuildinfo +1 -1
  88. package/dist/types/AuthManager.d.ts +26 -53
  89. package/dist/types/AuthManagerTypes.d.ts +5 -9
  90. package/dist/types/CrossDomainAuth.d.ts +13 -52
  91. package/dist/types/HttpService.d.ts +9 -8
  92. package/dist/types/OxyServices.base.d.ts +1 -1
  93. package/dist/types/OxyServices.d.ts +4 -10
  94. package/dist/types/index.d.ts +1 -1
  95. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
  96. package/dist/types/mixins/OxyServices.appData.d.ts +1 -1
  97. package/dist/types/mixins/OxyServices.applications.d.ts +1 -1
  98. package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
  99. package/dist/types/mixins/OxyServices.auth.d.ts +10 -31
  100. package/dist/types/mixins/OxyServices.contacts.d.ts +1 -1
  101. package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
  102. package/dist/types/mixins/OxyServices.features.d.ts +1 -1
  103. package/dist/types/mixins/OxyServices.fedcm.d.ts +5 -5
  104. package/dist/types/mixins/OxyServices.language.d.ts +1 -1
  105. package/dist/types/mixins/OxyServices.location.d.ts +1 -1
  106. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -1
  107. package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
  108. package/dist/types/mixins/OxyServices.popup.d.ts +18 -120
  109. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
  110. package/dist/types/mixins/OxyServices.redirect.d.ts +13 -174
  111. package/dist/types/mixins/OxyServices.reputation.d.ts +1 -1
  112. package/dist/types/mixins/OxyServices.security.d.ts +1 -1
  113. package/dist/types/mixins/OxyServices.silent.d.ts +131 -0
  114. package/dist/types/mixins/OxyServices.sso.d.ts +4 -5
  115. package/dist/types/mixins/OxyServices.topics.d.ts +1 -1
  116. package/dist/types/mixins/OxyServices.user.d.ts +1 -1
  117. package/dist/types/mixins/OxyServices.utility.d.ts +3 -8
  118. package/dist/types/mixins/OxyServices.workspaces.d.ts +1 -1
  119. package/dist/types/mixins/index.d.ts +3 -3
  120. package/dist/types/models/interfaces.d.ts +5 -16
  121. package/dist/types/models/session.d.ts +0 -2
  122. package/dist/types/server/index.d.ts +18 -0
  123. package/dist/types/server/rateLimit.d.ts +40 -0
  124. package/dist/types/shared/utils/debugUtils.d.ts +1 -1
  125. package/dist/types/utils/authHelpers.d.ts +4 -3
  126. package/dist/types/utils/coldBoot.d.ts +2 -2
  127. package/dist/types/utils/fapiAutoDetect.d.ts +1 -1
  128. package/package.json +25 -3
  129. package/src/AuthManager.ts +100 -370
  130. package/src/AuthManagerTypes.ts +5 -9
  131. package/src/CrossDomainAuth.ts +22 -129
  132. package/src/HttpService.ts +55 -73
  133. package/src/OxyServices.base.ts +2 -3
  134. package/src/OxyServices.ts +9 -11
  135. package/src/__tests__/authManager.cookiePath.test.ts +19 -17
  136. package/src/__tests__/authManager.security.test.ts +7 -3
  137. package/src/__tests__/crossDomainAuth.test.ts +26 -118
  138. package/src/i18n/index.ts +7 -1
  139. package/src/i18n/locales/ar-SA.json +18 -2
  140. package/src/i18n/locales/ca-ES.json +18 -2
  141. package/src/i18n/locales/de-DE.json +18 -2
  142. package/src/i18n/locales/en-US.json +17 -3
  143. package/src/i18n/locales/es-ES.json +16 -2
  144. package/src/i18n/locales/fr-FR.json +18 -2
  145. package/src/i18n/locales/it-IT.json +18 -2
  146. package/src/i18n/locales/ja-JP.json +18 -2
  147. package/src/i18n/locales/ko-KR.json +18 -2
  148. package/src/i18n/locales/pt-PT.json +18 -2
  149. package/src/i18n/locales/zh-CN.json +18 -2
  150. package/src/index.ts +1 -1
  151. package/src/mixins/OxyServices.auth.ts +23 -75
  152. package/src/mixins/OxyServices.fedcm.ts +10 -12
  153. package/src/mixins/OxyServices.redirect.ts +82 -371
  154. package/src/mixins/OxyServices.silent.ts +272 -0
  155. package/src/mixins/OxyServices.sso.ts +5 -6
  156. package/src/mixins/OxyServices.utility.ts +9 -22
  157. package/src/mixins/__tests__/appData.test.ts +1 -1
  158. package/src/mixins/__tests__/onTokensChanged.test.ts +1 -1
  159. package/src/mixins/__tests__/reputation.test.ts +1 -1
  160. package/src/mixins/__tests__/serviceAuth.test.ts +7 -5
  161. package/src/mixins/__tests__/silent.test.ts +102 -0
  162. package/src/mixins/__tests__/verifyChallenge.test.ts +9 -14
  163. package/src/mixins/index.ts +6 -8
  164. package/src/models/interfaces.ts +5 -16
  165. package/src/models/session.ts +1 -3
  166. package/src/server/index.ts +19 -0
  167. package/src/server/rateLimit.ts +170 -0
  168. package/src/shared/utils/debugUtils.ts +1 -1
  169. package/src/utils/accountUtils.ts +4 -4
  170. package/src/utils/authHelpers.ts +23 -15
  171. package/src/utils/coldBoot.ts +4 -4
  172. package/src/utils/fapiAutoDetect.ts +1 -1
  173. package/src/mixins/OxyServices.popup.ts +0 -631
  174. package/src/mixins/__tests__/popup.test.ts +0 -374
@@ -29,8 +29,7 @@ const debug = createDebugLogger('SSO');
29
29
  *
30
30
  * Exposed as a module-level helper (in addition to the instance method below)
31
31
  * so consumers that do not yet hold an `OxyServices` instance can still mint a
32
- * bounce state. Reuses the same `crypto.randomUUID`-based generator the popup
33
- * mixin uses for its CSRF state, with a `getRandomValues` fallback.
32
+ * bounce state. Uses `crypto.randomUUID` with a `getRandomValues` fallback.
34
33
  */
35
34
  export function generateSsoState() {
36
35
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
@@ -51,8 +50,8 @@ export function OxyServicesSsoMixin(Base) {
51
50
  /**
52
51
  * Generate cryptographically secure state for the SSO bounce (CSRF
53
52
  * protection). Delegates to the module-level {@link generateSsoState}
54
- * helper, which uses the same `crypto.randomUUID`-based generator the popup
55
- * mixin's `generateState()` uses — one shared secure-random implementation.
53
+ * helper, which uses `crypto.randomUUID` when available and falls back to
54
+ * `crypto.getRandomValues`.
56
55
  */
57
56
  generateSsoState() {
58
57
  return generateSsoState();
@@ -123,7 +122,7 @@ export function OxyServicesSsoMixin(Base) {
123
122
  // Plant the access token exactly like exchangeIdTokenForSession does.
124
123
  // The SSO exchange does not return a refresh token (the central store
125
124
  // holds the refresh credential), so default it to an empty string.
126
- this.httpService.setTokens(payload.accessToken, '');
125
+ this.httpService.setTokens(payload.accessToken);
127
126
  debug.log('SSO exchange complete:', { hasSession: !!payload.sessionId });
128
127
  const session = {
129
128
  sessionId: payload.sessionId,
@@ -259,20 +259,12 @@ export function OxyServicesUtilityMixin(Base) {
259
259
  return true;
260
260
  };
261
261
  try {
262
- // Extract token from Authorization header or query params.
262
+ // Extract token from Authorization header.
263
263
  // Node/Express normalizes `Authorization` to a string; we guard
264
264
  // against the (legal but unusual) string[] case anyway.
265
265
  const rawAuthHeader = req.headers.authorization;
266
266
  const authHeader = Array.isArray(rawAuthHeader) ? rawAuthHeader[0] : rawAuthHeader;
267
- let token = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null;
268
- // Fallback to query params (useful for WebSocket upgrades)
269
- if (!token) {
270
- const q = req.query || {};
271
- if (typeof q.token === 'string' && q.token)
272
- token = q.token;
273
- else if (typeof q.access_token === 'string' && q.access_token)
274
- token = q.access_token;
275
- }
267
+ const token = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null;
276
268
  if (debug) {
277
269
  logger.debug(`[oxy.auth] ${req.method} ${req.path} | token: ${!!token}`, {
278
270
  component: 'auth',
@@ -420,13 +412,14 @@ export function OxyServicesUtilityMixin(Base) {
420
412
  }
421
413
  // Validate required service token fields
422
414
  const appId = decoded.appId;
423
- if (!appId) {
415
+ const credentialId = decoded.credentialId;
416
+ if (!appId || typeof credentialId !== 'string' || credentialId.length === 0) {
424
417
  if (optional) {
425
418
  req.userId = null;
426
419
  req.user = null;
427
420
  return next();
428
421
  }
429
- const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
422
+ const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token: missing required claims', code: 'INVALID_SERVICE_TOKEN', status: 401 };
430
423
  if (onError)
431
424
  return onError(error);
432
425
  return res.status(401).json(error);
@@ -471,10 +464,8 @@ export function OxyServicesUtilityMixin(Base) {
471
464
  req.serviceApp = {
472
465
  appId,
473
466
  appName: decoded.appName || 'unknown',
467
+ credentialId,
474
468
  scopes: Array.isArray(decoded.scopes) ? decoded.scopes : [],
475
- ...(typeof decoded.credentialId === 'string' && decoded.credentialId.length > 0
476
- ? { credentialId: decoded.credentialId }
477
- : {}),
478
469
  };
479
470
  if (debug) {
480
471
  logger.debug(`[oxy.auth] Service token OK app=${decoded.appName} delegateUser=${oxyUserId || '(none)'}`, {
@@ -7,7 +7,7 @@
7
7
  import { OxyServicesBase } from '../OxyServices.base.js';
8
8
  import { OxyServicesAuthMixin } from './OxyServices.auth.js';
9
9
  import { OxyServicesFedCMMixin } from './OxyServices.fedcm.js';
10
- import { OxyServicesPopupAuthMixin } from './OxyServices.popup.js';
10
+ import { OxyServicesSilentAuthMixin } from './OxyServices.silent.js';
11
11
  import { OxyServicesRedirectAuthMixin } from './OxyServices.redirect.js';
12
12
  import { OxyServicesSsoMixin } from './OxyServices.sso.js';
13
13
  import { OxyServicesUserMixin } from './OxyServices.user.js';
@@ -33,7 +33,7 @@ import { OxyServicesAppDataMixin } from './OxyServices.appData.js';
33
33
  *
34
34
  * Order matters for dependencies:
35
35
  * 1. Base auth mixin first (required by all others)
36
- * 2. Cross-domain auth mixins (FedCM, Popup, Redirect)
36
+ * 2. Cross-domain auth mixins (FedCM, silent iframe, Redirect)
37
37
  * 3. User mixin (requires auth)
38
38
  * 4. Feature mixins (can depend on user)
39
39
  * 5. Utility mixin last (augments all)
@@ -45,13 +45,12 @@ const MIXIN_PIPELINE = [
45
45
  OxyServicesAuthMixin,
46
46
  // Cross-domain authentication (web-only)
47
47
  // - FedCM: Modern browser-native identity federation (Google-style)
48
- // - Popup: OAuth2-style popup authentication
48
+ // - Silent: iframe-based restore for first-party IdP hosts
49
49
  // - Redirect: Traditional redirect-based authentication
50
50
  OxyServicesFedCMMixin,
51
- OxyServicesPopupAuthMixin,
51
+ OxyServicesSilentAuthMixin,
52
52
  OxyServicesRedirectAuthMixin,
53
- // Central cross-domain SSO (opaque-code exchange). After Popup so it can
54
- // reuse the popup mixin's secure-random `generateState()`.
53
+ // Central cross-domain SSO (opaque-code exchange).
55
54
  OxyServicesSsoMixin,
56
55
  // User management (requires auth)
57
56
  OxyServicesUserMixin,
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @oxyhq/core/server — Server-only utilities for Oxy backends
3
+ *
4
+ * This subpath export provides Express middleware and Node.js-specific
5
+ * utilities that are not available in React Native or browser environments.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { createOxyRateLimit } from '@oxyhq/core/server';
10
+ * import { oxyClient } from '@oxyhq/core';
11
+ *
12
+ * const oxy = oxyClient({ apiUrl: 'https://api.oxy.so' });
13
+ *
14
+ * app.use(createOxyRateLimit(oxy, { store: redisStore }));
15
+ * ```
16
+ */
17
+ export { createOxyRateLimit } from './rateLimit.js';
@@ -0,0 +1,71 @@
1
+ import rateLimit from 'express-rate-limit';
2
+ /**
3
+ * Built-in exemptions. A media app's cover-art/avatar fan-out and HLS
4
+ * sub-requests must not consume the coarse global budget; health probes from
5
+ * the load balancer must never be limited; CORS preflight is not a real call.
6
+ */
7
+ function isBuiltInExempt(req) {
8
+ const path = req.path;
9
+ return (req.method === 'OPTIONS' ||
10
+ path.startsWith('/files/upload') ||
11
+ path.includes('/images/') ||
12
+ path.includes('/media/') ||
13
+ path.startsWith('/api/stream/') ||
14
+ path.includes('/stream/') ||
15
+ path === '/health' ||
16
+ path.endsWith('/health'));
17
+ }
18
+ /** IPv6-safe IP key generator (replaces colons to avoid Redis namespace issues). */
19
+ function ipKeyGenerator(ip) {
20
+ return ip.replace(/:/g, '_');
21
+ }
22
+ /** Resolve the rate-limit key: per authenticated user, else per (IPv6-safe) IP. */
23
+ function resolveKey(req) {
24
+ const userId = req.userId ?? req.user?.id ?? req.user?._id;
25
+ if (userId) {
26
+ return `user:${userId}`;
27
+ }
28
+ const ip = req.ip || req.socket?.remoteAddress || 'unknown';
29
+ return ipKeyGenerator(ip);
30
+ }
31
+ /**
32
+ * Build the composed Oxy rate-limit middleware. See module docs for rationale.
33
+ */
34
+ export function createOxyRateLimit(oxy, options = {}) {
35
+ const { authenticatedMax = 5000, anonymousMax = 600, windowMs = 15 * 60 * 1000, store, exempt, message = 'Too many requests, please try again later.', auth, } = options;
36
+ // Idempotent optional-auth resolver. Reuses the SAME session resolution as
37
+ // every protected route, so the limiter keys by the real user identity.
38
+ const resolveSession = oxy.auth({ ...auth, optional: true });
39
+ const skip = (req) => isBuiltInExempt(req) || (exempt ? exempt(req) : false);
40
+ const limiter = rateLimit({
41
+ windowMs,
42
+ ...(store ? { store } : {}),
43
+ max: ((req) => {
44
+ const authed = req;
45
+ const userId = authed.userId ?? authed.user?.id ?? authed.user?._id;
46
+ return userId ? authenticatedMax : anonymousMax;
47
+ }),
48
+ keyGenerator: ((req) => resolveKey(req)),
49
+ message,
50
+ standardHeaders: true,
51
+ legacyHeaders: false,
52
+ skip: skip,
53
+ });
54
+ return ((req, res, next) => {
55
+ // Skipped paths bypass BOTH session resolution and limiting — cheap and
56
+ // safe for static/streaming/health traffic.
57
+ if (skip(req)) {
58
+ next();
59
+ return;
60
+ }
61
+ resolveSession(req, res, (err) => {
62
+ if (err) {
63
+ // Optional auth never rejects; a token error just means "anonymous".
64
+ // Swallow the error and continue to limit as anonymous.
65
+ next();
66
+ return;
67
+ }
68
+ limiter(req, res, next);
69
+ });
70
+ });
71
+ }
@@ -51,7 +51,7 @@ export const debugError = (prefix, ...args) => {
51
51
  };
52
52
  /**
53
53
  * Create a namespaced debug logger
54
- * @param namespace - Logger namespace (e.g., 'FedCM', 'PopupAuth')
54
+ * @param namespace - Logger namespace (e.g., 'FedCM', 'SilentAuth')
55
55
  * @returns Object with log, warn, error methods
56
56
  *
57
57
  * @example
@@ -138,10 +138,10 @@ export const mergeAccountsFromRefreshAll = (stored, fresh) => {
138
138
  }
139
139
  const merged = fresh.map((entry) => {
140
140
  const previous = storedByAuthuser.get(entry.authuser);
141
- // `entry.user` is null on the SDK legacy-fallback path; preserve any
142
- // previously cached identity for that slot rather than overwriting
143
- // it with blanks, and let the AuthManager's getCurrentUser() hydration
144
- // refresh it on the next snapshot.
141
+ // Preserve any previously cached identity for a slot that arrives
142
+ // without a user shape rather than overwriting it with blanks, and let
143
+ // AuthManager's getCurrentUser() hydration refresh it on the next
144
+ // snapshot.
145
145
  const wireUser = entry.user;
146
146
  const username = wireUser?.username ?? previous?.username ?? '';
147
147
  const displayName = getAccountDisplayName({
@@ -22,25 +22,32 @@ export class AuthenticationFailedError extends Error {
22
22
  }
23
23
  /**
24
24
  * Ensures a valid token exists before making authenticated API calls.
25
- * If no valid token exists and an active session ID is available,
26
- * attempts to refresh the token using the session.
25
+ * If no valid token exists, callers may provide a session synchronizer that
26
+ * uses the platform-appropriate new flow (cookie restore, device claim, or
27
+ * native secure restore). This helper never exchanges a session id for a token.
27
28
  *
28
29
  * @throws {SessionSyncRequiredError} If the session needs to be synced (offline session)
29
30
  */
30
- export async function ensureValidToken(oxyServices, activeSessionId) {
31
- if (oxyServices.hasValidToken() || !activeSessionId) {
31
+ export async function ensureValidToken(oxyServices, _activeSessionId, syncSession) {
32
+ if (oxyServices.hasValidToken()) {
32
33
  return;
33
34
  }
34
- try {
35
- await oxyServices.getTokenBySession(activeSessionId);
36
- }
37
- catch (tokenError) {
38
- const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
39
- if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
40
- throw new SessionSyncRequiredError();
35
+ if (syncSession) {
36
+ try {
37
+ await syncSession();
38
+ if (oxyServices.hasValidToken()) {
39
+ return;
40
+ }
41
+ }
42
+ catch (syncError) {
43
+ const errorMessage = syncError instanceof Error ? syncError.message : String(syncError);
44
+ if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
45
+ throw new SessionSyncRequiredError();
46
+ }
47
+ throw syncError;
41
48
  }
42
- throw tokenError;
43
49
  }
50
+ throw new SessionSyncRequiredError('No active access token is available. Sync the session before calling authenticated APIs.');
44
51
  }
45
52
  /**
46
53
  * Checks if an error is an authentication error (401 or auth-related message)
@@ -70,10 +77,9 @@ export async function withAuthErrorHandling(apiCall, options) {
70
77
  if (!isAuthenticationError(error)) {
71
78
  throw error;
72
79
  }
73
- if (options?.syncSession && options?.activeSessionId && options?.oxyServices) {
80
+ if (options?.syncSession && options?.oxyServices) {
74
81
  try {
75
82
  await options.syncSession();
76
- await options.oxyServices.getTokenBySession(options.activeSessionId);
77
83
  return await apiCall();
78
84
  }
79
85
  catch {
@@ -96,7 +102,7 @@ export async function withAuthErrorHandling(apiCall, options) {
96
102
  * ```
97
103
  */
98
104
  export async function authenticatedApiCall(oxyServices, activeSessionId, apiCall, syncSession) {
99
- await ensureValidToken(oxyServices, activeSessionId);
105
+ await ensureValidToken(oxyServices, activeSessionId, syncSession);
100
106
  return withAuthErrorHandling(apiCall, {
101
107
  syncSession,
102
108
  activeSessionId,
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * On a fresh page load / app launch the SDK may have several ways to recover an
6
6
  * existing session (silent FedCM, a persisted refresh token, a cross-domain
7
- * claim, an explicit popup flow, ). They must be attempted in a *deterministic
7
+ * claim, a redirect SSO return, ...). They must be attempted in a deterministic
8
8
  * order*, and the FIRST one that yields a session wins — every later step is
9
9
  * skipped. This module encodes exactly that contract and nothing else.
10
10
  *
@@ -81,8 +81,8 @@ export async function runColdBoot(options) {
81
81
  }
82
82
  let result;
83
83
  try {
84
- // Without a deadline: legacy behaviour — await the step directly.
85
- // With a deadline: race the step against the shared deadline. The
84
+ // Without a deadline, await the step directly. With a deadline, race
85
+ // the step against the shared deadline. The
86
86
  // step's `run()` still STARTS synchronously up to its first `await`
87
87
  // (so a terminal step's synchronous navigation side effect always
88
88
  // executes), but a non-settling step can no longer block the loop —
@@ -8,7 +8,7 @@
8
8
  * Clerk-style multi-domain SSO depends on the IdP being reachable on a
9
9
  * subdomain of the RP's own apex (e.g. `auth.mention.earth` CNAMEd to the
10
10
  * central Oxy IdP). That way every FedCM endpoint, the session cookie,
11
- * and any popup/redirect target are same-site with the RP — the only way
11
+ * and any redirect target are same-site with the RP — the only way
12
12
  * to get first-party cookies in Safari ITP and Firefox Total Cookie
13
13
  * Protection.
14
14
  *