@oxyhq/core 3.4.1 → 3.4.2
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/.tsbuildinfo +1 -1
- package/dist/cjs/AuthManager.js +91 -319
- package/dist/cjs/CrossDomainAuth.js +19 -106
- package/dist/cjs/HttpService.js +49 -73
- package/dist/cjs/OxyServices.base.js +2 -2
- package/dist/cjs/i18n/index.js +7 -1
- package/dist/cjs/i18n/locales/ar-SA.json +18 -2
- package/dist/cjs/i18n/locales/ca-ES.json +18 -2
- package/dist/cjs/i18n/locales/de-DE.json +18 -2
- package/dist/cjs/i18n/locales/en-US.json +16 -2
- package/dist/cjs/i18n/locales/es-ES.json +16 -2
- package/dist/cjs/i18n/locales/fr-FR.json +18 -2
- package/dist/cjs/i18n/locales/it-IT.json +18 -2
- package/dist/cjs/i18n/locales/ja-JP.json +18 -2
- package/dist/cjs/i18n/locales/ko-KR.json +18 -2
- package/dist/cjs/i18n/locales/locales/ar-SA.json +18 -2
- package/dist/cjs/i18n/locales/locales/ca-ES.json +18 -2
- package/dist/cjs/i18n/locales/locales/de-DE.json +18 -2
- package/dist/cjs/i18n/locales/locales/en-US.json +17 -3
- package/dist/cjs/i18n/locales/locales/es-ES.json +16 -2
- package/dist/cjs/i18n/locales/locales/fr-FR.json +18 -2
- package/dist/cjs/i18n/locales/locales/it-IT.json +18 -2
- package/dist/cjs/i18n/locales/locales/ja-JP.json +18 -2
- package/dist/cjs/i18n/locales/locales/ko-KR.json +18 -2
- package/dist/cjs/i18n/locales/locales/pt-PT.json +18 -2
- package/dist/cjs/i18n/locales/locales/zh-CN.json +18 -2
- package/dist/cjs/i18n/locales/pt-PT.json +18 -2
- package/dist/cjs/i18n/locales/zh-CN.json +18 -2
- package/dist/cjs/mixins/OxyServices.auth.js +20 -63
- package/dist/cjs/mixins/OxyServices.fedcm.js +10 -12
- package/dist/cjs/mixins/OxyServices.popup.js +50 -299
- package/dist/cjs/mixins/OxyServices.redirect.js +84 -348
- package/dist/cjs/mixins/OxyServices.silent.js +204 -0
- package/dist/cjs/mixins/OxyServices.sso.js +4 -5
- package/dist/cjs/mixins/OxyServices.utility.js +6 -15
- package/dist/cjs/mixins/index.js +5 -6
- package/dist/cjs/server/index.js +21 -0
- package/dist/cjs/server/rateLimit.js +77 -0
- package/dist/cjs/shared/utils/debugUtils.js +1 -1
- package/dist/cjs/utils/accountUtils.js +4 -4
- package/dist/cjs/utils/authHelpers.js +21 -15
- package/dist/cjs/utils/coldBoot.js +3 -3
- package/dist/cjs/utils/fapiAutoDetect.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +91 -319
- package/dist/esm/CrossDomainAuth.js +19 -106
- package/dist/esm/HttpService.js +49 -73
- package/dist/esm/OxyServices.base.js +2 -2
- package/dist/esm/i18n/index.js +7 -1
- package/dist/esm/i18n/locales/ar-SA.json +18 -2
- package/dist/esm/i18n/locales/ca-ES.json +18 -2
- package/dist/esm/i18n/locales/de-DE.json +18 -2
- package/dist/esm/i18n/locales/en-US.json +16 -2
- package/dist/esm/i18n/locales/es-ES.json +16 -2
- package/dist/esm/i18n/locales/fr-FR.json +18 -2
- package/dist/esm/i18n/locales/it-IT.json +18 -2
- package/dist/esm/i18n/locales/ja-JP.json +18 -2
- package/dist/esm/i18n/locales/ko-KR.json +18 -2
- package/dist/esm/i18n/locales/locales/ar-SA.json +18 -2
- package/dist/esm/i18n/locales/locales/ca-ES.json +18 -2
- package/dist/esm/i18n/locales/locales/de-DE.json +18 -2
- package/dist/esm/i18n/locales/locales/en-US.json +17 -3
- package/dist/esm/i18n/locales/locales/es-ES.json +16 -2
- package/dist/esm/i18n/locales/locales/fr-FR.json +18 -2
- package/dist/esm/i18n/locales/locales/it-IT.json +18 -2
- package/dist/esm/i18n/locales/locales/ja-JP.json +18 -2
- package/dist/esm/i18n/locales/locales/ko-KR.json +18 -2
- package/dist/esm/i18n/locales/locales/pt-PT.json +18 -2
- package/dist/esm/i18n/locales/locales/zh-CN.json +18 -2
- package/dist/esm/i18n/locales/pt-PT.json +18 -2
- package/dist/esm/i18n/locales/zh-CN.json +18 -2
- package/dist/esm/mixins/OxyServices.auth.js +20 -63
- package/dist/esm/mixins/OxyServices.fedcm.js +10 -12
- package/dist/esm/mixins/OxyServices.popup.js +52 -301
- package/dist/esm/mixins/OxyServices.redirect.js +84 -349
- package/dist/esm/mixins/OxyServices.silent.js +202 -0
- package/dist/esm/mixins/OxyServices.sso.js +4 -5
- package/dist/esm/mixins/OxyServices.utility.js +6 -15
- package/dist/esm/mixins/index.js +5 -6
- package/dist/esm/server/index.js +17 -0
- package/dist/esm/server/rateLimit.js +71 -0
- package/dist/esm/shared/utils/debugUtils.js +1 -1
- package/dist/esm/utils/accountUtils.js +4 -4
- package/dist/esm/utils/authHelpers.js +21 -15
- package/dist/esm/utils/coldBoot.js +3 -3
- package/dist/esm/utils/fapiAutoDetect.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/AuthManager.d.ts +26 -53
- package/dist/types/AuthManagerTypes.d.ts +5 -9
- package/dist/types/CrossDomainAuth.d.ts +13 -52
- package/dist/types/HttpService.d.ts +9 -8
- package/dist/types/OxyServices.base.d.ts +1 -1
- package/dist/types/OxyServices.d.ts +4 -10
- package/dist/types/index.d.ts +1 -1
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.appData.d.ts +1 -1
- package/dist/types/mixins/OxyServices.applications.d.ts +1 -1
- package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +10 -31
- package/dist/types/mixins/OxyServices.contacts.d.ts +1 -1
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
- package/dist/types/mixins/OxyServices.features.d.ts +1 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +5 -5
- package/dist/types/mixins/OxyServices.language.d.ts +1 -1
- package/dist/types/mixins/OxyServices.location.d.ts +1 -1
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -1
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
- package/dist/types/mixins/OxyServices.popup.d.ts +18 -120
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
- package/dist/types/mixins/OxyServices.redirect.d.ts +13 -174
- package/dist/types/mixins/OxyServices.reputation.d.ts +1 -1
- package/dist/types/mixins/OxyServices.security.d.ts +1 -1
- package/dist/types/mixins/OxyServices.silent.d.ts +131 -0
- package/dist/types/mixins/OxyServices.sso.d.ts +4 -5
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.user.d.ts +1 -1
- package/dist/types/mixins/OxyServices.utility.d.ts +3 -8
- package/dist/types/mixins/OxyServices.workspaces.d.ts +1 -1
- package/dist/types/mixins/index.d.ts +3 -3
- package/dist/types/models/interfaces.d.ts +5 -16
- package/dist/types/models/session.d.ts +0 -2
- package/dist/types/server/index.d.ts +18 -0
- package/dist/types/server/rateLimit.d.ts +40 -0
- package/dist/types/shared/utils/debugUtils.d.ts +1 -1
- package/dist/types/utils/authHelpers.d.ts +4 -3
- package/dist/types/utils/coldBoot.d.ts +2 -2
- package/dist/types/utils/fapiAutoDetect.d.ts +1 -1
- package/package.json +24 -2
- package/src/AuthManager.ts +100 -370
- package/src/AuthManagerTypes.ts +5 -9
- package/src/CrossDomainAuth.ts +22 -129
- package/src/HttpService.ts +55 -73
- package/src/OxyServices.base.ts +2 -3
- package/src/OxyServices.ts +9 -11
- package/src/__tests__/authManager.cookiePath.test.ts +19 -17
- package/src/__tests__/authManager.security.test.ts +7 -3
- package/src/__tests__/crossDomainAuth.test.ts +26 -118
- package/src/i18n/index.ts +7 -1
- package/src/i18n/locales/ar-SA.json +18 -2
- package/src/i18n/locales/ca-ES.json +18 -2
- package/src/i18n/locales/de-DE.json +18 -2
- package/src/i18n/locales/en-US.json +17 -3
- package/src/i18n/locales/es-ES.json +16 -2
- package/src/i18n/locales/fr-FR.json +18 -2
- package/src/i18n/locales/it-IT.json +18 -2
- package/src/i18n/locales/ja-JP.json +18 -2
- package/src/i18n/locales/ko-KR.json +18 -2
- package/src/i18n/locales/pt-PT.json +18 -2
- package/src/i18n/locales/zh-CN.json +18 -2
- package/src/index.ts +1 -1
- package/src/mixins/OxyServices.auth.ts +23 -75
- package/src/mixins/OxyServices.fedcm.ts +10 -12
- package/src/mixins/OxyServices.redirect.ts +82 -371
- package/src/mixins/OxyServices.silent.ts +272 -0
- package/src/mixins/OxyServices.sso.ts +5 -6
- package/src/mixins/OxyServices.utility.ts +9 -22
- package/src/mixins/__tests__/appData.test.ts +1 -1
- package/src/mixins/__tests__/onTokensChanged.test.ts +1 -1
- package/src/mixins/__tests__/reputation.test.ts +1 -1
- package/src/mixins/__tests__/serviceAuth.test.ts +7 -5
- package/src/mixins/__tests__/silent.test.ts +102 -0
- package/src/mixins/__tests__/verifyChallenge.test.ts +9 -14
- package/src/mixins/index.ts +6 -8
- package/src/models/interfaces.ts +5 -16
- package/src/models/session.ts +1 -3
- package/src/server/index.ts +19 -0
- package/src/server/rateLimit.ts +170 -0
- package/src/shared/utils/debugUtils.ts +1 -1
- package/src/utils/accountUtils.ts +4 -4
- package/src/utils/authHelpers.ts +23 -15
- package/src/utils/coldBoot.ts +4 -4
- package/src/utils/fapiAutoDetect.ts +1 -1
- package/src/mixins/OxyServices.popup.ts +0 -631
- package/src/mixins/__tests__/popup.test.ts +0 -374
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* selects the best authentication method based on browser capabilities:
|
|
7
7
|
*
|
|
8
8
|
* 1. FedCM (if supported) - Modern, Google-style browser-native auth
|
|
9
|
-
* 2.
|
|
10
|
-
* 3. Redirect (final fallback) - Traditional full-page redirect
|
|
9
|
+
* 2. Redirect (fallback) - Tokenless central SSO full-page redirect
|
|
11
10
|
*
|
|
12
11
|
* Usage:
|
|
13
12
|
* ```typescript
|
|
@@ -18,8 +17,8 @@
|
|
|
18
17
|
* // Automatic method selection
|
|
19
18
|
* const session = await auth.signIn();
|
|
20
19
|
*
|
|
21
|
-
* // Or use specific method
|
|
22
|
-
*
|
|
20
|
+
* // Or use a specific method
|
|
21
|
+
* auth.signInWithRedirect();
|
|
23
22
|
* ```
|
|
24
23
|
*/
|
|
25
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -35,53 +34,23 @@ class CrossDomainAuth {
|
|
|
35
34
|
*
|
|
36
35
|
* Tries methods in this order:
|
|
37
36
|
* 1. FedCM (if supported and not in private browsing)
|
|
38
|
-
* 2.
|
|
39
|
-
* 3. Redirect (always works)
|
|
37
|
+
* 2. Redirect (always works)
|
|
40
38
|
*
|
|
41
39
|
* @param options - Authentication options
|
|
42
40
|
* @returns Session with user data and access token
|
|
43
41
|
*/
|
|
44
42
|
async signIn(options = {}) {
|
|
45
43
|
const method = options.method || 'auto';
|
|
46
|
-
// If specific method requested, use it directly. The caller MAY have
|
|
47
|
-
// pre-opened a popup on the raw click (the standard pattern in
|
|
48
|
-
// WebOxyProvider / services useAuth). For the FedCM and redirect paths
|
|
49
|
-
// that popup is unused — close it so it doesn't linger as an orphaned
|
|
50
|
-
// blank window. Close in both success and failure paths.
|
|
51
44
|
if (method === 'fedcm') {
|
|
52
|
-
|
|
53
|
-
const session = await this.signInWithFedCM(options);
|
|
54
|
-
this.closeOrphanPopup(options.popup);
|
|
55
|
-
return session;
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
this.closeOrphanPopup(options.popup);
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (method === 'popup') {
|
|
63
|
-
return this.signInWithPopup(options);
|
|
45
|
+
return this.signInWithFedCM(options);
|
|
64
46
|
}
|
|
65
47
|
if (method === 'redirect') {
|
|
66
|
-
this.closeOrphanPopup(options.popup);
|
|
67
48
|
this.signInWithRedirect(options);
|
|
68
49
|
return null; // Redirect doesn't return immediately
|
|
69
50
|
}
|
|
70
|
-
// Auto mode:
|
|
51
|
+
// Auto mode: try methods in order of preference.
|
|
71
52
|
return this.autoSignIn(options);
|
|
72
53
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Close a caller-supplied popup window that is no longer needed (e.g. the
|
|
75
|
-
* resolved auth method didn't end up using it). Safe against null / already
|
|
76
|
-
* closed handles.
|
|
77
|
-
*
|
|
78
|
-
* @private
|
|
79
|
-
*/
|
|
80
|
-
closeOrphanPopup(popup) {
|
|
81
|
-
if (popup && !popup.closed) {
|
|
82
|
-
popup.close();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
54
|
/**
|
|
86
55
|
* Automatic sign-in with progressive enhancement
|
|
87
56
|
*
|
|
@@ -92,27 +61,13 @@ class CrossDomainAuth {
|
|
|
92
61
|
if (this.isFedCMSupported()) {
|
|
93
62
|
try {
|
|
94
63
|
options.onMethodSelected?.('fedcm');
|
|
95
|
-
|
|
96
|
-
// FedCM succeeded — close the pre-opened popup so it doesn't linger
|
|
97
|
-
// as an orphaned blank window.
|
|
98
|
-
this.closeOrphanPopup(options.popup);
|
|
99
|
-
return session;
|
|
64
|
+
return await this.signInWithFedCM(options);
|
|
100
65
|
}
|
|
101
66
|
catch (error) {
|
|
102
|
-
loggerUtils_1.logger.warn('FedCM failed,
|
|
67
|
+
loggerUtils_1.logger.warn('FedCM failed, falling back to redirect', { component: 'CrossDomainAuth', method: 'autoSignIn' }, error);
|
|
103
68
|
}
|
|
104
69
|
}
|
|
105
|
-
// 2.
|
|
106
|
-
try {
|
|
107
|
-
options.onMethodSelected?.('popup');
|
|
108
|
-
return await this.signInWithPopup(options);
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
loggerUtils_1.logger.warn('Popup failed, falling back to redirect', { component: 'CrossDomainAuth', method: 'autoSignIn' }, error);
|
|
112
|
-
// Popup path failed — close the pre-opened popup before redirecting.
|
|
113
|
-
this.closeOrphanPopup(options.popup);
|
|
114
|
-
}
|
|
115
|
-
// 3. Fallback to redirect (always works)
|
|
70
|
+
// 2. Fallback to redirect (always works)
|
|
116
71
|
options.onMethodSelected?.('redirect');
|
|
117
72
|
this.signInWithRedirect(options);
|
|
118
73
|
return null;
|
|
@@ -120,26 +75,13 @@ class CrossDomainAuth {
|
|
|
120
75
|
/**
|
|
121
76
|
* Sign in using FedCM (Federated Credential Management)
|
|
122
77
|
*
|
|
123
|
-
* Best method - browser-native,
|
|
78
|
+
* Best method - browser-native, Google-like experience
|
|
124
79
|
*/
|
|
125
80
|
async signInWithFedCM(options = {}) {
|
|
126
81
|
return this.oxyServices.signInWithFedCM({
|
|
127
82
|
context: options.isSignup ? 'signup' : 'signin',
|
|
128
83
|
});
|
|
129
84
|
}
|
|
130
|
-
/**
|
|
131
|
-
* Sign in using popup window
|
|
132
|
-
*
|
|
133
|
-
* Good method - preserves app state, no full page reload
|
|
134
|
-
*/
|
|
135
|
-
async signInWithPopup(options = {}) {
|
|
136
|
-
return this.oxyServices.signInWithPopup({
|
|
137
|
-
mode: options.isSignup ? 'signup' : 'login',
|
|
138
|
-
width: options.popupDimensions?.width,
|
|
139
|
-
height: options.popupDimensions?.height,
|
|
140
|
-
popup: options.popup ?? undefined,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
85
|
/**
|
|
144
86
|
* Sign in using full-page redirect
|
|
145
87
|
*
|
|
@@ -163,7 +105,7 @@ class CrossDomainAuth {
|
|
|
163
105
|
* Silent sign-in (check for existing session)
|
|
164
106
|
*
|
|
165
107
|
* Tries to automatically sign in without user interaction.
|
|
166
|
-
* Works with
|
|
108
|
+
* Works with FedCM and iframe-based silent auth.
|
|
167
109
|
*
|
|
168
110
|
* @returns Session if user is already signed in, null otherwise
|
|
169
111
|
*/
|
|
@@ -190,22 +132,13 @@ class CrossDomainAuth {
|
|
|
190
132
|
}
|
|
191
133
|
}
|
|
192
134
|
/**
|
|
193
|
-
* Restore session from storage
|
|
135
|
+
* Restore session from storage.
|
|
194
136
|
*
|
|
195
|
-
*
|
|
137
|
+
* Access tokens are no longer persisted in browser storage; providers restore
|
|
138
|
+
* through refresh cookies / SSO code exchange instead.
|
|
196
139
|
*/
|
|
197
140
|
restoreSession() {
|
|
198
|
-
return
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Open a blank popup SYNCHRONOUSLY (call from a raw user-gesture handler
|
|
202
|
-
* BEFORE any `await`). Returns `null` if the popup was blocked. Pass the
|
|
203
|
-
* handle into `signIn({ popup })` / `signInWithPopup({ popup })` so the
|
|
204
|
-
* popup is not blocked by Chrome after any prior `await` consumed the
|
|
205
|
-
* transient user activation. Delegates to `OxyServices.openBlankPopup`.
|
|
206
|
-
*/
|
|
207
|
-
openBlankPopup(width, height) {
|
|
208
|
-
return this.oxyServices.openBlankPopup(width, height);
|
|
141
|
+
return false;
|
|
209
142
|
}
|
|
210
143
|
/**
|
|
211
144
|
* Check if FedCM is supported in current browser
|
|
@@ -229,8 +162,8 @@ class CrossDomainAuth {
|
|
|
229
162
|
}
|
|
230
163
|
if (typeof window !== 'undefined') {
|
|
231
164
|
return {
|
|
232
|
-
method: '
|
|
233
|
-
reason: 'Browser environment -
|
|
165
|
+
method: 'redirect',
|
|
166
|
+
reason: 'Browser environment - redirect SSO works without token callback URLs',
|
|
234
167
|
};
|
|
235
168
|
}
|
|
236
169
|
return {
|
|
@@ -243,8 +176,7 @@ class CrossDomainAuth {
|
|
|
243
176
|
*
|
|
244
177
|
* This handles:
|
|
245
178
|
* 1. Redirect callback (if returning from auth.oxy.so)
|
|
246
|
-
* 2.
|
|
247
|
-
* 3. Silent sign-in (check for existing SSO session)
|
|
179
|
+
* 2. Silent sign-in (check for existing SSO session)
|
|
248
180
|
*
|
|
249
181
|
* @returns Session if user is authenticated, null otherwise
|
|
250
182
|
*/
|
|
@@ -254,26 +186,7 @@ class CrossDomainAuth {
|
|
|
254
186
|
if (callbackSession) {
|
|
255
187
|
return callbackSession;
|
|
256
188
|
}
|
|
257
|
-
// 2. Try
|
|
258
|
-
const restored = this.restoreSession();
|
|
259
|
-
if (restored) {
|
|
260
|
-
// Verify session is still valid by fetching user
|
|
261
|
-
try {
|
|
262
|
-
const user = await this.oxyServices.getCurrentUser();
|
|
263
|
-
if (user) {
|
|
264
|
-
return {
|
|
265
|
-
sessionId: this.oxyServices.getStoredSessionId?.() || '',
|
|
266
|
-
deviceId: '',
|
|
267
|
-
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
268
|
-
user,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
catch (error) {
|
|
273
|
-
loggerUtils_1.logger.debug('stored session invalid', { component: 'CrossDomainAuth', method: 'initialize' }, error);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
// 3. Try silent sign-in (check for SSO session at auth.oxy.so)
|
|
189
|
+
// 2. Try silent sign-in (check for SSO session at auth.oxy.so)
|
|
277
190
|
return await this.silentSignIn();
|
|
278
191
|
}
|
|
279
192
|
}
|
package/dist/cjs/HttpService.js
CHANGED
|
@@ -60,23 +60,17 @@ function fnv1a32(str) {
|
|
|
60
60
|
class TokenStore {
|
|
61
61
|
constructor() {
|
|
62
62
|
this.accessToken = null;
|
|
63
|
-
this.refreshToken = null;
|
|
64
63
|
this.csrfToken = null;
|
|
65
64
|
this.csrfTokenFetchPromise = null;
|
|
66
65
|
}
|
|
67
|
-
setTokens(accessToken
|
|
66
|
+
setTokens(accessToken) {
|
|
68
67
|
this.accessToken = accessToken;
|
|
69
|
-
this.refreshToken = refreshToken;
|
|
70
68
|
}
|
|
71
69
|
getAccessToken() {
|
|
72
70
|
return this.accessToken;
|
|
73
71
|
}
|
|
74
|
-
getRefreshToken() {
|
|
75
|
-
return this.refreshToken;
|
|
76
|
-
}
|
|
77
72
|
clearTokens() {
|
|
78
73
|
this.accessToken = null;
|
|
79
|
-
this.refreshToken = null;
|
|
80
74
|
}
|
|
81
75
|
hasAccessToken() {
|
|
82
76
|
return !!this.accessToken;
|
|
@@ -108,13 +102,12 @@ class HttpService {
|
|
|
108
102
|
constructor(config) {
|
|
109
103
|
this.tokenRefreshPromise = null;
|
|
110
104
|
this.tokenRefreshCooldownUntil = 0;
|
|
111
|
-
this.
|
|
105
|
+
this.authRefreshHandler = null;
|
|
112
106
|
/**
|
|
113
107
|
* Fan-out listeners notified on EVERY access-token change on this instance:
|
|
114
|
-
* explicit `setTokens`, `clearTokens`,
|
|
115
|
-
* internal 401-driven clear.
|
|
116
|
-
*
|
|
117
|
-
* independent observers can mirror token state without clobbering each other.
|
|
108
|
+
* explicit `setTokens`, `clearTokens`, an AuthManager-owned refresh, and the
|
|
109
|
+
* internal 401-driven clear. This is a Set so multiple independent observers
|
|
110
|
+
* can mirror token state without clobbering each other.
|
|
118
111
|
*
|
|
119
112
|
* Each listener receives the resulting access token, or `null` when cleared.
|
|
120
113
|
*/
|
|
@@ -301,23 +294,13 @@ class HttpService {
|
|
|
301
294
|
clearTimeout(timeoutId);
|
|
302
295
|
// Handle response
|
|
303
296
|
if (!response.ok) {
|
|
304
|
-
// On 401,
|
|
297
|
+
// On 401, delegate refresh to AuthManager and retry once before
|
|
298
|
+
// giving up. HttpService deliberately does not know any session
|
|
299
|
+
// routes; the AuthManager is the single session authority.
|
|
305
300
|
if (response.status === 401 && !config._isAuthRetry) {
|
|
306
|
-
const
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
const decoded = (0, jwt_decode_1.jwtDecode)(currentToken);
|
|
310
|
-
if (decoded.sessionId) {
|
|
311
|
-
const refreshResult = await this._refreshTokenFromSession(decoded.sessionId);
|
|
312
|
-
if (refreshResult) {
|
|
313
|
-
// Retry the request with the new token
|
|
314
|
-
return this.request({ ...config, _isAuthRetry: true, retry: false });
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
// Token decode failed, fall through to clear
|
|
320
|
-
}
|
|
301
|
+
const refreshed = await this.refreshAccessToken('response-401');
|
|
302
|
+
if (refreshed) {
|
|
303
|
+
return this.request({ ...config, _isAuthRetry: true, retry: false });
|
|
321
304
|
}
|
|
322
305
|
// Refresh failed or no token — clear tokens and stale CSRF
|
|
323
306
|
this.tokenStore.clearTokens();
|
|
@@ -344,7 +327,7 @@ class HttpService {
|
|
|
344
327
|
if (contentType && contentType.includes('application/json')) {
|
|
345
328
|
try {
|
|
346
329
|
const errorData = await response.json();
|
|
347
|
-
//
|
|
330
|
+
// Accept either structured error field from API responses.
|
|
348
331
|
if (errorData?.message) {
|
|
349
332
|
errorMessage = errorData.message;
|
|
350
333
|
}
|
|
@@ -711,26 +694,10 @@ class HttpService {
|
|
|
711
694
|
const decoded = (0, jwt_decode_1.jwtDecode)(accessToken);
|
|
712
695
|
const currentTime = Math.floor(Date.now() / 1000);
|
|
713
696
|
// If token expires in less than 60 seconds, refresh it
|
|
714
|
-
if (decoded.exp && decoded.exp - currentTime < 60
|
|
715
|
-
|
|
716
|
-
if (
|
|
717
|
-
return `Bearer ${
|
|
718
|
-
}
|
|
719
|
-
// Deduplicate concurrent refresh attempts. The promise is shared
|
|
720
|
-
// across all concurrent callers and cleared only after it settles,
|
|
721
|
-
// so every awaiter receives the same result.
|
|
722
|
-
if (!this.tokenRefreshPromise) {
|
|
723
|
-
this.tokenRefreshPromise = this._refreshTokenFromSession(decoded.sessionId)
|
|
724
|
-
.then((result) => {
|
|
725
|
-
if (!result)
|
|
726
|
-
this.tokenRefreshCooldownUntil = Date.now() + 15000;
|
|
727
|
-
return result;
|
|
728
|
-
})
|
|
729
|
-
.finally(() => { this.tokenRefreshPromise = null; });
|
|
730
|
-
}
|
|
731
|
-
const result = await this.tokenRefreshPromise;
|
|
732
|
-
if (result)
|
|
733
|
-
return result;
|
|
697
|
+
if (decoded.exp && decoded.exp - currentTime < 60) {
|
|
698
|
+
const refreshed = await this.refreshAccessToken('preflight');
|
|
699
|
+
if (refreshed)
|
|
700
|
+
return `Bearer ${refreshed}`;
|
|
734
701
|
// Refresh failed — don't use the expired token (would cause 401 loop)
|
|
735
702
|
return null;
|
|
736
703
|
}
|
|
@@ -741,28 +708,37 @@ class HttpService {
|
|
|
741
708
|
return null;
|
|
742
709
|
}
|
|
743
710
|
}
|
|
744
|
-
async
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
signal: AbortSignal.timeout(5000),
|
|
751
|
-
credentials: 'include',
|
|
752
|
-
});
|
|
753
|
-
if (response.ok) {
|
|
754
|
-
const { accessToken: newToken } = await response.json();
|
|
755
|
-
this.tokenStore.setTokens(newToken);
|
|
756
|
-
this._onTokenRefreshed?.(newToken);
|
|
757
|
-
this.notifyTokenChange();
|
|
758
|
-
this.logger.debug('Token refreshed');
|
|
759
|
-
return `Bearer ${newToken}`;
|
|
760
|
-
}
|
|
711
|
+
async refreshAccessToken(reason) {
|
|
712
|
+
if (!this.authRefreshHandler) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
if (Date.now() < this.tokenRefreshCooldownUntil) {
|
|
716
|
+
return null;
|
|
761
717
|
}
|
|
762
|
-
|
|
763
|
-
this.
|
|
718
|
+
if (!this.tokenRefreshPromise) {
|
|
719
|
+
this.tokenRefreshPromise = this.authRefreshHandler(reason)
|
|
720
|
+
.then((newToken) => {
|
|
721
|
+
if (!newToken) {
|
|
722
|
+
this.tokenRefreshCooldownUntil = Date.now() + 15000;
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
if (this.tokenStore.getAccessToken() !== newToken) {
|
|
726
|
+
this.tokenStore.setTokens(newToken);
|
|
727
|
+
this.notifyTokenChange();
|
|
728
|
+
}
|
|
729
|
+
this.logger.debug('Token refreshed via AuthManager');
|
|
730
|
+
return newToken;
|
|
731
|
+
})
|
|
732
|
+
.catch((error) => {
|
|
733
|
+
this.logger.warn('Token refresh failed:', error);
|
|
734
|
+
this.tokenRefreshCooldownUntil = Date.now() + 15000;
|
|
735
|
+
return null;
|
|
736
|
+
})
|
|
737
|
+
.finally(() => {
|
|
738
|
+
this.tokenRefreshPromise = null;
|
|
739
|
+
});
|
|
764
740
|
}
|
|
765
|
-
return
|
|
741
|
+
return this.tokenRefreshPromise;
|
|
766
742
|
}
|
|
767
743
|
/**
|
|
768
744
|
* Unwrap standardized API response format
|
|
@@ -818,12 +794,12 @@ class HttpService {
|
|
|
818
794
|
return this._actingAsUserId;
|
|
819
795
|
}
|
|
820
796
|
// Token management
|
|
821
|
-
setTokens(accessToken
|
|
822
|
-
this.tokenStore.setTokens(accessToken
|
|
797
|
+
setTokens(accessToken) {
|
|
798
|
+
this.tokenStore.setTokens(accessToken);
|
|
823
799
|
this.notifyTokenChange();
|
|
824
800
|
}
|
|
825
|
-
|
|
826
|
-
this.
|
|
801
|
+
setAuthRefreshHandler(handler) {
|
|
802
|
+
this.authRefreshHandler = handler;
|
|
827
803
|
}
|
|
828
804
|
clearTokens() {
|
|
829
805
|
this.tokenStore.clearTokens();
|
|
@@ -130,8 +130,8 @@ class OxyServicesBase {
|
|
|
130
130
|
/**
|
|
131
131
|
* Set authentication tokens
|
|
132
132
|
*/
|
|
133
|
-
setTokens(accessToken
|
|
134
|
-
this.httpService.setTokens(accessToken
|
|
133
|
+
setTokens(accessToken) {
|
|
134
|
+
this.httpService.setTokens(accessToken);
|
|
135
135
|
}
|
|
136
136
|
/**
|
|
137
137
|
* Clear stored authentication tokens
|
package/dist/cjs/i18n/index.js
CHANGED
|
@@ -48,8 +48,14 @@ function translate(locale, key, vars) {
|
|
|
48
48
|
const lang = locale && DICTS[locale] ? locale : FALLBACK;
|
|
49
49
|
const dict = DICTS[lang] || DICTS[FALLBACK];
|
|
50
50
|
let val = getNested(dict, key);
|
|
51
|
+
// Per-key fallback to the English dictionary when a key is missing from the
|
|
52
|
+
// resolved (non-English) locale. Without this, a key present in en-US but not
|
|
53
|
+
// yet translated in e.g. es-ES would render the raw dotted key to users.
|
|
54
|
+
if (typeof val !== 'string' && lang !== FALLBACK) {
|
|
55
|
+
val = getNested(DICTS[FALLBACK], key);
|
|
56
|
+
}
|
|
51
57
|
if (typeof val !== 'string')
|
|
52
|
-
return key; //
|
|
58
|
+
return key; // last resort: echo the key when truly absent everywhere
|
|
53
59
|
if (vars) {
|
|
54
60
|
Object.keys(vars).forEach(k => {
|
|
55
61
|
const token = `{{${k}}}`;
|
|
@@ -103,7 +103,9 @@
|
|
|
103
103
|
"createAccount": "إنشاء حساب",
|
|
104
104
|
"signIn": "تسجيل الدخول",
|
|
105
105
|
"verify": "التحقق",
|
|
106
|
-
"resetPassword": "إعادة تعيين كلمة المرور"
|
|
106
|
+
"resetPassword": "إعادة تعيين كلمة المرور",
|
|
107
|
+
"signedOut": "تم تسجيل الخروج",
|
|
108
|
+
"close": "إغلاق"
|
|
107
109
|
},
|
|
108
110
|
"links": {
|
|
109
111
|
"recoverAccount": "استعادة حسابك",
|
|
@@ -115,7 +117,10 @@
|
|
|
115
117
|
"password": "كلمة المرور",
|
|
116
118
|
"confirmPassword": "تأكيد كلمة المرور"
|
|
117
119
|
},
|
|
118
|
-
"revoke": "Revoke"
|
|
120
|
+
"revoke": "Revoke",
|
|
121
|
+
"errors": {
|
|
122
|
+
"signOutAllFailed": "حدثت مشكلة أثناء تسجيل الخروج من جميع الحسابات. يرجى المحاولة مرة أخرى."
|
|
123
|
+
}
|
|
119
124
|
},
|
|
120
125
|
"notifications": {
|
|
121
126
|
"title": "Notifications",
|
|
@@ -198,5 +203,16 @@
|
|
|
198
203
|
"revoked": "Revoked access for {{name}}",
|
|
199
204
|
"revokeFailed": "Failed to revoke access"
|
|
200
205
|
}
|
|
206
|
+
},
|
|
207
|
+
"accountMenu": {
|
|
208
|
+
"label": "قائمة الحساب",
|
|
209
|
+
"manage": "إدارة حساب Oxy الخاص بك",
|
|
210
|
+
"addAnother": "إضافة حساب آخر",
|
|
211
|
+
"signOutAll": "تسجيل الخروج من جميع الحسابات",
|
|
212
|
+
"open": "قائمة الحساب",
|
|
213
|
+
"openHint": "يفتح قائمة الحساب",
|
|
214
|
+
"openWithUser": "قائمة حساب {{name}}",
|
|
215
|
+
"switching": "جارٍ تبديل الحساب…",
|
|
216
|
+
"signOutAccount": "تسجيل خروج {{name}}"
|
|
201
217
|
}
|
|
202
218
|
}
|
|
@@ -103,7 +103,9 @@
|
|
|
103
103
|
"createAccount": "Crear compte",
|
|
104
104
|
"signIn": "Iniciar sessió",
|
|
105
105
|
"verify": "Verificar",
|
|
106
|
-
"resetPassword": "Restablir contrasenya"
|
|
106
|
+
"resetPassword": "Restablir contrasenya",
|
|
107
|
+
"signedOut": "Sessió tancada",
|
|
108
|
+
"close": "Tanca"
|
|
107
109
|
},
|
|
108
110
|
"links": {
|
|
109
111
|
"recoverAccount": "Recuperar el teu compte",
|
|
@@ -115,7 +117,10 @@
|
|
|
115
117
|
"password": "Contrasenya",
|
|
116
118
|
"confirmPassword": "Confirmar contrasenya"
|
|
117
119
|
},
|
|
118
|
-
"revoke": "Revoke"
|
|
120
|
+
"revoke": "Revoke",
|
|
121
|
+
"errors": {
|
|
122
|
+
"signOutAllFailed": "Hi ha hagut un problema en tancar la sessió de tots els comptes. Torna-ho a provar."
|
|
123
|
+
}
|
|
119
124
|
},
|
|
120
125
|
"notifications": {
|
|
121
126
|
"title": "Notifications",
|
|
@@ -198,5 +203,16 @@
|
|
|
198
203
|
"revoked": "Revoked access for {{name}}",
|
|
199
204
|
"revokeFailed": "Failed to revoke access"
|
|
200
205
|
}
|
|
206
|
+
},
|
|
207
|
+
"accountMenu": {
|
|
208
|
+
"label": "Menú del compte",
|
|
209
|
+
"manage": "Gestiona el teu compte d'Oxy",
|
|
210
|
+
"addAnother": "Afegeix un altre compte",
|
|
211
|
+
"signOutAll": "Tanca la sessió de tots els comptes",
|
|
212
|
+
"open": "Menú del compte",
|
|
213
|
+
"openHint": "Obre el menú del compte",
|
|
214
|
+
"openWithUser": "Menú del compte de {{name}}",
|
|
215
|
+
"switching": "Canviant de compte…",
|
|
216
|
+
"signOutAccount": "Tanca la sessió de {{name}}"
|
|
201
217
|
}
|
|
202
218
|
}
|
|
@@ -103,7 +103,9 @@
|
|
|
103
103
|
"createAccount": "Konto erstellen",
|
|
104
104
|
"signIn": "Anmelden",
|
|
105
105
|
"verify": "Überprüfen",
|
|
106
|
-
"resetPassword": "Passwort zurücksetzen"
|
|
106
|
+
"resetPassword": "Passwort zurücksetzen",
|
|
107
|
+
"signedOut": "Abgemeldet",
|
|
108
|
+
"close": "Schließen"
|
|
107
109
|
},
|
|
108
110
|
"links": {
|
|
109
111
|
"recoverAccount": "Ihr Konto wiederherstellen",
|
|
@@ -115,7 +117,10 @@
|
|
|
115
117
|
"password": "Passwort",
|
|
116
118
|
"confirmPassword": "Passwort bestätigen"
|
|
117
119
|
},
|
|
118
|
-
"revoke": "Revoke"
|
|
120
|
+
"revoke": "Revoke",
|
|
121
|
+
"errors": {
|
|
122
|
+
"signOutAllFailed": "Beim Abmelden von allen Konten ist ein Problem aufgetreten. Bitte versuchen Sie es erneut."
|
|
123
|
+
}
|
|
119
124
|
},
|
|
120
125
|
"notifications": {
|
|
121
126
|
"title": "Notifications",
|
|
@@ -198,5 +203,16 @@
|
|
|
198
203
|
"revoked": "Revoked access for {{name}}",
|
|
199
204
|
"revokeFailed": "Failed to revoke access"
|
|
200
205
|
}
|
|
206
|
+
},
|
|
207
|
+
"accountMenu": {
|
|
208
|
+
"label": "Kontomenü",
|
|
209
|
+
"manage": "Ihr Oxy-Konto verwalten",
|
|
210
|
+
"addAnother": "Weiteres Konto hinzufügen",
|
|
211
|
+
"signOutAll": "Von allen Konten abmelden",
|
|
212
|
+
"open": "Kontomenü",
|
|
213
|
+
"openHint": "Öffnet das Kontomenü",
|
|
214
|
+
"openWithUser": "Kontomenü für {{name}}",
|
|
215
|
+
"switching": "Konto wird gewechselt…",
|
|
216
|
+
"signOutAccount": "{{name}} abmelden"
|
|
201
217
|
}
|
|
202
218
|
}
|
|
@@ -1038,7 +1038,8 @@
|
|
|
1038
1038
|
},
|
|
1039
1039
|
"common": {
|
|
1040
1040
|
"errors": {
|
|
1041
|
-
"signOutFailed": "There was a problem signing you out. Please try again."
|
|
1041
|
+
"signOutFailed": "There was a problem signing you out. Please try again.",
|
|
1042
|
+
"signOutAllFailed": "There was a problem signing out of all accounts. Please try again."
|
|
1042
1043
|
},
|
|
1043
1044
|
"confirms": {
|
|
1044
1045
|
"signOut": "Are you sure you want to sign out?",
|
|
@@ -1057,7 +1058,9 @@
|
|
|
1057
1058
|
"signIn": "Sign In",
|
|
1058
1059
|
"signOut": "Sign Out",
|
|
1059
1060
|
"verify": "Verify",
|
|
1060
|
-
"resetPassword": "Reset Password"
|
|
1061
|
+
"resetPassword": "Reset Password",
|
|
1062
|
+
"signedOut": "Signed out",
|
|
1063
|
+
"close": "Close"
|
|
1061
1064
|
},
|
|
1062
1065
|
"cancel": "Cancel",
|
|
1063
1066
|
"revoke": "Revoke",
|
|
@@ -1495,5 +1498,16 @@
|
|
|
1495
1498
|
"revoked": "Revoked access for {{name}}",
|
|
1496
1499
|
"revokeFailed": "Failed to revoke access"
|
|
1497
1500
|
}
|
|
1501
|
+
},
|
|
1502
|
+
"accountMenu": {
|
|
1503
|
+
"label": "Account menu",
|
|
1504
|
+
"manage": "Manage your Oxy Account",
|
|
1505
|
+
"addAnother": "Add another account",
|
|
1506
|
+
"signOutAll": "Sign out of all accounts",
|
|
1507
|
+
"open": "Account menu",
|
|
1508
|
+
"openHint": "Opens the account menu",
|
|
1509
|
+
"openWithUser": "Account menu for {{name}}",
|
|
1510
|
+
"switching": "Switching account…",
|
|
1511
|
+
"signOutAccount": "Sign out {{name}}"
|
|
1498
1512
|
}
|
|
1499
1513
|
}
|
|
@@ -281,7 +281,9 @@
|
|
|
281
281
|
"signIn": "Entrar",
|
|
282
282
|
"signOut": "Cerrar sesión",
|
|
283
283
|
"verify": "Verificar",
|
|
284
|
-
"resetPassword": "Restablecer contraseña"
|
|
284
|
+
"resetPassword": "Restablecer contraseña",
|
|
285
|
+
"signedOut": "Sesión cerrada",
|
|
286
|
+
"close": "Cerrar"
|
|
285
287
|
},
|
|
286
288
|
"cancel": "Cancelar",
|
|
287
289
|
"revoke": "Revocar",
|
|
@@ -302,7 +304,8 @@
|
|
|
302
304
|
"confirmPassword": "Confirmar contraseña"
|
|
303
305
|
},
|
|
304
306
|
"errors": {
|
|
305
|
-
"signOutFailed": "Hubo un problema al cerrar sesión. Inténtalo de nuevo."
|
|
307
|
+
"signOutFailed": "Hubo un problema al cerrar sesión. Inténtalo de nuevo.",
|
|
308
|
+
"signOutAllFailed": "Hubo un problema al cerrar sesión en todas las cuentas. Inténtalo de nuevo."
|
|
306
309
|
},
|
|
307
310
|
"confirms": {
|
|
308
311
|
"signOut": "¿Seguro que quieres cerrar sesión?",
|
|
@@ -1495,5 +1498,16 @@
|
|
|
1495
1498
|
"revoked": "Acceso revocado para {{name}}",
|
|
1496
1499
|
"revokeFailed": "No se ha podido revocar el acceso"
|
|
1497
1500
|
}
|
|
1501
|
+
},
|
|
1502
|
+
"accountMenu": {
|
|
1503
|
+
"label": "Menú de cuenta",
|
|
1504
|
+
"manage": "Gestiona tu cuenta de Oxy",
|
|
1505
|
+
"addAnother": "Añadir otra cuenta",
|
|
1506
|
+
"signOutAll": "Cerrar sesión en todas las cuentas",
|
|
1507
|
+
"open": "Menú de cuenta",
|
|
1508
|
+
"openHint": "Abre el menú de cuenta",
|
|
1509
|
+
"openWithUser": "Menú de cuenta de {{name}}",
|
|
1510
|
+
"switching": "Cambiando de cuenta…",
|
|
1511
|
+
"signOutAccount": "Cerrar sesión de {{name}}"
|
|
1498
1512
|
}
|
|
1499
1513
|
}
|