@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.
- package/dist/cjs/AuthManager.js +2 -0
- package/dist/cjs/mixins/OxyServices.fedcm.js +39 -3
- package/dist/esm/AuthManager.js +3 -1
- package/dist/esm/HttpService.js +6 -6
- package/dist/esm/OxyServices.base.js +3 -3
- package/dist/esm/OxyServices.js +2 -2
- package/dist/esm/crypto/index.js +5 -5
- package/dist/esm/crypto/keyManager.js +3 -3
- package/dist/esm/crypto/recoveryPhrase.js +1 -1
- package/dist/esm/crypto/signatureService.js +2 -2
- package/dist/esm/i18n/index.js +11 -11
- package/dist/esm/index.js +26 -26
- package/dist/esm/mixins/OxyServices.analytics.js +1 -1
- package/dist/esm/mixins/OxyServices.auth.js +1 -1
- package/dist/esm/mixins/OxyServices.developer.js +1 -1
- package/dist/esm/mixins/OxyServices.features.js +1 -1
- package/dist/esm/mixins/OxyServices.fedcm.js +41 -5
- package/dist/esm/mixins/OxyServices.karma.js +1 -1
- package/dist/esm/mixins/OxyServices.language.js +2 -2
- package/dist/esm/mixins/OxyServices.payment.js +1 -1
- package/dist/esm/mixins/OxyServices.popup.js +2 -2
- package/dist/esm/mixins/OxyServices.privacy.js +1 -1
- package/dist/esm/mixins/OxyServices.redirect.js +1 -1
- package/dist/esm/mixins/OxyServices.security.js +1 -1
- package/dist/esm/mixins/OxyServices.user.js +1 -1
- package/dist/esm/mixins/OxyServices.utility.js +1 -1
- package/dist/esm/mixins/index.js +18 -18
- package/dist/esm/shared/index.js +5 -5
- package/dist/esm/shared/utils/index.js +4 -4
- package/dist/esm/utils/asyncUtils.js +1 -1
- package/dist/esm/utils/errorUtils.js +1 -1
- package/dist/esm/utils/index.js +4 -4
- package/dist/types/mixins/OxyServices.fedcm.d.ts +6 -0
- package/package.json +2 -2
- package/src/AuthManager.ts +2 -0
- package/src/mixins/OxyServices.fedcm.ts +44 -3
package/dist/cjs/AuthManager.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
package/dist/esm/AuthManager.js
CHANGED
|
@@ -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.
|
package/dist/esm/HttpService.js
CHANGED
|
@@ -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
|
*/
|
package/dist/esm/OxyServices.js
CHANGED
|
@@ -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
|
*
|
package/dist/esm/crypto/index.js
CHANGED
|
@@ -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');
|
package/dist/esm/i18n/index.js
CHANGED
|
@@ -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,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
|
-
|
|
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.
|
|
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,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,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
|
|
@@ -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) {
|
package/dist/esm/mixins/index.js
CHANGED
|
@@ -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
|
*
|
package/dist/esm/shared/index.js
CHANGED
|
@@ -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
|
package/dist/esm/utils/index.js
CHANGED
|
@@ -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.
|
|
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",
|
package/src/AuthManager.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
|