@oxyhq/core 12.8.0 → 12.10.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.
Files changed (52) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/boot/sessionColdBoot.js +16 -3
  3. package/dist/cjs/crypto/identityMarker.js +255 -0
  4. package/dist/cjs/crypto/keyManager.js +933 -106
  5. package/dist/cjs/index.js +11 -4
  6. package/dist/cjs/mixins/OxyServices.auth.js +25 -7
  7. package/dist/cjs/mixins/OxyServices.deviceBoot.js +9 -1
  8. package/dist/cjs/mixins/OxyServices.identityBackup.js +11 -0
  9. package/dist/cjs/mixins/OxyServices.user.js +4 -0
  10. package/dist/cjs/utils/commonsApproval.js +31 -0
  11. package/dist/esm/.tsbuildinfo +1 -1
  12. package/dist/esm/boot/sessionColdBoot.js +16 -3
  13. package/dist/esm/crypto/identityMarker.js +248 -0
  14. package/dist/esm/crypto/keyManager.js +932 -106
  15. package/dist/esm/index.js +4 -2
  16. package/dist/esm/mixins/OxyServices.auth.js +22 -6
  17. package/dist/esm/mixins/OxyServices.deviceBoot.js +9 -1
  18. package/dist/esm/mixins/OxyServices.identityBackup.js +11 -0
  19. package/dist/esm/mixins/OxyServices.user.js +4 -0
  20. package/dist/esm/utils/commonsApproval.js +27 -0
  21. package/dist/types/.tsbuildinfo +1 -1
  22. package/dist/types/boot/sessionColdBoot.d.ts +25 -0
  23. package/dist/types/crypto/identityMarker.d.ts +94 -0
  24. package/dist/types/crypto/keyManager.d.ts +245 -3
  25. package/dist/types/index.d.ts +6 -3
  26. package/dist/types/mixins/OxyServices.auth.d.ts +31 -6
  27. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +8 -0
  28. package/dist/types/utils/commonsApproval.d.ts +13 -0
  29. package/package.json +1 -1
  30. package/src/boot/__tests__/sessionColdBoot.test.ts +113 -0
  31. package/src/boot/sessionColdBoot.ts +42 -3
  32. package/src/crypto/__tests__/identityMocks.ts +125 -0
  33. package/src/crypto/__tests__/keyManager.atomicity.test.ts +79 -94
  34. package/src/crypto/__tests__/keyManager.cacheSafety.test.ts +175 -0
  35. package/src/crypto/__tests__/keyManager.identityStatus.test.ts +217 -0
  36. package/src/crypto/__tests__/keyManager.recoveryLadder.test.ts +179 -0
  37. package/src/crypto/__tests__/keyManager.recoveryMnemonic.test.ts +138 -0
  38. package/src/crypto/__tests__/keyManager.storageMigration.test.ts +227 -0
  39. package/src/crypto/__tests__/keyManager.test.ts +77 -87
  40. package/src/crypto/identityMarker.ts +291 -0
  41. package/src/crypto/keyManager.ts +1125 -105
  42. package/src/index.ts +14 -2
  43. package/src/mixins/OxyServices.auth.ts +40 -12
  44. package/src/mixins/OxyServices.deviceBoot.ts +9 -1
  45. package/src/mixins/OxyServices.identityBackup.ts +13 -0
  46. package/src/mixins/OxyServices.user.ts +4 -0
  47. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +4 -2
  48. package/src/mixins/__tests__/commonsSignIn.test.ts +84 -1
  49. package/src/mixins/__tests__/identityBackup.test.ts +57 -0
  50. package/src/mixins/__tests__/privacyCacheInvalidation.test.ts +6 -0
  51. package/src/utils/__tests__/commonsApproval.test.ts +60 -0
  52. package/src/utils/commonsApproval.ts +39 -0
package/dist/cjs/index.js CHANGED
@@ -18,10 +18,10 @@
18
18
  * If a symbol does not appear here, it is NOT part of the public API.
19
19
  */
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- 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.AssetUrlResolutionError = exports.OxyServices = void 0;
22
- 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 = exports.getNativeLanguageName = void 0;
23
- 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 = exports.validateRequiredFields = 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 = exports.DEFAULT_OAUTH_SCOPE = void 0;
21
+ 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.updateIdentityMarker = exports.readIdentityMarker = exports.IdentityUnavailableError = 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.parseCommonsApprovalExpiresAt = exports.getCommonsApprovalBlockingReason = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.AssetUrlResolutionError = exports.OxyServices = void 0;
22
+ 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 = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.isSupportedLocale = exports.normalizeLocale = exports.getBaseLanguage = void 0;
23
+ 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 = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = 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 = exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = void 0;
25
25
  // Ensure crypto polyfills are loaded before anything else
26
26
  require("./crypto/polyfill");
27
27
  // ---------------------------------------------------------------------------
@@ -40,6 +40,9 @@ Object.defineProperty(exports, "oxyClient", { enumerable: true, get: function ()
40
40
  // ---------------------------------------------------------------------------
41
41
  var OxyServices_auth_1 = require("./mixins/OxyServices.auth");
42
42
  Object.defineProperty(exports, "ServiceCredentialMismatchError", { enumerable: true, get: function () { return OxyServices_auth_1.ServiceCredentialMismatchError; } });
43
+ var commonsApproval_1 = require("./utils/commonsApproval");
44
+ Object.defineProperty(exports, "getCommonsApprovalBlockingReason", { enumerable: true, get: function () { return commonsApproval_1.getCommonsApprovalBlockingReason; } });
45
+ Object.defineProperty(exports, "parseCommonsApprovalExpiresAt", { enumerable: true, get: function () { return commonsApproval_1.parseCommonsApprovalExpiresAt; } });
43
46
  var OxyServices_appData_1 = require("./mixins/OxyServices.appData");
44
47
  Object.defineProperty(exports, "OxyAppDataIdentifierError", { enumerable: true, get: function () { return OxyServices_appData_1.OxyAppDataIdentifierError; } });
45
48
  // ---------------------------------------------------------------------------
@@ -100,6 +103,10 @@ var keyManager_1 = require("./crypto/keyManager");
100
103
  Object.defineProperty(exports, "KeyManager", { enumerable: true, get: function () { return keyManager_1.KeyManager; } });
101
104
  Object.defineProperty(exports, "IdentityAlreadyExistsError", { enumerable: true, get: function () { return keyManager_1.IdentityAlreadyExistsError; } });
102
105
  Object.defineProperty(exports, "IdentityPersistError", { enumerable: true, get: function () { return keyManager_1.IdentityPersistError; } });
106
+ Object.defineProperty(exports, "IdentityUnavailableError", { enumerable: true, get: function () { return keyManager_1.IdentityUnavailableError; } });
107
+ var identityMarker_1 = require("./crypto/identityMarker");
108
+ Object.defineProperty(exports, "readIdentityMarker", { enumerable: true, get: function () { return identityMarker_1.readIdentityMarker; } });
109
+ Object.defineProperty(exports, "updateIdentityMarker", { enumerable: true, get: function () { return identityMarker_1.updateIdentityMarker; } });
103
110
  var signatureService_1 = require("./crypto/signatureService");
104
111
  Object.defineProperty(exports, "SignatureService", { enumerable: true, get: function () { return signatureService_1.SignatureService; } });
105
112
  var recoveryPhrase_1 = require("./crypto/recoveryPhrase");
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ServiceCredentialMismatchError = void 0;
3
+ exports.ServiceCredentialMismatchError = exports.parseCommonsApprovalExpiresAt = exports.getCommonsApprovalBlockingReason = void 0;
4
4
  exports.OxyServicesAuthMixin = OxyServicesAuthMixin;
5
5
  const contracts_1 = require("@oxyhq/contracts");
6
+ var commonsApproval_1 = require("../utils/commonsApproval");
7
+ Object.defineProperty(exports, "getCommonsApprovalBlockingReason", { enumerable: true, get: function () { return commonsApproval_1.getCommonsApprovalBlockingReason; } });
8
+ Object.defineProperty(exports, "parseCommonsApprovalExpiresAt", { enumerable: true, get: function () { return commonsApproval_1.parseCommonsApprovalExpiresAt; } });
6
9
  const OxyServices_errors_1 = require("../OxyServices.errors");
7
10
  const keyManager_1 = require("../crypto/keyManager");
8
11
  const signatureService_1 = require("../crypto/signatureService");
@@ -298,12 +301,16 @@ function OxyServicesAuthMixin(Base) {
298
301
  * The client must sign this challenge with their private key
299
302
  *
300
303
  * @param publicKey - The user's public key
304
+ * @param requestOptions - Optional per-call transport overrides (`retry`,
305
+ * `timeout`). Interactive callers omit it (defaults keep retries); the
306
+ * cold-boot `shared-key-signin` step passes `{ retry: false }` so a slow
307
+ * network cannot multiply boot latency via the inner retry loop.
301
308
  */
302
- async requestChallenge(publicKey) {
309
+ async requestChallenge(publicKey, requestOptions) {
303
310
  try {
304
311
  return await this.makeRequest('POST', '/auth/challenge', {
305
312
  publicKey,
306
- }, { cache: false });
313
+ }, { cache: false, ...requestOptions });
307
314
  }
308
315
  catch (error) {
309
316
  throw this.handleError(error);
@@ -318,8 +325,12 @@ function OxyServicesAuthMixin(Base) {
318
325
  * @param timestamp - Timestamp when the signature was created
319
326
  * @param deviceName - Optional device name
320
327
  * @param deviceFingerprint - Optional device fingerprint
328
+ * @param requestOptions - Optional per-call transport overrides (`retry`,
329
+ * `timeout`). Interactive callers omit it (defaults keep retries); the
330
+ * cold-boot `shared-key-signin` step passes `{ retry: false }` so a slow
331
+ * network cannot multiply boot latency via the inner retry loop.
321
332
  */
322
- async verifyChallenge(publicKey, challenge, signature, timestamp, deviceName, deviceFingerprint) {
333
+ async verifyChallenge(publicKey, challenge, signature, timestamp, deviceName, deviceFingerprint, requestOptions) {
323
334
  try {
324
335
  const res = await this.makeRequest('POST', '/auth/verify', {
325
336
  publicKey,
@@ -328,7 +339,7 @@ function OxyServicesAuthMixin(Base) {
328
339
  timestamp,
329
340
  deviceName,
330
341
  deviceFingerprint,
331
- }, { cache: false });
342
+ }, { cache: false, ...requestOptions });
332
343
  // Plant the freshly-minted tokens, mirroring `claimSessionByToken`.
333
344
  // `/auth/verify` returns the first access token (and refresh token) in
334
345
  // its body, so installing it here means callers get an authenticated
@@ -466,6 +477,13 @@ function OxyServicesAuthMixin(Base) {
466
477
  *
467
478
  * The cold-boot wiring that CALLS this lives in `OxyContext`
468
479
  * (`@oxyhq/services`); this method just performs the exchange.
480
+ *
481
+ * @param opts.requestOptions - Optional per-call transport overrides
482
+ * (`retry`, `timeout`) forwarded to BOTH the `requestChallenge` and
483
+ * `verifyChallenge` round-trips. Interactive flows omit it (defaults keep
484
+ * retries); the cold-boot `shared-key-signin` step passes `{ retry: false }`
485
+ * so this network step cannot multiply boot latency via the inner retry
486
+ * loop. The token-refresh scheduler / 401 lane still retry later.
469
487
  */
470
488
  async signInWithSharedIdentity(opts = {}) {
471
489
  try {
@@ -479,10 +497,10 @@ function OxyServicesAuthMixin(Base) {
479
497
  if (!sharedPublicKey) {
480
498
  return null;
481
499
  }
482
- const { challenge } = await this.requestChallenge(sharedPublicKey);
500
+ const { challenge } = await this.requestChallenge(sharedPublicKey, opts.requestOptions);
483
501
  const signed = await signatureService_1.SignatureService.signChallengeWithSharedKey(challenge);
484
502
  // `signed.challenge` carries the SIGNATURE (mirrors `signChallenge`).
485
- return await this.verifyChallenge(signed.publicKey, challenge, signed.challenge, signed.timestamp, opts.deviceName, opts.deviceFingerprint);
503
+ return await this.verifyChallenge(signed.publicKey, challenge, signed.challenge, signed.timestamp, opts.deviceName, opts.deviceFingerprint, opts.requestOptions);
486
504
  }
487
505
  catch (error) {
488
506
  throw this.handleError(error);
@@ -32,11 +32,19 @@ function OxyServicesDeviceBootMixin(Base) {
32
32
  * `no_active_session`) to decide whether to drop the secret and fall back or
33
33
  * resolve signed-out.
34
34
  *
35
+ * `retry: false`: the mint is a single logical attempt. The proactive
36
+ * token-refresh scheduler and the reactive 401 lane already own backoff and
37
+ * re-arm, so `HttpService`'s inner retry loop here would only multiply the
38
+ * mint's latency on a slow/black-hole network (3 retries × 5s timeout ≈ 20s
39
+ * per lane) with no correctness benefit — it is the dominant term in the cold
40
+ * boot's worst-case time-to-route. A transient failure surfaces once and the
41
+ * scheduler/401 path retries it later.
42
+ *
35
43
  * @throws if the response does not match {@link deviceTokenMintResponseSchema}.
36
44
  */
37
45
  async mintFromDeviceSecret(deviceId, deviceSecret) {
38
46
  try {
39
- const res = await this.makeRequest('POST', '/session/device/token', { deviceId, deviceSecret }, { cache: false, skipAuth: true });
47
+ const res = await this.makeRequest('POST', '/session/device/token', { deviceId, deviceSecret }, { cache: false, skipAuth: true, retry: false });
40
48
  const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceTokenMintResponseSchema, res);
41
49
  if (!parsed) {
42
50
  throw new Error('session/device/token returned an unexpected response shape');
@@ -141,6 +141,17 @@ function OxyServicesIdentityBackupMixin(Base) {
141
141
  const aad = buildBackupAad(envelope.version, envelope.publicKeyHint);
142
142
  const plaintext = (0, aead_1.decryptAead)(backupKey, fromHex(envelope.nonce), fromHex(envelope.ciphertext), aad);
143
143
  const payload = JSON.parse(new TextDecoder().decode(plaintext));
144
+ if (!payload.privateKey || !payload.publicKey) {
145
+ throw new Error('Backup payload is missing key material');
146
+ }
147
+ const derivedFromPhrase = await recoveryPhrase_1.RecoveryPhraseService.derivePublicKeyFromPhrase(phrase);
148
+ const derivedFromPrivate = keyManager_1.KeyManager.derivePublicKey(payload.privateKey);
149
+ const phrasePk = derivedFromPhrase.toLowerCase();
150
+ const payloadPk = payload.publicKey.toLowerCase();
151
+ const privatePk = derivedFromPrivate.toLowerCase();
152
+ if (phrasePk !== payloadPk || privatePk !== payloadPk) {
153
+ throw new Error('Backup payload does not match the recovery phrase');
154
+ }
144
155
  // Persist the recovered key. Native-only; refuses to clobber a different
145
156
  // identity unless overwrite — the IdentityAlreadyExistsError propagates.
146
157
  return await keyManager_1.KeyManager.importKeyPair(payload.privateKey, {
@@ -423,6 +423,10 @@ function OxyServicesUserMixin(Base) {
423
423
  const result = await this.makeRequest('PATCH', `/privacy/${id}/privacy`, settings, {
424
424
  cache: false,
425
425
  });
426
+ this.clearCacheByPrefix('GET:/session/user/');
427
+ this.clearCacheByPrefix('GET:/users/me');
428
+ this.clearCacheByPrefix('GET:/profiles/username/');
429
+ this.clearCacheEntry(`GET:/users/${id}`);
426
430
  this.clearCacheEntry(`GET:/privacy/${id}/privacy`);
427
431
  return result;
428
432
  }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCommonsApprovalBlockingReason = getCommonsApprovalBlockingReason;
4
+ exports.parseCommonsApprovalExpiresAt = parseCommonsApprovalExpiresAt;
5
+ /**
6
+ * Returns a user-facing blocking reason when an approval payload must not be
7
+ * shown as actionable, or `null` when the request is still pending and valid.
8
+ */
9
+ function getCommonsApprovalBlockingReason(info) {
10
+ if (!info.application?.id) {
11
+ return 'The requesting application could not be resolved.';
12
+ }
13
+ if (info.status !== 'pending') {
14
+ return 'This sign-in request is invalid, already used, or expired.';
15
+ }
16
+ const expiresAtMs = parseCommonsApprovalExpiresAt(info.expiresAt);
17
+ if (expiresAtMs !== null && expiresAtMs < Date.now()) {
18
+ return 'This sign-in request has expired. Ask for a new QR code.';
19
+ }
20
+ return null;
21
+ }
22
+ /** Normalize API `expiresAt` (number or ISO string) to epoch ms. */
23
+ function parseCommonsApprovalExpiresAt(expiresAt) {
24
+ if (typeof expiresAt === 'number' && Number.isFinite(expiresAt))
25
+ return expiresAt;
26
+ if (typeof expiresAt === 'string') {
27
+ const ms = Date.parse(expiresAt);
28
+ return Number.isFinite(ms) ? ms : null;
29
+ }
30
+ return null;
31
+ }