@oxyhq/core 9.1.0 → 9.2.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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/.tsbuildinfo +1 -1
  3. package/dist/cjs/boot/{coldBootV2.js → sessionColdBoot.js} +15 -2
  4. package/dist/cjs/index.js +27 -6
  5. package/dist/cjs/mixins/OxyServices.accounts.js +3 -0
  6. package/dist/cjs/mixins/OxyServices.auth.js +61 -0
  7. package/dist/cjs/mixins/OxyServices.deviceBoot.js +29 -1
  8. package/dist/cjs/server/index.js +8 -1
  9. package/dist/cjs/session/SessionClient.js +57 -25
  10. package/dist/cjs/session/accountDialogController.js +21 -9
  11. package/dist/cjs/session/authStateStore.js +13 -7
  12. package/dist/cjs/session/hubSync.js +55 -0
  13. package/dist/cjs/session/sessionClientHost.js +5 -0
  14. package/dist/cjs/utils/coldBoot.js +1 -1
  15. package/dist/cjs/utils/oauthPkce.js +65 -1
  16. package/dist/cjs/utils/officialOrigins.js +128 -0
  17. package/dist/esm/.tsbuildinfo +1 -1
  18. package/dist/esm/boot/{coldBootV2.js → sessionColdBoot.js} +15 -2
  19. package/dist/esm/index.js +5 -2
  20. package/dist/esm/mixins/OxyServices.accounts.js +1 -0
  21. package/dist/esm/mixins/OxyServices.auth.js +61 -0
  22. package/dist/esm/mixins/OxyServices.deviceBoot.js +30 -2
  23. package/dist/esm/server/index.js +1 -0
  24. package/dist/esm/session/SessionClient.js +57 -25
  25. package/dist/esm/session/accountDialogController.js +21 -9
  26. package/dist/esm/session/authStateStore.js +13 -7
  27. package/dist/esm/session/hubSync.js +51 -0
  28. package/dist/esm/session/sessionClientHost.js +5 -0
  29. package/dist/esm/utils/coldBoot.js +1 -1
  30. package/dist/esm/utils/oauthPkce.js +60 -0
  31. package/dist/esm/utils/officialOrigins.js +119 -0
  32. package/dist/types/.tsbuildinfo +1 -1
  33. package/dist/types/boot/{coldBootV2.d.ts → sessionColdBoot.d.ts} +1 -1
  34. package/dist/types/index.d.ts +9 -5
  35. package/dist/types/mixins/OxyServices.accounts.d.ts +8 -0
  36. package/dist/types/mixins/OxyServices.auth.d.ts +15 -0
  37. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +6 -2
  38. package/dist/types/models/interfaces.d.ts +3 -1
  39. package/dist/types/models/session.d.ts +6 -0
  40. package/dist/types/server/index.d.ts +1 -0
  41. package/dist/types/session/SessionClient.d.ts +6 -0
  42. package/dist/types/session/accountDialogController.d.ts +9 -1
  43. package/dist/types/session/authStateStore.d.ts +1 -1
  44. package/dist/types/session/hubSync.d.ts +20 -0
  45. package/dist/types/session/sessionClientHost.d.ts +2 -1
  46. package/dist/types/utils/coldBoot.d.ts +2 -2
  47. package/dist/types/utils/oauthPkce.d.ts +27 -0
  48. package/dist/types/utils/officialOrigins.d.ts +17 -0
  49. package/package.json +2 -2
  50. package/src/boot/__tests__/{coldBootV2.test.ts → sessionColdBoot.test.ts} +21 -1
  51. package/src/boot/{coldBootV2.ts → sessionColdBoot.ts} +15 -2
  52. package/src/index.ts +30 -3
  53. package/src/mixins/OxyServices.accounts.ts +9 -0
  54. package/src/mixins/OxyServices.auth.ts +72 -1
  55. package/src/mixins/OxyServices.deviceBoot.ts +46 -1
  56. package/src/models/interfaces.ts +3 -1
  57. package/src/models/session.ts +6 -0
  58. package/src/server/index.ts +8 -0
  59. package/src/session/SessionClient.ts +68 -26
  60. package/src/session/__tests__/SessionClient.additive.test.ts +1 -0
  61. package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +1 -0
  62. package/src/session/__tests__/SessionClient.diagnostics.test.ts +1 -0
  63. package/src/session/__tests__/SessionClient.rest.test.ts +1 -0
  64. package/src/session/__tests__/SessionClient.socket.test.ts +1 -0
  65. package/src/session/__tests__/SessionClient.socketFactory.test.ts +1 -0
  66. package/src/session/__tests__/SessionClient.state.test.ts +1 -0
  67. package/src/session/__tests__/accountDialogController.test.ts +46 -7
  68. package/src/session/__tests__/sessionIntegration.test.ts +7 -0
  69. package/src/session/accountDialogController.ts +45 -11
  70. package/src/session/authStateStore.ts +18 -9
  71. package/src/session/hubSync.ts +79 -0
  72. package/src/session/sessionClientHost.ts +10 -2
  73. package/src/utils/__tests__/officialOrigins.test.ts +74 -0
  74. package/src/utils/coldBoot.ts +2 -2
  75. package/src/utils/oauthPkce.ts +71 -0
  76. package/src/utils/officialOrigins.ts +124 -0
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runSessionColdBoot = runSessionColdBoot;
4
4
  /**
5
- * coldBootV2 — one device-first cold boot for every consumer.
5
+ * Device-first session cold boot for every consumer.
6
6
  *
7
7
  * On a fresh page load / app launch this resolves the device's session in a
8
8
  * deterministic order, built on the pure `runColdBoot` primitive. It NEVER
@@ -95,7 +95,7 @@ async function runSessionColdBoot(opts) {
95
95
  return { kind: 'skip' };
96
96
  }
97
97
  // Transient (network / 5xx): keep the secret; a later attempt can succeed.
98
- loggerUtils_1.logger.debug('device-secret mint failed (transient) — keeping secret', { component: 'coldBootV2', method: 'device-secret-mint' }, error);
98
+ loggerUtils_1.logger.debug('device-secret mint failed (transient) — keeping secret', { component: 'sessionColdBoot', method: 'device-secret-mint' }, error);
99
99
  return { kind: 'skip' };
100
100
  }
101
101
  },
@@ -109,6 +109,19 @@ async function runSessionColdBoot(opts) {
109
109
  if (!session?.accessToken) {
110
110
  return { kind: 'skip' };
111
111
  }
112
+ // `verifyChallenge` mints a rotating deviceSecret; persist it so the next
113
+ // boot can use the faster device-secret-mint lane (sockets + tab-focus
114
+ // re-mint depend on the credential being in the store).
115
+ if (session.deviceId && session.deviceSecret) {
116
+ await store.save({
117
+ sessionId: session.sessionId,
118
+ userId: session.user.id,
119
+ deviceId: session.deviceId,
120
+ deviceSecret: session.deviceSecret,
121
+ accessToken: session.accessToken,
122
+ expiresAt: session.expiresAt,
123
+ });
124
+ }
112
125
  return {
113
126
  kind: 'session',
114
127
  session: {
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.lightenColor = exports.darkenColor = exports.isWebBrowser = 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.verifyPublicCardAttestation = exports.parseAttestPayload = exports.parseIdPayload = exports.buildUserDid = 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
- 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 = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = void 0;
23
- exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.OXY_AUTHORIZE_URL = exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.CENTRAL_IDP_APEX = exports.registrableApex = exports.getAccountColor = 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.isValidDisplayName = void 0;
24
- exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshPersistedSession = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = void 0;
21
+ exports.darkenColor = exports.isWebBrowser = 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.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
+ 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 = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = void 0;
23
+ 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 = exports.CENTRAL_IDP_APEX = exports.registrableApex = exports.getAccountColor = 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.isValidDisplayName = exports.isValidPassword = void 0;
24
+ exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = 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.isIdpHubOrigin = exports.buildHubSyncUrl = void 0;
25
25
  // Ensure crypto polyfills are loaded before anything else
26
26
  require("./crypto/polyfill");
27
27
  // ---------------------------------------------------------------------------
@@ -53,6 +53,8 @@ Object.defineProperty(exports, "getCanonicalUserHandle", { enumerable: true, get
53
53
  Object.defineProperty(exports, "getNormalizedUserHandle", { enumerable: true, get: function () { return userHandle_1.getNormalizedUserHandle; } });
54
54
  var profileLinks_1 = require("./utils/profileLinks");
55
55
  Object.defineProperty(exports, "normalizeProfileLinks", { enumerable: true, get: function () { return profileLinks_1.normalizeProfileLinks; } });
56
+ var OxyServices_accounts_1 = require("./mixins/OxyServices.accounts");
57
+ Object.defineProperty(exports, "ORGANIZATION_CATEGORIES", { enumerable: true, get: function () { return OxyServices_accounts_1.ORGANIZATION_CATEGORIES; } });
56
58
  // ---------------------------------------------------------------------------
57
59
  // Self-sovereign identity (DID, signed records, auth-method ↔ VM mapping,
58
60
  // verified domains). Wire shapes (DidDocument, SignedRecordEnvelope,
@@ -278,6 +280,25 @@ Object.defineProperty(exports, "generateOAuthState", { enumerable: true, get: fu
278
280
  Object.defineProperty(exports, "generatePkcePair", { enumerable: true, get: function () { return oauthPkce_1.generatePkcePair; } });
279
281
  Object.defineProperty(exports, "DEFAULT_OAUTH_SCOPE", { enumerable: true, get: function () { return oauthPkce_1.DEFAULT_OAUTH_SCOPE; } });
280
282
  Object.defineProperty(exports, "OXY_AUTHORIZE_URL", { enumerable: true, get: function () { return oauthPkce_1.OXY_AUTHORIZE_URL; } });
283
+ Object.defineProperty(exports, "OXY_OAUTH_STATE_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_STATE_STORAGE_KEY; } });
284
+ Object.defineProperty(exports, "OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY; } });
285
+ Object.defineProperty(exports, "OXY_SILENT_OAUTH_ATTEMPTED_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_SILENT_OAUTH_ATTEMPTED_KEY; } });
286
+ Object.defineProperty(exports, "OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY; } });
287
+ Object.defineProperty(exports, "normalizeOAuthRedirectUri", { enumerable: true, get: function () { return oauthPkce_1.normalizeOAuthRedirectUri; } });
288
+ Object.defineProperty(exports, "persistOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.persistOAuthHandshake; } });
289
+ Object.defineProperty(exports, "readOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.readOAuthHandshake; } });
290
+ Object.defineProperty(exports, "clearOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.clearOAuthHandshake; } });
291
+ var officialOrigins_1 = require("./utils/officialOrigins");
292
+ Object.defineProperty(exports, "buildIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.buildIdpHubOrigin; } });
293
+ Object.defineProperty(exports, "buildHubSyncUrl", { enumerable: true, get: function () { return officialOrigins_1.buildHubSyncUrl; } });
294
+ Object.defineProperty(exports, "isIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.isIdpHubOrigin; } });
295
+ Object.defineProperty(exports, "isOfficialWebOrigin", { enumerable: true, get: function () { return officialOrigins_1.isOfficialWebOrigin; } });
296
+ Object.defineProperty(exports, "isAllowedDeviceJoinOrigin", { enumerable: true, get: function () { return officialOrigins_1.isAllowedDeviceJoinOrigin; } });
297
+ Object.defineProperty(exports, "normalizeOfficialReturnOrigin", { enumerable: true, get: function () { return officialOrigins_1.normalizeOfficialReturnOrigin; } });
298
+ Object.defineProperty(exports, "parseHubSyncReturnUrl", { enumerable: true, get: function () { return officialOrigins_1.parseHubSyncReturnUrl; } });
299
+ var hubSync_1 = require("./session/hubSync");
300
+ Object.defineProperty(exports, "syncHubAfterSignIn", { enumerable: true, get: function () { return hubSync_1.syncHubAfterSignIn; } });
301
+ Object.defineProperty(exports, "redeemHubTicketOnHub", { enumerable: true, get: function () { return hubSync_1.redeemHubTicketOnHub; } });
281
302
  // ---------------------------------------------------------------------------
282
303
  // Session sync (device-scoped multi-account session client)
283
304
  // ---------------------------------------------------------------------------
@@ -330,8 +351,8 @@ Object.defineProperty(exports, "createAuthRefreshHandler", { enumerable: true, g
330
351
  Object.defineProperty(exports, "installAuthRefreshHandler", { enumerable: true, get: function () { return refresh_1.installAuthRefreshHandler; } });
331
352
  Object.defineProperty(exports, "startTokenRefreshScheduler", { enumerable: true, get: function () { return refresh_1.startTokenRefreshScheduler; } });
332
353
  Object.defineProperty(exports, "TOKEN_REFRESH_LEAD_MS", { enumerable: true, get: function () { return refresh_1.TOKEN_REFRESH_LEAD_MS; } });
333
- var coldBootV2_1 = require("./boot/coldBootV2");
334
- Object.defineProperty(exports, "runSessionColdBoot", { enumerable: true, get: function () { return coldBootV2_1.runSessionColdBoot; } });
354
+ var sessionColdBoot_1 = require("./boot/sessionColdBoot");
355
+ Object.defineProperty(exports, "runSessionColdBoot", { enumerable: true, get: function () { return sessionColdBoot_1.runSessionColdBoot; } });
335
356
  // API response contracts (request/response Zod schemas + inferred types) live in
336
357
  // `@oxyhq/contracts` — the single source of truth shared by the backend and every
337
358
  // client SDK. Import them directly from `@oxyhq/contracts`; `@oxyhq/core` does NOT
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ORGANIZATION_CATEGORIES = void 0;
3
4
  exports.OxyServicesAccountsMixin = OxyServicesAccountsMixin;
4
5
  const userIdentity_1 = require("../utils/userIdentity");
5
6
  const mixinHelpers_1 = require("./mixinHelpers");
7
+ var contracts_1 = require("@oxyhq/contracts");
8
+ Object.defineProperty(exports, "ORGANIZATION_CATEGORIES", { enumerable: true, get: function () { return contracts_1.ORGANIZATION_CATEGORIES; } });
6
9
  function OxyServicesAccountsMixin(Base) {
7
10
  return class extends Base {
8
11
  constructor(...args) {
@@ -808,6 +808,7 @@ function OxyServicesAuthMixin(Base) {
808
808
  password,
809
809
  deviceName: options.deviceName,
810
810
  deviceFingerprint: options.deviceFingerprint,
811
+ ...(options.deviceId ? { deviceId: options.deviceId } : {}),
811
812
  }, { cache: false });
812
813
  const parsed = (0, contracts_1.safeParseContract)(contracts_1.loginResultSchema, res);
813
814
  if (!parsed) {
@@ -836,6 +837,7 @@ function OxyServicesAuthMixin(Base) {
836
837
  token: params.token,
837
838
  backupCode: params.backupCode,
838
839
  deviceName: params.deviceName,
840
+ ...(params.deviceId ? { deviceId: params.deviceId } : {}),
839
841
  }, { cache: false });
840
842
  const parsed = (0, contracts_1.safeParseContract)(contracts_1.loginResultSchema, res);
841
843
  if (!parsed || 'twoFactorRequired' in parsed) {
@@ -850,5 +852,64 @@ function OxyServicesAuthMixin(Base) {
850
852
  throw this.handleError(error);
851
853
  }
852
854
  }
855
+ /**
856
+ * Exchange an OAuth authorization code (returned to the RP redirect URI
857
+ * after password sign-in at auth.oxy.so) for a device-first session.
858
+ * Public first-party clients use PKCE (`codeVerifier`); the access token is
859
+ * planted immediately on success.
860
+ */
861
+ async exchangeOAuthCode(params) {
862
+ try {
863
+ const res = await this.makeRequest('POST', '/auth/oauth/token', {
864
+ code: params.code,
865
+ clientId: params.clientId,
866
+ redirectUri: params.redirectUri,
867
+ codeVerifier: params.codeVerifier,
868
+ }, { cache: false });
869
+ const payload = res.data ??
870
+ res;
871
+ if (!payload || typeof payload !== 'object') {
872
+ throw new Error('auth/oauth/token returned an unexpected response shape');
873
+ }
874
+ const record = payload;
875
+ const accessToken = (record.access_token ?? record.accessToken);
876
+ const sessionId = (record.session_id ?? record.sessionId);
877
+ const deviceId = (record.deviceId ?? record.device_id);
878
+ const deviceSecret = (record.deviceSecret ?? record.device_secret);
879
+ const userRaw = record.user;
880
+ if (!sessionId || !deviceId || !userRaw || typeof userRaw !== 'object') {
881
+ throw new Error('auth/oauth/token returned an incomplete session payload');
882
+ }
883
+ const userObj = userRaw;
884
+ const userId = userObj.id;
885
+ if (!userId) {
886
+ throw new Error('auth/oauth/token returned a session without user.id');
887
+ }
888
+ const expiresInSec = typeof record.expires_in === 'number'
889
+ ? record.expires_in
890
+ : typeof record.expiresIn === 'number'
891
+ ? record.expiresIn
892
+ : 15 * 60;
893
+ const expiresAt = new Date(Date.now() + expiresInSec * 1000).toISOString();
894
+ if (accessToken) {
895
+ this.setTokens(accessToken);
896
+ }
897
+ return {
898
+ sessionId,
899
+ deviceId,
900
+ expiresAt,
901
+ accessToken,
902
+ deviceSecret,
903
+ user: {
904
+ id: userId,
905
+ username: typeof userObj.username === 'string' ? userObj.username : undefined,
906
+ avatar: typeof userObj.avatar === 'string' ? userObj.avatar : undefined,
907
+ },
908
+ };
909
+ }
910
+ catch (error) {
911
+ throw this.handleError(error);
912
+ }
913
+ }
853
914
  };
854
915
  }
@@ -5,7 +5,7 @@ exports.OxyServicesDeviceBootMixin = OxyServicesDeviceBootMixin;
5
5
  * Device-first token mint mixin.
6
6
  *
7
7
  * The client half of the zero-cookie device transport: the single network call
8
- * the cold boot (`coldBootV2`) and the unified re-mint handler (`refresh.ts`)
8
+ * the cold boot (`sessionColdBoot`) and the unified re-mint handler (`refresh.ts`)
9
9
  * make to turn a first-party `deviceId` + `deviceSecret` into a fresh access
10
10
  * token. The response is validated against the `@oxyhq/contracts`
11
11
  * `deviceTokenMintResponseSchema`, so producer (oxy-api) and consumer cannot
@@ -47,5 +47,33 @@ function OxyServicesDeviceBootMixin(Base) {
47
47
  throw this.handleError(error);
48
48
  }
49
49
  }
50
+ /** Mint a one-time hub sync ticket (bearer required). */
51
+ async issueHubTicket(returnOrigin) {
52
+ try {
53
+ const res = await this.makeRequest('POST', '/session/device/hub-ticket', { returnOrigin }, { cache: false });
54
+ const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceHubTicketIssueResponseSchema, res);
55
+ if (!parsed) {
56
+ throw new Error('session/device/hub-ticket returned an unexpected response shape');
57
+ }
58
+ return parsed;
59
+ }
60
+ catch (error) {
61
+ throw this.handleError(error);
62
+ }
63
+ }
64
+ /** Redeem a hub sync ticket for a fresh device secret (public). */
65
+ async redeemHubTicket(ticket, returnOrigin) {
66
+ try {
67
+ const res = await this.makeRequest('POST', '/session/device/redeem-ticket', { ticket, returnOrigin }, { cache: false, skipAuth: true });
68
+ const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceHubTicketRedeemResponseSchema, res);
69
+ if (!parsed) {
70
+ throw new Error('session/device/redeem-ticket returned an unexpected response shape');
71
+ }
72
+ return parsed;
73
+ }
74
+ catch (error) {
75
+ throw this.handleError(error);
76
+ }
77
+ }
50
78
  };
51
79
  }
@@ -16,7 +16,7 @@
16
16
  * ```
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.registrableApex = exports.verifySecret = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getOxyUserId = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
19
+ exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isOfficialWebOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.registrableApex = exports.verifySecret = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getOxyUserId = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
20
20
  var auth_1 = require("./auth");
21
21
  Object.defineProperty(exports, "createOptionalOxyAuth", { enumerable: true, get: function () { return auth_1.createOptionalOxyAuth; } });
22
22
  Object.defineProperty(exports, "createOxyAuthMiddleware", { enumerable: true, get: function () { return auth_1.createOxyAuthMiddleware; } });
@@ -52,3 +52,10 @@ Object.defineProperty(exports, "verifySecret", { enumerable: true, get: function
52
52
  // lets `@oxyhq/api` derive `auth.<apex>` without duplicating PSL logic.
53
53
  var registrableApex_1 = require("../utils/registrableApex");
54
54
  Object.defineProperty(exports, "registrableApex", { enumerable: true, get: function () { return registrableApex_1.registrableApex; } });
55
+ var officialOrigins_1 = require("../utils/officialOrigins");
56
+ Object.defineProperty(exports, "buildIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.buildIdpHubOrigin; } });
57
+ Object.defineProperty(exports, "buildHubSyncUrl", { enumerable: true, get: function () { return officialOrigins_1.buildHubSyncUrl; } });
58
+ Object.defineProperty(exports, "isIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.isIdpHubOrigin; } });
59
+ Object.defineProperty(exports, "isOfficialWebOrigin", { enumerable: true, get: function () { return officialOrigins_1.isOfficialWebOrigin; } });
60
+ Object.defineProperty(exports, "normalizeOfficialReturnOrigin", { enumerable: true, get: function () { return officialOrigins_1.normalizeOfficialReturnOrigin; } });
61
+ Object.defineProperty(exports, "parseHubSyncReturnUrl", { enumerable: true, get: function () { return officialOrigins_1.parseHubSyncReturnUrl; } });
@@ -63,21 +63,32 @@ class SessionClient {
63
63
  return false;
64
64
  }
65
65
  this.state = next;
66
- this.notify();
67
- if (this.options.transport) {
68
- void this.options.transport.ensureActiveToken(next).catch((error) => {
66
+ const transport = this.options.transport;
67
+ const needsMintBeforeNotify = transport != null && next.accounts.length > 0 && !this.host.getAccessToken();
68
+ const finishApply = () => {
69
+ this.notify();
70
+ if (next.accounts.length === 0 && this.options.onUnauthenticated) {
71
+ try {
72
+ this.options.onUnauthenticated();
73
+ }
74
+ catch (error) {
75
+ loggerUtils_1.logger.error('[SessionClient] onUnauthenticated threw', error);
76
+ }
77
+ }
78
+ };
79
+ if (needsMintBeforeNotify) {
80
+ void transport.ensureActiveToken(next).then(finishApply).catch((error) => {
69
81
  loggerUtils_1.logger.warn('[SessionClient] ensureActiveToken failed', { component: 'SessionClient' }, error);
82
+ finishApply();
70
83
  });
71
84
  }
72
- // A device signout-all leaves zero accounts — tell the provider to clear
73
- // the persisted store so a reload does not try to restore a dead session.
74
- if (next.accounts.length === 0 && this.options.onUnauthenticated) {
75
- try {
76
- this.options.onUnauthenticated();
77
- }
78
- catch (error) {
79
- loggerUtils_1.logger.error('[SessionClient] onUnauthenticated threw', error);
85
+ else {
86
+ if (transport) {
87
+ void transport.ensureActiveToken(next).catch((error) => {
88
+ loggerUtils_1.logger.warn('[SessionClient] ensureActiveToken failed', { component: 'SessionClient' }, error);
89
+ });
80
90
  }
91
+ finishApply();
81
92
  }
82
93
  return true;
83
94
  }
@@ -155,19 +166,24 @@ class SessionClient {
155
166
  this.started = true;
156
167
  this.tokenUnsub = this.host.onTokensChanged((token) => {
157
168
  // A rotated/fresh bearer landed — reconnect a dropped socket so its
158
- // handshake re-runs with the current token. Sign-out (null token) is
159
- // handled by the consumer calling `stop()`.
160
- if (!token || !this.socket)
169
+ // handshake re-runs with the current token. Sign-out (null token) keeps
170
+ // the device-scoped socket when a device credential is available.
171
+ if (!this.socket)
161
172
  return;
162
- if (!this.socket.connected) {
173
+ if (token) {
174
+ if (!this.socket.connected) {
175
+ this.socket.connect();
176
+ }
177
+ return;
178
+ }
179
+ const cred = this.host.getDeviceCredential();
180
+ if (cred && !this.socket.connected) {
163
181
  this.socket.connect();
164
182
  }
165
183
  });
166
184
  this.openBroadcastChannel();
167
- // `bootstrap` (`GET /session/device/state`) is bearer-authenticated. A
168
- // signed-out client opens no socket and runs no bootstrap; a bootstrap
169
- // failure is non-fatal — the socket still connects so realtime sync
170
- // survives a transient state-fetch error.
185
+ // Device-scoped socket: bearer when authenticated, else deviceId+deviceSecret so
186
+ // signed-out tabs still receive `session_state` and can mint on change.
171
187
  if (this.host.getAccessToken()) {
172
188
  try {
173
189
  await this.bootstrap();
@@ -210,15 +226,29 @@ class SessionClient {
210
226
  }
211
227
  if (!this.started)
212
228
  return; // stopped while the dynamic import was in flight
213
- // Sockets are BEARER-ONLY: the server rejects any handshake without a valid
214
- // bearer, so a signed-out client never opens a socket.
215
- if (!this.host.getAccessToken())
229
+ const token = this.host.getAccessToken();
230
+ const deviceCredential = this.host.getDeviceCredential();
231
+ if (!token && !deviceCredential)
216
232
  return;
217
233
  const socket = io(this.host.getBaseURL(), {
218
234
  transports: ['websocket'],
219
235
  autoConnect: true,
236
+ reconnection: true,
237
+ reconnectionAttempts: Infinity,
238
+ reconnectionDelay: 1000,
239
+ reconnectionDelayMax: 10000,
220
240
  auth: (cb) => {
221
- cb({ token: this.host.getAccessToken() ?? '' });
241
+ const bearer = this.host.getAccessToken();
242
+ if (bearer) {
243
+ cb({ token: bearer });
244
+ return;
245
+ }
246
+ const cred = this.host.getDeviceCredential();
247
+ if (cred) {
248
+ cb({ deviceId: cred.deviceId, deviceSecret: cred.deviceSecret });
249
+ return;
250
+ }
251
+ cb({ token: '' });
222
252
  },
223
253
  });
224
254
  socket.on('session_state', (payload) => {
@@ -226,9 +256,11 @@ class SessionClient {
226
256
  if (!applied)
227
257
  return;
228
258
  // A push changed the active account on another device/tab — re-fetch state
229
- // to plant the access token for the newly-active account.
259
+ // to plant the access token for the newly-active account. When this tab is
260
+ // still signed out, applyState mints via ensureActiveToken first; bootstrap
261
+ // requires a bearer and must not run until then.
230
262
  const active = this.state?.activeAccountId ?? null;
231
- if (active && active !== this.host.getCurrentAccountId()) {
263
+ if (active && active !== this.host.getCurrentAccountId() && this.host.getAccessToken()) {
232
264
  void this.bootstrap().catch((error) => {
233
265
  loggerUtils_1.logger.warn('[SessionClient] post-push token fetch failed', { component: 'SessionClient' }, error);
234
266
  });
@@ -31,6 +31,7 @@ exports.AccountDialogController = void 0;
31
31
  exports.createAccountDialogController = createAccountDialogController;
32
32
  const loggerUtils_1 = require("../utils/loggerUtils");
33
33
  const authWebUrl_1 = require("../utils/authWebUrl");
34
+ const oauthPkce_1 = require("../utils/oauthPkce");
34
35
  const accountProjection_1 = require("./accountProjection");
35
36
  const DEFAULT_POLL_INTERVAL_MS = 3000;
36
37
  const IDLE_SIGN_IN = {
@@ -72,6 +73,7 @@ class AccountDialogController {
72
73
  this.commitSession = options.commitSession;
73
74
  this.onSignedIn = options.onSignedIn;
74
75
  this.idpApex = options.idpApex ?? authWebUrl_1.CENTRAL_IDP_APEX;
76
+ this.authRedirectUri = options.authRedirectUri ?? null;
75
77
  this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
76
78
  this.openUrl = options.openUrl;
77
79
  this.snapshot = this.computeSnapshot();
@@ -425,18 +427,27 @@ class AccountDialogController {
425
427
  * @param params.state - Optional opaque state echoed back on return.
426
428
  * @returns The absolute auth.oxy.so sign-in URL.
427
429
  */
428
- openPasswordAtOxyAuth(params = {}) {
430
+ async openPasswordAtOxyAuth(params = {}) {
429
431
  const base = `https://auth.${this.idpApex}`;
430
432
  const url = new URL('/login', base);
431
- const returnUrl = params.returnUrl ?? currentLocationHref();
432
- if (returnUrl) {
433
- url.searchParams.set('redirect_uri', returnUrl);
433
+ const rawRedirect = params.redirectUri ??
434
+ this.authRedirectUri ??
435
+ params.returnUrl ??
436
+ currentLocationOrigin();
437
+ const redirectUri = rawRedirect ? (0, oauthPkce_1.normalizeOAuthRedirectUri)(rawRedirect) : '';
438
+ if (redirectUri) {
439
+ url.searchParams.set('redirect_uri', redirectUri);
434
440
  }
435
441
  if (this.clientId) {
436
442
  url.searchParams.set('client_id', this.clientId);
437
443
  }
438
- if (params.state) {
439
- url.searchParams.set('state', params.state);
444
+ const state = params.state ?? (await (0, oauthPkce_1.generateOAuthState)());
445
+ url.searchParams.set('state', state);
446
+ const { codeChallenge, codeVerifier } = await (0, oauthPkce_1.generatePkcePair)();
447
+ url.searchParams.set('code_challenge', codeChallenge);
448
+ url.searchParams.set('code_challenge_method', 'S256');
449
+ if (!(0, oauthPkce_1.persistOAuthHandshake)(state, codeVerifier)) {
450
+ throw new Error('Could not persist OAuth handshake for password sign-in');
440
451
  }
441
452
  const href = url.toString();
442
453
  this.openUrl?.(href);
@@ -516,6 +527,7 @@ class AccountDialogController {
516
527
  expiresAt: claimed.expiresAt ?? '',
517
528
  user: minimalUser,
518
529
  accessToken: claimed.accessToken,
530
+ ...(claimed.deviceSecret ? { deviceSecret: claimed.deviceSecret } : {}),
519
531
  }, minimalUser);
520
532
  }
521
533
  catch (error) {
@@ -606,8 +618,8 @@ function createAccountDialogController(options) {
606
618
  // ---------------------------------------------------------------------------
607
619
  // Local helpers
608
620
  // ---------------------------------------------------------------------------
609
- /** Current document URL on web; empty string where `location` is absent (native/SSR). */
610
- function currentLocationHref() {
621
+ /** Current document origin on web; empty string where `location` is absent (native/SSR). */
622
+ function currentLocationOrigin() {
611
623
  const location = globalThis.location;
612
- return typeof location?.href === 'string' ? location.href : '';
624
+ return typeof location?.origin === 'string' ? location.origin : '';
613
625
  }
@@ -6,7 +6,7 @@
6
6
  * a reload restores the session locally without a redirect: `deviceId` +
7
7
  * `deviceSecret` mint a fresh access token via `POST /session/device/token`.
8
8
  * This module is the storage seam: a tiny `load / save / clear` interface plus
9
- * platform factories, so the cold boot (`coldBootV2`) and the unified re-mint
9
+ * platform factories, so the cold boot (`sessionColdBoot`) and the unified re-mint
10
10
  * handler (`refresh.ts`) never touch a platform storage API directly.
11
11
  *
12
12
  * Platform-agnostic — the native factory takes an INJECTED key/value store
@@ -48,15 +48,21 @@ function deserialize(raw) {
48
48
  return null;
49
49
  }
50
50
  const candidate = parsed;
51
- if (typeof candidate.sessionId !== 'string' ||
52
- typeof candidate.userId !== 'string' ||
53
- candidate.sessionId.length === 0 ||
54
- candidate.userId.length === 0) {
51
+ const hasDeviceCredential = typeof candidate.deviceId === 'string' &&
52
+ candidate.deviceId.length > 0 &&
53
+ typeof candidate.deviceSecret === 'string' &&
54
+ candidate.deviceSecret.length > 0;
55
+ const hasSessionIdentity = typeof candidate.sessionId === 'string' &&
56
+ typeof candidate.userId === 'string' &&
57
+ candidate.sessionId.length > 0 &&
58
+ candidate.userId.length > 0;
59
+ // Device-only bootstrap (post join, pre-sign-in): durable credential without session.
60
+ if (!hasSessionIdentity && !hasDeviceCredential) {
55
61
  return null;
56
62
  }
57
63
  const state = {
58
- sessionId: candidate.sessionId,
59
- userId: candidate.userId,
64
+ sessionId: hasSessionIdentity ? candidate.sessionId : '',
65
+ userId: hasSessionIdentity ? candidate.userId : '',
60
66
  };
61
67
  if (typeof candidate.deviceId === 'string' && candidate.deviceId.length > 0) {
62
68
  state.deviceId = candidate.deviceId;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * Post-sign-in hub sync — plant device credentials on auth.oxy.so via a
4
+ * one-time server ticket (no secrets in URL fragments).
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.syncHubAfterSignIn = syncHubAfterSignIn;
8
+ exports.redeemHubTicketOnHub = redeemHubTicketOnHub;
9
+ const officialOrigins_1 = require("../utils/officialOrigins");
10
+ /**
11
+ * After a successful sign-in on an official web app, mint a hub ticket and
12
+ * redirect to auth.oxy.so/sync so the IdP hub can redeem it and persist the
13
+ * shared device credential for silent OAuth restore on other origins.
14
+ *
15
+ * No-op on native, non-official origins, and when already on the IdP hub.
16
+ */
17
+ async function syncHubAfterSignIn(oxy, opts) {
18
+ if (opts?.enabled === false) {
19
+ return false;
20
+ }
21
+ if (typeof globalThis === 'undefined') {
22
+ return false;
23
+ }
24
+ const location = globalThis.location;
25
+ if (!location) {
26
+ return false;
27
+ }
28
+ if ((0, officialOrigins_1.isIdpHubOrigin)()) {
29
+ return false;
30
+ }
31
+ if (!(0, officialOrigins_1.isOfficialWebOrigin)(location.origin)) {
32
+ return false;
33
+ }
34
+ const hubOrigin = (0, officialOrigins_1.buildIdpHubOrigin)();
35
+ const issued = await oxy.issueHubTicket(hubOrigin);
36
+ const returnUrl = `${location.origin}${location.pathname}${location.search}`;
37
+ const syncUrl = (0, officialOrigins_1.buildHubSyncUrl)(issued.ticket, returnUrl);
38
+ window.location.assign(syncUrl);
39
+ return true;
40
+ }
41
+ /** Redeem a hub ticket on auth.oxy.so and persist credentials locally. */
42
+ async function redeemHubTicketOnHub(oxy, store, ticket) {
43
+ const hubOrigin = (0, officialOrigins_1.buildIdpHubOrigin)();
44
+ const creds = await oxy.redeemHubTicket(ticket, hubOrigin);
45
+ const prior = await store.load();
46
+ await store.save({
47
+ sessionId: prior?.sessionId ?? '',
48
+ userId: prior?.userId ?? '',
49
+ deviceId: creds.deviceId,
50
+ deviceSecret: creds.deviceSecret,
51
+ ...(prior?.accessToken ? { accessToken: prior.accessToken } : {}),
52
+ ...(prior?.expiresAt ? { expiresAt: prior.expiresAt } : {}),
53
+ });
54
+ return true;
55
+ }
@@ -15,15 +15,20 @@ exports.createSessionClientHost = createSessionClientHost;
15
15
  */
16
16
  function createSessionClientHost(oxyServices) {
17
17
  let currentAccountId = null;
18
+ let deviceCredential = null;
18
19
  return {
19
20
  makeRequest: (method, url, data, options) => oxyServices.makeRequest(method, url, data, options),
20
21
  getBaseURL: () => oxyServices.getBaseURL(),
21
22
  getAccessToken: () => oxyServices.getAccessToken(),
23
+ getDeviceCredential: () => deviceCredential,
22
24
  onTokensChanged: (listener) => oxyServices.onTokensChanged(listener),
23
25
  setTokens: (accessToken) => oxyServices.setTokens(accessToken),
24
26
  getCurrentAccountId: () => currentAccountId,
25
27
  setCurrentAccountId: (id) => {
26
28
  currentAccountId = id;
27
29
  },
30
+ setDeviceCredential: (credential) => {
31
+ deviceCredential = credential;
32
+ },
28
33
  };
29
34
  }
@@ -17,7 +17,7 @@
17
17
  * web bundle — the reason any run-once guard for a step must live in the
18
18
  * calling consumer, never in a core module-level singleton).
19
19
  * - Architecture-agnostic: it knows nothing about HOW a step resolves a
20
- * session; `runSessionColdBoot` (`boot/coldBootV2.ts`) is the current
20
+ * session; `runSessionColdBoot` (`boot/sessionColdBoot.ts`) is the current
21
21
  * device-first consumer.
22
22
  *
23
23
  * A step is skipped (without running) when its `enabled` predicate returns