@oxyhq/core 2.2.2 → 2.3.1

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.
package/dist/cjs/index.js CHANGED
@@ -20,7 +20,7 @@
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = exports.darkenColor = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.isRTLLocale = exports.normalizeLanguageCode = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.SUPPORTED_LANGUAGES = exports.TopicSource = exports.TopicType = exports.SECURITY_EVENT_SEVERITY_MAP = exports.DeviceManager = exports.RecoveryPhraseService = exports.SignatureService = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.createCrossDomainAuth = exports.CrossDomainAuth = exports.createAuthManager = exports.AuthManager = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
22
22
  exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = exports.createDebugLogger = exports.debugError = exports.debugWarn = exports.debugLog = exports.isDev = exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = exports.isRateLimitError = exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = void 0;
23
- exports.packageInfo = exports.runColdBoot = exports.guardActive = exports.isCentralIdPOrigin = exports.buildSsoBounceUrl = exports.ssoNavigate = exports.ssoNoSessionKey = exports.ssoDestKey = exports.ssoGuardKey = exports.ssoStateKey = exports.SSO_GUARD_TTL_MS = exports.SSO_CALLBACK_PATH = exports.generateSsoState = exports.consumeSsoReturn = exports.parseSsoReturnFragment = exports.resolveCentralAuthUrl = exports.CENTRAL_IDP_APEX = exports.CENTRAL_AUTH_URL = exports.MULTIPART_TLDS = exports.registrableApex = exports.autoDetectAuthWebUrl = exports.getAccountColor = exports.mergeAccountsFromRefreshAll = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.logPerformance = exports.logPayment = exports.logDevice = exports.logUser = exports.logSession = exports.logApi = exports.logAuth = exports.LogLevel = exports.logger = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = void 0;
23
+ exports.packageInfo = exports.runColdBoot = exports.guardActive = exports.isCentralIdPOrigin = exports.buildSsoBounceUrl = exports.ssoNavigate = exports.ssoAttemptedKey = exports.ssoNoSessionKey = exports.ssoDestKey = exports.ssoGuardKey = exports.ssoStateKey = exports.SSO_GUARD_TTL_MS = exports.SSO_CALLBACK_PATH = exports.generateSsoState = exports.consumeSsoReturn = exports.parseSsoReturnFragment = exports.resolveCentralAuthUrl = exports.CENTRAL_IDP_APEX = exports.CENTRAL_AUTH_URL = exports.MULTIPART_TLDS = exports.registrableApex = exports.autoDetectAuthWebUrl = exports.getAccountColor = exports.mergeAccountsFromRefreshAll = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.logPerformance = exports.logPayment = exports.logDevice = exports.logUser = exports.logSession = exports.logApi = exports.logAuth = exports.LogLevel = exports.logger = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = void 0;
24
24
  // Ensure crypto polyfills are loaded before anything else
25
25
  require("./crypto/polyfill");
26
26
  // ---------------------------------------------------------------------------
@@ -250,6 +250,7 @@ Object.defineProperty(exports, "ssoStateKey", { enumerable: true, get: function
250
250
  Object.defineProperty(exports, "ssoGuardKey", { enumerable: true, get: function () { return ssoBounce_1.ssoGuardKey; } });
251
251
  Object.defineProperty(exports, "ssoDestKey", { enumerable: true, get: function () { return ssoBounce_1.ssoDestKey; } });
252
252
  Object.defineProperty(exports, "ssoNoSessionKey", { enumerable: true, get: function () { return ssoBounce_1.ssoNoSessionKey; } });
253
+ Object.defineProperty(exports, "ssoAttemptedKey", { enumerable: true, get: function () { return ssoBounce_1.ssoAttemptedKey; } });
253
254
  Object.defineProperty(exports, "ssoNavigate", { enumerable: true, get: function () { return ssoBounce_1.ssoNavigate; } });
254
255
  Object.defineProperty(exports, "buildSsoBounceUrl", { enumerable: true, get: function () { return ssoBounce_1.buildSsoBounceUrl; } });
255
256
  Object.defineProperty(exports, "isCentralIdPOrigin", { enumerable: true, get: function () { return ssoBounce_1.isCentralIdPOrigin; } });
@@ -27,11 +27,15 @@
27
27
  * the session, then restores the original destination.
28
28
  *
29
29
  * Loop proof (logged-out): first load all steps skip → `sso-bounce` sets
30
- * guard/state/dest and navigates; the IdP (no central session) returns
30
+ * guard/state/dest + the outcome-independent attempted-flag
31
+ * ({@link ssoAttemptedKey}) and navigates; the IdP (no central session) returns
31
32
  * `#oxy_sso=none`; the callback load's `sso-return` sees `none`, sets the
32
33
  * NO_SESSION flag ({@link ssoNoSessionKey}), and `sso-bounce` is then disabled.
33
34
  * Exactly ONE bounce, no loop. An interrupted bounce (user hit back
34
35
  * mid-redirect) self-heals once the {@link SSO_GUARD_TTL_MS} guard TTL lapses.
36
+ * The attempted-flag is the definitive, outcome-INDEPENDENT loop breaker: it is
37
+ * set pre-bounce so even if the return-side NO_SESSION write never lands, the
38
+ * bounce can never re-fire this tab after the self-heal TTL lapses.
35
39
  *
36
40
  * All state lives in `sessionStorage` (per tab, cleared on tab close) and is
37
41
  * keyed per-origin so two RPs hosted in the same browser never collide. The
@@ -44,6 +48,7 @@ exports.ssoStateKey = ssoStateKey;
44
48
  exports.ssoGuardKey = ssoGuardKey;
45
49
  exports.ssoDestKey = ssoDestKey;
46
50
  exports.ssoNoSessionKey = ssoNoSessionKey;
51
+ exports.ssoAttemptedKey = ssoAttemptedKey;
47
52
  exports.ssoNavigate = ssoNavigate;
48
53
  exports.buildSsoBounceUrl = buildSsoBounceUrl;
49
54
  exports.isCentralIdPOrigin = isCentralIdPOrigin;
@@ -69,6 +74,7 @@ const STATE_KEY_PREFIX = 'oxy_sso_state:';
69
74
  const GUARD_KEY_PREFIX = 'oxy_sso_guard:';
70
75
  const DEST_KEY_PREFIX = 'oxy_sso_dest:';
71
76
  const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
77
+ const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
72
78
  /** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
73
79
  function ssoStateKey(origin) {
74
80
  return `${STATE_KEY_PREFIX}${origin}`;
@@ -89,6 +95,18 @@ function ssoDestKey(origin) {
89
95
  function ssoNoSessionKey(origin) {
90
96
  return `${NO_SESSION_KEY_PREFIX}${origin}`;
91
97
  }
98
+ /**
99
+ * Per-origin, OUTCOME-INDEPENDENT once-guard. Set in `sessionStorage` BEFORE
100
+ * the terminal SSO bounce navigates. Gates the bounce so the silent
101
+ * cross-domain probe fires AT MOST ONCE per tab session — independent of
102
+ * whether the return-side NO_SESSION flag ever lands. The definitive loop
103
+ * breaker; survives the 30s self-heal `ssoGuardKey` TTL. Cleared only on an
104
+ * explicit sign-out/clear so a later cold boot (after the user signs in
105
+ * centrally) can probe again.
106
+ */
107
+ function ssoAttemptedKey(origin) {
108
+ return `${ATTEMPTED_KEY_PREFIX}${origin}`;
109
+ }
92
110
  /**
93
111
  * Perform the terminal top-level SSO bounce navigation.
94
112
  *
@@ -90,8 +90,8 @@ function parseSsoReturnFragment(hash) {
90
90
  * or a `Referer` header even if a later step throws.
91
91
  * - `state` must match (CSRF). A mismatch or a missing code sets the
92
92
  * NO_SESSION flag so `sso-bounce` is disabled (no rebounce loop).
93
- * - `none`/`error` outcomes set the NO_SESSION flag (the load2 half of the
94
- * loop proof).
93
+ * - `none`/`error` outcomes set BOTH the NO_SESSION flag and the
94
+ * outcome-independent attempted-flag (the load2 half of the loop proof).
95
95
  * - A throwing exchange is caught, reported via `onExchangeError`, and
96
96
  * treated exactly like "no session" (never loops, never rethrows).
97
97
  * - After a successful exchange landing on {@link SSO_CALLBACK_PATH}, the real
@@ -133,6 +133,10 @@ async function consumeSsoReturn(oxy, deps = {}) {
133
133
  storage.removeItem((0, ssoBounce_1.ssoGuardKey)(origin));
134
134
  const markNoSession = () => {
135
135
  storage.setItem((0, ssoBounce_1.ssoNoSessionKey)(origin), '1');
136
+ // A return was consumed, so the probe definitively happened. Set the
137
+ // outcome-independent attempted-flag too so the bounce can never re-fire
138
+ // even if some consumer path skipped setting it pre-bounce.
139
+ storage.setItem((0, ssoBounce_1.ssoAttemptedKey)(origin), '1');
136
140
  };
137
141
  if (ret.kind === 'none' || ret.kind === 'error') {
138
142
  // The central IdP had no session (or the bounce failed). Record it so we do