@oxyhq/core 1.11.9 → 1.11.11
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/HttpService.js +13 -0
- package/dist/cjs/OxyServices.base.js +21 -0
- 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 +9 -4
- package/dist/cjs/mixins/OxyServices.managedAccounts.js +117 -0
- package/dist/cjs/mixins/OxyServices.popup.js +9 -5
- package/dist/cjs/mixins/OxyServices.utility.js +81 -2
- package/dist/cjs/mixins/index.js +2 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +158 -1
- package/dist/esm/HttpService.js +13 -0
- package/dist/esm/OxyServices.base.js +21 -0
- 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 +9 -4
- package/dist/esm/mixins/OxyServices.managedAccounts.js +114 -0
- package/dist/esm/mixins/OxyServices.popup.js +9 -5
- package/dist/esm/mixins/OxyServices.utility.js +81 -2
- package/dist/esm/mixins/index.js +2 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/AuthManager.d.ts +21 -0
- package/dist/types/HttpService.d.ts +3 -0
- package/dist/types/OxyServices.base.d.ts +17 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
- package/dist/types/mixins/OxyServices.features.d.ts +5 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +3 -0
- package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
- package/dist/types/mixins/OxyServices.language.d.ts +2 -0
- package/dist/types/mixins/OxyServices.location.d.ts +2 -0
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +125 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +4 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
- package/dist/types/mixins/OxyServices.security.d.ts +2 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +2 -0
- package/dist/types/mixins/OxyServices.user.d.ts +2 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +22 -0
- package/dist/types/models/interfaces.d.ts +2 -0
- package/package.json +1 -1
- package/src/AuthManager.ts +186 -4
- package/src/HttpService.ts +17 -0
- package/src/OxyServices.base.ts +23 -0
- package/src/crypto/keyManager.ts +4 -6
- package/src/crypto/polyfill.ts +23 -12
- package/src/crypto/signatureService.ts +7 -4
- package/src/index.ts +1 -0
- package/src/mixins/OxyServices.fedcm.ts +11 -4
- package/src/mixins/OxyServices.managedAccounts.ts +147 -0
- package/src/mixins/OxyServices.popup.ts +11 -5
- package/src/mixins/OxyServices.utility.ts +103 -2
- package/src/mixins/index.ts +2 -0
- package/src/models/interfaces.ts +3 -0
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;
|
package/dist/cjs/HttpService.js
CHANGED
|
@@ -84,6 +84,8 @@ class HttpService {
|
|
|
84
84
|
this.tokenRefreshPromise = null;
|
|
85
85
|
this.tokenRefreshCooldownUntil = 0;
|
|
86
86
|
this._onTokenRefreshed = null;
|
|
87
|
+
// Acting-as identity for managed accounts
|
|
88
|
+
this._actingAsUserId = null;
|
|
87
89
|
// Performance monitoring
|
|
88
90
|
this.requestMetrics = {
|
|
89
91
|
totalRequests: 0,
|
|
@@ -200,6 +202,10 @@ class HttpService {
|
|
|
200
202
|
hasNativeAppHeader: headers['X-Native-App'] === 'true',
|
|
201
203
|
});
|
|
202
204
|
}
|
|
205
|
+
// Add X-Acting-As header for managed account identity delegation
|
|
206
|
+
if (this._actingAsUserId) {
|
|
207
|
+
headers['X-Acting-As'] = this._actingAsUserId;
|
|
208
|
+
}
|
|
203
209
|
// Merge custom headers if provided
|
|
204
210
|
if (config.headers) {
|
|
205
211
|
Object.entries(config.headers).forEach(([key, value]) => {
|
|
@@ -582,6 +588,13 @@ class HttpService {
|
|
|
582
588
|
async delete(url, config) {
|
|
583
589
|
return this.request({ method: 'DELETE', url, ...config });
|
|
584
590
|
}
|
|
591
|
+
// Acting-as identity management (managed accounts)
|
|
592
|
+
setActingAs(userId) {
|
|
593
|
+
this._actingAsUserId = userId;
|
|
594
|
+
}
|
|
595
|
+
getActingAs() {
|
|
596
|
+
return this._actingAsUserId;
|
|
597
|
+
}
|
|
585
598
|
// Token management
|
|
586
599
|
setTokens(accessToken, refreshToken = '') {
|
|
587
600
|
this.tokenStore.setTokens(accessToken, refreshToken);
|
|
@@ -141,6 +141,27 @@ class OxyServicesBase {
|
|
|
141
141
|
getAccessToken() {
|
|
142
142
|
return this.httpService.getAccessToken();
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Set the acting-as identity for managed accounts.
|
|
146
|
+
*
|
|
147
|
+
* When set, all subsequent API requests will include the `X-Acting-As` header,
|
|
148
|
+
* causing the server to attribute actions to the managed account. The
|
|
149
|
+
* authenticated user must be an authorized manager of the target account.
|
|
150
|
+
*
|
|
151
|
+
* Pass `null` to clear and revert to the authenticated user's own identity.
|
|
152
|
+
*
|
|
153
|
+
* @param userId - The managed account user ID, or null to clear
|
|
154
|
+
*/
|
|
155
|
+
setActingAs(userId) {
|
|
156
|
+
this.httpService.setActingAs(userId);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the current acting-as identity (managed account user ID), or null
|
|
160
|
+
* if operating as the authenticated user's own identity.
|
|
161
|
+
*/
|
|
162
|
+
getActingAs() {
|
|
163
|
+
return this.httpService.getActingAs();
|
|
164
|
+
}
|
|
144
165
|
/**
|
|
145
166
|
* Wait for authentication to be ready
|
|
146
167
|
*
|
|
@@ -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,6 +38,11 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
38
38
|
constructor(...args) {
|
|
39
39
|
super(...args);
|
|
40
40
|
}
|
|
41
|
+
resolveFedcmConfigUrl() {
|
|
42
|
+
return this.config.authWebUrl
|
|
43
|
+
? `${this.config.authWebUrl}/fedcm.json`
|
|
44
|
+
: this.constructor.DEFAULT_CONFIG_URL;
|
|
45
|
+
}
|
|
41
46
|
/**
|
|
42
47
|
* Check if FedCM is supported in the current browser
|
|
43
48
|
*/
|
|
@@ -89,7 +94,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
89
94
|
// Request credential from browser's native identity flow
|
|
90
95
|
// mode: 'button' signals this is a user-gesture-initiated flow (Chrome 125+)
|
|
91
96
|
const credential = await this.requestIdentityCredential({
|
|
92
|
-
configURL:
|
|
97
|
+
configURL: this.resolveFedcmConfigUrl(),
|
|
93
98
|
clientId,
|
|
94
99
|
nonce,
|
|
95
100
|
context: options.context,
|
|
@@ -179,7 +184,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
179
184
|
const nonce = this.generateNonce();
|
|
180
185
|
debug.log('Silent SSO: Attempting silent mediation...', loginHint ? `(hint: ${loginHint})` : '');
|
|
181
186
|
credential = await this.requestIdentityCredential({
|
|
182
|
-
configURL:
|
|
187
|
+
configURL: this.resolveFedcmConfigUrl(),
|
|
183
188
|
clientId,
|
|
184
189
|
nonce,
|
|
185
190
|
loginHint,
|
|
@@ -393,7 +398,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
393
398
|
if ('IdentityCredential' in window && 'disconnect' in window.IdentityCredential) {
|
|
394
399
|
const clientId = this.getClientId();
|
|
395
400
|
await window.IdentityCredential.disconnect({
|
|
396
|
-
configURL:
|
|
401
|
+
configURL: this.resolveFedcmConfigUrl(),
|
|
397
402
|
clientId,
|
|
398
403
|
accountHint: accountHint || '*',
|
|
399
404
|
});
|
|
@@ -412,7 +417,7 @@ function OxyServicesFedCMMixin(Base) {
|
|
|
412
417
|
getFedCMConfig() {
|
|
413
418
|
return {
|
|
414
419
|
enabled: this.isFedCMSupported(),
|
|
415
|
-
configURL:
|
|
420
|
+
configURL: this.resolveFedcmConfigUrl(),
|
|
416
421
|
clientId: this.getClientId(),
|
|
417
422
|
};
|
|
418
423
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OxyServicesManagedAccountsMixin = OxyServicesManagedAccountsMixin;
|
|
4
|
+
function OxyServicesManagedAccountsMixin(Base) {
|
|
5
|
+
return class extends Base {
|
|
6
|
+
constructor(...args) {
|
|
7
|
+
super(...args);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create a new managed account (sub-account).
|
|
11
|
+
*
|
|
12
|
+
* The server creates a User document with `isManagedAccount: true` and links
|
|
13
|
+
* it to the authenticated user as owner.
|
|
14
|
+
*/
|
|
15
|
+
async createManagedAccount(data) {
|
|
16
|
+
try {
|
|
17
|
+
return await this.makeRequest('POST', '/managed-accounts', data, {
|
|
18
|
+
cache: false,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw this.handleError(error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* List all accounts the authenticated user manages.
|
|
27
|
+
*/
|
|
28
|
+
async getManagedAccounts() {
|
|
29
|
+
try {
|
|
30
|
+
return await this.makeRequest('GET', '/managed-accounts', undefined, {
|
|
31
|
+
cache: true,
|
|
32
|
+
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw this.handleError(error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get details for a specific managed account.
|
|
41
|
+
*/
|
|
42
|
+
async getManagedAccountDetails(accountId) {
|
|
43
|
+
try {
|
|
44
|
+
return await this.makeRequest('GET', `/managed-accounts/${accountId}`, undefined, {
|
|
45
|
+
cache: true,
|
|
46
|
+
cacheTTL: 2 * 60 * 1000,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw this.handleError(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Update a managed account's profile data.
|
|
55
|
+
* Requires owner or admin role.
|
|
56
|
+
*/
|
|
57
|
+
async updateManagedAccount(accountId, data) {
|
|
58
|
+
try {
|
|
59
|
+
return await this.makeRequest('PUT', `/managed-accounts/${accountId}`, data, {
|
|
60
|
+
cache: false,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw this.handleError(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Delete a managed account permanently.
|
|
69
|
+
* Requires owner role.
|
|
70
|
+
*/
|
|
71
|
+
async deleteManagedAccount(accountId) {
|
|
72
|
+
try {
|
|
73
|
+
await this.makeRequest('DELETE', `/managed-accounts/${accountId}`, undefined, {
|
|
74
|
+
cache: false,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw this.handleError(error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Add a manager to a managed account.
|
|
83
|
+
* Requires owner or admin role on the account.
|
|
84
|
+
*
|
|
85
|
+
* @param accountId - The managed account to add the manager to
|
|
86
|
+
* @param userId - The user to grant management access
|
|
87
|
+
* @param role - The role to assign: 'admin' or 'editor'
|
|
88
|
+
*/
|
|
89
|
+
async addManager(accountId, userId, role) {
|
|
90
|
+
try {
|
|
91
|
+
await this.makeRequest('POST', `/managed-accounts/${accountId}/managers`, { userId, role }, {
|
|
92
|
+
cache: false,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw this.handleError(error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Remove a manager from a managed account.
|
|
101
|
+
* Requires owner role.
|
|
102
|
+
*
|
|
103
|
+
* @param accountId - The managed account
|
|
104
|
+
* @param userId - The manager to remove
|
|
105
|
+
*/
|
|
106
|
+
async removeManager(accountId, userId) {
|
|
107
|
+
try {
|
|
108
|
+
await this.makeRequest('DELETE', `/managed-accounts/${accountId}/managers/${userId}`, undefined, {
|
|
109
|
+
cache: false,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
throw this.handleError(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -33,6 +33,10 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
33
33
|
constructor(...args) {
|
|
34
34
|
super(...args);
|
|
35
35
|
}
|
|
36
|
+
/** Resolve auth URL from config or static default (method, not getter — getters break in TS mixins) */
|
|
37
|
+
resolveAuthUrl() {
|
|
38
|
+
return this.config.authWebUrl || this.constructor.DEFAULT_AUTH_URL;
|
|
39
|
+
}
|
|
36
40
|
/**
|
|
37
41
|
* Sign in using popup window
|
|
38
42
|
*
|
|
@@ -75,7 +79,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
75
79
|
state,
|
|
76
80
|
nonce,
|
|
77
81
|
clientId: window.location.origin,
|
|
78
|
-
redirectUri: `${
|
|
82
|
+
redirectUri: `${this.resolveAuthUrl()}/auth/callback`,
|
|
79
83
|
});
|
|
80
84
|
const popup = this.openCenteredPopup(authUrl, 'Oxy Sign In', width, height);
|
|
81
85
|
if (!popup) {
|
|
@@ -163,7 +167,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
163
167
|
iframe.style.width = '0';
|
|
164
168
|
iframe.style.height = '0';
|
|
165
169
|
iframe.style.border = 'none';
|
|
166
|
-
const silentUrl = `${
|
|
170
|
+
const silentUrl = `${this.resolveAuthUrl()}/auth/silent?` + `client_id=${encodeURIComponent(clientId)}&` + `nonce=${nonce}`;
|
|
167
171
|
iframe.src = silentUrl;
|
|
168
172
|
document.body.appendChild(iframe);
|
|
169
173
|
try {
|
|
@@ -214,7 +218,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
214
218
|
reject(new OxyServices_errors_1.OxyAuthenticationError('Authentication timeout'));
|
|
215
219
|
}, timeout);
|
|
216
220
|
const messageHandler = (event) => {
|
|
217
|
-
const authUrl =
|
|
221
|
+
const authUrl = this.resolveAuthUrl();
|
|
218
222
|
// Log all messages for debugging
|
|
219
223
|
if (event.data && typeof event.data === 'object' && event.data.type) {
|
|
220
224
|
debug.log('Message received:', {
|
|
@@ -286,7 +290,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
286
290
|
}, timeout);
|
|
287
291
|
const messageHandler = (event) => {
|
|
288
292
|
// Verify origin
|
|
289
|
-
if (event.origin !==
|
|
293
|
+
if (event.origin !== this.resolveAuthUrl()) {
|
|
290
294
|
return;
|
|
291
295
|
}
|
|
292
296
|
const { type, session } = event.data;
|
|
@@ -309,7 +313,7 @@ function OxyServicesPopupAuthMixin(Base) {
|
|
|
309
313
|
* @private
|
|
310
314
|
*/
|
|
311
315
|
buildAuthUrl(params) {
|
|
312
|
-
const url = new URL(`${
|
|
316
|
+
const url = new URL(`${this.resolveAuthUrl()}/${params.mode}`);
|
|
313
317
|
url.searchParams.set('response_type', 'token');
|
|
314
318
|
url.searchParams.set('client_id', params.clientId);
|
|
315
319
|
url.searchParams.set('redirect_uri', params.redirectUri);
|