@oxyhq/core 1.6.1 → 1.6.3

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 (36) hide show
  1. package/dist/cjs/AuthManager.js +2 -0
  2. package/dist/cjs/mixins/OxyServices.fedcm.js +39 -3
  3. package/dist/esm/AuthManager.js +3 -1
  4. package/dist/esm/HttpService.js +6 -6
  5. package/dist/esm/OxyServices.base.js +3 -3
  6. package/dist/esm/OxyServices.js +2 -2
  7. package/dist/esm/crypto/index.js +5 -5
  8. package/dist/esm/crypto/keyManager.js +3 -3
  9. package/dist/esm/crypto/recoveryPhrase.js +1 -1
  10. package/dist/esm/crypto/signatureService.js +2 -2
  11. package/dist/esm/i18n/index.js +11 -11
  12. package/dist/esm/index.js +26 -26
  13. package/dist/esm/mixins/OxyServices.analytics.js +1 -1
  14. package/dist/esm/mixins/OxyServices.auth.js +1 -1
  15. package/dist/esm/mixins/OxyServices.developer.js +1 -1
  16. package/dist/esm/mixins/OxyServices.features.js +1 -1
  17. package/dist/esm/mixins/OxyServices.fedcm.js +41 -5
  18. package/dist/esm/mixins/OxyServices.karma.js +1 -1
  19. package/dist/esm/mixins/OxyServices.language.js +2 -2
  20. package/dist/esm/mixins/OxyServices.payment.js +1 -1
  21. package/dist/esm/mixins/OxyServices.popup.js +2 -2
  22. package/dist/esm/mixins/OxyServices.privacy.js +1 -1
  23. package/dist/esm/mixins/OxyServices.redirect.js +1 -1
  24. package/dist/esm/mixins/OxyServices.security.js +1 -1
  25. package/dist/esm/mixins/OxyServices.user.js +1 -1
  26. package/dist/esm/mixins/OxyServices.utility.js +1 -1
  27. package/dist/esm/mixins/index.js +18 -18
  28. package/dist/esm/shared/index.js +5 -5
  29. package/dist/esm/shared/utils/index.js +4 -4
  30. package/dist/esm/utils/asyncUtils.js +1 -1
  31. package/dist/esm/utils/errorUtils.js +1 -1
  32. package/dist/esm/utils/index.js +4 -4
  33. package/dist/types/mixins/OxyServices.fedcm.d.ts +6 -0
  34. package/package.json +2 -2
  35. package/src/AuthManager.ts +2 -0
  36. package/src/mixins/OxyServices.fedcm.ts +44 -3
@@ -20,6 +20,7 @@ const STORAGE_KEYS = {
20
20
  SESSION: 'oxy_session',
21
21
  USER: 'oxy_user',
22
22
  AUTH_METHOD: 'oxy_auth_method',
23
+ FEDCM_LOGIN_HINT: 'oxy_fedcm_login_hint',
23
24
  };
24
25
  /**
25
26
  * Default in-memory storage for non-browser environments.
@@ -340,6 +341,7 @@ class AuthManager {
340
341
  await this.storage.removeItem(STORAGE_KEYS.SESSION);
341
342
  await this.storage.removeItem(STORAGE_KEYS.USER);
342
343
  await this.storage.removeItem(STORAGE_KEYS.AUTH_METHOD);
344
+ await this.storage.removeItem(STORAGE_KEYS.FEDCM_LOGIN_HINT);
343
345
  }
344
346
  /**
345
347
  * Get current user.
@@ -5,6 +5,7 @@ exports.FedCMMixin = OxyServicesFedCMMixin;
5
5
  const OxyServices_errors_1 = require("../OxyServices.errors");
6
6
  const debugUtils_1 = require("../shared/utils/debugUtils");
7
7
  const debug = (0, debugUtils_1.createDebugLogger)('FedCM');
8
+ const FEDCM_LOGIN_HINT_KEY = 'oxy_fedcm_login_hint';
8
9
  // Global lock to prevent concurrent FedCM requests
9
10
  // FedCM only allows one navigator.credentials.get request at a time
10
11
  let fedCMRequestInProgress = false;
@@ -82,13 +83,16 @@ function OxyServicesFedCMMixin(Base) {
82
83
  try {
83
84
  const nonce = options.nonce || this.generateNonce();
84
85
  const clientId = this.getClientId();
85
- debug.log('Interactive sign-in: Requesting credential for', clientId);
86
+ // Use provided loginHint, or fall back to stored last-used account ID
87
+ const loginHint = options.loginHint || this.getStoredLoginHint();
88
+ debug.log('Interactive sign-in: Requesting credential for', clientId, loginHint ? `(hint: ${loginHint})` : '');
86
89
  // Request credential from browser's native identity flow
87
90
  const credential = await this.requestIdentityCredential({
88
91
  configURL: this.constructor.DEFAULT_CONFIG_URL,
89
92
  clientId,
90
93
  nonce,
91
94
  context: options.context,
95
+ loginHint,
92
96
  });
93
97
  if (!credential || !credential.token) {
94
98
  throw new OxyServices_errors_1.OxyAuthenticationError('No credential received from browser');
@@ -100,6 +104,10 @@ function OxyServicesFedCMMixin(Base) {
100
104
  if (session && session.accessToken) {
101
105
  this.httpService.setTokens(session.accessToken);
102
106
  }
107
+ // Store the user ID as loginHint for future FedCM requests
108
+ if (session?.user?.id) {
109
+ this.storeLoginHint(session.user.id);
110
+ }
103
111
  debug.log('Interactive sign-in: Success!', { userId: session?.user?.id });
104
112
  return session;
105
113
  }
@@ -164,13 +172,15 @@ function OxyServicesFedCMMixin(Base) {
164
172
  // this runs on app startup — showing browser UI without user action is bad UX.
165
173
  // Optional/interactive mediation should only happen when the user clicks "Sign In".
166
174
  let credential = null;
175
+ const loginHint = this.getStoredLoginHint();
167
176
  try {
168
177
  const nonce = this.generateNonce();
169
- debug.log('Silent SSO: Attempting silent mediation...');
178
+ debug.log('Silent SSO: Attempting silent mediation...', loginHint ? `(hint: ${loginHint})` : '');
170
179
  credential = await this.requestIdentityCredential({
171
180
  configURL: this.constructor.DEFAULT_CONFIG_URL,
172
181
  clientId,
173
182
  nonce,
183
+ loginHint,
174
184
  mediation: 'silent',
175
185
  });
176
186
  debug.log('Silent SSO: Silent mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
@@ -225,6 +235,10 @@ function OxyServicesFedCMMixin(Base) {
225
235
  else {
226
236
  debug.warn('Silent SSO: No accessToken in session response');
227
237
  }
238
+ // Store the user ID as loginHint for future FedCM requests
239
+ if (session.user?.id) {
240
+ this.storeLoginHint(session.user.id);
241
+ }
228
242
  debug.log('Silent SSO: Success!', {
229
243
  sessionId: session.sessionId?.substring(0, 8) + '...',
230
244
  userId: session.user?.id
@@ -299,7 +313,7 @@ function OxyServicesFedCMMixin(Base) {
299
313
  params: {
300
314
  nonce: options.nonce, // For Chrome 145+
301
315
  },
302
- ...(options.context && { loginHint: options.context }),
316
+ ...(options.loginHint && { loginHint: options.loginHint }),
303
317
  },
304
318
  ],
305
319
  },
@@ -419,6 +433,28 @@ function OxyServicesFedCMMixin(Base) {
419
433
  }
420
434
  return window.location.origin;
421
435
  }
436
+ /** @internal */
437
+ getStoredLoginHint() {
438
+ if (typeof window === 'undefined')
439
+ return undefined;
440
+ try {
441
+ return localStorage.getItem(FEDCM_LOGIN_HINT_KEY) || undefined;
442
+ }
443
+ catch {
444
+ return undefined;
445
+ }
446
+ }
447
+ /** @internal */
448
+ storeLoginHint(userId) {
449
+ if (typeof window === 'undefined')
450
+ return;
451
+ try {
452
+ localStorage.setItem(FEDCM_LOGIN_HINT_KEY, userId);
453
+ }
454
+ catch {
455
+ // Storage full or blocked
456
+ }
457
+ }
422
458
  },
423
459
  _a.DEFAULT_CONFIG_URL = 'https://auth.oxy.so/fedcm.json',
424
460
  _a.FEDCM_TIMEOUT = 15000 // 15 seconds for interactive
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module core/AuthManager
8
8
  */
9
- import { retryAsync } from './utils/asyncUtils';
9
+ import { retryAsync } from './utils/asyncUtils.js';
10
10
  /**
11
11
  * Storage keys used by AuthManager.
12
12
  */
@@ -16,6 +16,7 @@ const STORAGE_KEYS = {
16
16
  SESSION: 'oxy_session',
17
17
  USER: 'oxy_user',
18
18
  AUTH_METHOD: 'oxy_auth_method',
19
+ FEDCM_LOGIN_HINT: 'oxy_fedcm_login_hint',
19
20
  };
20
21
  /**
21
22
  * Default in-memory storage for non-browser environments.
@@ -336,6 +337,7 @@ export class AuthManager {
336
337
  await this.storage.removeItem(STORAGE_KEYS.SESSION);
337
338
  await this.storage.removeItem(STORAGE_KEYS.USER);
338
339
  await this.storage.removeItem(STORAGE_KEYS.AUTH_METHOD);
340
+ await this.storage.removeItem(STORAGE_KEYS.FEDCM_LOGIN_HINT);
339
341
  }
340
342
  /**
341
343
  * Get current user.
@@ -12,13 +12,13 @@
12
12
  * - Error handling
13
13
  * - Request queuing
14
14
  */
15
- import { TTLCache, registerCacheForCleanup } from './utils/cache';
16
- import { RequestDeduplicator, RequestQueue, SimpleLogger } from './utils/requestUtils';
17
- import { retryAsync } from './utils/asyncUtils';
18
- import { handleHttpError } from './utils/errorUtils';
19
- import { isDev } from './shared/utils/debugUtils';
15
+ import { TTLCache, registerCacheForCleanup } from './utils/cache.js';
16
+ import { RequestDeduplicator, RequestQueue, SimpleLogger } from './utils/requestUtils.js';
17
+ import { retryAsync } from './utils/asyncUtils.js';
18
+ import { handleHttpError } from './utils/errorUtils.js';
19
+ import { isDev } from './shared/utils/debugUtils.js';
20
20
  import { jwtDecode } from 'jwt-decode';
21
- import { isNative, getPlatformOS } from './utils/platform';
21
+ import { isNative, getPlatformOS } from './utils/platform.js';
22
22
  /**
23
23
  * Check if we're running in a native app environment (React Native, not web)
24
24
  * This is used to determine CSRF handling mode
@@ -4,9 +4,9 @@
4
4
  * Contains core infrastructure, HTTP client, request management, and error handling
5
5
  */
6
6
  import { jwtDecode } from 'jwt-decode';
7
- import { handleHttpError } from './utils/errorUtils';
8
- import { HttpService } from './HttpService';
9
- import { OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.errors';
7
+ import { handleHttpError } from './utils/errorUtils.js';
8
+ import { HttpService } from './HttpService.js';
9
+ import { OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.errors.js';
10
10
  /**
11
11
  * Base class for OxyServices with core infrastructure
12
12
  */
@@ -1,6 +1,6 @@
1
- import { OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.errors';
1
+ import { OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.errors.js';
2
2
  // Import mixin composition helper
3
- import { composeOxyServices } from './mixins';
3
+ import { composeOxyServices } from './mixins/index.js';
4
4
  /**
5
5
  * OxyServices - Unified client library for interacting with the Oxy API
6
6
  *
@@ -5,9 +5,9 @@
5
5
  * Handles key generation, secure storage, digital signatures, and recovery phrases.
6
6
  */
7
7
  // Import polyfills first - this ensures Buffer is available for bip39 and other libraries
8
- import './polyfill';
9
- export { KeyManager } from './keyManager';
10
- export { SignatureService } from './signatureService';
11
- export { RecoveryPhraseService } from './recoveryPhrase';
8
+ import './polyfill.js';
9
+ export { KeyManager } from './keyManager.js';
10
+ export { SignatureService } from './signatureService.js';
11
+ export { RecoveryPhraseService } from './recoveryPhrase.js';
12
12
  // Re-export for convenience
13
- export { KeyManager as default } from './keyManager';
13
+ export { KeyManager as default } from './keyManager.js';
@@ -5,9 +5,9 @@
5
5
  * Private keys are stored securely using expo-secure-store and never leave the device.
6
6
  */
7
7
  import { ec as EC } from 'elliptic';
8
- import { isWeb, isIOS, isAndroid, isReactNative, isNodeJS } from '../utils/platform';
9
- import { logger } from '../utils/loggerUtils';
10
- import { isDev } from '../shared/utils/debugUtils';
8
+ import { isWeb, isIOS, isAndroid, isReactNative, isNodeJS } from '../utils/platform.js';
9
+ import { logger } from '../utils/loggerUtils.js';
10
+ import { isDev } from '../shared/utils/debugUtils.js';
11
11
  // Lazy imports for React Native specific modules
12
12
  let SecureStore = null;
13
13
  let ExpoCrypto = null;
@@ -7,7 +7,7 @@
7
7
  * Note: This module requires the polyfill to be loaded first (done via crypto/index.ts)
8
8
  */
9
9
  import * as bip39 from 'bip39';
10
- import { KeyManager } from './keyManager';
10
+ import { KeyManager } from './keyManager.js';
11
11
  /**
12
12
  * Convert Uint8Array or array-like to hexadecimal string
13
13
  * Works in both Node.js and React Native without depending on Buffer
@@ -5,8 +5,8 @@
5
5
  * Used for authenticating requests and proving identity ownership.
6
6
  */
7
7
  import { ec as EC } from 'elliptic';
8
- import { KeyManager } from './keyManager';
9
- import { isReactNative, isNodeJS } from '../utils/platform';
8
+ import { KeyManager } from './keyManager.js';
9
+ import { isReactNative, isNodeJS } from '../utils/platform.js';
10
10
  // Lazy import for expo-crypto
11
11
  let ExpoCrypto = null;
12
12
  const ec = new EC('secp256k1');
@@ -1,14 +1,14 @@
1
- import enUS from './locales/en-US.json';
2
- import esES from './locales/es-ES.json';
3
- import caES from './locales/ca-ES.json';
4
- import frFR from './locales/fr-FR.json';
5
- import deDE from './locales/de-DE.json';
6
- import itIT from './locales/it-IT.json';
7
- import ptPT from './locales/pt-PT.json';
8
- import jaJP from './locales/ja-JP.json';
9
- import koKR from './locales/ko-KR.json';
10
- import zhCN from './locales/zh-CN.json';
11
- import arSA from './locales/ar-SA.json';
1
+ import enUS from './locales/en-US.json.js';
2
+ import esES from './locales/es-ES.json.js';
3
+ import caES from './locales/ca-ES.json.js';
4
+ import frFR from './locales/fr-FR.json.js';
5
+ import deDE from './locales/de-DE.json.js';
6
+ import itIT from './locales/it-IT.json.js';
7
+ import ptPT from './locales/pt-PT.json.js';
8
+ import jaJP from './locales/ja-JP.json.js';
9
+ import koKR from './locales/ko-KR.json.js';
10
+ import zhCN from './locales/zh-CN.json.js';
11
+ import arSA from './locales/ar-SA.json.js';
12
12
  const DICTS = {
13
13
  'en': enUS,
14
14
  'en-US': enUS,
package/dist/esm/index.js CHANGED
@@ -14,44 +14,44 @@
14
14
  * ```
15
15
  */
16
16
  // Ensure crypto polyfills are loaded before anything else
17
- import './crypto/polyfill';
17
+ import './crypto/polyfill.js';
18
18
  // --- Core API Client ---
19
- export { OxyServices, OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices';
20
- export { OXY_CLOUD_URL, oxyClient } from './OxyServices';
19
+ export { OxyServices, OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.js';
20
+ export { OXY_CLOUD_URL, oxyClient } from './OxyServices.js';
21
21
  // --- Authentication ---
22
- export { AuthManager, createAuthManager } from './AuthManager';
23
- export { CrossDomainAuth, createCrossDomainAuth } from './CrossDomainAuth';
22
+ export { AuthManager, createAuthManager } from './AuthManager.js';
23
+ export { CrossDomainAuth, createCrossDomainAuth } from './CrossDomainAuth.js';
24
24
  // --- Crypto / Identity ---
25
- export { KeyManager, SignatureService, RecoveryPhraseService } from './crypto';
25
+ export { KeyManager, SignatureService, RecoveryPhraseService } from './crypto/index.js';
26
26
  // --- Models & Types ---
27
- export * from './models/interfaces';
28
- export * from './models/session';
27
+ export * from './models/interfaces.js';
28
+ export * from './models/session.js';
29
29
  // --- Device Management ---
30
- export { DeviceManager } from './utils/deviceManager';
30
+ export { DeviceManager } from './utils/deviceManager.js';
31
31
  // --- Language Utilities ---
32
- export { SUPPORTED_LANGUAGES, getLanguageMetadata, getLanguageName, getNativeLanguageName, normalizeLanguageCode, } from './utils/languageUtils';
32
+ export { SUPPORTED_LANGUAGES, getLanguageMetadata, getLanguageName, getNativeLanguageName, normalizeLanguageCode, } from './utils/languageUtils.js';
33
33
  // --- Platform Detection ---
34
- export { getPlatformOS, setPlatformOS, isWeb, isNative, isIOS, isAndroid, } from './utils/platform';
34
+ export { getPlatformOS, setPlatformOS, isWeb, isNative, isIOS, isAndroid, } from './utils/platform.js';
35
35
  // --- Shared Utilities ---
36
- export { darkenColor, lightenColor, hexToRgb, rgbToHex, withOpacity, isLightColor, getContrastTextColor, } from './shared/utils/colorUtils';
37
- export { normalizeTheme, normalizeColorScheme, getOppositeTheme, systemPrefersDarkMode, getSystemColorScheme, } from './shared/utils/themeUtils';
38
- export { HttpStatus, getErrorStatus, getErrorMessage, isAlreadyRegisteredError, isUnauthorizedError, isForbiddenError, isNotFoundError, isRateLimitError, isServerError, isNetworkError, isRetryableError, } from './shared/utils/errorUtils';
39
- export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBackoffInterval, recordFailure, recordSuccess, shouldAllowRequest, delay, withRetry, } from './shared/utils/networkUtils';
40
- export { isDev, debugLog, debugWarn, debugError, createDebugLogger, } from './shared/utils/debugUtils';
36
+ export { darkenColor, lightenColor, hexToRgb, rgbToHex, withOpacity, isLightColor, getContrastTextColor, } from './shared/utils/colorUtils.js';
37
+ export { normalizeTheme, normalizeColorScheme, getOppositeTheme, systemPrefersDarkMode, getSystemColorScheme, } from './shared/utils/themeUtils.js';
38
+ export { HttpStatus, getErrorStatus, getErrorMessage, isAlreadyRegisteredError, isUnauthorizedError, isForbiddenError, isNotFoundError, isRateLimitError, isServerError, isNetworkError, isRetryableError, } from './shared/utils/errorUtils.js';
39
+ export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBackoffInterval, recordFailure, recordSuccess, shouldAllowRequest, delay, withRetry, } from './shared/utils/networkUtils.js';
40
+ export { isDev, debugLog, debugWarn, debugError, createDebugLogger, } from './shared/utils/debugUtils.js';
41
41
  // --- i18n ---
42
- export { translate } from './i18n';
42
+ export { translate } from './i18n/index.js';
43
43
  // --- Auth Helpers ---
44
- export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers';
44
+ export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers.js';
45
45
  // --- Session Utilities ---
46
- export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual } from './utils/sessionUtils';
46
+ export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual } from './utils/sessionUtils.js';
47
47
  // --- Constants ---
48
- export { packageInfo } from './constants/version';
48
+ export { packageInfo } from './constants/version.js';
49
49
  // --- API & Error Utilities ---
50
- export * from './utils/apiUtils';
51
- export { ErrorCodes, createApiError, handleHttpError, validateRequiredFields, } from './utils/errorUtils';
52
- export { retryAsync } from './utils/asyncUtils';
53
- export * from './utils/validationUtils';
54
- export { logger, LogLevel, logAuth, logApi, logSession, logUser, logDevice, logPayment, logPerformance, } from './utils/loggerUtils';
50
+ export * from './utils/apiUtils.js';
51
+ export { ErrorCodes, createApiError, handleHttpError, validateRequiredFields, } from './utils/errorUtils.js';
52
+ export { retryAsync } from './utils/asyncUtils.js';
53
+ export * from './utils/validationUtils.js';
54
+ export { logger, LogLevel, logAuth, logApi, logSession, logUser, logDevice, logPayment, logPerformance, } from './utils/loggerUtils.js';
55
55
  // Default export
56
- import { OxyServices } from './OxyServices';
56
+ import { OxyServices } from './OxyServices.js';
57
57
  export default OxyServices;
@@ -1,4 +1,4 @@
1
- import { CACHE_TIMES } from './mixinHelpers';
1
+ import { CACHE_TIMES } from './mixinHelpers.js';
2
2
  export function OxyServicesAnalyticsMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -1,4 +1,4 @@
1
- import { OxyAuthenticationError } from '../OxyServices.errors';
1
+ import { OxyAuthenticationError } from '../OxyServices.errors.js';
2
2
  export function OxyServicesAuthMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -1,4 +1,4 @@
1
- import { CACHE_TIMES } from './mixinHelpers';
1
+ import { CACHE_TIMES } from './mixinHelpers.js';
2
2
  export function OxyServicesDeveloperMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -1,4 +1,4 @@
1
- import { CACHE_TIMES } from './mixinHelpers';
1
+ import { CACHE_TIMES } from './mixinHelpers.js';
2
2
  export function OxyServicesFeaturesMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -1,6 +1,7 @@
1
- import { OxyAuthenticationError } from '../OxyServices.errors';
2
- import { createDebugLogger } from '../shared/utils/debugUtils';
1
+ import { OxyAuthenticationError } from '../OxyServices.errors.js';
2
+ import { createDebugLogger } from '../shared/utils/debugUtils.js';
3
3
  const debug = createDebugLogger('FedCM');
4
+ const FEDCM_LOGIN_HINT_KEY = 'oxy_fedcm_login_hint';
4
5
  // Global lock to prevent concurrent FedCM requests
5
6
  // FedCM only allows one navigator.credentials.get request at a time
6
7
  let fedCMRequestInProgress = false;
@@ -78,13 +79,16 @@ export function OxyServicesFedCMMixin(Base) {
78
79
  try {
79
80
  const nonce = options.nonce || this.generateNonce();
80
81
  const clientId = this.getClientId();
81
- debug.log('Interactive sign-in: Requesting credential for', clientId);
82
+ // Use provided loginHint, or fall back to stored last-used account ID
83
+ const loginHint = options.loginHint || this.getStoredLoginHint();
84
+ debug.log('Interactive sign-in: Requesting credential for', clientId, loginHint ? `(hint: ${loginHint})` : '');
82
85
  // Request credential from browser's native identity flow
83
86
  const credential = await this.requestIdentityCredential({
84
87
  configURL: this.constructor.DEFAULT_CONFIG_URL,
85
88
  clientId,
86
89
  nonce,
87
90
  context: options.context,
91
+ loginHint,
88
92
  });
89
93
  if (!credential || !credential.token) {
90
94
  throw new OxyAuthenticationError('No credential received from browser');
@@ -96,6 +100,10 @@ export function OxyServicesFedCMMixin(Base) {
96
100
  if (session && session.accessToken) {
97
101
  this.httpService.setTokens(session.accessToken);
98
102
  }
103
+ // Store the user ID as loginHint for future FedCM requests
104
+ if (session?.user?.id) {
105
+ this.storeLoginHint(session.user.id);
106
+ }
99
107
  debug.log('Interactive sign-in: Success!', { userId: session?.user?.id });
100
108
  return session;
101
109
  }
@@ -160,13 +168,15 @@ export function OxyServicesFedCMMixin(Base) {
160
168
  // this runs on app startup — showing browser UI without user action is bad UX.
161
169
  // Optional/interactive mediation should only happen when the user clicks "Sign In".
162
170
  let credential = null;
171
+ const loginHint = this.getStoredLoginHint();
163
172
  try {
164
173
  const nonce = this.generateNonce();
165
- debug.log('Silent SSO: Attempting silent mediation...');
174
+ debug.log('Silent SSO: Attempting silent mediation...', loginHint ? `(hint: ${loginHint})` : '');
166
175
  credential = await this.requestIdentityCredential({
167
176
  configURL: this.constructor.DEFAULT_CONFIG_URL,
168
177
  clientId,
169
178
  nonce,
179
+ loginHint,
170
180
  mediation: 'silent',
171
181
  });
172
182
  debug.log('Silent SSO: Silent mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
@@ -221,6 +231,10 @@ export function OxyServicesFedCMMixin(Base) {
221
231
  else {
222
232
  debug.warn('Silent SSO: No accessToken in session response');
223
233
  }
234
+ // Store the user ID as loginHint for future FedCM requests
235
+ if (session.user?.id) {
236
+ this.storeLoginHint(session.user.id);
237
+ }
224
238
  debug.log('Silent SSO: Success!', {
225
239
  sessionId: session.sessionId?.substring(0, 8) + '...',
226
240
  userId: session.user?.id
@@ -295,7 +309,7 @@ export function OxyServicesFedCMMixin(Base) {
295
309
  params: {
296
310
  nonce: options.nonce, // For Chrome 145+
297
311
  },
298
- ...(options.context && { loginHint: options.context }),
312
+ ...(options.loginHint && { loginHint: options.loginHint }),
299
313
  },
300
314
  ],
301
315
  },
@@ -415,6 +429,28 @@ export function OxyServicesFedCMMixin(Base) {
415
429
  }
416
430
  return window.location.origin;
417
431
  }
432
+ /** @internal */
433
+ getStoredLoginHint() {
434
+ if (typeof window === 'undefined')
435
+ return undefined;
436
+ try {
437
+ return localStorage.getItem(FEDCM_LOGIN_HINT_KEY) || undefined;
438
+ }
439
+ catch {
440
+ return undefined;
441
+ }
442
+ }
443
+ /** @internal */
444
+ storeLoginHint(userId) {
445
+ if (typeof window === 'undefined')
446
+ return;
447
+ try {
448
+ localStorage.setItem(FEDCM_LOGIN_HINT_KEY, userId);
449
+ }
450
+ catch {
451
+ // Storage full or blocked
452
+ }
453
+ }
418
454
  },
419
455
  _a.DEFAULT_CONFIG_URL = 'https://auth.oxy.so/fedcm.json',
420
456
  _a.FEDCM_TIMEOUT = 15000 // 15 seconds for interactive
@@ -1,4 +1,4 @@
1
- import { CACHE_TIMES } from './mixinHelpers';
1
+ import { CACHE_TIMES } from './mixinHelpers.js';
2
2
  export function OxyServicesKarmaMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Language Methods Mixin
3
3
  */
4
- import { normalizeLanguageCode, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils';
5
- import { isDev } from '../shared/utils/debugUtils';
4
+ import { normalizeLanguageCode, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils.js';
5
+ import { isDev } from '../shared/utils/debugUtils.js';
6
6
  export function OxyServicesLanguageMixin(Base) {
7
7
  return class extends Base {
8
8
  constructor(...args) {
@@ -1,4 +1,4 @@
1
- import { CACHE_TIMES } from './mixinHelpers';
1
+ import { CACHE_TIMES } from './mixinHelpers.js';
2
2
  export function OxyServicesPaymentMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -1,5 +1,5 @@
1
- import { OxyAuthenticationError } from '../OxyServices.errors';
2
- import { createDebugLogger } from '../shared/utils/debugUtils';
1
+ import { OxyAuthenticationError } from '../OxyServices.errors.js';
2
+ import { createDebugLogger } from '../shared/utils/debugUtils.js';
3
3
  const debug = createDebugLogger('PopupAuth');
4
4
  /**
5
5
  * Popup-based Cross-Domain Authentication Mixin
@@ -1,4 +1,4 @@
1
- import { isDev } from '../shared/utils/debugUtils';
1
+ import { isDev } from '../shared/utils/debugUtils.js';
2
2
  export function OxyServicesPrivacyMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -1,4 +1,4 @@
1
- import { OxyAuthenticationError } from '../OxyServices.errors';
1
+ import { OxyAuthenticationError } from '../OxyServices.errors.js';
2
2
  /**
3
3
  * Redirect-based Cross-Domain Authentication Mixin
4
4
  *
@@ -1,4 +1,4 @@
1
- import { isDev } from '../shared/utils/debugUtils';
1
+ import { isDev } from '../shared/utils/debugUtils.js';
2
2
  export function OxyServicesSecurityMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -1,4 +1,4 @@
1
- import { buildSearchParams, buildPaginationParams } from '../utils/apiUtils';
1
+ import { buildSearchParams, buildPaginationParams } from '../utils/apiUtils.js';
2
2
  export function OxyServicesUserMixin(Base) {
3
3
  return class extends Base {
4
4
  constructor(...args) {
@@ -5,7 +5,7 @@
5
5
  * and Express.js authentication middleware
6
6
  */
7
7
  import { jwtDecode } from 'jwt-decode';
8
- import { CACHE_TIMES } from './mixinHelpers';
8
+ import { CACHE_TIMES } from './mixinHelpers.js';
9
9
  export function OxyServicesUtilityMixin(Base) {
10
10
  return class extends Base {
11
11
  constructor(...args) {
@@ -4,24 +4,24 @@
4
4
  * This module provides a clean way to compose all mixins
5
5
  * and ensures consistent ordering for better maintainability
6
6
  */
7
- import { OxyServicesBase } from '../OxyServices.base';
8
- import { OxyServicesAuthMixin } from './OxyServices.auth';
9
- import { OxyServicesFedCMMixin } from './OxyServices.fedcm';
10
- import { OxyServicesPopupAuthMixin } from './OxyServices.popup';
11
- import { OxyServicesRedirectAuthMixin } from './OxyServices.redirect';
12
- import { OxyServicesUserMixin } from './OxyServices.user';
13
- import { OxyServicesPrivacyMixin } from './OxyServices.privacy';
14
- import { OxyServicesLanguageMixin } from './OxyServices.language';
15
- import { OxyServicesPaymentMixin } from './OxyServices.payment';
16
- import { OxyServicesKarmaMixin } from './OxyServices.karma';
17
- import { OxyServicesAssetsMixin } from './OxyServices.assets';
18
- import { OxyServicesDeveloperMixin } from './OxyServices.developer';
19
- import { OxyServicesLocationMixin } from './OxyServices.location';
20
- import { OxyServicesAnalyticsMixin } from './OxyServices.analytics';
21
- import { OxyServicesDevicesMixin } from './OxyServices.devices';
22
- import { OxyServicesSecurityMixin } from './OxyServices.security';
23
- import { OxyServicesUtilityMixin } from './OxyServices.utility';
24
- import { OxyServicesFeaturesMixin } from './OxyServices.features';
7
+ import { OxyServicesBase } from '../OxyServices.base.js';
8
+ import { OxyServicesAuthMixin } from './OxyServices.auth.js';
9
+ import { OxyServicesFedCMMixin } from './OxyServices.fedcm.js';
10
+ import { OxyServicesPopupAuthMixin } from './OxyServices.popup.js';
11
+ import { OxyServicesRedirectAuthMixin } from './OxyServices.redirect.js';
12
+ import { OxyServicesUserMixin } from './OxyServices.user.js';
13
+ import { OxyServicesPrivacyMixin } from './OxyServices.privacy.js';
14
+ import { OxyServicesLanguageMixin } from './OxyServices.language.js';
15
+ import { OxyServicesPaymentMixin } from './OxyServices.payment.js';
16
+ import { OxyServicesKarmaMixin } from './OxyServices.karma.js';
17
+ import { OxyServicesAssetsMixin } from './OxyServices.assets.js';
18
+ import { OxyServicesDeveloperMixin } from './OxyServices.developer.js';
19
+ import { OxyServicesLocationMixin } from './OxyServices.location.js';
20
+ import { OxyServicesAnalyticsMixin } from './OxyServices.analytics.js';
21
+ import { OxyServicesDevicesMixin } from './OxyServices.devices.js';
22
+ import { OxyServicesSecurityMixin } from './OxyServices.security.js';
23
+ import { OxyServicesUtilityMixin } from './OxyServices.utility.js';
24
+ import { OxyServicesFeaturesMixin } from './OxyServices.features.js';
25
25
  /**
26
26
  * Mixin pipeline - applied in order from first to last.
27
27
  *
@@ -20,12 +20,12 @@
20
20
  * ```
21
21
  */
22
22
  // Color utilities
23
- export { darkenColor, lightenColor, hexToRgb, rgbToHex, withOpacity, isLightColor, getContrastTextColor, } from './utils/colorUtils';
23
+ export { darkenColor, lightenColor, hexToRgb, rgbToHex, withOpacity, isLightColor, getContrastTextColor, } from './utils/colorUtils.js';
24
24
  // Theme utilities
25
- export { normalizeTheme, normalizeColorScheme, getOppositeTheme, systemPrefersDarkMode, getSystemColorScheme, } from './utils/themeUtils';
25
+ export { normalizeTheme, normalizeColorScheme, getOppositeTheme, systemPrefersDarkMode, getSystemColorScheme, } from './utils/themeUtils.js';
26
26
  // Error utilities
27
- export { HttpStatus, getErrorStatus, getErrorMessage, isAlreadyRegisteredError, isUnauthorizedError, isForbiddenError, isNotFoundError, isRateLimitError, isServerError, isNetworkError, isRetryableError, } from './utils/errorUtils';
27
+ export { HttpStatus, getErrorStatus, getErrorMessage, isAlreadyRegisteredError, isUnauthorizedError, isForbiddenError, isNotFoundError, isRateLimitError, isServerError, isNetworkError, isRetryableError, } from './utils/errorUtils.js';
28
28
  // Network utilities
29
- export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBackoffInterval, recordFailure, recordSuccess, shouldAllowRequest, delay, withRetry, } from './utils/networkUtils';
29
+ export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBackoffInterval, recordFailure, recordSuccess, shouldAllowRequest, delay, withRetry, } from './utils/networkUtils.js';
30
30
  // Debug utilities
31
- export { isDev, debugLog, debugWarn, debugError, createDebugLogger, } from './utils/debugUtils';
31
+ export { isDev, debugLog, debugWarn, debugError, createDebugLogger, } from './utils/debugUtils.js';
@@ -6,10 +6,10 @@
6
6
  * @module shared/utils
7
7
  */
8
8
  // Color utilities
9
- export { darkenColor, lightenColor, hexToRgb, rgbToHex, withOpacity, isLightColor, getContrastTextColor, } from './colorUtils';
9
+ export { darkenColor, lightenColor, hexToRgb, rgbToHex, withOpacity, isLightColor, getContrastTextColor, } from './colorUtils.js';
10
10
  // Theme utilities
11
- export { normalizeTheme, normalizeColorScheme, getOppositeTheme, systemPrefersDarkMode, getSystemColorScheme, } from './themeUtils';
11
+ export { normalizeTheme, normalizeColorScheme, getOppositeTheme, systemPrefersDarkMode, getSystemColorScheme, } from './themeUtils.js';
12
12
  // Error utilities
13
- export { HttpStatus, getErrorStatus, getErrorMessage, isAlreadyRegisteredError, isUnauthorizedError, isForbiddenError, isNotFoundError, isRateLimitError, isServerError, isNetworkError, isRetryableError, } from './errorUtils';
13
+ export { HttpStatus, getErrorStatus, getErrorMessage, isAlreadyRegisteredError, isUnauthorizedError, isForbiddenError, isNotFoundError, isRateLimitError, isServerError, isNetworkError, isRetryableError, } from './errorUtils.js';
14
14
  // Network utilities
15
- export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBackoffInterval, recordFailure, recordSuccess, shouldAllowRequest, delay, withRetry, } from './networkUtils';
15
+ export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBackoffInterval, recordFailure, recordSuccess, shouldAllowRequest, delay, withRetry, } from './networkUtils.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Async utilities for common asynchronous patterns and error handling
3
3
  */
4
- import { logger } from './loggerUtils';
4
+ import { logger } from './loggerUtils.js';
5
5
  /**
6
6
  * Wrapper for async operations with automatic error handling
7
7
  * Returns null on error instead of throwing
@@ -1,4 +1,4 @@
1
- import { logger } from './loggerUtils';
1
+ import { logger } from './loggerUtils.js';
2
2
  /**
3
3
  * Error handling utilities for consistent error processing
4
4
  */
@@ -1,7 +1,7 @@
1
- export { DeviceManager } from './deviceManager';
1
+ export { DeviceManager } from './deviceManager.js';
2
2
  // Request utilities
3
- export { RequestDeduplicator, RequestQueue, SimpleLogger } from './requestUtils';
3
+ export { RequestDeduplicator, RequestQueue, SimpleLogger } from './requestUtils.js';
4
4
  // Cache utilities
5
- export { TTLCache, createCache, registerCacheForCleanup, unregisterCacheFromCleanup } from './cache';
5
+ export { TTLCache, createCache, registerCacheForCleanup, unregisterCacheFromCleanup } from './cache.js';
6
6
  // Session utilities
7
- export { normalizeSession, sortSessions, deduplicateSessions, deduplicateSessionsByUserId, normalizeAndSortSessions, mergeSessions, sessionsEqual, sessionsArraysEqual } from './sessionUtils';
7
+ export { normalizeSession, sortSessions, deduplicateSessions, deduplicateSessionsByUserId, normalizeAndSortSessions, mergeSessions, sessionsEqual, sessionsArraysEqual } from './sessionUtils.js';
@@ -3,6 +3,7 @@ import type { SessionLoginResponse } from '../models/session';
3
3
  export interface FedCMAuthOptions {
4
4
  nonce?: string;
5
5
  context?: 'signin' | 'signup' | 'continue' | 'use';
6
+ loginHint?: string;
6
7
  }
7
8
  export interface FedCMConfig {
8
9
  enabled: boolean;
@@ -107,6 +108,7 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
107
108
  clientId: string;
108
109
  nonce: string;
109
110
  context?: string;
111
+ loginHint?: string;
110
112
  mediation?: "silent" | "optional" | "required";
111
113
  }): Promise<{
112
114
  token: string;
@@ -145,6 +147,10 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
145
147
  * @private
146
148
  */
147
149
  getClientId(): string;
150
+ /** @internal */
151
+ getStoredLoginHint(): string | undefined;
152
+ /** @internal */
153
+ storeLoginHint(userId: string): void;
148
154
  httpService: import("../HttpService").HttpService;
149
155
  cloudURL: string;
150
156
  config: import("../OxyServices.base").OxyConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -58,7 +58,7 @@
58
58
  "build": "npm run build:cjs && npm run build:esm && npm run build:types && npm run copy-assets",
59
59
  "copy-assets": "cp -r src/i18n/locales dist/cjs/i18n/locales && cp -r src/i18n/locales dist/esm/i18n/locales",
60
60
  "build:cjs": "tsc -p tsconfig.cjs.json",
61
- "build:esm": "tsc -p tsconfig.esm.json",
61
+ "build:esm": "tsc -p tsconfig.esm.json && node scripts/fix-esm-imports.mjs",
62
62
  "build:types": "tsc -p tsconfig.types.json",
63
63
  "clean": "rm -rf dist",
64
64
  "typescript": "tsc --noEmit",
@@ -58,6 +58,7 @@ const STORAGE_KEYS = {
58
58
  SESSION: 'oxy_session',
59
59
  USER: 'oxy_user',
60
60
  AUTH_METHOD: 'oxy_auth_method',
61
+ FEDCM_LOGIN_HINT: 'oxy_fedcm_login_hint',
61
62
  } as const;
62
63
 
63
64
  /**
@@ -409,6 +410,7 @@ export class AuthManager {
409
410
  await this.storage.removeItem(STORAGE_KEYS.SESSION);
410
411
  await this.storage.removeItem(STORAGE_KEYS.USER);
411
412
  await this.storage.removeItem(STORAGE_KEYS.AUTH_METHOD);
413
+ await this.storage.removeItem(STORAGE_KEYS.FEDCM_LOGIN_HINT);
412
414
  }
413
415
 
414
416
  /**
@@ -8,6 +8,7 @@ const debug = createDebugLogger('FedCM');
8
8
  export interface FedCMAuthOptions {
9
9
  nonce?: string;
10
10
  context?: 'signin' | 'signup' | 'continue' | 'use';
11
+ loginHint?: string;
11
12
  }
12
13
 
13
14
  export interface FedCMConfig {
@@ -16,6 +17,8 @@ export interface FedCMConfig {
16
17
  clientId?: string;
17
18
  }
18
19
 
20
+ const FEDCM_LOGIN_HINT_KEY = 'oxy_fedcm_login_hint';
21
+
19
22
  // Global lock to prevent concurrent FedCM requests
20
23
  // FedCM only allows one navigator.credentials.get request at a time
21
24
  let fedCMRequestInProgress = false;
@@ -102,7 +105,10 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
102
105
  const nonce = options.nonce || this.generateNonce();
103
106
  const clientId = this.getClientId();
104
107
 
105
- debug.log('Interactive sign-in: Requesting credential for', clientId);
108
+ // Use provided loginHint, or fall back to stored last-used account ID
109
+ const loginHint = options.loginHint || this.getStoredLoginHint();
110
+
111
+ debug.log('Interactive sign-in: Requesting credential for', clientId, loginHint ? `(hint: ${loginHint})` : '');
106
112
 
107
113
  // Request credential from browser's native identity flow
108
114
  const credential = await this.requestIdentityCredential({
@@ -110,6 +116,7 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
110
116
  clientId,
111
117
  nonce,
112
118
  context: options.context,
119
+ loginHint,
113
120
  });
114
121
 
115
122
  if (!credential || !credential.token) {
@@ -126,6 +133,11 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
126
133
  this.httpService.setTokens((session as any).accessToken);
127
134
  }
128
135
 
136
+ // Store the user ID as loginHint for future FedCM requests
137
+ if (session?.user?.id) {
138
+ this.storeLoginHint(session.user.id);
139
+ }
140
+
129
141
  debug.log('Interactive sign-in: Success!', { userId: (session as any)?.user?.id });
130
142
 
131
143
  return session;
@@ -195,14 +207,17 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
195
207
  // Optional/interactive mediation should only happen when the user clicks "Sign In".
196
208
  let credential: { token: string } | null = null;
197
209
 
210
+ const loginHint = this.getStoredLoginHint();
211
+
198
212
  try {
199
213
  const nonce = this.generateNonce();
200
- debug.log('Silent SSO: Attempting silent mediation...');
214
+ debug.log('Silent SSO: Attempting silent mediation...', loginHint ? `(hint: ${loginHint})` : '');
201
215
 
202
216
  credential = await this.requestIdentityCredential({
203
217
  configURL: (this.constructor as any).DEFAULT_CONFIG_URL,
204
218
  clientId,
205
219
  nonce,
220
+ loginHint,
206
221
  mediation: 'silent',
207
222
  });
208
223
 
@@ -263,6 +278,11 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
263
278
  debug.warn('Silent SSO: No accessToken in session response');
264
279
  }
265
280
 
281
+ // Store the user ID as loginHint for future FedCM requests
282
+ if (session.user?.id) {
283
+ this.storeLoginHint(session.user.id);
284
+ }
285
+
266
286
  debug.log('Silent SSO: Success!', {
267
287
  sessionId: session.sessionId?.substring(0, 8) + '...',
268
288
  userId: session.user?.id
@@ -286,6 +306,7 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
286
306
  clientId: string;
287
307
  nonce: string;
288
308
  context?: string;
309
+ loginHint?: string;
289
310
  mediation?: 'silent' | 'optional' | 'required';
290
311
  }): Promise<{ token: string } | null> {
291
312
  const requestedMediation = options.mediation || 'optional';
@@ -346,7 +367,7 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
346
367
  params: {
347
368
  nonce: options.nonce, // For Chrome 145+
348
369
  },
349
- ...(options.context && { loginHint: options.context }),
370
+ ...(options.loginHint && { loginHint: options.loginHint }),
350
371
  },
351
372
  ],
352
373
  },
@@ -480,6 +501,26 @@ export function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(Base: T)
480
501
  }
481
502
  return window.location.origin;
482
503
  }
504
+
505
+ /** @internal */
506
+ public getStoredLoginHint(): string | undefined {
507
+ if (typeof window === 'undefined') return undefined;
508
+ try {
509
+ return localStorage.getItem(FEDCM_LOGIN_HINT_KEY) || undefined;
510
+ } catch {
511
+ return undefined;
512
+ }
513
+ }
514
+
515
+ /** @internal */
516
+ public storeLoginHint(userId: string): void {
517
+ if (typeof window === 'undefined') return;
518
+ try {
519
+ localStorage.setItem(FEDCM_LOGIN_HINT_KEY, userId);
520
+ } catch {
521
+ // Storage full or blocked
522
+ }
523
+ }
483
524
  };
484
525
  }
485
526