@oxy.so/core 4.5.0-oxy-one.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/HttpService.js +63 -19
- package/dist/cjs/api/accounts.js +10 -3
- package/dist/cjs/api/agency.js +3 -1
- package/dist/cjs/api/apps.js +15 -4
- package/dist/cjs/api/assets.js +49 -12
- package/dist/cjs/api/auth.js +37 -11
- package/dist/cjs/api/billing.js +24 -7
- package/dist/cjs/api/civic.js +14 -4
- package/dist/cjs/api/follows.js +9 -2
- package/dist/cjs/api/identity.js +31 -9
- package/dist/cjs/api/nodes.js +3 -1
- package/dist/cjs/api/privacy.js +8 -2
- package/dist/cjs/api/session.js +15 -4
- package/dist/cjs/api/store.js +15 -5
- package/dist/cjs/api/topics.js +10 -3
- package/dist/cjs/api/users.js +16 -4
- package/dist/cjs/boot/sessionColdBoot.js +40 -13
- package/dist/cjs/constants/version.js +6 -6
- package/dist/cjs/crypto/identityMarker.js +3 -1
- package/dist/cjs/crypto/identityProof.js +6 -1
- package/dist/cjs/crypto/keyManager.js +87 -32
- package/dist/cjs/crypto/recoveryPhrase.js +2 -2
- package/dist/cjs/crypto/signatureService.js +3 -4
- package/dist/cjs/i18n/index.js +16 -3
- package/dist/cjs/i18n/locales/ar-SA.js +366 -366
- package/dist/cjs/i18n/locales/ca-ES.js +366 -366
- package/dist/cjs/i18n/locales/de-DE.js +366 -366
- package/dist/cjs/i18n/locales/en-US.js +1994 -1994
- package/dist/cjs/i18n/locales/es-ES.js +1994 -1994
- package/dist/cjs/i18n/locales/fr-FR.js +366 -366
- package/dist/cjs/i18n/locales/it-IT.js +366 -366
- package/dist/cjs/i18n/locales/ja-JP.js +366 -366
- package/dist/cjs/i18n/locales/ko-KR.js +366 -366
- package/dist/cjs/i18n/locales/pt-PT.js +366 -366
- package/dist/cjs/i18n/locales/zh-CN.js +366 -366
- package/dist/cjs/inference/OxyInferenceClient.js +12 -18
- package/dist/cjs/models/interfaces.js +11 -11
- package/dist/cjs/server/OxyServer.js +86 -28
- package/dist/cjs/server/agentAccount.js +6 -2
- package/dist/cjs/server/aliaMachineCredential.js +39 -14
- package/dist/cjs/server/auth.js +5 -4
- package/dist/cjs/server/capabilityTicket.js +5 -5
- package/dist/cjs/server/index.js +2 -1
- package/dist/cjs/server/liveCapabilityTicket.js +4 -1
- package/dist/cjs/server/middleware.js +61 -42
- package/dist/cjs/server/namespaces.js +28 -7
- package/dist/cjs/server/requesterAssertion.js +36 -30
- package/dist/cjs/server/serviceTokenLifetime.js +7 -4
- package/dist/cjs/server/traffic.js +53 -14
- package/dist/cjs/server/trafficNodeHttp.js +33 -8
- package/dist/cjs/server/workloadIdentity.client.js +4 -0
- package/dist/cjs/server/workloadIdentity.js +28 -1
- package/dist/cjs/session/SessionClient.js +54 -17
- package/dist/cjs/session/accountDelegationAccess.js +4 -6
- package/dist/cjs/session/accountDialogController.js +1 -1
- package/dist/cjs/session/authStateStore.js +18 -9
- package/dist/cjs/session/identityPin.js +6 -2
- package/dist/cjs/session/refresh.js +81 -23
- package/dist/cjs/session/sharedDeviceCredential.js +28 -15
- package/dist/cjs/shared/utils/errorUtils.js +1 -1
- package/dist/cjs/shared/utils/networkUtils.js +1 -1
- package/dist/cjs/shared/utils/themeUtils.js +1 -1
- package/dist/cjs/utils/accountCacheSweep.js +3 -1
- package/dist/cjs/utils/accountUtils.js +1 -3
- package/dist/cjs/utils/asyncUtils.js +1 -1
- package/dist/cjs/utils/authHelpers.js +2 -1
- package/dist/cjs/utils/avatarUtils.js +4 -2
- package/dist/cjs/utils/cache.js +1 -1
- package/dist/cjs/utils/cacheKey.js +3 -1
- package/dist/cjs/utils/deviceManager.js +4 -4
- package/dist/cjs/utils/errorUtils.js +6 -4
- package/dist/cjs/utils/languageUtils.js +404 -57
- package/dist/cjs/utils/profileLinks.js +1 -3
- package/dist/cjs/utils/requestUtils.js +7 -1
- package/dist/cjs/utils/userIdentity.js +1 -1
- package/dist/cjs/utils/validationUtils.js +5 -6
- package/dist/esm/HttpService.js +64 -20
- package/dist/esm/api/accounts.js +11 -4
- package/dist/esm/api/agency.js +3 -1
- package/dist/esm/api/apps.js +15 -4
- package/dist/esm/api/assets.js +49 -12
- package/dist/esm/api/auth.js +37 -11
- package/dist/esm/api/billing.js +25 -8
- package/dist/esm/api/civic.js +14 -4
- package/dist/esm/api/follows.js +9 -2
- package/dist/esm/api/identity.js +31 -9
- package/dist/esm/api/nodes.js +3 -1
- package/dist/esm/api/privacy.js +8 -2
- package/dist/esm/api/session.js +15 -4
- package/dist/esm/api/store.js +15 -5
- package/dist/esm/api/topics.js +10 -3
- package/dist/esm/api/users.js +16 -4
- package/dist/esm/boot/sessionColdBoot.js +41 -14
- package/dist/esm/constants/version.js +6 -6
- package/dist/esm/crypto/identityMarker.js +3 -1
- package/dist/esm/crypto/identityProof.js +6 -1
- package/dist/esm/crypto/internal.js +1 -1
- package/dist/esm/crypto/keyManager.js +87 -32
- package/dist/esm/crypto/recoveryPhrase.js +2 -2
- package/dist/esm/crypto/signatureService.js +3 -4
- package/dist/esm/i18n/index.js +16 -3
- package/dist/esm/i18n/locales/ar-SA.js +366 -366
- package/dist/esm/i18n/locales/ca-ES.js +366 -366
- package/dist/esm/i18n/locales/de-DE.js +366 -366
- package/dist/esm/i18n/locales/en-US.js +1994 -1994
- package/dist/esm/i18n/locales/es-ES.js +1994 -1994
- package/dist/esm/i18n/locales/fr-FR.js +366 -366
- package/dist/esm/i18n/locales/it-IT.js +366 -366
- package/dist/esm/i18n/locales/ja-JP.js +366 -366
- package/dist/esm/i18n/locales/ko-KR.js +366 -366
- package/dist/esm/i18n/locales/pt-PT.js +366 -366
- package/dist/esm/i18n/locales/zh-CN.js +366 -366
- package/dist/esm/index.js +1 -1
- package/dist/esm/inference/OxyInferenceClient.js +13 -19
- package/dist/esm/models/interfaces.js +11 -11
- package/dist/esm/server/OxyServer.js +87 -29
- package/dist/esm/server/agentAccount.js +6 -2
- package/dist/esm/server/aliaMachineCredential.js +39 -14
- package/dist/esm/server/auth.js +6 -5
- package/dist/esm/server/capabilityTicket.js +6 -6
- package/dist/esm/server/index.js +5 -5
- package/dist/esm/server/liveCapabilityTicket.js +6 -3
- package/dist/esm/server/middleware.js +62 -43
- package/dist/esm/server/namespaces.js +31 -10
- package/dist/esm/server/requesterAssertion.js +36 -30
- package/dist/esm/server/serviceTokenLifetime.js +7 -4
- package/dist/esm/server/traffic.js +55 -16
- package/dist/esm/server/trafficNodeHttp.js +34 -9
- package/dist/esm/server/userInvalidation.js +1 -1
- package/dist/esm/server/workloadIdentity.client.js +3 -0
- package/dist/esm/server/workloadIdentity.js +27 -1
- package/dist/esm/session/SessionClient.js +54 -17
- package/dist/esm/session/accountDelegationAccess.js +5 -7
- package/dist/esm/session/accountDialogController.js +1 -1
- package/dist/esm/session/authStateStore.js +18 -9
- package/dist/esm/session/identityPin.js +6 -2
- package/dist/esm/session/refresh.js +82 -24
- package/dist/esm/session/sharedDeviceCredential.js +28 -15
- package/dist/esm/shared/utils/errorUtils.js +1 -1
- package/dist/esm/shared/utils/networkUtils.js +1 -1
- package/dist/esm/shared/utils/themeUtils.js +1 -1
- package/dist/esm/utils/accountCacheSweep.js +3 -1
- package/dist/esm/utils/accountUtils.js +1 -3
- package/dist/esm/utils/asyncUtils.js +1 -1
- package/dist/esm/utils/authHelpers.js +2 -1
- package/dist/esm/utils/avatarUtils.js +4 -2
- package/dist/esm/utils/cache.js +1 -1
- package/dist/esm/utils/cacheKey.js +3 -1
- package/dist/esm/utils/deviceManager.js +4 -4
- package/dist/esm/utils/errorUtils.js +6 -4
- package/dist/esm/utils/languageUtils.js +404 -57
- package/dist/esm/utils/profileLinks.js +1 -3
- package/dist/esm/utils/requestUtils.js +7 -1
- package/dist/esm/utils/userIdentity.js +1 -1
- package/dist/esm/utils/validationUtils.js +5 -6
- package/dist/types/HttpService.d.ts +1 -1
- package/dist/types/api/accounts.d.ts +1 -1
- package/dist/types/api/apps.d.ts +4 -4
- package/dist/types/api/assets.d.ts +2 -0
- package/dist/types/api/auth.d.ts +8 -3
- package/dist/types/crypto/internal.d.ts +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/server/index.d.ts +6 -6
- package/dist/types/server/requesterAssertion.d.ts +2 -2
- package/dist/types/server/workloadIdentity.client.d.ts +5 -1
- package/dist/types/server/workloadIdentity.d.ts +26 -0
- package/dist/types/session/accountDelegationAccess.d.ts +1 -1
- package/dist/types/session/index.d.ts +1 -1
- package/dist/types/utils/userIdentity.d.ts +1 -1
- package/dist/types/utils/validationUtils.d.ts +4 -5
- package/package.json +2 -3
- package/src/HttpService.ts +183 -53
- package/src/OxyServices.errors.ts +7 -3
- package/src/OxyServices.ts +6 -2
- package/src/api/accounts.ts +76 -26
- package/src/api/agency.ts +24 -9
- package/src/api/appData.ts +12 -2
- package/src/api/apps.ts +81 -31
- package/src/api/assets.ts +122 -40
- package/src/api/auth.ts +154 -79
- package/src/api/billing.ts +112 -65
- package/src/api/civic.ts +61 -19
- package/src/api/contacts.ts +4 -1
- package/src/api/devices.ts +54 -21
- package/src/api/follows.ts +88 -31
- package/src/api/identity.ts +101 -33
- package/src/api/linkedAccounts.ts +26 -8
- package/src/api/nodes.ts +18 -6
- package/src/api/notifications.ts +26 -6
- package/src/api/privacy.ts +42 -8
- package/src/api/reputation.ts +18 -7
- package/src/api/session.ts +23 -5
- package/src/api/store.ts +81 -31
- package/src/api/topics.ts +18 -6
- package/src/api/users.ts +98 -35
- package/src/boot/sessionColdBoot.ts +60 -19
- package/src/civic/payloads.ts +1 -4
- package/src/client/context.ts +6 -1
- package/src/constants/version.ts +7 -6
- package/src/crypto/aead.ts +1 -5
- package/src/crypto/ecdh.ts +1 -4
- package/src/crypto/identityDerivations.ts +5 -1
- package/src/crypto/identityMarker.ts +3 -1
- package/src/crypto/identityProof.ts +6 -1
- package/src/crypto/index.ts +11 -11
- package/src/crypto/internal.ts +6 -1
- package/src/crypto/keyManager.ts +348 -98
- package/src/crypto/recoveryPhrase.ts +14 -9
- package/src/crypto/registrationPow.ts +1 -1
- package/src/crypto/signatureService.ts +14 -6
- package/src/i18n/accountCategoryLabels.ts +1 -4
- package/src/i18n/accountRoleLabels.ts +1 -4
- package/src/i18n/index.ts +25 -7
- package/src/i18n/locales/ar-SA.ts +375 -366
- package/src/i18n/locales/ca-ES.ts +382 -366
- package/src/i18n/locales/de-DE.ts +387 -366
- package/src/i18n/locales/en-US.ts +2034 -1994
- package/src/i18n/locales/es-ES.ts +2043 -1994
- package/src/i18n/locales/fr-FR.ts +387 -366
- package/src/i18n/locales/it-IT.ts +380 -366
- package/src/i18n/locales/ja-JP.ts +380 -366
- package/src/i18n/locales/ko-KR.ts +377 -366
- package/src/i18n/locales/pt-PT.ts +383 -366
- package/src/i18n/locales/zh-CN.ts +373 -366
- package/src/i18n/trustTierLabels.ts +1 -4
- package/src/index.ts +371 -349
- package/src/inference/OxyInferenceClient.ts +784 -780
- package/src/models/interfaces.ts +30 -27
- package/src/server/OxyServer.ts +227 -68
- package/src/server/agentAccount.ts +8 -2
- package/src/server/aliaMachineCredential.ts +57 -16
- package/src/server/auth.ts +9 -5
- package/src/server/capabilityTicket.ts +30 -21
- package/src/server/index.ts +29 -6
- package/src/server/liveCapabilityTicket.ts +31 -9
- package/src/server/middleware.ts +212 -121
- package/src/server/namespaces.ts +247 -94
- package/src/server/rateLimit.ts +2 -4
- package/src/server/requesterAssertion.ts +93 -54
- package/src/server/serviceTokenLifetime.ts +9 -4
- package/src/server/traffic.ts +117 -23
- package/src/server/trafficNodeHttp.ts +61 -14
- package/src/server/userInvalidation.ts +1 -4
- package/src/server/workloadIdentity.client.ts +12 -1
- package/src/server/workloadIdentity.ts +52 -5
- package/src/session/SessionClient.ts +170 -49
- package/src/session/accountDelegationAccess.ts +24 -31
- package/src/session/accountDialogController.ts +59 -14
- package/src/session/accountSwitchTargets.ts +10 -15
- package/src/session/authStateStore.ts +121 -81
- package/src/session/deviceDirectory.ts +1 -3
- package/src/session/identityPin.ts +10 -11
- package/src/session/identitySession.ts +3 -1
- package/src/session/index.ts +87 -82
- package/src/session/refresh.ts +138 -44
- package/src/session/sessionClientHost.ts +1 -3
- package/src/session/sharedDeviceCredential.ts +35 -14
- package/src/session/socketLoader.ts +9 -2
- package/src/shared/utils/errorUtils.ts +5 -2
- package/src/shared/utils/networkUtils.ts +5 -8
- package/src/shared/utils/themeUtils.ts +2 -6
- package/src/types/buffer.d.ts +66 -21
- package/src/types/expo-secure-store.d.ts +5 -1
- package/src/utils/accountCacheSweep.ts +4 -5
- package/src/utils/accountUtils.ts +107 -108
- package/src/utils/apiUtils.ts +3 -3
- package/src/utils/asyncUtils.ts +2 -2
- package/src/utils/authHelpers.ts +10 -5
- package/src/utils/avatarUtils.ts +11 -6
- package/src/utils/cache.ts +9 -10
- package/src/utils/cacheKey.ts +5 -1
- package/src/utils/coldBoot.ts +1 -3
- package/src/utils/commonsDelivery.ts +1 -4
- package/src/utils/deviceManager.ts +15 -14
- package/src/utils/errorUtils.ts +68 -86
- package/src/utils/languageUtils.ts +407 -58
- package/src/utils/oauthPkce.ts +1 -4
- package/src/utils/platform.ts +0 -1
- package/src/utils/profileLinks.ts +2 -5
- package/src/utils/requestUtils.ts +18 -14
- package/src/utils/sessionUtils.ts +34 -33
- package/src/utils/userHandle.ts +3 -1
- package/src/utils/userIdentity.ts +1 -1
- package/src/utils/validationUtils.ts +11 -9
package/src/HttpService.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified HTTP Service
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Consolidates HttpClient + RequestManager into a single efficient class.
|
|
5
5
|
* Uses native fetch instead of axios for smaller bundle size.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Handles:
|
|
8
8
|
* - Authentication (token management, auto-refresh)
|
|
9
9
|
* - Caching (TTL-based)
|
|
@@ -16,7 +16,13 @@
|
|
|
16
16
|
import { TTLCache, registerCacheForCleanup, unregisterCacheFromCleanup } from './utils/cache';
|
|
17
17
|
import { RequestDeduplicator, RequestQueue, SimpleLogger } from './utils/requestUtils';
|
|
18
18
|
import { retryAsync } from './utils/asyncUtils';
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
createCancelledError,
|
|
21
|
+
ErrorCodes,
|
|
22
|
+
handleHttpError,
|
|
23
|
+
isCancelledError,
|
|
24
|
+
parseHttpErrorBody,
|
|
25
|
+
} from './utils/errorUtils';
|
|
20
26
|
import { decodeTokenClaims } from './utils/tokenClaims';
|
|
21
27
|
import { isReactNative } from '@oxy.so/protocol/random';
|
|
22
28
|
import { computeIdentityTag, fnv1a32 } from './utils/cacheKey';
|
|
@@ -46,15 +52,25 @@ function sameTokenSession(left: string | null, right: string | null): boolean {
|
|
|
46
52
|
if (!a || !b || !(a.userId || a.id || a.sub) || !(b.userId || b.id || b.sub)) return false;
|
|
47
53
|
// A refresh may rotate exp/iat/jti while preserving its account and session.
|
|
48
54
|
// Opaque/malformed context fields cannot establish that equivalence.
|
|
49
|
-
return [
|
|
50
|
-
|
|
55
|
+
return [
|
|
56
|
+
'userId',
|
|
57
|
+
'id',
|
|
58
|
+
'sub',
|
|
59
|
+
'sessionId',
|
|
60
|
+
'operatorId',
|
|
61
|
+
'clientId',
|
|
62
|
+
'appId',
|
|
63
|
+
'authMethodId',
|
|
64
|
+
].every((key) => (a[key] === undefined || typeof a[key] === 'string') && a[key] === b[key]);
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/** Cancel this wait only; the shared mint continues for other callers. */
|
|
54
68
|
function awaitRequestSignal<T>(pending: Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
55
69
|
if (!signal) return pending;
|
|
56
70
|
return new Promise<T>((resolve, reject) => {
|
|
57
|
-
const onAbort = (): void => {
|
|
71
|
+
const onAbort = (): void => {
|
|
72
|
+
reject(createCancelledError());
|
|
73
|
+
};
|
|
58
74
|
if (signal.aborted) onAbort();
|
|
59
75
|
else signal.addEventListener('abort', onAbort, { once: true });
|
|
60
76
|
// Both handlers remain attached even after cancellation, so a late shared
|
|
@@ -336,7 +352,9 @@ function isAbortLike(error: unknown): boolean {
|
|
|
336
352
|
function linkAbort(parent: AbortSignal, child: AbortController): () => void {
|
|
337
353
|
if (parent.aborted) {
|
|
338
354
|
child.abort((parent as AbortSignal & { reason?: unknown }).reason);
|
|
339
|
-
return () => {
|
|
355
|
+
return () => {
|
|
356
|
+
/* nothing was ever attached */
|
|
357
|
+
};
|
|
340
358
|
}
|
|
341
359
|
const onAbort = (): void => {
|
|
342
360
|
child.abort((parent as AbortSignal & { reason?: unknown }).reason);
|
|
@@ -461,11 +479,11 @@ export class HttpService {
|
|
|
461
479
|
this.config = config;
|
|
462
480
|
this.baseURL = config.baseURL;
|
|
463
481
|
this.tokenStore = new TokenStore();
|
|
464
|
-
|
|
482
|
+
|
|
465
483
|
this.logger = new SimpleLogger(
|
|
466
484
|
config.enableLogging || false,
|
|
467
485
|
config.logLevel || 'error',
|
|
468
|
-
'HttpService'
|
|
486
|
+
'HttpService',
|
|
469
487
|
);
|
|
470
488
|
|
|
471
489
|
// Initialize performance infrastructure. The per-instance GET response
|
|
@@ -475,16 +493,17 @@ export class HttpService {
|
|
|
475
493
|
// the cache for the global cleanup interval. Default (config unset) keeps
|
|
476
494
|
// caching ON with the 5-minute TTL — unchanged for existing consumers.
|
|
477
495
|
this.cacheDisabled =
|
|
478
|
-
config.enableCache === false ||
|
|
479
|
-
|
|
480
|
-
|
|
496
|
+
config.enableCache === false || (typeof config.cacheTTL === 'number' && config.cacheTTL <= 0);
|
|
497
|
+
this.cache = new TTLCache<unknown>(
|
|
498
|
+
config.cacheTTL && config.cacheTTL > 0 ? config.cacheTTL : 5 * 60 * 1000,
|
|
499
|
+
);
|
|
481
500
|
if (!this.cacheDisabled) {
|
|
482
501
|
registerCacheForCleanup(this.cache);
|
|
483
502
|
}
|
|
484
503
|
this.deduplicator = new RequestDeduplicator();
|
|
485
504
|
this.requestQueue = new RequestQueue(
|
|
486
505
|
config.maxConcurrentRequests || 10,
|
|
487
|
-
config.requestQueueSize || 100
|
|
506
|
+
config.requestQueueSize || 100,
|
|
488
507
|
);
|
|
489
508
|
}
|
|
490
509
|
|
|
@@ -690,7 +709,7 @@ export class HttpService {
|
|
|
690
709
|
|
|
691
710
|
// Build headers - start with defaults
|
|
692
711
|
const headers: Record<string, string> = {
|
|
693
|
-
|
|
712
|
+
Accept: 'application/json',
|
|
694
713
|
};
|
|
695
714
|
|
|
696
715
|
// Only set Content-Type for non-FormData requests (FormData sets it automatically with boundary)
|
|
@@ -711,7 +730,9 @@ export class HttpService {
|
|
|
711
730
|
// For FormData, explicitly remove Content-Type if user tries to set it
|
|
712
731
|
// The browser/fetch API will set it automatically with the boundary
|
|
713
732
|
if (isFormData && key.toLowerCase() === 'content-type') {
|
|
714
|
-
this.logger.debug(
|
|
733
|
+
this.logger.debug(
|
|
734
|
+
'Ignoring Content-Type header for FormData - will be set automatically',
|
|
735
|
+
);
|
|
715
736
|
continue;
|
|
716
737
|
}
|
|
717
738
|
headers[key] = value;
|
|
@@ -727,8 +748,13 @@ export class HttpService {
|
|
|
727
748
|
// `URLSearchParams` is serialised explicitly rather than handed to
|
|
728
749
|
// `fetch` as-is: RN's fetch does not consistently encode it, and doing
|
|
729
750
|
// it here keeps the body identical across every platform.
|
|
730
|
-
const bodyValue =
|
|
731
|
-
|
|
751
|
+
const bodyValue =
|
|
752
|
+
method !== 'GET' && data
|
|
753
|
+
? isFormData
|
|
754
|
+
? data
|
|
755
|
+
: isUrlEncoded
|
|
756
|
+
? data.toString()
|
|
757
|
+
: JSON.stringify(data)
|
|
732
758
|
: undefined;
|
|
733
759
|
|
|
734
760
|
// React Native FormData workaround:
|
|
@@ -737,7 +763,8 @@ export class HttpService {
|
|
|
737
763
|
// RN's native XMLHttpRequest handles those descriptors correctly, so we
|
|
738
764
|
// route multipart uploads through XHR on RN only. JSON, text, etc. still
|
|
739
765
|
// use fetch on every platform.
|
|
740
|
-
const useXhrForUpload =
|
|
766
|
+
const useXhrForUpload =
|
|
767
|
+
isFormData && isReactNative() && typeof XMLHttpRequest !== 'undefined';
|
|
741
768
|
|
|
742
769
|
const response = useXhrForUpload
|
|
743
770
|
? await this.uploadViaXHR(
|
|
@@ -781,7 +808,13 @@ export class HttpService {
|
|
|
781
808
|
// queue slot, so a queued retry waits for a slot while holding one.
|
|
782
809
|
// With every slot held by a 401 (a revoked session under load) no
|
|
783
810
|
// retry could ever start and the whole client stalled.
|
|
784
|
-
return this.request<T>({
|
|
811
|
+
return this.request<T>({
|
|
812
|
+
...config,
|
|
813
|
+
_isAuthRetry: true,
|
|
814
|
+
retry: false,
|
|
815
|
+
deduplicate: false,
|
|
816
|
+
bypassQueue: true,
|
|
817
|
+
});
|
|
785
818
|
}
|
|
786
819
|
// Refresh failed or no token — clear tokens
|
|
787
820
|
this.tokenStore.clearTokens();
|
|
@@ -816,7 +849,11 @@ export class HttpService {
|
|
|
816
849
|
response?: { status: number; statusText: string; data?: unknown };
|
|
817
850
|
};
|
|
818
851
|
error.status = response.status;
|
|
819
|
-
error.response = {
|
|
852
|
+
error.response = {
|
|
853
|
+
status: response.status,
|
|
854
|
+
statusText: response.statusText,
|
|
855
|
+
data: errorBody,
|
|
856
|
+
};
|
|
820
857
|
// Only set `code`/`details` when the server actually sent them.
|
|
821
858
|
// Assigning `undefined` would still create the property, which changes
|
|
822
859
|
// how `handleHttpError` classifies the error downstream.
|
|
@@ -832,7 +869,7 @@ export class HttpService {
|
|
|
832
869
|
// Handle different response types (optimized - read response once)
|
|
833
870
|
const contentType = response.headers.get('content-type');
|
|
834
871
|
let responseData: unknown;
|
|
835
|
-
|
|
872
|
+
|
|
836
873
|
if (config.responseType === 'blob') {
|
|
837
874
|
responseData = await response.blob();
|
|
838
875
|
} else if (contentType?.includes('application/json')) {
|
|
@@ -860,7 +897,13 @@ export class HttpService {
|
|
|
860
897
|
throw new Error('Failed to read response from server');
|
|
861
898
|
}
|
|
862
899
|
}
|
|
863
|
-
} else if (
|
|
900
|
+
} else if (
|
|
901
|
+
contentType &&
|
|
902
|
+
(contentType.includes('application/octet-stream') ||
|
|
903
|
+
contentType.includes('image/') ||
|
|
904
|
+
contentType.includes('video/') ||
|
|
905
|
+
contentType.includes('audio/'))
|
|
906
|
+
) {
|
|
864
907
|
// For binary responses (blobs), return the blob directly without unwrapping
|
|
865
908
|
responseData = await response.blob();
|
|
866
909
|
} else {
|
|
@@ -878,7 +921,11 @@ export class HttpService {
|
|
|
878
921
|
const duration = Date.now() - startTime;
|
|
879
922
|
this.updateMetrics(false, duration);
|
|
880
923
|
this.config.onRequestEnd?.(url, method, duration, false);
|
|
881
|
-
this.config.onRequestError?.(
|
|
924
|
+
this.config.onRequestError?.(
|
|
925
|
+
url,
|
|
926
|
+
method,
|
|
927
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
928
|
+
);
|
|
882
929
|
|
|
883
930
|
// An abort is the one failure whose CAUSE the error cannot carry on its
|
|
884
931
|
// own: `fetch` reports a caller cancellation and our own timeout as the
|
|
@@ -931,7 +978,9 @@ export class HttpService {
|
|
|
931
978
|
// Control-plane calls the auth lane depends on (`bypassQueue`, e.g. the
|
|
932
979
|
// device-secret mint) run DIRECTLY — a queued mint could never acquire a
|
|
933
980
|
// slot when every slot is parked awaiting it.
|
|
934
|
-
config.bypassQueue
|
|
981
|
+
config.bypassQueue
|
|
982
|
+
? attempt(domain)
|
|
983
|
+
: this.requestQueue.enqueue(() => attempt(domain), domain);
|
|
935
984
|
const execute = (domain: AbortSignal): Promise<T> =>
|
|
936
985
|
retry
|
|
937
986
|
? retryAsync(() => runAttempt(domain), {
|
|
@@ -1050,7 +1099,11 @@ export class HttpService {
|
|
|
1050
1099
|
}
|
|
1051
1100
|
|
|
1052
1101
|
const onAbort = (): void => {
|
|
1053
|
-
try {
|
|
1102
|
+
try {
|
|
1103
|
+
xhr.abort();
|
|
1104
|
+
} catch {
|
|
1105
|
+
/* xhr already finished */
|
|
1106
|
+
}
|
|
1054
1107
|
};
|
|
1055
1108
|
if (abortSignal.aborted) {
|
|
1056
1109
|
reject(new DOMException('The user aborted a request.', 'AbortError'));
|
|
@@ -1065,11 +1118,13 @@ export class HttpService {
|
|
|
1065
1118
|
xhr.onload = (): void => {
|
|
1066
1119
|
cleanup();
|
|
1067
1120
|
const responseHeaders = HttpService.parseXHRHeaders(xhr.getAllResponseHeaders());
|
|
1068
|
-
resolve(
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1121
|
+
resolve(
|
|
1122
|
+
new Response(xhr.responseText, {
|
|
1123
|
+
status: xhr.status,
|
|
1124
|
+
statusText: xhr.statusText,
|
|
1125
|
+
headers: responseHeaders,
|
|
1126
|
+
}),
|
|
1127
|
+
);
|
|
1073
1128
|
};
|
|
1074
1129
|
xhr.onerror = (): void => {
|
|
1075
1130
|
cleanup();
|
|
@@ -1200,7 +1255,7 @@ export class HttpService {
|
|
|
1200
1255
|
const base = /^https?:\/\//i.test(url)
|
|
1201
1256
|
? url
|
|
1202
1257
|
: `${trimTrailingSlashes(this.baseURL)}/${trimLeadingSlashes(url)}`;
|
|
1203
|
-
|
|
1258
|
+
|
|
1204
1259
|
if (!params || Object.keys(params).length === 0) {
|
|
1205
1260
|
return base;
|
|
1206
1261
|
}
|
|
@@ -1511,13 +1566,20 @@ export class HttpService {
|
|
|
1511
1566
|
return this.requestRawResponse(config, false);
|
|
1512
1567
|
}
|
|
1513
1568
|
|
|
1514
|
-
private async requestRawResponse(
|
|
1569
|
+
private async requestRawResponse(
|
|
1570
|
+
config: ResponseRequest,
|
|
1571
|
+
requireAuth: boolean,
|
|
1572
|
+
): Promise<Response> {
|
|
1515
1573
|
// Validate before obtaining a bearer: an unrelated origin must not trigger
|
|
1516
1574
|
// an auth mint, even when the trusted transport accepts absolute URLs.
|
|
1517
1575
|
const target = new URL(this.buildURL(config.url));
|
|
1518
1576
|
const base = new URL(this.baseURL);
|
|
1519
|
-
if (
|
|
1520
|
-
|
|
1577
|
+
if (
|
|
1578
|
+
target.origin !== base.origin ||
|
|
1579
|
+
!['http:', 'https:'].includes(target.protocol) ||
|
|
1580
|
+
target.username ||
|
|
1581
|
+
target.password
|
|
1582
|
+
) {
|
|
1521
1583
|
throw new Error('Raw response requests must target the configured API origin');
|
|
1522
1584
|
}
|
|
1523
1585
|
if (config.signal?.aborted) throw createCancelledError();
|
|
@@ -1526,14 +1588,27 @@ export class HttpService {
|
|
|
1526
1588
|
const contextToken = this.syncAccessTokenFromProvider();
|
|
1527
1589
|
const beforeAuthEpoch = this.sessionEpoch;
|
|
1528
1590
|
const authHeader = await awaitRequestSignal(this.getAuthHeader(), config.signal);
|
|
1529
|
-
if (
|
|
1591
|
+
if (
|
|
1592
|
+
beforeAuthEpoch !== this.sessionEpoch ||
|
|
1593
|
+
!sameTokenSession(contextToken, this.tokenStore.getAccessToken())
|
|
1594
|
+
) {
|
|
1530
1595
|
throw new OxyAuthenticationError('The request session changed', 'AUTH_SESSION_CHANGED');
|
|
1531
1596
|
}
|
|
1532
1597
|
if (config.signal?.aborted) throw createCancelledError();
|
|
1533
1598
|
if (requireAuth && !authHeader) {
|
|
1534
|
-
throw new OxyAuthenticationError(
|
|
1599
|
+
throw new OxyAuthenticationError(
|
|
1600
|
+
'An active Oxy session is required for this request',
|
|
1601
|
+
'AUTH_REQUIRED',
|
|
1602
|
+
);
|
|
1535
1603
|
}
|
|
1536
|
-
return this.requestResponseAttempt(
|
|
1604
|
+
return this.requestResponseAttempt(
|
|
1605
|
+
request,
|
|
1606
|
+
target.href,
|
|
1607
|
+
authHeader,
|
|
1608
|
+
false,
|
|
1609
|
+
this.sessionEpoch,
|
|
1610
|
+
contextToken,
|
|
1611
|
+
);
|
|
1537
1612
|
}
|
|
1538
1613
|
|
|
1539
1614
|
private async requestResponseAttempt(
|
|
@@ -1546,7 +1621,10 @@ export class HttpService {
|
|
|
1546
1621
|
): Promise<Response> {
|
|
1547
1622
|
const assertCurrent = (): void => {
|
|
1548
1623
|
this.syncAccessTokenFromProvider();
|
|
1549
|
-
if (
|
|
1624
|
+
if (
|
|
1625
|
+
epoch !== this.sessionEpoch ||
|
|
1626
|
+
!sameTokenSession(contextToken, this.tokenStore.getAccessToken())
|
|
1627
|
+
) {
|
|
1550
1628
|
throw new OxyAuthenticationError('The request session changed', 'AUTH_SESSION_CHANGED');
|
|
1551
1629
|
}
|
|
1552
1630
|
};
|
|
@@ -1556,7 +1634,8 @@ export class HttpService {
|
|
|
1556
1634
|
const headers = new Headers(config.headers);
|
|
1557
1635
|
if (!headers.has('Accept')) headers.set('Accept', 'application/json');
|
|
1558
1636
|
// Never accept ambient credentials or another authority from a caller.
|
|
1559
|
-
for (const name of ['Authorization', 'Cookie', 'Cookie2', 'Host', 'Proxy-Authorization'])
|
|
1637
|
+
for (const name of ['Authorization', 'Cookie', 'Cookie2', 'Host', 'Proxy-Authorization'])
|
|
1638
|
+
headers.delete(name);
|
|
1560
1639
|
if (authHeader) headers.set('Authorization', authHeader);
|
|
1561
1640
|
const edgeRegionHeader = peekBrowserEdgeRegionHeader();
|
|
1562
1641
|
for (const [name, value] of Object.entries(edgeRegionHeader)) headers.set(name, value);
|
|
@@ -1577,21 +1656,47 @@ export class HttpService {
|
|
|
1577
1656
|
// multipart bodies keep the platform's existing body serialization.
|
|
1578
1657
|
if (body && typeof body === 'object' && 'getReader' in body) init.duplex = 'half';
|
|
1579
1658
|
const response = await (config.fetch ?? globalThis.fetch)(fullUrl, init);
|
|
1580
|
-
try {
|
|
1659
|
+
try {
|
|
1660
|
+
assertCurrent();
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
await response.body?.cancel();
|
|
1663
|
+
throw error;
|
|
1664
|
+
}
|
|
1581
1665
|
const replayable = body === undefined || typeof body === 'string';
|
|
1582
|
-
if (
|
|
1666
|
+
if (
|
|
1667
|
+
response.status === 401 &&
|
|
1668
|
+
!isAuthRetry &&
|
|
1669
|
+
authHeader &&
|
|
1670
|
+
replayable &&
|
|
1671
|
+
!config.signal?.aborted
|
|
1672
|
+
) {
|
|
1583
1673
|
let refreshed: string | null;
|
|
1584
1674
|
try {
|
|
1585
|
-
refreshed = await awaitRequestSignal(
|
|
1675
|
+
refreshed = await awaitRequestSignal(
|
|
1676
|
+
this.refreshAccessToken('response-401'),
|
|
1677
|
+
config.signal,
|
|
1678
|
+
);
|
|
1586
1679
|
} catch (error) {
|
|
1587
1680
|
void response.body?.cancel().catch(() => undefined);
|
|
1588
1681
|
throw error;
|
|
1589
1682
|
}
|
|
1590
|
-
try {
|
|
1683
|
+
try {
|
|
1684
|
+
assertCurrent();
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
await response.body?.cancel();
|
|
1687
|
+
throw error;
|
|
1688
|
+
}
|
|
1591
1689
|
if (refreshed) {
|
|
1592
1690
|
await response.body?.cancel();
|
|
1593
1691
|
if (config.signal?.aborted) throw createCancelledError();
|
|
1594
|
-
return this.requestResponseAttempt(
|
|
1692
|
+
return this.requestResponseAttempt(
|
|
1693
|
+
config,
|
|
1694
|
+
fullUrl,
|
|
1695
|
+
`Bearer ${refreshed}`,
|
|
1696
|
+
true,
|
|
1697
|
+
epoch,
|
|
1698
|
+
contextToken,
|
|
1699
|
+
);
|
|
1595
1700
|
}
|
|
1596
1701
|
this.clearTokens();
|
|
1597
1702
|
}
|
|
@@ -1604,11 +1709,15 @@ export class HttpService {
|
|
|
1604
1709
|
const duration = Date.now() - startTime;
|
|
1605
1710
|
this.updateMetrics(false, duration);
|
|
1606
1711
|
this.config.onRequestEnd?.(config.url, config.method, duration, false);
|
|
1607
|
-
const normalizedError =
|
|
1608
|
-
|
|
1609
|
-
|
|
1712
|
+
const normalizedError =
|
|
1713
|
+
config.signal?.aborted || isCancelledError(error)
|
|
1714
|
+
? createCancelledError()
|
|
1715
|
+
: error instanceof Error
|
|
1716
|
+
? error
|
|
1717
|
+
: new Error(String(error));
|
|
1610
1718
|
this.config.onRequestError?.(config.url, config.method, normalizedError);
|
|
1611
|
-
if (isCancelledError(normalizedError) || normalizedError instanceof OxyAuthenticationError)
|
|
1719
|
+
if (isCancelledError(normalizedError) || normalizedError instanceof OxyAuthenticationError)
|
|
1720
|
+
throw normalizedError;
|
|
1612
1721
|
throw handleHttpError(normalizedError);
|
|
1613
1722
|
}
|
|
1614
1723
|
}
|
|
@@ -1618,19 +1727,34 @@ export class HttpService {
|
|
|
1618
1727
|
return this.request<T>({ method: 'GET', url, ...config });
|
|
1619
1728
|
}
|
|
1620
1729
|
|
|
1621
|
-
async post<T = unknown>(
|
|
1730
|
+
async post<T = unknown>(
|
|
1731
|
+
url: string,
|
|
1732
|
+
data?: unknown,
|
|
1733
|
+
config?: Omit<RequestConfig, 'method' | 'url' | 'data'>,
|
|
1734
|
+
): Promise<T> {
|
|
1622
1735
|
return this.request<T>({ method: 'POST', url, data, ...config });
|
|
1623
1736
|
}
|
|
1624
1737
|
|
|
1625
|
-
async put<T = unknown>(
|
|
1738
|
+
async put<T = unknown>(
|
|
1739
|
+
url: string,
|
|
1740
|
+
data?: unknown,
|
|
1741
|
+
config?: Omit<RequestConfig, 'method' | 'url' | 'data'>,
|
|
1742
|
+
): Promise<T> {
|
|
1626
1743
|
return this.request<T>({ method: 'PUT', url, data, ...config });
|
|
1627
1744
|
}
|
|
1628
1745
|
|
|
1629
|
-
async patch<T = unknown>(
|
|
1746
|
+
async patch<T = unknown>(
|
|
1747
|
+
url: string,
|
|
1748
|
+
data?: unknown,
|
|
1749
|
+
config?: Omit<RequestConfig, 'method' | 'url' | 'data'>,
|
|
1750
|
+
): Promise<T> {
|
|
1630
1751
|
return this.request<T>({ method: 'PATCH', url, data, ...config });
|
|
1631
1752
|
}
|
|
1632
1753
|
|
|
1633
|
-
async delete<T = unknown>(
|
|
1754
|
+
async delete<T = unknown>(
|
|
1755
|
+
url: string,
|
|
1756
|
+
config?: Omit<RequestConfig, 'method' | 'url'>,
|
|
1757
|
+
): Promise<T> {
|
|
1634
1758
|
return this.request<T>({ method: 'DELETE', url, ...config });
|
|
1635
1759
|
}
|
|
1636
1760
|
|
|
@@ -1794,7 +1918,13 @@ export class HttpService {
|
|
|
1794
1918
|
* per resource.
|
|
1795
1919
|
* @returns Number of entries deleted
|
|
1796
1920
|
*/
|
|
1797
|
-
invalidateCache({
|
|
1921
|
+
invalidateCache({
|
|
1922
|
+
keys = [],
|
|
1923
|
+
prefixes = [],
|
|
1924
|
+
}: {
|
|
1925
|
+
keys?: readonly string[];
|
|
1926
|
+
prefixes?: readonly string[];
|
|
1927
|
+
}): number {
|
|
1798
1928
|
if (keys.length === 0 && prefixes.length === 0) return 0;
|
|
1799
1929
|
const exact = new Set(keys);
|
|
1800
1930
|
const delim = HttpService.CACHE_IDENTITY_DELIM;
|
|
@@ -54,7 +54,12 @@ export class AssetUrlResolutionError extends Error {
|
|
|
54
54
|
*/
|
|
55
55
|
public readonly cause?: unknown;
|
|
56
56
|
|
|
57
|
-
constructor(
|
|
57
|
+
constructor(
|
|
58
|
+
fileId: string,
|
|
59
|
+
variant: string | undefined,
|
|
60
|
+
status: number | undefined,
|
|
61
|
+
cause?: unknown,
|
|
62
|
+
) {
|
|
58
63
|
const variantSuffix = variant ? ` (variant "${variant}")` : '';
|
|
59
64
|
const statusSuffix = typeof status === 'number' ? ` — status ${status}` : '';
|
|
60
65
|
super(`Could not resolve a download URL for asset "${fileId}"${variantSuffix}${statusSuffix}`);
|
|
@@ -152,13 +157,12 @@ export class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
|
|
|
152
157
|
super(
|
|
153
158
|
`Authentication timeout (${timeoutMs}ms): ${operationName} requires user authentication. Please ensure the user is logged in before calling this method.`,
|
|
154
159
|
'AUTH_TIMEOUT',
|
|
155
|
-
408
|
|
160
|
+
408,
|
|
156
161
|
);
|
|
157
162
|
this.name = 'OxyAuthenticationTimeoutError';
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
|
|
161
|
-
|
|
162
166
|
/**
|
|
163
167
|
* The one error every `OxyServices` call rejects with.
|
|
164
168
|
*
|
package/src/OxyServices.ts
CHANGED
|
@@ -98,7 +98,12 @@ export class OxyServices {
|
|
|
98
98
|
* built on, for the routes no namespace covers yet. GET sends `data` as the
|
|
99
99
|
* query string, every other method as the body. Rejects with `OxyApiError`.
|
|
100
100
|
*/
|
|
101
|
-
async request<T>(
|
|
101
|
+
async request<T>(
|
|
102
|
+
method: HttpMethod,
|
|
103
|
+
url: string,
|
|
104
|
+
data?: unknown,
|
|
105
|
+
options: RequestOptions = {},
|
|
106
|
+
): Promise<T> {
|
|
102
107
|
try {
|
|
103
108
|
return await this.http.request<T>({
|
|
104
109
|
method,
|
|
@@ -303,4 +308,3 @@ export class OxyServices {
|
|
|
303
308
|
return this._contacts;
|
|
304
309
|
}
|
|
305
310
|
}
|
|
306
|
-
|