@oxyhq/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/cjs/AuthManager.js +361 -0
- package/dist/cjs/CrossDomainAuth.js +258 -0
- package/dist/cjs/HttpService.js +618 -0
- package/dist/cjs/OxyServices.base.js +263 -0
- package/dist/cjs/OxyServices.errors.js +22 -0
- package/dist/cjs/OxyServices.js +63 -0
- package/dist/cjs/constants/version.js +16 -0
- package/dist/cjs/crypto/index.js +20 -0
- package/dist/cjs/crypto/keyManager.js +887 -0
- package/dist/cjs/crypto/polyfill.js +64 -0
- package/dist/cjs/crypto/recoveryPhrase.js +169 -0
- package/dist/cjs/crypto/signatureService.js +296 -0
- package/dist/cjs/i18n/index.js +73 -0
- package/dist/cjs/i18n/locales/ar-SA.json +120 -0
- package/dist/cjs/i18n/locales/ca-ES.json +120 -0
- package/dist/cjs/i18n/locales/de-DE.json +120 -0
- package/dist/cjs/i18n/locales/en-US.json +956 -0
- package/dist/cjs/i18n/locales/es-ES.json +944 -0
- package/dist/cjs/i18n/locales/fr-FR.json +120 -0
- package/dist/cjs/i18n/locales/it-IT.json +120 -0
- package/dist/cjs/i18n/locales/ja-JP.json +119 -0
- package/dist/cjs/i18n/locales/ko-KR.json +120 -0
- package/dist/cjs/i18n/locales/locales/ar-SA.json +120 -0
- package/dist/cjs/i18n/locales/locales/ca-ES.json +120 -0
- package/dist/cjs/i18n/locales/locales/de-DE.json +120 -0
- package/dist/cjs/i18n/locales/locales/en-US.json +956 -0
- package/dist/cjs/i18n/locales/locales/es-ES.json +944 -0
- package/dist/cjs/i18n/locales/locales/fr-FR.json +120 -0
- package/dist/cjs/i18n/locales/locales/it-IT.json +120 -0
- package/dist/cjs/i18n/locales/locales/ja-JP.json +119 -0
- package/dist/cjs/i18n/locales/locales/ko-KR.json +120 -0
- package/dist/cjs/i18n/locales/locales/pt-PT.json +120 -0
- package/dist/cjs/i18n/locales/locales/zh-CN.json +120 -0
- package/dist/cjs/i18n/locales/pt-PT.json +120 -0
- package/dist/cjs/i18n/locales/zh-CN.json +120 -0
- package/dist/cjs/index.js +153 -0
- package/dist/cjs/mixins/OxyServices.analytics.js +49 -0
- package/dist/cjs/mixins/OxyServices.assets.js +380 -0
- package/dist/cjs/mixins/OxyServices.auth.js +259 -0
- package/dist/cjs/mixins/OxyServices.developer.js +97 -0
- package/dist/cjs/mixins/OxyServices.devices.js +116 -0
- package/dist/cjs/mixins/OxyServices.features.js +309 -0
- package/dist/cjs/mixins/OxyServices.fedcm.js +435 -0
- package/dist/cjs/mixins/OxyServices.karma.js +108 -0
- package/dist/cjs/mixins/OxyServices.language.js +154 -0
- package/dist/cjs/mixins/OxyServices.location.js +43 -0
- package/dist/cjs/mixins/OxyServices.payment.js +158 -0
- package/dist/cjs/mixins/OxyServices.popup.js +371 -0
- package/dist/cjs/mixins/OxyServices.privacy.js +162 -0
- package/dist/cjs/mixins/OxyServices.redirect.js +345 -0
- package/dist/cjs/mixins/OxyServices.security.js +81 -0
- package/dist/cjs/mixins/OxyServices.user.js +355 -0
- package/dist/cjs/mixins/OxyServices.utility.js +156 -0
- package/dist/cjs/mixins/index.js +79 -0
- package/dist/cjs/mixins/mixinHelpers.js +53 -0
- package/dist/cjs/models/interfaces.js +20 -0
- package/dist/cjs/models/session.js +2 -0
- package/dist/cjs/shared/index.js +70 -0
- package/dist/cjs/shared/utils/colorUtils.js +153 -0
- package/dist/cjs/shared/utils/debugUtils.js +73 -0
- package/dist/cjs/shared/utils/errorUtils.js +183 -0
- package/dist/cjs/shared/utils/index.js +49 -0
- package/dist/cjs/shared/utils/networkUtils.js +183 -0
- package/dist/cjs/shared/utils/themeUtils.js +106 -0
- package/dist/cjs/utils/apiUtils.js +61 -0
- package/dist/cjs/utils/asyncUtils.js +194 -0
- package/dist/cjs/utils/cache.js +226 -0
- package/dist/cjs/utils/deviceManager.js +205 -0
- package/dist/cjs/utils/errorUtils.js +154 -0
- package/dist/cjs/utils/index.js +26 -0
- package/dist/cjs/utils/languageUtils.js +165 -0
- package/dist/cjs/utils/loggerUtils.js +126 -0
- package/dist/cjs/utils/platform.js +144 -0
- package/dist/cjs/utils/requestUtils.js +209 -0
- package/dist/cjs/utils/sessionUtils.js +181 -0
- package/dist/cjs/utils/validationUtils.js +173 -0
- package/dist/esm/AuthManager.js +356 -0
- package/dist/esm/CrossDomainAuth.js +253 -0
- package/dist/esm/HttpService.js +614 -0
- package/dist/esm/OxyServices.base.js +259 -0
- package/dist/esm/OxyServices.errors.js +17 -0
- package/dist/esm/OxyServices.js +59 -0
- package/dist/esm/constants/version.js +13 -0
- package/dist/esm/crypto/index.js +13 -0
- package/dist/esm/crypto/keyManager.js +850 -0
- package/dist/esm/crypto/polyfill.js +61 -0
- package/dist/esm/crypto/recoveryPhrase.js +132 -0
- package/dist/esm/crypto/signatureService.js +259 -0
- package/dist/esm/i18n/index.js +69 -0
- package/dist/esm/i18n/locales/ar-SA.json +120 -0
- package/dist/esm/i18n/locales/ca-ES.json +120 -0
- package/dist/esm/i18n/locales/de-DE.json +120 -0
- package/dist/esm/i18n/locales/en-US.json +956 -0
- package/dist/esm/i18n/locales/es-ES.json +944 -0
- package/dist/esm/i18n/locales/fr-FR.json +120 -0
- package/dist/esm/i18n/locales/it-IT.json +120 -0
- package/dist/esm/i18n/locales/ja-JP.json +119 -0
- package/dist/esm/i18n/locales/ko-KR.json +120 -0
- package/dist/esm/i18n/locales/locales/ar-SA.json +120 -0
- package/dist/esm/i18n/locales/locales/ca-ES.json +120 -0
- package/dist/esm/i18n/locales/locales/de-DE.json +120 -0
- package/dist/esm/i18n/locales/locales/en-US.json +956 -0
- package/dist/esm/i18n/locales/locales/es-ES.json +944 -0
- package/dist/esm/i18n/locales/locales/fr-FR.json +120 -0
- package/dist/esm/i18n/locales/locales/it-IT.json +120 -0
- package/dist/esm/i18n/locales/locales/ja-JP.json +119 -0
- package/dist/esm/i18n/locales/locales/ko-KR.json +120 -0
- package/dist/esm/i18n/locales/locales/pt-PT.json +120 -0
- package/dist/esm/i18n/locales/locales/zh-CN.json +120 -0
- package/dist/esm/i18n/locales/pt-PT.json +120 -0
- package/dist/esm/i18n/locales/zh-CN.json +120 -0
- package/dist/esm/index.js +55 -0
- package/dist/esm/mixins/OxyServices.analytics.js +46 -0
- package/dist/esm/mixins/OxyServices.assets.js +377 -0
- package/dist/esm/mixins/OxyServices.auth.js +256 -0
- package/dist/esm/mixins/OxyServices.developer.js +94 -0
- package/dist/esm/mixins/OxyServices.devices.js +113 -0
- package/dist/esm/mixins/OxyServices.features.js +306 -0
- package/dist/esm/mixins/OxyServices.fedcm.js +433 -0
- package/dist/esm/mixins/OxyServices.karma.js +105 -0
- package/dist/esm/mixins/OxyServices.language.js +118 -0
- package/dist/esm/mixins/OxyServices.location.js +40 -0
- package/dist/esm/mixins/OxyServices.payment.js +155 -0
- package/dist/esm/mixins/OxyServices.popup.js +369 -0
- package/dist/esm/mixins/OxyServices.privacy.js +159 -0
- package/dist/esm/mixins/OxyServices.redirect.js +343 -0
- package/dist/esm/mixins/OxyServices.security.js +78 -0
- package/dist/esm/mixins/OxyServices.user.js +352 -0
- package/dist/esm/mixins/OxyServices.utility.js +153 -0
- package/dist/esm/mixins/index.js +76 -0
- package/dist/esm/mixins/mixinHelpers.js +48 -0
- package/dist/esm/models/interfaces.js +17 -0
- package/dist/esm/models/session.js +1 -0
- package/dist/esm/shared/index.js +31 -0
- package/dist/esm/shared/utils/colorUtils.js +143 -0
- package/dist/esm/shared/utils/debugUtils.js +65 -0
- package/dist/esm/shared/utils/errorUtils.js +170 -0
- package/dist/esm/shared/utils/index.js +15 -0
- package/dist/esm/shared/utils/networkUtils.js +173 -0
- package/dist/esm/shared/utils/themeUtils.js +98 -0
- package/dist/esm/utils/apiUtils.js +55 -0
- package/dist/esm/utils/asyncUtils.js +179 -0
- package/dist/esm/utils/cache.js +218 -0
- package/dist/esm/utils/deviceManager.js +168 -0
- package/dist/esm/utils/errorUtils.js +146 -0
- package/dist/esm/utils/index.js +7 -0
- package/dist/esm/utils/languageUtils.js +158 -0
- package/dist/esm/utils/loggerUtils.js +115 -0
- package/dist/esm/utils/platform.js +102 -0
- package/dist/esm/utils/requestUtils.js +203 -0
- package/dist/esm/utils/sessionUtils.js +171 -0
- package/dist/esm/utils/validationUtils.js +153 -0
- package/dist/types/AuthManager.d.ts +143 -0
- package/dist/types/CrossDomainAuth.d.ts +160 -0
- package/dist/types/HttpService.d.ts +163 -0
- package/dist/types/OxyServices.base.d.ts +126 -0
- package/dist/types/OxyServices.d.ts +81 -0
- package/dist/types/OxyServices.errors.d.ts +11 -0
- package/dist/types/constants/version.d.ts +13 -0
- package/dist/types/crypto/index.d.ts +11 -0
- package/dist/types/crypto/keyManager.d.ts +189 -0
- package/dist/types/crypto/polyfill.d.ts +11 -0
- package/dist/types/crypto/recoveryPhrase.d.ts +58 -0
- package/dist/types/crypto/signatureService.d.ts +86 -0
- package/dist/types/i18n/index.d.ts +3 -0
- package/dist/types/index.d.ts +50 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +66 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +135 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +186 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +99 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +96 -0
- package/dist/types/mixins/OxyServices.features.d.ts +228 -0
- package/dist/types/mixins/OxyServices.fedcm.d.ts +200 -0
- package/dist/types/mixins/OxyServices.karma.d.ts +85 -0
- package/dist/types/mixins/OxyServices.language.d.ts +81 -0
- package/dist/types/mixins/OxyServices.location.d.ts +64 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +111 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +205 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +122 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +245 -0
- package/dist/types/mixins/OxyServices.security.d.ts +78 -0
- package/dist/types/mixins/OxyServices.user.d.ts +182 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +93 -0
- package/dist/types/mixins/index.d.ts +30 -0
- package/dist/types/mixins/mixinHelpers.d.ts +31 -0
- package/dist/types/models/interfaces.d.ts +415 -0
- package/dist/types/models/session.d.ts +27 -0
- package/dist/types/shared/index.d.ts +28 -0
- package/dist/types/shared/utils/colorUtils.d.ts +104 -0
- package/dist/types/shared/utils/debugUtils.d.ts +48 -0
- package/dist/types/shared/utils/errorUtils.d.ts +97 -0
- package/dist/types/shared/utils/index.d.ts +13 -0
- package/dist/types/shared/utils/networkUtils.d.ts +139 -0
- package/dist/types/shared/utils/themeUtils.d.ts +90 -0
- package/dist/types/utils/apiUtils.d.ts +53 -0
- package/dist/types/utils/asyncUtils.d.ts +58 -0
- package/dist/types/utils/cache.d.ts +127 -0
- package/dist/types/utils/deviceManager.d.ts +65 -0
- package/dist/types/utils/errorUtils.d.ts +46 -0
- package/dist/types/utils/index.d.ts +6 -0
- package/dist/types/utils/languageUtils.d.ts +37 -0
- package/dist/types/utils/loggerUtils.d.ts +48 -0
- package/dist/types/utils/platform.d.ts +40 -0
- package/dist/types/utils/requestUtils.d.ts +123 -0
- package/dist/types/utils/sessionUtils.d.ts +54 -0
- package/dist/types/utils/validationUtils.d.ts +85 -0
- package/package.json +84 -0
- package/src/AuthManager.ts +436 -0
- package/src/CrossDomainAuth.ts +307 -0
- package/src/HttpService.ts +752 -0
- package/src/OxyServices.base.ts +334 -0
- package/src/OxyServices.errors.ts +26 -0
- package/src/OxyServices.ts +129 -0
- package/src/constants/version.ts +15 -0
- package/src/crypto/index.ts +25 -0
- package/src/crypto/keyManager.ts +962 -0
- package/src/crypto/polyfill.ts +70 -0
- package/src/crypto/recoveryPhrase.ts +166 -0
- package/src/crypto/signatureService.ts +323 -0
- package/src/i18n/index.ts +75 -0
- package/src/i18n/locales/ar-SA.json +120 -0
- package/src/i18n/locales/ca-ES.json +120 -0
- package/src/i18n/locales/de-DE.json +120 -0
- package/src/i18n/locales/en-US.json +956 -0
- package/src/i18n/locales/es-ES.json +944 -0
- package/src/i18n/locales/fr-FR.json +120 -0
- package/src/i18n/locales/it-IT.json +120 -0
- package/src/i18n/locales/ja-JP.json +119 -0
- package/src/i18n/locales/ko-KR.json +120 -0
- package/src/i18n/locales/pt-PT.json +120 -0
- package/src/i18n/locales/zh-CN.json +120 -0
- package/src/index.ts +153 -0
- package/src/mixins/OxyServices.analytics.ts +53 -0
- package/src/mixins/OxyServices.assets.ts +412 -0
- package/src/mixins/OxyServices.auth.ts +358 -0
- package/src/mixins/OxyServices.developer.ts +114 -0
- package/src/mixins/OxyServices.devices.ts +119 -0
- package/src/mixins/OxyServices.features.ts +428 -0
- package/src/mixins/OxyServices.fedcm.ts +494 -0
- package/src/mixins/OxyServices.karma.ts +111 -0
- package/src/mixins/OxyServices.language.ts +127 -0
- package/src/mixins/OxyServices.location.ts +46 -0
- package/src/mixins/OxyServices.payment.ts +163 -0
- package/src/mixins/OxyServices.popup.ts +443 -0
- package/src/mixins/OxyServices.privacy.ts +182 -0
- package/src/mixins/OxyServices.redirect.ts +397 -0
- package/src/mixins/OxyServices.security.ts +103 -0
- package/src/mixins/OxyServices.user.ts +392 -0
- package/src/mixins/OxyServices.utility.ts +191 -0
- package/src/mixins/index.ts +91 -0
- package/src/mixins/mixinHelpers.ts +69 -0
- package/src/models/interfaces.ts +511 -0
- package/src/models/session.ts +30 -0
- package/src/shared/index.ts +82 -0
- package/src/shared/utils/colorUtils.ts +155 -0
- package/src/shared/utils/debugUtils.ts +73 -0
- package/src/shared/utils/errorUtils.ts +181 -0
- package/src/shared/utils/index.ts +59 -0
- package/src/shared/utils/networkUtils.ts +248 -0
- package/src/shared/utils/themeUtils.ts +115 -0
- package/src/types/bip39.d.ts +32 -0
- package/src/types/buffer.d.ts +97 -0
- package/src/types/color.d.ts +20 -0
- package/src/types/elliptic.d.ts +62 -0
- package/src/utils/apiUtils.ts +88 -0
- package/src/utils/asyncUtils.ts +252 -0
- package/src/utils/cache.ts +264 -0
- package/src/utils/deviceManager.ts +198 -0
- package/src/utils/errorUtils.ts +216 -0
- package/src/utils/index.ts +21 -0
- package/src/utils/languageUtils.ts +174 -0
- package/src/utils/loggerUtils.ts +153 -0
- package/src/utils/platform.ts +117 -0
- package/src/utils/requestUtils.ts +237 -0
- package/src/utils/sessionUtils.ts +206 -0
- package/src/utils/validationUtils.ts +174 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OxyServicesFedCMMixin = OxyServicesFedCMMixin;
|
|
4
|
+
exports.FedCMMixin = OxyServicesFedCMMixin;
|
|
5
|
+
const OxyServices_errors_1 = require("../OxyServices.errors");
|
|
6
|
+
const debugUtils_1 = require("../shared/utils/debugUtils");
|
|
7
|
+
const debug = (0, debugUtils_1.createDebugLogger)('FedCM');
|
|
8
|
+
// Global lock to prevent concurrent FedCM requests
|
|
9
|
+
// FedCM only allows one navigator.credentials.get request at a time
|
|
10
|
+
let fedCMRequestInProgress = false;
|
|
11
|
+
let fedCMRequestPromise = null;
|
|
12
|
+
let currentMediationMode = null;
|
|
13
|
+
/**
|
|
14
|
+
* Federated Credential Management (FedCM) Authentication Mixin
|
|
15
|
+
*
|
|
16
|
+
* Implements the modern browser-native identity federation API that enables
|
|
17
|
+
* Google-style cross-domain authentication without third-party cookies.
|
|
18
|
+
*
|
|
19
|
+
* Browser Support:
|
|
20
|
+
* - Chrome 108+
|
|
21
|
+
* - Safari 16.4+
|
|
22
|
+
* - Edge 108+
|
|
23
|
+
* - Firefox: Not yet supported (fallback required)
|
|
24
|
+
*
|
|
25
|
+
* Key Features:
|
|
26
|
+
* - No redirects or popups required
|
|
27
|
+
* - Browser-native UI prompts
|
|
28
|
+
* - Privacy-preserving (IdP can't track users)
|
|
29
|
+
* - Automatic SSO across domains
|
|
30
|
+
* - Silent re-authentication support
|
|
31
|
+
*
|
|
32
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/FedCM_API
|
|
33
|
+
*/
|
|
34
|
+
function OxyServicesFedCMMixin(Base) {
|
|
35
|
+
var _a;
|
|
36
|
+
return _a = class extends Base {
|
|
37
|
+
constructor(...args) {
|
|
38
|
+
super(...args);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if FedCM is supported in the current browser
|
|
42
|
+
*/
|
|
43
|
+
static isFedCMSupported() {
|
|
44
|
+
if (typeof window === 'undefined')
|
|
45
|
+
return false;
|
|
46
|
+
return 'IdentityCredential' in window && 'navigator' in window && 'credentials' in navigator;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Instance method to check FedCM support
|
|
50
|
+
*/
|
|
51
|
+
isFedCMSupported() {
|
|
52
|
+
return this.constructor.isFedCMSupported();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Sign in using FedCM (Federated Credential Management API)
|
|
56
|
+
*
|
|
57
|
+
* This provides a Google-style authentication experience:
|
|
58
|
+
* - Browser shows native "Sign in with Oxy" prompt
|
|
59
|
+
* - No redirect or popup required
|
|
60
|
+
* - User approves → credential exchange happens in browser
|
|
61
|
+
* - All apps automatically get SSO after first sign-in
|
|
62
|
+
*
|
|
63
|
+
* @param options - Authentication options
|
|
64
|
+
* @returns Session with access token and user data
|
|
65
|
+
* @throws {OxyAuthenticationError} If FedCM not supported or user cancels
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* try {
|
|
70
|
+
* const session = await oxyServices.signInWithFedCM();
|
|
71
|
+
* console.log('Signed in:', session.user);
|
|
72
|
+
* } catch (error) {
|
|
73
|
+
* // Fallback to popup or redirect auth
|
|
74
|
+
* await oxyServices.signInWithPopup();
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
async signInWithFedCM(options = {}) {
|
|
79
|
+
if (!this.isFedCMSupported()) {
|
|
80
|
+
throw new OxyServices_errors_1.OxyAuthenticationError('FedCM not supported in this browser. Please update your browser or use an alternative sign-in method.');
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const nonce = options.nonce || this.generateNonce();
|
|
84
|
+
const clientId = this.getClientId();
|
|
85
|
+
debug.log('Interactive sign-in: Requesting credential for', clientId);
|
|
86
|
+
// Request credential from browser's native identity flow
|
|
87
|
+
const credential = await this.requestIdentityCredential({
|
|
88
|
+
configURL: this.constructor.DEFAULT_CONFIG_URL,
|
|
89
|
+
clientId,
|
|
90
|
+
nonce,
|
|
91
|
+
context: options.context,
|
|
92
|
+
});
|
|
93
|
+
if (!credential || !credential.token) {
|
|
94
|
+
throw new OxyServices_errors_1.OxyAuthenticationError('No credential received from browser');
|
|
95
|
+
}
|
|
96
|
+
debug.log('Interactive sign-in: Got credential, exchanging for session');
|
|
97
|
+
// Exchange FedCM ID token for Oxy session
|
|
98
|
+
const session = await this.exchangeIdTokenForSession(credential.token);
|
|
99
|
+
// Store access token in HttpService (extract from response or get from session)
|
|
100
|
+
if (session && session.accessToken) {
|
|
101
|
+
this.httpService.setTokens(session.accessToken);
|
|
102
|
+
}
|
|
103
|
+
debug.log('Interactive sign-in: Success!', { userId: session?.user?.id });
|
|
104
|
+
return session;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
debug.log('Interactive sign-in failed:', error);
|
|
108
|
+
if (error.name === 'AbortError') {
|
|
109
|
+
throw new OxyServices_errors_1.OxyAuthenticationError('Sign-in was cancelled by user');
|
|
110
|
+
}
|
|
111
|
+
if (error.name === 'NetworkError') {
|
|
112
|
+
throw new OxyServices_errors_1.OxyAuthenticationError('Network error during sign-in. Please check your connection.');
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Silent sign-in using FedCM
|
|
119
|
+
*
|
|
120
|
+
* Attempts to automatically re-authenticate the user without any UI.
|
|
121
|
+
* This is what enables "instant sign-in" across all Oxy domains after
|
|
122
|
+
* the user has signed in once.
|
|
123
|
+
*
|
|
124
|
+
* The browser will:
|
|
125
|
+
* 1. Check if user has previously signed in to Oxy
|
|
126
|
+
* 2. Check if user is still signed in at auth.oxy.so
|
|
127
|
+
* 3. If yes, automatically provide credential without prompting
|
|
128
|
+
*
|
|
129
|
+
* @returns Session if user is already signed in, null otherwise
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* // On app startup
|
|
134
|
+
* useEffect(() => {
|
|
135
|
+
* const checkAuth = async () => {
|
|
136
|
+
* const session = await oxyServices.silentSignInWithFedCM();
|
|
137
|
+
* if (session) {
|
|
138
|
+
* setUser(session.user);
|
|
139
|
+
* } else {
|
|
140
|
+
* // Show sign-in button
|
|
141
|
+
* }
|
|
142
|
+
* };
|
|
143
|
+
* checkAuth();
|
|
144
|
+
* }, []);
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
async silentSignInWithFedCM() {
|
|
148
|
+
if (!this.isFedCMSupported()) {
|
|
149
|
+
debug.log('Silent SSO: FedCM not supported in this browser');
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const clientId = this.getClientId();
|
|
153
|
+
debug.log('Silent SSO: Starting for', clientId);
|
|
154
|
+
// First try silent mediation (no UI) - works if user previously consented
|
|
155
|
+
let credential = null;
|
|
156
|
+
try {
|
|
157
|
+
const nonce = this.generateNonce();
|
|
158
|
+
debug.log('Silent SSO: Attempting silent mediation...');
|
|
159
|
+
credential = await this.requestIdentityCredential({
|
|
160
|
+
configURL: this.constructor.DEFAULT_CONFIG_URL,
|
|
161
|
+
clientId,
|
|
162
|
+
nonce,
|
|
163
|
+
mediation: 'silent',
|
|
164
|
+
});
|
|
165
|
+
debug.log('Silent SSO: Silent mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
|
|
166
|
+
}
|
|
167
|
+
catch (silentError) {
|
|
168
|
+
// Silent mediation failed - this is expected if user hasn't consented before or is in quiet period
|
|
169
|
+
const errorName = silentError instanceof Error ? silentError.name : 'Unknown';
|
|
170
|
+
const errorMessage = silentError instanceof Error ? silentError.message : String(silentError);
|
|
171
|
+
debug.log('Silent SSO: Silent mediation error (will try optional):', { name: errorName, message: errorMessage });
|
|
172
|
+
}
|
|
173
|
+
// If silent failed, try optional mediation which shows browser UI if needed
|
|
174
|
+
if (!credential || !credential.token) {
|
|
175
|
+
try {
|
|
176
|
+
const nonce = this.generateNonce();
|
|
177
|
+
debug.log('Silent SSO: Trying optional mediation (may show browser UI)...');
|
|
178
|
+
credential = await this.requestIdentityCredential({
|
|
179
|
+
configURL: this.constructor.DEFAULT_CONFIG_URL,
|
|
180
|
+
clientId,
|
|
181
|
+
nonce,
|
|
182
|
+
mediation: 'optional',
|
|
183
|
+
});
|
|
184
|
+
debug.log('Silent SSO: Optional mediation result:', { hasCredential: !!credential, hasToken: !!credential?.token });
|
|
185
|
+
}
|
|
186
|
+
catch (optionalError) {
|
|
187
|
+
const errorName = optionalError instanceof Error ? optionalError.name : 'Unknown';
|
|
188
|
+
const errorMessage = optionalError instanceof Error ? optionalError.message : String(optionalError);
|
|
189
|
+
debug.log('Silent SSO: Optional mediation also failed:', { name: errorName, message: errorMessage });
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!credential || !credential.token) {
|
|
194
|
+
debug.log('Silent SSO: No credential returned (user may have dismissed prompt or is not logged in at IdP)');
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
debug.log('Silent SSO: Got credential, exchanging for session...');
|
|
198
|
+
let session;
|
|
199
|
+
try {
|
|
200
|
+
session = await this.exchangeIdTokenForSession(credential.token);
|
|
201
|
+
}
|
|
202
|
+
catch (exchangeError) {
|
|
203
|
+
debug.error('Silent SSO: Token exchange failed:', exchangeError);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
// Validate session response has required fields
|
|
207
|
+
if (!session) {
|
|
208
|
+
debug.error('Silent SSO: Exchange returned null session');
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (!session.sessionId) {
|
|
212
|
+
debug.error('Silent SSO: Exchange returned session without sessionId:', session);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
if (!session.user) {
|
|
216
|
+
debug.error('Silent SSO: Exchange returned session without user:', session);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
// Set the access token
|
|
220
|
+
if (session.accessToken) {
|
|
221
|
+
this.httpService.setTokens(session.accessToken);
|
|
222
|
+
debug.log('Silent SSO: Access token set');
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
debug.warn('Silent SSO: No accessToken in session response');
|
|
226
|
+
}
|
|
227
|
+
debug.log('Silent SSO: Success!', {
|
|
228
|
+
sessionId: session.sessionId?.substring(0, 8) + '...',
|
|
229
|
+
userId: session.user?.id
|
|
230
|
+
});
|
|
231
|
+
return session;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Request identity credential from browser using FedCM API
|
|
235
|
+
*
|
|
236
|
+
* Uses a global lock to prevent concurrent requests, as FedCM only
|
|
237
|
+
* allows one navigator.credentials.get request at a time.
|
|
238
|
+
*
|
|
239
|
+
* Interactive requests (optional/required) wait for any silent request to finish first.
|
|
240
|
+
*
|
|
241
|
+
* @private
|
|
242
|
+
*/
|
|
243
|
+
async requestIdentityCredential(options) {
|
|
244
|
+
const requestedMediation = options.mediation || 'optional';
|
|
245
|
+
const isInteractive = requestedMediation !== 'silent';
|
|
246
|
+
debug.log('requestIdentityCredential called:', {
|
|
247
|
+
mediation: requestedMediation,
|
|
248
|
+
clientId: options.clientId,
|
|
249
|
+
inProgress: fedCMRequestInProgress,
|
|
250
|
+
});
|
|
251
|
+
// If a request is already in progress...
|
|
252
|
+
if (fedCMRequestInProgress && fedCMRequestPromise) {
|
|
253
|
+
debug.log('Request already in progress, waiting...');
|
|
254
|
+
// If current request is silent and new request is interactive,
|
|
255
|
+
// wait for silent to finish, then make the interactive request
|
|
256
|
+
if (currentMediationMode === 'silent' && isInteractive) {
|
|
257
|
+
try {
|
|
258
|
+
await fedCMRequestPromise;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// Ignore silent request errors
|
|
262
|
+
}
|
|
263
|
+
// Now fall through to make the interactive request
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
// Same type of request - wait for the existing one
|
|
267
|
+
try {
|
|
268
|
+
return await fedCMRequestPromise;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
fedCMRequestInProgress = true;
|
|
276
|
+
currentMediationMode = requestedMediation;
|
|
277
|
+
const controller = new AbortController();
|
|
278
|
+
// Use shorter timeout for silent mediation since it should be quick
|
|
279
|
+
const timeoutMs = requestedMediation === 'silent'
|
|
280
|
+
? this.constructor.FEDCM_SILENT_TIMEOUT
|
|
281
|
+
: this.constructor.FEDCM_TIMEOUT;
|
|
282
|
+
const timeout = setTimeout(() => {
|
|
283
|
+
debug.log('Request timed out after', timeoutMs, 'ms (mediation:', requestedMediation + ')');
|
|
284
|
+
controller.abort();
|
|
285
|
+
}, timeoutMs);
|
|
286
|
+
fedCMRequestPromise = (async () => {
|
|
287
|
+
try {
|
|
288
|
+
debug.log('Calling navigator.credentials.get with mediation:', requestedMediation);
|
|
289
|
+
// Type assertion needed as FedCM types may not be in all TypeScript versions
|
|
290
|
+
const credential = (await navigator.credentials.get({
|
|
291
|
+
identity: {
|
|
292
|
+
providers: [
|
|
293
|
+
{
|
|
294
|
+
configURL: options.configURL,
|
|
295
|
+
clientId: options.clientId,
|
|
296
|
+
// Send nonce at both levels for backward compatibility
|
|
297
|
+
nonce: options.nonce, // For older browsers
|
|
298
|
+
params: {
|
|
299
|
+
nonce: options.nonce, // For Chrome 145+
|
|
300
|
+
},
|
|
301
|
+
...(options.context && { loginHint: options.context }),
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
mediation: requestedMediation,
|
|
306
|
+
signal: controller.signal,
|
|
307
|
+
}));
|
|
308
|
+
debug.log('navigator.credentials.get returned:', {
|
|
309
|
+
hasCredential: !!credential,
|
|
310
|
+
type: credential?.type,
|
|
311
|
+
hasToken: !!credential?.token,
|
|
312
|
+
});
|
|
313
|
+
if (!credential || credential.type !== 'identity') {
|
|
314
|
+
debug.log('No valid identity credential returned');
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
debug.log('Got valid identity credential with token');
|
|
318
|
+
return { token: credential.token };
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
const errorName = error instanceof Error ? error.name : 'Unknown';
|
|
322
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
323
|
+
debug.log('navigator.credentials.get error:', { name: errorName, message: errorMessage });
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
finally {
|
|
327
|
+
clearTimeout(timeout);
|
|
328
|
+
fedCMRequestInProgress = false;
|
|
329
|
+
fedCMRequestPromise = null;
|
|
330
|
+
currentMediationMode = null;
|
|
331
|
+
}
|
|
332
|
+
})();
|
|
333
|
+
return fedCMRequestPromise;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Exchange FedCM ID token for Oxy session
|
|
337
|
+
*
|
|
338
|
+
* The ID token is a JWT issued by auth.oxy.so that proves the user's
|
|
339
|
+
* identity. We exchange it for a full Oxy session with access token.
|
|
340
|
+
*
|
|
341
|
+
* @private
|
|
342
|
+
*/
|
|
343
|
+
async exchangeIdTokenForSession(idToken) {
|
|
344
|
+
debug.log('exchangeIdTokenForSession: Starting exchange...');
|
|
345
|
+
debug.log('exchangeIdTokenForSession: Token length:', idToken?.length);
|
|
346
|
+
debug.log('exchangeIdTokenForSession: Token preview:', idToken?.substring(0, 50) + '...');
|
|
347
|
+
try {
|
|
348
|
+
const response = await this.makeRequest('POST', '/api/fedcm/exchange', { id_token: idToken }, { cache: false });
|
|
349
|
+
debug.log('exchangeIdTokenForSession: Response received:', {
|
|
350
|
+
hasResponse: !!response,
|
|
351
|
+
hasSessionId: !!response?.sessionId,
|
|
352
|
+
hasUser: !!response?.user,
|
|
353
|
+
hasAccessToken: !!response?.accessToken,
|
|
354
|
+
userId: response?.user?.id,
|
|
355
|
+
username: response?.user?.username,
|
|
356
|
+
responseKeys: response ? Object.keys(response) : [],
|
|
357
|
+
});
|
|
358
|
+
return response;
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
debug.error('exchangeIdTokenForSession: Error:', {
|
|
362
|
+
name: error instanceof Error ? error.name : 'Unknown',
|
|
363
|
+
message: error instanceof Error ? error.message : String(error),
|
|
364
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
365
|
+
});
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Revoke FedCM credential (sign out)
|
|
371
|
+
*
|
|
372
|
+
* This tells the browser to forget the FedCM credential for this app.
|
|
373
|
+
* The user will need to re-authenticate next time.
|
|
374
|
+
*/
|
|
375
|
+
async revokeFedCMCredential() {
|
|
376
|
+
if (!this.isFedCMSupported()) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
// FedCM logout API (if available)
|
|
381
|
+
if ('IdentityCredential' in window && 'logout' in window.IdentityCredential) {
|
|
382
|
+
const clientId = this.getClientId();
|
|
383
|
+
await window.IdentityCredential.logout({
|
|
384
|
+
configURL: this.constructor.DEFAULT_CONFIG_URL,
|
|
385
|
+
clientId,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
// Silent failure
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Get configuration for FedCM
|
|
395
|
+
*
|
|
396
|
+
* @returns FedCM configuration with browser support info
|
|
397
|
+
*/
|
|
398
|
+
getFedCMConfig() {
|
|
399
|
+
return {
|
|
400
|
+
enabled: this.isFedCMSupported(),
|
|
401
|
+
configURL: this.constructor.DEFAULT_CONFIG_URL,
|
|
402
|
+
clientId: this.getClientId(),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Generate a cryptographically secure nonce for FedCM
|
|
407
|
+
*
|
|
408
|
+
* @private
|
|
409
|
+
*/
|
|
410
|
+
generateNonce() {
|
|
411
|
+
if (typeof window !== 'undefined' && window.crypto && window.crypto.randomUUID) {
|
|
412
|
+
return window.crypto.randomUUID();
|
|
413
|
+
}
|
|
414
|
+
// Fallback for older browsers
|
|
415
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get the client ID for this origin
|
|
419
|
+
*
|
|
420
|
+
* @private
|
|
421
|
+
*/
|
|
422
|
+
getClientId() {
|
|
423
|
+
if (typeof window === 'undefined') {
|
|
424
|
+
return 'unknown';
|
|
425
|
+
}
|
|
426
|
+
return window.location.origin;
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
_a.DEFAULT_CONFIG_URL = 'https://auth.oxy.so/fedcm.json',
|
|
430
|
+
_a.FEDCM_TIMEOUT = 60000 // 1 minute for interactive
|
|
431
|
+
,
|
|
432
|
+
_a.FEDCM_SILENT_TIMEOUT = 10000 // 10 seconds for silent mediation
|
|
433
|
+
,
|
|
434
|
+
_a;
|
|
435
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OxyServicesKarmaMixin = OxyServicesKarmaMixin;
|
|
4
|
+
const mixinHelpers_1 = require("./mixinHelpers");
|
|
5
|
+
function OxyServicesKarmaMixin(Base) {
|
|
6
|
+
return class extends Base {
|
|
7
|
+
constructor(...args) {
|
|
8
|
+
super(...args);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get user karma
|
|
12
|
+
*/
|
|
13
|
+
async getUserKarma(userId) {
|
|
14
|
+
try {
|
|
15
|
+
return await this.makeRequest('GET', `/api/karma/${userId}`, undefined, {
|
|
16
|
+
cache: true,
|
|
17
|
+
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw this.handleError(error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Give karma to user
|
|
26
|
+
*/
|
|
27
|
+
async giveKarma(userId, amount, reason) {
|
|
28
|
+
try {
|
|
29
|
+
return await this.makeRequest('POST', `/api/karma/${userId}/give`, {
|
|
30
|
+
amount,
|
|
31
|
+
reason
|
|
32
|
+
}, { cache: false });
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw this.handleError(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get user karma total
|
|
40
|
+
* @param userId - The user ID
|
|
41
|
+
* @returns User karma total
|
|
42
|
+
*/
|
|
43
|
+
async getUserKarmaTotal(userId) {
|
|
44
|
+
try {
|
|
45
|
+
return await this.makeRequest('GET', `/api/karma/${userId}/total`, undefined, {
|
|
46
|
+
cache: true,
|
|
47
|
+
cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw this.handleError(error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get user karma history
|
|
56
|
+
* @param userId - The user ID
|
|
57
|
+
* @param limit - Optional limit for results
|
|
58
|
+
* @param offset - Optional offset for pagination
|
|
59
|
+
* @returns User karma history
|
|
60
|
+
*/
|
|
61
|
+
async getUserKarmaHistory(userId, limit, offset) {
|
|
62
|
+
try {
|
|
63
|
+
const params = {};
|
|
64
|
+
if (limit)
|
|
65
|
+
params.limit = limit;
|
|
66
|
+
if (offset)
|
|
67
|
+
params.offset = offset;
|
|
68
|
+
return await this.makeRequest('GET', `/api/karma/${userId}/history`, params, {
|
|
69
|
+
cache: true,
|
|
70
|
+
cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw this.handleError(error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get karma leaderboard
|
|
79
|
+
* @returns Karma leaderboard
|
|
80
|
+
*/
|
|
81
|
+
async getKarmaLeaderboard() {
|
|
82
|
+
try {
|
|
83
|
+
return await this.makeRequest('GET', '/api/karma/leaderboard', undefined, {
|
|
84
|
+
cache: true,
|
|
85
|
+
cacheTTL: mixinHelpers_1.CACHE_TIMES.LONG,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw this.handleError(error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get karma rules
|
|
94
|
+
* @returns Karma rules
|
|
95
|
+
*/
|
|
96
|
+
async getKarmaRules() {
|
|
97
|
+
try {
|
|
98
|
+
return await this.makeRequest('GET', '/api/karma/rules', undefined, {
|
|
99
|
+
cache: true,
|
|
100
|
+
cacheTTL: mixinHelpers_1.CACHE_TIMES.EXTRA_LONG, // Rules don't change often
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw this.handleError(error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|