@oxyhq/core 5.4.2 → 5.5.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.
package/dist/cjs/index.js CHANGED
@@ -21,7 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
21
21
  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.verifyPublicCardAttestation = exports.parseAttestPayload = exports.parseIdPayload = exports.buildUserDid = exports.normalizeProfileLinks = exports.getNormalizedUserHandle = exports.getCanonicalUserHandle = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = 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.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 = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = exports.darkenColor = exports.isAndroid = void 0;
23
23
  exports.ssoAttemptedKey = exports.ssoNoSessionKey = exports.ssoDestKey = exports.ssoGuardKey = exports.ssoStateKey = exports.SSO_GUARD_TTL_MS = exports.SSO_CALLBACK_PATH = exports.establishIdpSessionAfterClaim = exports.generateSsoState = exports.consumeSsoReturn = exports.parseSsoReturnFragment = exports.resolveCentralAuthUrl = exports.CENTRAL_IDP_APEX = exports.CENTRAL_AUTH_URL = 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 = exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = void 0;
24
- exports.packageInfo = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.runColdBoot = exports.allowSsoBounce = exports.silentRestoreSuppressed = exports.guardActive = exports.isCentralIdPOrigin = exports.buildSsoBounceUrl = exports.getSsoCallbackBootstrapScript = exports.ssoNavigate = exports.ssoCallbackBootstrapKey = exports.ssoSignedOutKey = exports.ssoPriorSessionKey = void 0;
24
+ exports.packageInfo = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.runColdBoot = exports.allowSsoBounce = exports.silentRestoreSuppressed = exports.guardActive = exports.isCentralIdPOrigin = exports.buildSsoBounceUrl = exports.getSsoCallbackBootstrapScript = exports.ssoNavigate = exports.ssoCallbackBootstrapKey = exports.ssoOutcomeKey = exports.ssoSignedOutKey = exports.ssoPriorSessionKey = void 0;
25
25
  // Ensure crypto polyfills are loaded before anything else
26
26
  require("./crypto/polyfill");
27
27
  // ---------------------------------------------------------------------------
@@ -288,6 +288,7 @@ Object.defineProperty(exports, "ssoNoSessionKey", { enumerable: true, get: funct
288
288
  Object.defineProperty(exports, "ssoAttemptedKey", { enumerable: true, get: function () { return ssoBounce_1.ssoAttemptedKey; } });
289
289
  Object.defineProperty(exports, "ssoPriorSessionKey", { enumerable: true, get: function () { return ssoBounce_1.ssoPriorSessionKey; } });
290
290
  Object.defineProperty(exports, "ssoSignedOutKey", { enumerable: true, get: function () { return ssoBounce_1.ssoSignedOutKey; } });
291
+ Object.defineProperty(exports, "ssoOutcomeKey", { enumerable: true, get: function () { return ssoBounce_1.ssoOutcomeKey; } });
291
292
  Object.defineProperty(exports, "ssoCallbackBootstrapKey", { enumerable: true, get: function () { return ssoBounce_1.ssoCallbackBootstrapKey; } });
292
293
  Object.defineProperty(exports, "ssoNavigate", { enumerable: true, get: function () { return ssoBounce_1.ssoNavigate; } });
293
294
  Object.defineProperty(exports, "getSsoCallbackBootstrapScript", { enumerable: true, get: function () { return ssoBounce_1.getSsoCallbackBootstrapScript; } });
@@ -19,12 +19,23 @@ function OxyServicesUserMixin(Base) {
19
19
  super(...args);
20
20
  }
21
21
  /**
22
- * Get profile by username
23
- */
24
- async getProfileByUsername(username) {
22
+ * Get profile by username.
23
+ *
24
+ * @param username - The profile's username.
25
+ * @param options.cache - Defaults to `true` (5-minute TTL), matching prior
26
+ * behavior. Pass `{ cache: false }` to force a registry-fresh read: the
27
+ * request bypasses BOTH the cache lookup and the post-fetch cache write
28
+ * (see {@link HttpService.request}'s `cache` handling), so it neither
29
+ * serves nor overwrites any entry already cached for this key — a
30
+ * previously cached response (if one exists) is left in place until its
31
+ * own TTL expires or is explicitly invalidated elsewhere. Use this when a
32
+ * caller must observe a just-written change (e.g. a privacy/consent flag)
33
+ * that would otherwise be masked by the TTL window.
34
+ */
35
+ async getProfileByUsername(username, options) {
25
36
  try {
26
37
  const user = await this.makeRequest('GET', `/profiles/username/${username}`, undefined, {
27
- cache: true,
38
+ cache: options?.cache ?? true,
28
39
  cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
29
40
  });
30
41
  return (0, userIdentity_1.normalizeUserIdentity)(user);
@@ -207,12 +218,23 @@ function OxyServicesUserMixin(Base) {
207
218
  return users.map((user) => (0, userIdentity_1.normalizeUserIdentity)(user));
208
219
  }
209
220
  /**
210
- * Get user by ID
211
- */
212
- async getUserById(userId) {
221
+ * Get user by ID.
222
+ *
223
+ * @param userId - The target user's id.
224
+ * @param options.cache - Defaults to `true` (5-minute TTL), matching prior
225
+ * behavior. Pass `{ cache: false }` to force a registry-fresh read: the
226
+ * request bypasses BOTH the cache lookup and the post-fetch cache write
227
+ * (see {@link HttpService.request}'s `cache` handling), so it neither
228
+ * serves nor overwrites any entry already cached for this key — a
229
+ * previously cached response (if one exists) is left in place until its
230
+ * own TTL expires or is explicitly invalidated elsewhere. Use this when a
231
+ * caller must observe a just-written change (e.g. a privacy/consent flag)
232
+ * that would otherwise be masked by the TTL window.
233
+ */
234
+ async getUserById(userId, options) {
213
235
  try {
214
236
  const user = await this.makeRequest('GET', `/users/${userId}`, undefined, {
215
- cache: true,
237
+ cache: options?.cache ?? true,
216
238
  cacheTTL: 5 * 60 * 1000, // 5 minutes cache
217
239
  });
218
240
  return (0, userIdentity_1.normalizeUserIdentity)(user);
@@ -120,7 +120,10 @@ class SessionClient {
120
120
  }
121
121
  }
122
122
  async connectSocket() {
123
- const io = await (0, socketLoader_1.getSocketIO)();
123
+ // Prefer a statically-injected factory (services/auth-sdk bundle
124
+ // socket.io-client as a real dep); fall back to the lazy loader — and warn
125
+ // if THAT yields nothing — only when no factory was injected.
126
+ const io = this.options.socketFactory ?? (await (0, socketLoader_1.getSocketIO)());
124
127
  if (!io) {
125
128
  loggerUtils_1.logger.warn('[SessionClient] no socket.io-client; running REST-only (no realtime sync)', { component: 'SessionClient' });
126
129
  return;
@@ -18,9 +18,15 @@ const sessionClientHost_1 = require("./sessionClientHost");
18
18
  * The host is returned alongside the client (not just the client) so the
19
19
  * caller can call `host.setCurrentAccountId(...)` as the active account
20
20
  * changes.
21
+ *
22
+ * `socketFactory` is the statically-injected `socket.io-client` `io` export.
23
+ * Consumers that bundle socket.io-client as a real dependency pass it so
24
+ * realtime sync never depends on core's lazy dynamic import of a bare
25
+ * specifier (bundler-fragile in Metro/Expo-web and Vite against the published
26
+ * dist). When omitted, the client falls back to the lazy loader.
21
27
  */
22
- function createSessionClient(oxyServices, transport) {
28
+ function createSessionClient(oxyServices, transport, socketFactory) {
23
29
  const host = (0, sessionClientHost_1.createSessionClientHost)(oxyServices);
24
- const client = new SessionClient_1.SessionClient(host, { transport });
30
+ const client = new SessionClient_1.SessionClient(host, { transport, socketFactory });
25
31
  return { client, host };
26
32
  }
@@ -51,6 +51,7 @@ exports.ssoNoSessionKey = ssoNoSessionKey;
51
51
  exports.ssoAttemptedKey = ssoAttemptedKey;
52
52
  exports.ssoPriorSessionKey = ssoPriorSessionKey;
53
53
  exports.ssoSignedOutKey = ssoSignedOutKey;
54
+ exports.ssoOutcomeKey = ssoOutcomeKey;
54
55
  exports.ssoCallbackBootstrapKey = ssoCallbackBootstrapKey;
55
56
  exports.getSsoCallbackBootstrapScript = getSsoCallbackBootstrapScript;
56
57
  exports.ssoNavigate = ssoNavigate;
@@ -84,6 +85,7 @@ const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
84
85
  const CALLBACK_BOOTSTRAP_KEY_PREFIX = 'oxy_sso_callback_bootstrap:';
85
86
  const PRIOR_SESSION_KEY_PREFIX = 'oxy_sso_prior_session:';
86
87
  const SIGNED_OUT_KEY_PREFIX = 'oxy_signed_out:';
88
+ const OUTCOME_KEY_PREFIX = 'oxy_sso_outcome:';
87
89
  /** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
88
90
  function ssoStateKey(origin) {
89
91
  return `${STATE_KEY_PREFIX}${origin}`;
@@ -163,6 +165,26 @@ function ssoPriorSessionKey(origin) {
163
165
  function ssoSignedOutKey(origin) {
164
166
  return `${SIGNED_OUT_KEY_PREFIX}${origin}`;
165
167
  }
168
+ /**
169
+ * Per-origin key holding the LAST consumed SSO-return outcome (`ok` | `none` |
170
+ * `error`, plus an optional machine-readable `reason` on the non-`ok` outcomes).
171
+ *
172
+ * Lives in per-tab `sessionStorage` like the other loop-breaker keys, and for
173
+ * the same reason: a `none`/`error` return HARD-navigates the RP off the
174
+ * internal callback path back to its real destination (a fresh document load),
175
+ * so the outcome an RP wants to render ("the central IdP had no session — show a
176
+ * branded sign-in screen instead of bouncing again") must survive that
177
+ * round-trip. The RP reads it on the destination load to decide whether an
178
+ * AUTOMATIC (guard-driven) sign-in should re-bounce or defer to a user gesture.
179
+ *
180
+ * Written as a small JSON blob (`{kind, reason?}`). Set whenever a return is
181
+ * consumed; cleared on a successful session commit and on an explicit
182
+ * user-gesture sign-in / full sign-out (so a deliberate retry is never
183
+ * suppressed by a prior automatic none/error).
184
+ */
185
+ function ssoOutcomeKey(origin) {
186
+ return `${OUTCOME_KEY_PREFIX}${origin}`;
187
+ }
166
188
  /**
167
189
  * Per-origin marker written by the pre-hydration callback bootstrap.
168
190
  *
@@ -70,6 +70,14 @@ function parseSsoReturnFragment(hash) {
70
70
  result.code = code;
71
71
  }
72
72
  }
73
+ else {
74
+ // A machine-readable reason accompanies a NON-`ok` outcome when the IdP
75
+ // supplies one. Success carries no reason, so it is only read here.
76
+ const reason = params.get('reason');
77
+ if (reason !== null && reason.length > 0) {
78
+ result.reason = reason;
79
+ }
80
+ }
73
81
  return result;
74
82
  }
75
83
  /**