@oxyhq/core 12.3.0 → 12.4.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,8 +20,8 @@
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.isSupportedLocale = exports.normalizeLocale = exports.getBaseLanguage = exports.FALLBACK_LOCALE = exports.SUPPORTED_LANGUAGES = exports.TopicSource = exports.TopicType = exports.SECURITY_EVENT_SEVERITY_MAP = exports.DeviceManager = exports.deriveSharedSecret = exports.AEAD_NONCE_LENGTH = exports.AEAD_KEY_LENGTH = exports.decryptAead = exports.encryptAead = exports.hkdfSha256 = 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.ORGANIZATION_CATEGORIES = exports.normalizeProfileLinks = exports.getNormalizedUserHandle = exports.getCanonicalUserHandle = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = exports.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
22
22
  exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = 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.isWebBrowser = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.getPrimaryLanguage = exports.getUserLanguages = exports.isRTLLocale = void 0;
23
- exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.OXY_AUTHORIZE_URL = exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.isOxyRpOrigin = exports.CENTRAL_IDP_APEX = exports.registrableApex = exports.getAccountColor = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.isDev = exports.consoleSink = exports.resetLoggerConfig = exports.getLoggerConfig = exports.configureLogger = exports.createLogger = exports.logger = exports.normalizeMultilineText = exports.normalizeInlineText = 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.isValidDisplayName = exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = void 0;
24
- exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.redeemHubTicketOnHub = exports.syncHubAfterSignIn = exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.normalizeOAuthRedirectUri = exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = void 0;
23
+ exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.isOxyRpOrigin = exports.CENTRAL_IDP_APEX = exports.registrableApex = exports.getAccountColor = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.isDev = exports.consoleSink = exports.resetLoggerConfig = exports.getLoggerConfig = exports.configureLogger = exports.createLogger = exports.logger = exports.normalizeMultilineText = exports.normalizeInlineText = 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.DISPLAY_NAME_ORPHANED_MARK_SOURCE = exports.DISPLAY_NAME_DISALLOWED_SOURCE = exports.DISPLAY_NAME_ALLOWED_SCRIPTS = exports.isValidDisplayName = exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = void 0;
24
+ exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshDeviceSecretArm = exports.refreshPersistedSession = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.redeemHubTicketOnHub = exports.syncHubAfterSignIn = exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.normalizeOAuthRedirectUri = exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.OXY_AUTHORIZE_URL = void 0;
25
25
  // Ensure crypto polyfills are loaded before anything else
26
26
  require("./crypto/polyfill");
27
27
  // ---------------------------------------------------------------------------
@@ -222,6 +222,9 @@ Object.defineProperty(exports, "isValidEmail", { enumerable: true, get: function
222
222
  Object.defineProperty(exports, "isValidUsername", { enumerable: true, get: function () { return validationUtils_1.isValidUsername; } });
223
223
  Object.defineProperty(exports, "isValidPassword", { enumerable: true, get: function () { return validationUtils_1.isValidPassword; } });
224
224
  Object.defineProperty(exports, "isValidDisplayName", { enumerable: true, get: function () { return validationUtils_1.isValidDisplayName; } });
225
+ Object.defineProperty(exports, "DISPLAY_NAME_ALLOWED_SCRIPTS", { enumerable: true, get: function () { return validationUtils_1.DISPLAY_NAME_ALLOWED_SCRIPTS; } });
226
+ Object.defineProperty(exports, "DISPLAY_NAME_DISALLOWED_SOURCE", { enumerable: true, get: function () { return validationUtils_1.DISPLAY_NAME_DISALLOWED_SOURCE; } });
227
+ Object.defineProperty(exports, "DISPLAY_NAME_ORPHANED_MARK_SOURCE", { enumerable: true, get: function () { return validationUtils_1.DISPLAY_NAME_ORPHANED_MARK_SOURCE; } });
225
228
  Object.defineProperty(exports, "isRequiredString", { enumerable: true, get: function () { return validationUtils_1.isRequiredString; } });
226
229
  Object.defineProperty(exports, "isRequiredNumber", { enumerable: true, get: function () { return validationUtils_1.isRequiredNumber; } });
227
230
  Object.defineProperty(exports, "isRequiredBoolean", { enumerable: true, get: function () { return validationUtils_1.isRequiredBoolean; } });
@@ -75,9 +75,20 @@ async function refreshDeviceSecretArm(deps) {
75
75
  // Structural read (not `instanceof Error`): the thrown value can be a
76
76
  // plain ApiError-shaped object or come from another realm.
77
77
  const message = error?.message;
78
- return typeof message === 'string' && message.includes('no_active_session')
79
- ? { status: 'no-session' }
80
- : { status: 'invalid-secret' };
78
+ const body = typeof message === 'string' ? message : '';
79
+ // ONLY the server's explicit `invalid_device_secret` proves the presented
80
+ // secret is bad and may clear the durable device credential. `no_active_session`
81
+ // is an authoritative signed-out. ANY OTHER 401 — a middleware/CSRF/proxy 401,
82
+ // an ALB/starting-instance 401, a CORS error page, etc., all common during a
83
+ // deploy/restart window — is NOT proof the secret diverged: treat it as
84
+ // transient and KEEP the credential so a later attempt self-heals. Wiping the
85
+ // credential on an ambiguous 401 is what logged users out on every deploy,
86
+ // ecosystem-wide.
87
+ if (body.includes('invalid_device_secret'))
88
+ return { status: 'invalid-secret' };
89
+ if (body.includes('no_active_session'))
90
+ return { status: 'no-session' };
91
+ return { status: 'transient' };
81
92
  }
82
93
  return { status: 'transient' };
83
94
  }
@@ -3,7 +3,7 @@
3
3
  * Validation utilities for common data validation patterns
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = void 0;
6
+ exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE = exports.DISPLAY_NAME_DISALLOWED_SOURCE = exports.DISPLAY_NAME_ALLOWED_SCRIPTS = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = void 0;
7
7
  exports.isValidEmail = isValidEmail;
8
8
  exports.isValidUsername = isValidUsername;
9
9
  exports.isValidPassword = isValidPassword;
@@ -57,7 +57,11 @@ function isValidPassword(password) {
57
57
  * Display-name character policy.
58
58
  *
59
59
  * A clean display name is composed ONLY of:
60
- * - letters of any script (`\p{L}`),
60
+ * - letters from a curated ALLOWLIST of scripts that real names use
61
+ * ({@link DISPLAY_NAME_ALLOWED_SCRIPTS}) — NOT `\p{L}` (letters of ANY
62
+ * script), which admits decorative / historic / limited-use scripts whose
63
+ * characters are `\p{L}` yet never appear in a real name (e.g. `ᯅ` U+1BC5
64
+ * Batak, Runic, Deseret, dingbat letters),
61
65
  * - combining marks / accents (`\p{M}`, e.g. the acute accent in a decomposed
62
66
  * "é"),
63
67
  * - Unicode space separators (`\p{Zs}`: the ASCII space, NBSP, ideographic
@@ -66,32 +70,65 @@ function isValidPassword(password) {
66
70
  * - the straight apostrophe (`'`, e.g. "O'Brien").
67
71
  *
68
72
  * Everything else is rejected: emoji (🐧), symbols (⁂ ⏚), `:emoji:` shortcodes,
69
- * digits, hyphens, dots, control whitespace (tab/newline/CR), and any other
70
- * punctuation. The allowed set `\p{L}\p{M}\p{Zs}'` explicitly EXCLUDES `<`, `>`,
71
- * `&`, and `"`, so a value that passes this predicate can never contain an
72
- * HTML/XSS vector.
73
+ * digits, hyphens, dots, control whitespace (tab/newline/CR), letters from
74
+ * non-allowlisted scripts, and any other punctuation. The allowed set never
75
+ * includes `<`, `>`, `&`, or `"`, so a value that passes this predicate can
76
+ * never contain an HTML/XSS vector.
73
77
  *
74
- * This is the SINGLE definition of the policy, shared between the API 400-gate
75
- * (`@oxyhq/api` `displayNameSanitize.ts`) and client-side inline validation
76
- * (the RN profile editor) so the two can never drift. It is platform-agnostic
78
+ * The allowlist is expressed with Unicode Script_Extensions (`\p{scx=…}`)
79
+ * escapes so a letter shared by several scripts (e.g. a Han ideograph used in
80
+ * both Chinese and Japanese) still matches. It is the set of scripts Unicode
81
+ * UTS #39 marks "Recommended" for general interchange / identifiers, plus
82
+ * Cherokee and Mongolian (both in real modern name use). "Common" script is
83
+ * deliberately EXCLUDED — that is where ASCII digits and general punctuation
84
+ * live, and this policy excludes those; the space separators, combining marks,
85
+ * and apostrophe a name needs are added back explicitly. Limited-use / excluded
86
+ * / historic scripts (Batak, Runic, Deseret, Adlam, …) are simply absent.
87
+ *
88
+ * This is the SINGLE definition of the policy: the character-class sources below
89
+ * are the ONE source of truth, shared between the API strip/gate
90
+ * (`@oxyhq/api` `displayNameSanitize.ts` builds its global-flag patterns from
91
+ * them) and client-side inline validation (the RN profile editor via
92
+ * {@link isValidDisplayName}) so the two can never drift. It is platform-agnostic
77
93
  * (no react/react-native/expo).
78
94
  */
79
95
  /**
80
- * Single test for the presence of a disallowed character (non-global). The
81
- * whitespace class is `\p{Zs}` (space separators only), NOT `\s` the latter
82
- * would admit tab/newline/carriage return, which break layout and enable
83
- * multi-line spoofing.
96
+ * The curated allowlist of Unicode scripts permitted in a display name, as a
97
+ * character-class body of Script_Extensions (`scx`) property escapes. Ordered by
98
+ * rough script family for readability; order has no semantic effect.
99
+ */
100
+ exports.DISPLAY_NAME_ALLOWED_SCRIPTS = '\\p{scx=Latin}\\p{scx=Greek}\\p{scx=Cyrillic}\\p{scx=Armenian}' +
101
+ '\\p{scx=Hebrew}\\p{scx=Arabic}\\p{scx=Thaana}\\p{scx=Devanagari}' +
102
+ '\\p{scx=Bengali}\\p{scx=Gurmukhi}\\p{scx=Gujarati}\\p{scx=Oriya}' +
103
+ '\\p{scx=Tamil}\\p{scx=Telugu}\\p{scx=Kannada}\\p{scx=Malayalam}' +
104
+ '\\p{scx=Sinhala}\\p{scx=Thai}\\p{scx=Lao}\\p{scx=Tibetan}' +
105
+ '\\p{scx=Myanmar}\\p{scx=Georgian}\\p{scx=Hangul}\\p{scx=Ethiopic}' +
106
+ '\\p{scx=Cherokee}\\p{scx=Khmer}\\p{scx=Mongolian}\\p{scx=Hiragana}' +
107
+ '\\p{scx=Katakana}\\p{scx=Bopomofo}\\p{scx=Han}';
108
+ /**
109
+ * Source of the disallowed-character pattern: the negation of the full allowed
110
+ * set (allowlisted scripts + combining marks `\p{M}` + space separators `\p{Zs}`
111
+ * + the straight apostrophe). Consumers compile this with the `u` flag (and `g`
112
+ * for a global strip). The whitespace class is `\p{Zs}` (space separators only),
113
+ * NOT `\s` — the latter would admit tab/newline/carriage return, which break
114
+ * layout and enable multi-line spoofing.
84
115
  */
85
- const DISALLOWED_PROBE = /[^\p{L}\p{M}\p{Zs}']/u;
116
+ exports.DISPLAY_NAME_DISALLOWED_SOURCE = `[^${exports.DISPLAY_NAME_ALLOWED_SCRIPTS}\\p{M}\\p{Zs}']`;
86
117
  /**
87
- * Single test for the presence of an orphaned combining mark (non-global) a
88
- * `\p{M}` not attached to a base letter (string start, whitespace, the
89
- * apostrophe, or a position vacated by a stripped character). A mark preceded by
90
- * `\p{L}` (a base letter, e.g. the decomposed accent in "Renée") or by another
91
- * `\p{M}` (a multi-mark cluster) is NOT matched because the negative lookbehind
92
- * fails at its position.
118
+ * Source of the orphaned combining-mark pattern: a run of `\p{M}` NOT attached
119
+ * to a base letter (preceded by string start, whitespace, the apostrophe, or a
120
+ * position vacated by a stripped character). A mark preceded by `\p{L}` (a base
121
+ * letter, e.g. the decomposed accent in "Renée") or by another `\p{M}` (a
122
+ * multi-mark cluster) is NOT matched because the negative lookbehind fails at its
123
+ * position. Used both as a non-global probe (`.test`) and, with the `g` flag, to
124
+ * strip whole orphaned runs. The lookbehind intentionally still uses the broad
125
+ * `\p{L}` so that a mark riding on an allowlisted base letter is preserved.
93
126
  */
94
- const ORPHANED_MARK_PROBE = /(?<![\p{L}\p{M}])\p{M}/u;
127
+ exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE = '(?<![\\p{L}\\p{M}])\\p{M}+';
128
+ /** Non-global probe for the presence of a disallowed character. */
129
+ const DISALLOWED_PROBE = new RegExp(exports.DISPLAY_NAME_DISALLOWED_SOURCE, 'u');
130
+ /** Non-global probe for the presence of an orphaned combining mark. */
131
+ const ORPHANED_MARK_PROBE = new RegExp(exports.DISPLAY_NAME_ORPHANED_MARK_SOURCE, 'u');
95
132
  /**
96
133
  * Whether `raw` already satisfies the display-name policy, i.e. it contains no
97
134
  * disallowed characters AND no orphaned combining marks. Used to REJECT native