@oxyhq/core 1.11.8 → 1.11.10
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 +158 -1
- package/dist/cjs/crypto/keyManager.js +4 -6
- package/dist/cjs/crypto/polyfill.js +56 -12
- package/dist/cjs/crypto/signatureService.js +7 -4
- package/dist/cjs/mixins/OxyServices.fedcm.js +5 -7
- package/dist/cjs/mixins/OxyServices.popup.js +7 -7
- package/dist/cjs/mixins/OxyServices.redirect.js +1 -5
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +158 -1
- package/dist/esm/crypto/keyManager.js +4 -6
- package/dist/esm/crypto/polyfill.js +23 -12
- package/dist/esm/crypto/signatureService.js +7 -4
- package/dist/esm/mixins/OxyServices.fedcm.js +5 -7
- package/dist/esm/mixins/OxyServices.popup.js +7 -7
- package/dist/esm/mixins/OxyServices.redirect.js +1 -5
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/AuthManager.d.ts +21 -0
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -3
- package/dist/types/mixins/OxyServices.popup.d.ts +2 -2
- package/dist/types/mixins/OxyServices.redirect.d.ts +0 -2
- package/package.json +1 -1
- package/src/AuthManager.ts +186 -4
- package/src/crypto/keyManager.ts +4 -6
- package/src/crypto/polyfill.ts +23 -12
- package/src/crypto/signatureService.ts +7 -4
- package/src/mixins/OxyServices.fedcm.ts +6 -7
- package/src/mixins/OxyServices.popup.ts +8 -7
- package/src/mixins/OxyServices.redirect.ts +1 -6
package/dist/cjs/AuthManager.js
CHANGED
|
@@ -106,17 +106,106 @@ class AuthManager {
|
|
|
106
106
|
this.currentUser = null;
|
|
107
107
|
this.refreshTimer = null;
|
|
108
108
|
this.refreshPromise = null;
|
|
109
|
+
/** Tracks the access token this instance last knew about, for cross-tab adoption. */
|
|
110
|
+
this._lastKnownAccessToken = null;
|
|
111
|
+
/** BroadcastChannel for coordinating token refreshes across browser tabs. */
|
|
112
|
+
this._broadcastChannel = null;
|
|
113
|
+
/** Set to true when another tab broadcasts a successful refresh, so this tab can skip its own. */
|
|
114
|
+
this._otherTabRefreshed = false;
|
|
109
115
|
this.oxyServices = oxyServices;
|
|
116
|
+
const crossTabSync = config.crossTabSync ?? (typeof BroadcastChannel !== 'undefined');
|
|
110
117
|
this.config = {
|
|
111
118
|
storage: config.storage ?? this.getDefaultStorage(),
|
|
112
119
|
autoRefresh: config.autoRefresh ?? true,
|
|
113
120
|
refreshBuffer: config.refreshBuffer ?? 5 * 60 * 1000, // 5 minutes
|
|
121
|
+
crossTabSync,
|
|
114
122
|
};
|
|
115
123
|
this.storage = this.config.storage;
|
|
116
124
|
// Persist tokens to storage when HttpService refreshes them automatically
|
|
117
125
|
this.oxyServices.httpService.onTokenRefreshed = (accessToken) => {
|
|
126
|
+
this._lastKnownAccessToken = accessToken;
|
|
118
127
|
this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, accessToken);
|
|
119
128
|
};
|
|
129
|
+
// Setup cross-tab coordination in browser environments
|
|
130
|
+
if (this.config.crossTabSync) {
|
|
131
|
+
this._initBroadcastChannel();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Initialize BroadcastChannel for cross-tab token refresh coordination.
|
|
136
|
+
* Only called in browser environments where BroadcastChannel is available.
|
|
137
|
+
*/
|
|
138
|
+
_initBroadcastChannel() {
|
|
139
|
+
if (typeof BroadcastChannel === 'undefined')
|
|
140
|
+
return;
|
|
141
|
+
try {
|
|
142
|
+
this._broadcastChannel = new BroadcastChannel('oxy_auth_sync');
|
|
143
|
+
this._broadcastChannel.onmessage = (event) => {
|
|
144
|
+
this._handleCrossTabMessage(event.data);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// BroadcastChannel not supported or blocked (e.g., opaque origins)
|
|
149
|
+
this._broadcastChannel = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Handle messages from other tabs about token refresh activity.
|
|
154
|
+
*/
|
|
155
|
+
async _handleCrossTabMessage(message) {
|
|
156
|
+
if (!message || !message.type)
|
|
157
|
+
return;
|
|
158
|
+
switch (message.type) {
|
|
159
|
+
case 'tokens_refreshed': {
|
|
160
|
+
// Another tab successfully refreshed. Signal to cancel our pending refresh.
|
|
161
|
+
this._otherTabRefreshed = true;
|
|
162
|
+
// Adopt the new tokens from shared storage
|
|
163
|
+
const newToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
164
|
+
if (newToken && newToken !== this._lastKnownAccessToken) {
|
|
165
|
+
this._lastKnownAccessToken = newToken;
|
|
166
|
+
this.oxyServices.httpService.setTokens(newToken);
|
|
167
|
+
// Re-read session for updated expiry and schedule next refresh
|
|
168
|
+
const sessionJson = await this.storage.getItem(STORAGE_KEYS.SESSION);
|
|
169
|
+
if (sessionJson) {
|
|
170
|
+
try {
|
|
171
|
+
const session = JSON.parse(sessionJson);
|
|
172
|
+
if (session.expiresAt && this.config.autoRefresh) {
|
|
173
|
+
this.setupTokenRefresh(session.expiresAt);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Ignore parse errors
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'signed_out': {
|
|
184
|
+
// Another tab signed out. Clear our local state to stay consistent.
|
|
185
|
+
if (this.refreshTimer) {
|
|
186
|
+
clearTimeout(this.refreshTimer);
|
|
187
|
+
this.refreshTimer = null;
|
|
188
|
+
}
|
|
189
|
+
this.refreshPromise = null;
|
|
190
|
+
this._lastKnownAccessToken = null;
|
|
191
|
+
this.oxyServices.httpService.setTokens('');
|
|
192
|
+
this.currentUser = null;
|
|
193
|
+
this.notifyListeners();
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
// 'refresh_starting' is informational; we don't need to act on it currently
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Broadcast a message to other tabs.
|
|
201
|
+
*/
|
|
202
|
+
_broadcast(message) {
|
|
203
|
+
try {
|
|
204
|
+
this._broadcastChannel?.postMessage(message);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Channel closed or unavailable
|
|
208
|
+
}
|
|
120
209
|
}
|
|
121
210
|
/**
|
|
122
211
|
* Get default storage based on environment.
|
|
@@ -163,6 +252,7 @@ class AuthManager {
|
|
|
163
252
|
async handleAuthSuccess(session, method = 'credentials') {
|
|
164
253
|
// Store tokens
|
|
165
254
|
if (session.accessToken) {
|
|
255
|
+
this._lastKnownAccessToken = session.accessToken;
|
|
166
256
|
await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, session.accessToken);
|
|
167
257
|
this.oxyServices.httpService.setTokens(session.accessToken);
|
|
168
258
|
}
|
|
@@ -227,6 +317,8 @@ class AuthManager {
|
|
|
227
317
|
}
|
|
228
318
|
}
|
|
229
319
|
async _doRefreshToken() {
|
|
320
|
+
// Reset the cross-tab flag before starting
|
|
321
|
+
this._otherTabRefreshed = false;
|
|
230
322
|
// Get session info to find sessionId for token refresh
|
|
231
323
|
const sessionJson = await this.storage.getItem(STORAGE_KEYS.SESSION);
|
|
232
324
|
if (!sessionJson) {
|
|
@@ -243,8 +335,22 @@ class AuthManager {
|
|
|
243
335
|
console.error('AuthManager: Failed to parse session from storage.', err);
|
|
244
336
|
return false;
|
|
245
337
|
}
|
|
338
|
+
// Record the token we know about before attempting refresh
|
|
339
|
+
const tokenBeforeRefresh = this._lastKnownAccessToken;
|
|
340
|
+
// Broadcast that we're starting a refresh (informational for other tabs)
|
|
341
|
+
this._broadcast({ type: 'refresh_starting', sessionId, timestamp: Date.now() });
|
|
246
342
|
try {
|
|
247
343
|
await (0, asyncUtils_1.retryAsync)(async () => {
|
|
344
|
+
// Before each attempt, check if another tab already refreshed
|
|
345
|
+
if (this._otherTabRefreshed) {
|
|
346
|
+
const adoptedToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
347
|
+
if (adoptedToken && adoptedToken !== tokenBeforeRefresh) {
|
|
348
|
+
// Another tab succeeded. Adopt its tokens and short-circuit.
|
|
349
|
+
this._lastKnownAccessToken = adoptedToken;
|
|
350
|
+
this.oxyServices.httpService.setTokens(adoptedToken);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
248
354
|
const httpService = this.oxyServices.httpService;
|
|
249
355
|
// Use session-based token endpoint which handles auto-refresh server-side
|
|
250
356
|
const response = await httpService.request({
|
|
@@ -257,6 +363,7 @@ class AuthManager {
|
|
|
257
363
|
throw new Error('No access token in refresh response');
|
|
258
364
|
}
|
|
259
365
|
// Update access token in storage and HTTP client
|
|
366
|
+
this._lastKnownAccessToken = response.accessToken;
|
|
260
367
|
await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, response.accessToken);
|
|
261
368
|
this.oxyServices.httpService.setTokens(response.accessToken);
|
|
262
369
|
// Update session expiry and schedule next refresh
|
|
@@ -274,6 +381,8 @@ class AuthManager {
|
|
|
274
381
|
this.setupTokenRefresh(response.expiresAt);
|
|
275
382
|
}
|
|
276
383
|
}
|
|
384
|
+
// Broadcast success so other tabs can adopt these tokens
|
|
385
|
+
this._broadcast({ type: 'tokens_refreshed', sessionId, timestamp: Date.now() });
|
|
277
386
|
}, 2, // 2 retries = 3 total attempts
|
|
278
387
|
1000, // 1s base delay with exponential backoff + jitter
|
|
279
388
|
(error) => {
|
|
@@ -286,7 +395,41 @@ class AuthManager {
|
|
|
286
395
|
return true;
|
|
287
396
|
}
|
|
288
397
|
catch {
|
|
289
|
-
// All retry attempts exhausted,
|
|
398
|
+
// All retry attempts exhausted. Before clearing the session, check if
|
|
399
|
+
// another tab managed to refresh successfully while we were retrying.
|
|
400
|
+
// Since all tabs share the same storage (localStorage), a successful
|
|
401
|
+
// refresh from another tab will have written a different access token.
|
|
402
|
+
const currentStoredToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
403
|
+
if (currentStoredToken && currentStoredToken !== tokenBeforeRefresh) {
|
|
404
|
+
// Another tab refreshed successfully. Adopt its tokens instead of logging out.
|
|
405
|
+
this._lastKnownAccessToken = currentStoredToken;
|
|
406
|
+
this.oxyServices.httpService.setTokens(currentStoredToken);
|
|
407
|
+
// Restore user from storage in case it was updated
|
|
408
|
+
const userJson = await this.storage.getItem(STORAGE_KEYS.USER);
|
|
409
|
+
if (userJson) {
|
|
410
|
+
try {
|
|
411
|
+
this.currentUser = JSON.parse(userJson);
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
// Ignore parse errors
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Re-read session expiry and schedule next refresh
|
|
418
|
+
const updatedSessionJson = await this.storage.getItem(STORAGE_KEYS.SESSION);
|
|
419
|
+
if (updatedSessionJson) {
|
|
420
|
+
try {
|
|
421
|
+
const session = JSON.parse(updatedSessionJson);
|
|
422
|
+
if (session.expiresAt && this.config.autoRefresh) {
|
|
423
|
+
this.setupTokenRefresh(session.expiresAt);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Ignore parse errors
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
// No other tab rescued us -- truly clear the session
|
|
290
433
|
await this.clearSession();
|
|
291
434
|
this.currentUser = null;
|
|
292
435
|
this.notifyListeners();
|
|
@@ -328,8 +471,11 @@ class AuthManager {
|
|
|
328
471
|
}
|
|
329
472
|
// Clear HTTP client tokens
|
|
330
473
|
this.oxyServices.httpService.setTokens('');
|
|
474
|
+
this._lastKnownAccessToken = null;
|
|
331
475
|
// Clear storage
|
|
332
476
|
await this.clearSession();
|
|
477
|
+
// Notify other tabs so they also sign out
|
|
478
|
+
this._broadcast({ type: 'signed_out', timestamp: Date.now() });
|
|
333
479
|
// Update state and notify
|
|
334
480
|
this.currentUser = null;
|
|
335
481
|
this.notifyListeners();
|
|
@@ -404,6 +550,7 @@ class AuthManager {
|
|
|
404
550
|
// Restore token to HTTP client
|
|
405
551
|
const token = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
406
552
|
if (token) {
|
|
553
|
+
this._lastKnownAccessToken = token;
|
|
407
554
|
this.oxyServices.httpService.setTokens(token);
|
|
408
555
|
}
|
|
409
556
|
// Check session expiry
|
|
@@ -444,6 +591,16 @@ class AuthManager {
|
|
|
444
591
|
this.refreshTimer = null;
|
|
445
592
|
}
|
|
446
593
|
this.listeners.clear();
|
|
594
|
+
// Close BroadcastChannel
|
|
595
|
+
if (this._broadcastChannel) {
|
|
596
|
+
try {
|
|
597
|
+
this._broadcastChannel.close();
|
|
598
|
+
}
|
|
599
|
+
catch {
|
|
600
|
+
// Ignore close errors
|
|
601
|
+
}
|
|
602
|
+
this._broadcastChannel = null;
|
|
603
|
+
}
|
|
447
604
|
}
|
|
448
605
|
}
|
|
449
606
|
exports.AuthManager = AuthManager;
|
|
@@ -126,13 +126,11 @@ async function getSecureRandomBytes(length) {
|
|
|
126
126
|
return Crypto.getRandomBytes(length);
|
|
127
127
|
}
|
|
128
128
|
// In Node.js, use Node's crypto module
|
|
129
|
-
//
|
|
130
|
-
// This ensures the require is only evaluated in Node.js runtime, not during Metro bundling
|
|
129
|
+
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
131
130
|
try {
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
return new Uint8Array(crypto.randomBytes(length));
|
|
131
|
+
const cryptoModuleName = 'crypto';
|
|
132
|
+
const nodeCrypto = await Promise.resolve(`${cryptoModuleName}`).then(s => __importStar(require(s)));
|
|
133
|
+
return new Uint8Array(nodeCrypto.randomBytes(length));
|
|
136
134
|
}
|
|
137
135
|
catch (error) {
|
|
138
136
|
// Fallback to expo-crypto if Node crypto fails
|
|
@@ -8,6 +8,39 @@
|
|
|
8
8
|
* - Browser/Node.js: Uses native crypto
|
|
9
9
|
* - React Native: Falls back to expo-crypto if native crypto unavailable
|
|
10
10
|
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
11
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
45
|
exports.Buffer = void 0;
|
|
13
46
|
const buffer_1 = require("buffer");
|
|
@@ -30,27 +63,35 @@ if (!globalObject.Buffer) {
|
|
|
30
63
|
}
|
|
31
64
|
// Cache for expo-crypto module (lazy loaded only in React Native)
|
|
32
65
|
let expoCryptoModule = null;
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
66
|
+
let expoCryptoLoadPromise = null;
|
|
67
|
+
/**
|
|
68
|
+
* Eagerly start loading expo-crypto. The module is cached once resolved so
|
|
69
|
+
* the synchronous getRandomValues shim can read from it immediately.
|
|
70
|
+
* Uses dynamic import with variable indirection to prevent ESM bundlers
|
|
71
|
+
* (Vite, webpack) from statically resolving the specifier.
|
|
72
|
+
*/
|
|
73
|
+
function startExpoCryptoLoad() {
|
|
74
|
+
if (expoCryptoLoadPromise)
|
|
75
|
+
return;
|
|
76
|
+
expoCryptoLoadPromise = (async () => {
|
|
37
77
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (typeof require !== 'undefined') {
|
|
41
|
-
const moduleName = 'expo-crypto';
|
|
42
|
-
expoCryptoModule = require(moduleName);
|
|
43
|
-
}
|
|
78
|
+
const moduleName = 'expo-crypto';
|
|
79
|
+
expoCryptoModule = await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s)));
|
|
44
80
|
}
|
|
45
81
|
catch {
|
|
46
82
|
// expo-crypto not available — expected in non-RN environments
|
|
47
83
|
}
|
|
48
|
-
}
|
|
84
|
+
})();
|
|
85
|
+
}
|
|
86
|
+
function getRandomBytesSync(byteCount) {
|
|
87
|
+
// Kick off loading if not already started (should have been started at module init)
|
|
88
|
+
startExpoCryptoLoad();
|
|
49
89
|
if (expoCryptoModule) {
|
|
50
90
|
return expoCryptoModule.getRandomBytes(byteCount);
|
|
51
91
|
}
|
|
52
92
|
throw new Error('No crypto.getRandomValues implementation available. ' +
|
|
53
|
-
'In React Native, install expo-crypto.'
|
|
93
|
+
'In React Native, install expo-crypto. ' +
|
|
94
|
+
'If expo-crypto is installed, ensure the polyfill module is imported early enough for the async load to complete.');
|
|
54
95
|
}
|
|
55
96
|
const cryptoPolyfill = {
|
|
56
97
|
getRandomValues(array) {
|
|
@@ -62,8 +103,11 @@ const cryptoPolyfill = {
|
|
|
62
103
|
};
|
|
63
104
|
// Only polyfill if crypto or crypto.getRandomValues is not available
|
|
64
105
|
if (typeof globalObject.crypto === 'undefined') {
|
|
106
|
+
// Start loading expo-crypto eagerly so it is ready by the time getRandomValues is called
|
|
107
|
+
startExpoCryptoLoad();
|
|
65
108
|
globalObject.crypto = cryptoPolyfill;
|
|
66
109
|
}
|
|
67
110
|
else if (typeof globalObject.crypto.getRandomValues !== 'function') {
|
|
111
|
+
startExpoCryptoLoad();
|
|
68
112
|
globalObject.crypto.getRandomValues = cryptoPolyfill.getRandomValues;
|
|
69
113
|
}
|
|
@@ -98,11 +98,11 @@ class SignatureService {
|
|
|
98
98
|
.join('');
|
|
99
99
|
}
|
|
100
100
|
// In Node.js, use Node's crypto module
|
|
101
|
+
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
101
102
|
if ((0, platform_1.isNodeJS)()) {
|
|
102
103
|
try {
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
const nodeCrypto = getCrypto();
|
|
104
|
+
const cryptoModuleName = 'crypto';
|
|
105
|
+
const nodeCrypto = await Promise.resolve(`${cryptoModuleName}`).then(s => __importStar(require(s)));
|
|
106
106
|
return nodeCrypto.randomBytes(32).toString('hex');
|
|
107
107
|
}
|
|
108
108
|
catch {
|
|
@@ -169,7 +169,10 @@ class SignatureService {
|
|
|
169
169
|
// In React Native, use async verify instead
|
|
170
170
|
throw new Error('verifySync should only be used in Node.js. Use verify() in React Native.');
|
|
171
171
|
}
|
|
172
|
-
//
|
|
172
|
+
// Intentionally using Function constructor here: this method is synchronous by design
|
|
173
|
+
// (Node.js backend hot-path) so we cannot use `await import()`. The Function constructor
|
|
174
|
+
// prevents Metro/bundlers from statically resolving the require. This is acceptable because
|
|
175
|
+
// verifySync is gated by isNodeJS() and will never execute in browser/RN environments.
|
|
173
176
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
174
177
|
const getCrypto = new Function('return require("crypto")');
|
|
175
178
|
const crypto = getCrypto();
|
|
@@ -38,8 +38,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
38
38
|
constructor(...args) {
|
|
39
39
|
super(...args);
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
get fedcmConfigUrl() {
|
|
41
|
+
resolveFedcmConfigUrl() {
|
|
43
42
|
return this.config.authWebUrl
|
|
44
43
|
? `${this.config.authWebUrl}/fedcm.json`
|
|
45
44
|
: this.constructor.DEFAULT_CONFIG_URL;
|
|
@@ -95,7 +94,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
95
94
|
// Request credential from browser's native identity flow
|
|
96
95
|
// mode: 'button' signals this is a user-gesture-initiated flow (Chrome 125+)
|
|
97
96
|
const credential = await this.requestIdentityCredential({
|
|
98
|
-
configURL: this.
|
|
97
|
+
configURL: this.resolveFedcmConfigUrl(),
|
|
99
98
|
clientId,
|
|
100
99
|
nonce,
|
|
101
100
|
context: options.context,
|
|
@@ -185,7 +184,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
185
184
|
const nonce = this.generateNonce();
|
|
186
185
|
debug.log('Silent SSO: Attempting silent mediation...', loginHint ? `(hint: ${loginHint})` : '');
|
|
187
186
|
credential = await this.requestIdentityCredential({
|
|
188
|
-
configURL: this.
|
|
187
|
+
configURL: this.resolveFedcmConfigUrl(),
|
|
189
188
|
clientId,
|
|
190
189
|
nonce,
|
|
191
190
|
loginHint,
|
|
@@ -399,7 +398,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
399
398
|
if ('IdentityCredential' in window && 'disconnect' in window.IdentityCredential) {
|
|
400
399
|
const clientId = this.getClientId();
|
|
401
400
|
await window.IdentityCredential.disconnect({
|
|
402
|
-
configURL: this.
|
|
401
|
+
configURL: this.resolveFedcmConfigUrl(),
|
|
403
402
|
clientId,
|
|
404
403
|
accountHint: accountHint || '*',
|
|
405
404
|
});
|
|
@@ -418,7 +417,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
418
417
|
getFedCMConfig() {
|
|
419
418
|
return {
|
|
420
419
|
enabled: this.isFedCMSupported(),
|
|
421
|
-
configURL: this.
|
|
420
|
+
configURL: this.resolveFedcmConfigUrl(),
|
|
422
421
|
clientId: this.getClientId(),
|
|
423
422
|
};
|
|
424
423
|
}
|
|
@@ -483,7 +482,6 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
483
482
|
}
|
|
484
483
|
}
|
|
485
484
|
},
|
|
486
|
-
_a.DEFAULT_AUTH_URL = 'https://auth.oxy.so',
|
|
487
485
|
_a.DEFAULT_CONFIG_URL = 'https://auth.oxy.so/fedcm.json',
|
|
488
486
|
_a.FEDCM_TIMEOUT = 15000 // 15 seconds for interactive
|
|
489
487
|
,
|
|
@@ -33,8 +33,8 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
33
33
|
constructor(...args) {
|
|
34
34
|
super(...args);
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/** Resolve auth URL from config or static default (method, not getter — getters break in TS mixins) */
|
|
37
|
+
resolveAuthUrl() {
|
|
38
38
|
return this.config.authWebUrl || this.constructor.DEFAULT_AUTH_URL;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
@@ -79,7 +79,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
79
79
|
state,
|
|
80
80
|
nonce,
|
|
81
81
|
clientId: window.location.origin,
|
|
82
|
-
redirectUri: `${this.
|
|
82
|
+
redirectUri: `${this.resolveAuthUrl()}/auth/callback`,
|
|
83
83
|
});
|
|
84
84
|
const popup = this.openCenteredPopup(authUrl, 'Oxy Sign In', width, height);
|
|
85
85
|
if (!popup) {
|
|
@@ -167,7 +167,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
167
167
|
iframe.style.width = '0';
|
|
168
168
|
iframe.style.height = '0';
|
|
169
169
|
iframe.style.border = 'none';
|
|
170
|
-
const silentUrl = `${this.
|
|
170
|
+
const silentUrl = `${this.resolveAuthUrl()}/auth/silent?` + `client_id=${encodeURIComponent(clientId)}&` + `nonce=${nonce}`;
|
|
171
171
|
iframe.src = silentUrl;
|
|
172
172
|
document.body.appendChild(iframe);
|
|
173
173
|
try {
|
|
@@ -218,7 +218,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
218
218
|
reject(new OxyServices_errors_1.OxyAuthenticationError('Authentication timeout'));
|
|
219
219
|
}, timeout);
|
|
220
220
|
const messageHandler = (event) => {
|
|
221
|
-
const authUrl = this.
|
|
221
|
+
const authUrl = this.resolveAuthUrl();
|
|
222
222
|
// Log all messages for debugging
|
|
223
223
|
if (event.data && typeof event.data === 'object' && event.data.type) {
|
|
224
224
|
debug.log('Message received:', {
|
|
@@ -290,7 +290,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
290
290
|
}, timeout);
|
|
291
291
|
const messageHandler = (event) => {
|
|
292
292
|
// Verify origin
|
|
293
|
-
if (event.origin !== this.
|
|
293
|
+
if (event.origin !== this.resolveAuthUrl()) {
|
|
294
294
|
return;
|
|
295
295
|
}
|
|
296
296
|
const { type, session } = event.data;
|
|
@@ -313,7 +313,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
313
313
|
* @private
|
|
314
314
|
*/
|
|
315
315
|
buildAuthUrl(params) {
|
|
316
|
-
const url = new URL(`${this.
|
|
316
|
+
const url = new URL(`${this.resolveAuthUrl()}/${params.mode}`);
|
|
317
317
|
url.searchParams.set('response_type', 'token');
|
|
318
318
|
url.searchParams.set('client_id', params.clientId);
|
|
319
319
|
url.searchParams.set('redirect_uri', params.redirectUri);
|
|
@@ -33,10 +33,6 @@ function OxyServicesRedirectAuthMixin(Base) {
|
|
|
33
33
|
constructor(...args) {
|
|
34
34
|
super(...args);
|
|
35
35
|
}
|
|
36
|
-
/** Resolved auth URL: config.authWebUrl takes precedence over the static default */
|
|
37
|
-
get authUrl() {
|
|
38
|
-
return this.config.authWebUrl || this.constructor.DEFAULT_AUTH_URL;
|
|
39
|
-
}
|
|
40
36
|
/**
|
|
41
37
|
* Sign in using full page redirect
|
|
42
38
|
*
|
|
@@ -232,7 +228,7 @@ function OxyServicesRedirectAuthMixin(Base) {
|
|
|
232
228
|
* @private
|
|
233
229
|
*/
|
|
234
230
|
buildAuthUrl(params) {
|
|
235
|
-
const url = new URL(`${this.
|
|
231
|
+
const url = new URL(`${(this.config.authWebUrl || this.constructor.DEFAULT_AUTH_URL)}/${params.mode}`);
|
|
236
232
|
url.searchParams.set('redirect_uri', params.redirectUri);
|
|
237
233
|
url.searchParams.set('state', params.state);
|
|
238
234
|
url.searchParams.set('nonce', params.nonce);
|