@oxyhq/core 3.4.1 → 3.4.3

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