@oxyhq/core 1.5.0 → 1.6.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/dist/cjs/AuthManager.js +1 -1
- package/dist/cjs/HttpService.js +87 -69
- package/dist/cjs/OxyServices.base.js +5 -4
- package/dist/cjs/crypto/keyManager.js +1 -13
- package/dist/cjs/crypto/signatureService.js +7 -20
- package/dist/cjs/mixins/OxyServices.analytics.js +2 -2
- package/dist/cjs/mixins/OxyServices.assets.js +14 -14
- package/dist/cjs/mixins/OxyServices.auth.js +19 -19
- package/dist/cjs/mixins/OxyServices.developer.js +6 -6
- package/dist/cjs/mixins/OxyServices.devices.js +7 -7
- package/dist/cjs/mixins/OxyServices.features.js +23 -23
- package/dist/cjs/mixins/OxyServices.fedcm.js +1 -1
- package/dist/cjs/mixins/OxyServices.karma.js +6 -6
- package/dist/cjs/mixins/OxyServices.location.js +2 -2
- package/dist/cjs/mixins/OxyServices.payment.js +6 -6
- package/dist/cjs/mixins/OxyServices.popup.js +1 -1
- package/dist/cjs/mixins/OxyServices.privacy.js +6 -6
- package/dist/cjs/mixins/OxyServices.security.js +3 -3
- package/dist/cjs/mixins/OxyServices.user.js +22 -22
- package/dist/cjs/mixins/OxyServices.utility.js +16 -7
- package/dist/cjs/utils/platform.js +14 -0
- package/dist/esm/AuthManager.js +1 -1
- package/dist/esm/HttpService.js +87 -69
- package/dist/esm/OxyServices.base.js +5 -4
- package/dist/esm/crypto/keyManager.js +1 -13
- package/dist/esm/crypto/signatureService.js +2 -15
- package/dist/esm/mixins/OxyServices.analytics.js +2 -2
- package/dist/esm/mixins/OxyServices.assets.js +14 -14
- package/dist/esm/mixins/OxyServices.auth.js +19 -19
- package/dist/esm/mixins/OxyServices.developer.js +6 -6
- package/dist/esm/mixins/OxyServices.devices.js +7 -7
- package/dist/esm/mixins/OxyServices.features.js +23 -23
- package/dist/esm/mixins/OxyServices.fedcm.js +1 -1
- package/dist/esm/mixins/OxyServices.karma.js +6 -6
- package/dist/esm/mixins/OxyServices.location.js +2 -2
- package/dist/esm/mixins/OxyServices.payment.js +6 -6
- package/dist/esm/mixins/OxyServices.popup.js +1 -1
- package/dist/esm/mixins/OxyServices.privacy.js +6 -6
- package/dist/esm/mixins/OxyServices.security.js +3 -3
- package/dist/esm/mixins/OxyServices.user.js +22 -22
- package/dist/esm/mixins/OxyServices.utility.js +16 -7
- package/dist/esm/utils/platform.js +12 -0
- package/dist/types/HttpService.d.ts +4 -1
- package/dist/types/OxyServices.base.d.ts +1 -1
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +1 -1
- package/dist/types/mixins/OxyServices.developer.d.ts +1 -1
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
- package/dist/types/mixins/OxyServices.features.d.ts +1 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -1
- package/dist/types/mixins/OxyServices.karma.d.ts +1 -1
- package/dist/types/mixins/OxyServices.language.d.ts +1 -1
- package/dist/types/mixins/OxyServices.location.d.ts +1 -1
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
- package/dist/types/mixins/OxyServices.popup.d.ts +1 -1
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
- package/dist/types/mixins/OxyServices.redirect.d.ts +1 -1
- package/dist/types/mixins/OxyServices.security.d.ts +1 -1
- package/dist/types/mixins/OxyServices.user.d.ts +1 -1
- package/dist/types/mixins/OxyServices.utility.d.ts +7 -6
- package/dist/types/utils/platform.d.ts +8 -0
- package/package.json +1 -1
- package/src/AuthManager.ts +1 -1
- package/src/HttpService.ts +85 -67
- package/src/OxyServices.base.ts +5 -4
- package/src/crypto/keyManager.ts +1 -15
- package/src/crypto/signatureService.ts +2 -17
- package/src/mixins/OxyServices.analytics.ts +2 -2
- package/src/mixins/OxyServices.assets.ts +14 -14
- package/src/mixins/OxyServices.auth.ts +19 -19
- package/src/mixins/OxyServices.developer.ts +6 -6
- package/src/mixins/OxyServices.devices.ts +7 -7
- package/src/mixins/OxyServices.features.ts +23 -23
- package/src/mixins/OxyServices.fedcm.ts +1 -1
- package/src/mixins/OxyServices.karma.ts +6 -6
- package/src/mixins/OxyServices.location.ts +2 -2
- package/src/mixins/OxyServices.payment.ts +6 -6
- package/src/mixins/OxyServices.popup.ts +1 -1
- package/src/mixins/OxyServices.privacy.ts +6 -6
- package/src/mixins/OxyServices.security.ts +3 -3
- package/src/mixins/OxyServices.user.ts +22 -22
- package/src/mixins/OxyServices.utility.ts +18 -8
- package/src/utils/platform.ts +14 -0
|
@@ -78,6 +78,7 @@ export declare function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase
|
|
|
78
78
|
httpService: import("../HttpService").HttpService;
|
|
79
79
|
cloudURL: string;
|
|
80
80
|
config: import("../OxyServices.base").OxyConfig;
|
|
81
|
+
__resetTokensForTests(): void;
|
|
81
82
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
82
83
|
getBaseURL(): string;
|
|
83
84
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -120,5 +121,4 @@ export declare function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase
|
|
|
120
121
|
[key: string]: any;
|
|
121
122
|
}>;
|
|
122
123
|
};
|
|
123
|
-
__resetTokensForTests(): void;
|
|
124
124
|
} & T;
|
|
@@ -194,6 +194,7 @@ export declare function OxyServicesRedirectAuthMixin<T extends typeof OxyService
|
|
|
194
194
|
httpService: import("../HttpService").HttpService;
|
|
195
195
|
cloudURL: string;
|
|
196
196
|
config: import("../OxyServices.base").OxyConfig;
|
|
197
|
+
__resetTokensForTests(): void;
|
|
197
198
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
198
199
|
getBaseURL(): string;
|
|
199
200
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -242,6 +243,5 @@ export declare function OxyServicesRedirectAuthMixin<T extends typeof OxyService
|
|
|
242
243
|
readonly STATE_STORAGE_KEY: "oxy_auth_state";
|
|
243
244
|
readonly PRE_AUTH_URL_KEY: "oxy_pre_auth_url";
|
|
244
245
|
readonly NONCE_STORAGE_KEY: "oxy_auth_nonce";
|
|
245
|
-
__resetTokensForTests(): void;
|
|
246
246
|
} & T;
|
|
247
247
|
export { OxyServicesRedirectAuthMixin as RedirectAuthMixin };
|
|
@@ -34,6 +34,7 @@ export declare function OxyServicesSecurityMixin<T extends typeof OxyServicesBas
|
|
|
34
34
|
httpService: import("../HttpService").HttpService;
|
|
35
35
|
cloudURL: string;
|
|
36
36
|
config: import("../OxyServices.base").OxyConfig;
|
|
37
|
+
__resetTokensForTests(): void;
|
|
37
38
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
38
39
|
getBaseURL(): string;
|
|
39
40
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -76,5 +77,4 @@ export declare function OxyServicesSecurityMixin<T extends typeof OxyServicesBas
|
|
|
76
77
|
[key: string]: any;
|
|
77
78
|
}>;
|
|
78
79
|
};
|
|
79
|
-
__resetTokensForTests(): void;
|
|
80
80
|
} & T;
|
|
@@ -138,6 +138,7 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
138
138
|
httpService: import("../HttpService").HttpService;
|
|
139
139
|
cloudURL: string;
|
|
140
140
|
config: import("../OxyServices.base").OxyConfig;
|
|
141
|
+
__resetTokensForTests(): void;
|
|
141
142
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
142
143
|
getBaseURL(): string;
|
|
143
144
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -180,5 +181,4 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
180
181
|
[key: string]: any;
|
|
181
182
|
}>;
|
|
182
183
|
};
|
|
183
|
-
__resetTokensForTests(): void;
|
|
184
184
|
} & T;
|
|
@@ -49,19 +49,19 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
49
49
|
*
|
|
50
50
|
* const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
|
|
51
51
|
*
|
|
52
|
-
* // Protect all routes under /
|
|
53
|
-
* app.use('/
|
|
52
|
+
* // Protect all routes under /protected
|
|
53
|
+
* app.use('/protected', oxy.auth());
|
|
54
54
|
*
|
|
55
55
|
* // Access user in route handler
|
|
56
|
-
* app.get('/
|
|
56
|
+
* app.get('/protected/me', (req, res) => {
|
|
57
57
|
* res.json({ userId: req.userId, user: req.user });
|
|
58
58
|
* });
|
|
59
59
|
*
|
|
60
60
|
* // Load full user profile from API
|
|
61
|
-
* app.use('/
|
|
61
|
+
* app.use('/admin', oxy.auth({ loadUser: true }));
|
|
62
62
|
*
|
|
63
63
|
* // Optional auth - attach user if present, don't block if absent
|
|
64
|
-
* app.use('/
|
|
64
|
+
* app.use('/public', oxy.auth({ optional: true }));
|
|
65
65
|
* ```
|
|
66
66
|
*
|
|
67
67
|
* @param options Optional configuration
|
|
@@ -111,10 +111,12 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
111
111
|
*/
|
|
112
112
|
serviceAuth(options?: {
|
|
113
113
|
debug?: boolean;
|
|
114
|
+
jwtSecret?: string;
|
|
114
115
|
}): (req: any, res: any, next: any) => Promise<void>;
|
|
115
116
|
httpService: import("../HttpService").HttpService;
|
|
116
117
|
cloudURL: string;
|
|
117
118
|
config: import("../OxyServices.base").OxyConfig;
|
|
119
|
+
__resetTokensForTests(): void;
|
|
118
120
|
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
119
121
|
getBaseURL(): string;
|
|
120
122
|
getClient(): import("../HttpService").HttpService;
|
|
@@ -157,6 +159,5 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
157
159
|
[key: string]: any;
|
|
158
160
|
}>;
|
|
159
161
|
};
|
|
160
|
-
__resetTokensForTests(): void;
|
|
161
162
|
} & T;
|
|
162
163
|
export {};
|
|
@@ -27,6 +27,14 @@ export declare function isIOS(): boolean;
|
|
|
27
27
|
* Check if running on Android
|
|
28
28
|
*/
|
|
29
29
|
export declare function isAndroid(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if running in React Native
|
|
32
|
+
*/
|
|
33
|
+
export declare function isReactNative(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Check if running in Node.js
|
|
36
|
+
*/
|
|
37
|
+
export declare function isNodeJS(): boolean;
|
|
30
38
|
/**
|
|
31
39
|
* Set the platform OS explicitly
|
|
32
40
|
* Called by React Native entry point to register the platform
|
package/package.json
CHANGED
package/src/AuthManager.ts
CHANGED
|
@@ -308,7 +308,7 @@ export class AuthManager {
|
|
|
308
308
|
// Use session-based token endpoint which handles auto-refresh server-side
|
|
309
309
|
const response = await httpService.request<{ accessToken: string; expiresAt: string }>({
|
|
310
310
|
method: 'GET',
|
|
311
|
-
url: `/
|
|
311
|
+
url: `/session/token/${sessionId}`,
|
|
312
312
|
cache: false,
|
|
313
313
|
retry: false,
|
|
314
314
|
});
|
package/src/HttpService.ts
CHANGED
|
@@ -54,27 +54,21 @@ interface RequestConfig extends RequestOptions {
|
|
|
54
54
|
params?: Record<string, unknown>;
|
|
55
55
|
/** @internal Used to prevent infinite auth retry loops */
|
|
56
56
|
_isAuthRetry?: boolean;
|
|
57
|
+
/** @internal Used to prevent infinite CSRF retry loops */
|
|
58
|
+
_isCsrfRetry?: boolean;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
|
-
* Token store for authentication (
|
|
62
|
+
* Token store for authentication (instance-based)
|
|
63
|
+
* Each HttpService gets its own TokenStore to prevent conflicts
|
|
64
|
+
* when multiple OxyServices instances coexist server-side.
|
|
61
65
|
*/
|
|
62
66
|
class TokenStore {
|
|
63
|
-
private static instance: TokenStore;
|
|
64
67
|
private accessToken: string | null = null;
|
|
65
68
|
private refreshToken: string | null = null;
|
|
66
69
|
private csrfToken: string | null = null;
|
|
67
70
|
private csrfTokenFetchPromise: Promise<string | null> | null = null;
|
|
68
71
|
|
|
69
|
-
private constructor() {}
|
|
70
|
-
|
|
71
|
-
static getInstance(): TokenStore {
|
|
72
|
-
if (!TokenStore.instance) {
|
|
73
|
-
TokenStore.instance = new TokenStore();
|
|
74
|
-
}
|
|
75
|
-
return TokenStore.instance;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
72
|
setTokens(accessToken: string, refreshToken = ''): void {
|
|
79
73
|
this.accessToken = accessToken;
|
|
80
74
|
this.refreshToken = refreshToken;
|
|
@@ -134,6 +128,7 @@ export class HttpService {
|
|
|
134
128
|
private logger: SimpleLogger;
|
|
135
129
|
private config: OxyConfig;
|
|
136
130
|
private tokenRefreshPromise: Promise<string | null> | null = null;
|
|
131
|
+
private tokenRefreshCooldownUntil: number = 0;
|
|
137
132
|
private _onTokenRefreshed: ((accessToken: string) => void) | null = null;
|
|
138
133
|
|
|
139
134
|
// Performance monitoring
|
|
@@ -149,7 +144,7 @@ export class HttpService {
|
|
|
149
144
|
constructor(config: OxyConfig) {
|
|
150
145
|
this.config = config;
|
|
151
146
|
this.baseURL = config.baseURL;
|
|
152
|
-
this.tokenStore = TokenStore
|
|
147
|
+
this.tokenStore = new TokenStore();
|
|
153
148
|
|
|
154
149
|
this.logger = new SimpleLogger(
|
|
155
150
|
config.enableLogging || false,
|
|
@@ -346,6 +341,20 @@ export class HttpService {
|
|
|
346
341
|
this.tokenStore.clearTokens();
|
|
347
342
|
}
|
|
348
343
|
|
|
344
|
+
// On 403 with CSRF error, clear cached token and retry once
|
|
345
|
+
if (response.status === 403 && !config._isCsrfRetry) {
|
|
346
|
+
try {
|
|
347
|
+
const clonedResponse = response.clone();
|
|
348
|
+
const errBody = await clonedResponse.json() as { code?: string } | null;
|
|
349
|
+
if (errBody?.code === 'CSRF_TOKEN_INVALID' || errBody?.code === 'CSRF_TOKEN_MISSING') {
|
|
350
|
+
this.tokenStore.clearCsrfToken();
|
|
351
|
+
return this.request<T>({ ...config, _isCsrfRetry: true, retry: false });
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
// Failed to parse error body — not a CSRF error
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
349
358
|
// Try to parse error response (handle empty/malformed JSON)
|
|
350
359
|
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
351
360
|
const contentType = response.headers.get('content-type');
|
|
@@ -518,52 +527,58 @@ export class HttpService {
|
|
|
518
527
|
}
|
|
519
528
|
|
|
520
529
|
const fetchPromise = (async () => {
|
|
521
|
-
|
|
522
|
-
|
|
530
|
+
const maxAttempts = 2;
|
|
531
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
532
|
+
try {
|
|
533
|
+
if (isDev()) console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/csrf-token`, `(attempt ${attempt})`);
|
|
523
534
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
535
|
+
// Use AbortController for timeout (more compatible than AbortSignal.timeout)
|
|
536
|
+
const controller = new AbortController();
|
|
537
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
527
538
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
539
|
+
const response = await fetch(`${this.baseURL}/csrf-token`, {
|
|
540
|
+
method: 'GET',
|
|
541
|
+
headers: { 'Accept': 'application/json' },
|
|
542
|
+
credentials: 'include', // Required to receive and send cookies
|
|
543
|
+
signal: controller.signal,
|
|
544
|
+
});
|
|
534
545
|
|
|
535
|
-
|
|
546
|
+
clearTimeout(timeoutId);
|
|
536
547
|
|
|
537
|
-
|
|
548
|
+
if (isDev()) console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
|
|
538
549
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
550
|
+
if (response.ok) {
|
|
551
|
+
const data = await response.json() as { csrfToken?: string };
|
|
552
|
+
if (isDev()) console.log('[HttpService] CSRF response data:', data);
|
|
553
|
+
const token = data.csrfToken || null;
|
|
554
|
+
this.tokenStore.setCsrfToken(token);
|
|
555
|
+
this.logger.debug('CSRF token fetched');
|
|
556
|
+
return token;
|
|
557
|
+
}
|
|
547
558
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
559
|
+
// Also check response header for CSRF token
|
|
560
|
+
const headerToken = response.headers.get('X-CSRF-Token');
|
|
561
|
+
if (headerToken) {
|
|
562
|
+
this.tokenStore.setCsrfToken(headerToken);
|
|
563
|
+
this.logger.debug('CSRF token from header');
|
|
564
|
+
return headerToken;
|
|
565
|
+
}
|
|
555
566
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
567
|
+
if (isDev()) console.log('[HttpService] CSRF fetch failed with status:', response.status);
|
|
568
|
+
this.logger.warn('Failed to fetch CSRF token:', response.status);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
if (isDev()) console.log('[HttpService] CSRF fetch error:', error);
|
|
571
|
+
this.logger.warn('CSRF token fetch error:', error);
|
|
572
|
+
}
|
|
573
|
+
// Wait before retry (500ms)
|
|
574
|
+
if (attempt < maxAttempts) {
|
|
575
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
576
|
+
}
|
|
565
577
|
}
|
|
566
|
-
|
|
578
|
+
return null;
|
|
579
|
+
})().finally(() => {
|
|
580
|
+
this.tokenStore.setCsrfTokenFetchPromise(null);
|
|
581
|
+
});
|
|
567
582
|
|
|
568
583
|
this.tokenStore.setCsrfTokenFetchPromise(fetchPromise);
|
|
569
584
|
return fetchPromise;
|
|
@@ -584,16 +599,23 @@ export class HttpService {
|
|
|
584
599
|
|
|
585
600
|
// If token expires in less than 60 seconds, refresh it
|
|
586
601
|
if (decoded.exp && decoded.exp - currentTime < 60 && decoded.sessionId) {
|
|
587
|
-
//
|
|
588
|
-
if (
|
|
589
|
-
|
|
602
|
+
// Skip if we recently failed a refresh (5s cooldown to prevent storms)
|
|
603
|
+
if (Date.now() < this.tokenRefreshCooldownUntil) {
|
|
604
|
+
return `Bearer ${accessToken}`;
|
|
590
605
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
this.tokenRefreshPromise =
|
|
606
|
+
// Deduplicate concurrent refresh attempts. The promise is shared
|
|
607
|
+
// across all concurrent callers and cleared only after it settles,
|
|
608
|
+
// so every awaiter receives the same result.
|
|
609
|
+
if (!this.tokenRefreshPromise) {
|
|
610
|
+
this.tokenRefreshPromise = this._refreshTokenFromSession(decoded.sessionId)
|
|
611
|
+
.then((result) => {
|
|
612
|
+
if (!result) this.tokenRefreshCooldownUntil = Date.now() + 5000;
|
|
613
|
+
return result;
|
|
614
|
+
})
|
|
615
|
+
.finally(() => { this.tokenRefreshPromise = null; });
|
|
596
616
|
}
|
|
617
|
+
const result = await this.tokenRefreshPromise;
|
|
618
|
+
if (result) return result;
|
|
597
619
|
}
|
|
598
620
|
|
|
599
621
|
return `Bearer ${accessToken}`;
|
|
@@ -605,7 +627,7 @@ export class HttpService {
|
|
|
605
627
|
|
|
606
628
|
private async _refreshTokenFromSession(sessionId: string): Promise<string | null> {
|
|
607
629
|
try {
|
|
608
|
-
const refreshUrl = `${this.baseURL}/
|
|
630
|
+
const refreshUrl = `${this.baseURL}/session/token/${sessionId}`;
|
|
609
631
|
const response = await fetch(refreshUrl, {
|
|
610
632
|
method: 'GET',
|
|
611
633
|
headers: { 'Accept': 'application/json' },
|
|
@@ -778,14 +800,10 @@ export class HttpService {
|
|
|
778
800
|
return { ...this.requestMetrics };
|
|
779
801
|
}
|
|
780
802
|
|
|
781
|
-
// Test-only utility
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
} catch (error) {
|
|
786
|
-
// Silently fail in test cleanup - this is expected behavior
|
|
787
|
-
// TokenStore might not be initialized in some test scenarios
|
|
788
|
-
}
|
|
803
|
+
// Test-only utility — clears tokens on this instance
|
|
804
|
+
__resetTokensForTests(): void {
|
|
805
|
+
this.tokenStore.clearTokens();
|
|
806
|
+
this.tokenStore.clearCsrfToken();
|
|
789
807
|
}
|
|
790
808
|
}
|
|
791
809
|
|
package/src/OxyServices.base.ts
CHANGED
|
@@ -41,9 +41,10 @@ export class OxyServicesBase {
|
|
|
41
41
|
this.httpService = new HttpService(config);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Test-only utility to reset
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Test-only utility to reset tokens on this instance between jest tests
|
|
45
|
+
// Note: tokens are now per-instance, so create new instances in tests for isolation
|
|
46
|
+
__resetTokensForTests(): void {
|
|
47
|
+
this.httpService.__resetTokensForTests();
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
/**
|
|
@@ -304,7 +305,7 @@ export class OxyServicesBase {
|
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
try {
|
|
307
|
-
const res = await this.makeRequest<{ valid: boolean }>('GET', '/
|
|
308
|
+
const res = await this.makeRequest<{ valid: boolean }>('GET', '/auth/validate', undefined, {
|
|
308
309
|
cache: false,
|
|
309
310
|
retry: false,
|
|
310
311
|
});
|
package/src/crypto/keyManager.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { ec as EC } from 'elliptic';
|
|
9
9
|
import type { ECKeyPair } from 'elliptic';
|
|
10
|
-
import { isWeb, isIOS, isAndroid } from '../utils/platform';
|
|
10
|
+
import { isWeb, isIOS, isAndroid, isReactNative, isNodeJS } from '../utils/platform';
|
|
11
11
|
import { logger } from '../utils/loggerUtils';
|
|
12
12
|
import { isDev } from '../shared/utils/debugUtils';
|
|
13
13
|
|
|
@@ -64,20 +64,6 @@ async function initSecureStore(): Promise<typeof import('expo-secure-store')> {
|
|
|
64
64
|
return SecureStore;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
* Check if we're in a React Native environment
|
|
69
|
-
*/
|
|
70
|
-
function isReactNative(): boolean {
|
|
71
|
-
return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Check if we're in a Node.js environment
|
|
76
|
-
*/
|
|
77
|
-
function isNodeJS(): boolean {
|
|
78
|
-
return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
67
|
/**
|
|
82
68
|
* Check if we're on web platform
|
|
83
69
|
* Identity storage is only available on native platforms (iOS/Android)
|
|
@@ -7,26 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import { ec as EC } from 'elliptic';
|
|
9
9
|
import { KeyManager } from './keyManager';
|
|
10
|
+
import { isReactNative, isNodeJS } from '../utils/platform';
|
|
10
11
|
|
|
11
12
|
// Lazy import for expo-crypto
|
|
12
13
|
let ExpoCrypto: typeof import('expo-crypto') | null = null;
|
|
13
14
|
|
|
14
15
|
const ec = new EC('secp256k1');
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
-
* Check if we're in a React Native environment
|
|
18
|
-
*/
|
|
19
|
-
function isReactNative(): boolean {
|
|
20
|
-
return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Check if we're in a Node.js environment
|
|
25
|
-
*/
|
|
26
|
-
function isNodeJS(): boolean {
|
|
27
|
-
return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
17
|
/**
|
|
31
18
|
* Initialize expo-crypto module
|
|
32
19
|
*/
|
|
@@ -55,9 +42,7 @@ async function sha256(message: string): Promise<string> {
|
|
|
55
42
|
// In Node.js, use Node's crypto module
|
|
56
43
|
if (isNodeJS()) {
|
|
57
44
|
try {
|
|
58
|
-
|
|
59
|
-
const getCrypto = new Function('return require("crypto")');
|
|
60
|
-
const nodeCrypto = getCrypto();
|
|
45
|
+
const nodeCrypto = await import('crypto');
|
|
61
46
|
return nodeCrypto.createHash('sha256').update(message).digest('hex');
|
|
62
47
|
} catch {
|
|
63
48
|
// Fall through to Web Crypto API
|
|
@@ -19,7 +19,7 @@ export function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBase>(Base
|
|
|
19
19
|
*/
|
|
20
20
|
async trackEvent(eventName: string, properties?: Record<string, any>): Promise<void> {
|
|
21
21
|
try {
|
|
22
|
-
await this.makeRequest('POST', '/
|
|
22
|
+
await this.makeRequest('POST', '/analytics/events', {
|
|
23
23
|
event: eventName,
|
|
24
24
|
properties
|
|
25
25
|
}, { cache: false, retry: false }); // Don't retry analytics events
|
|
@@ -40,7 +40,7 @@ export function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBase>(Base
|
|
|
40
40
|
if (startDate) params.startDate = startDate;
|
|
41
41
|
if (endDate) params.endDate = endDate;
|
|
42
42
|
|
|
43
|
-
return await this.makeRequest('GET', '/
|
|
43
|
+
return await this.makeRequest('GET', '/analytics', params, {
|
|
44
44
|
cache: true,
|
|
45
45
|
cacheTTL: CACHE_TIMES.LONG,
|
|
46
46
|
});
|
|
@@ -12,7 +12,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
12
12
|
*/
|
|
13
13
|
async deleteFile(fileId: string): Promise<any> {
|
|
14
14
|
try {
|
|
15
|
-
return await this.makeRequest('DELETE', `/
|
|
15
|
+
return await this.makeRequest('DELETE', `/assets/${encodeURIComponent(fileId)}`, undefined, { cache: false });
|
|
16
16
|
} catch (error) {
|
|
17
17
|
throw this.handleError(error);
|
|
18
18
|
}
|
|
@@ -31,7 +31,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
31
31
|
if (token) params.set('token', token);
|
|
32
32
|
|
|
33
33
|
const qs = params.toString();
|
|
34
|
-
return `${base}/
|
|
34
|
+
return `${base}/assets/${encodeURIComponent(fileId)}/stream${qs ? `?${qs}` : ''}`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -60,7 +60,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
60
60
|
const paramsObj: any = {};
|
|
61
61
|
if (limit) paramsObj.limit = limit;
|
|
62
62
|
if (offset) paramsObj.offset = offset;
|
|
63
|
-
return await this.makeRequest('GET', '/
|
|
63
|
+
return await this.makeRequest('GET', '/assets', paramsObj, {
|
|
64
64
|
cache: false,
|
|
65
65
|
});
|
|
66
66
|
} catch (error) {
|
|
@@ -73,7 +73,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
73
73
|
*/
|
|
74
74
|
async getAccountStorageUsage(): Promise<AccountStorageUsageResponse> {
|
|
75
75
|
try {
|
|
76
|
-
return await this.makeRequest<AccountStorageUsageResponse>('GET', '/
|
|
76
|
+
return await this.makeRequest<AccountStorageUsageResponse>('GET', '/storage/usage', undefined, {
|
|
77
77
|
cache: false,
|
|
78
78
|
});
|
|
79
79
|
} catch (error) {
|
|
@@ -128,7 +128,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
128
128
|
*/
|
|
129
129
|
async getBatchFileAccess(fileIds: string[], context?: string): Promise<Record<string, any>> {
|
|
130
130
|
try {
|
|
131
|
-
return await this.makeRequest('POST', '/
|
|
131
|
+
return await this.makeRequest('POST', '/assets/batch-access', {
|
|
132
132
|
fileIds,
|
|
133
133
|
context
|
|
134
134
|
});
|
|
@@ -191,7 +191,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
191
191
|
|
|
192
192
|
const response = await this.getClient().request<{ file: any }>({
|
|
193
193
|
method: 'POST',
|
|
194
|
-
url: '/
|
|
194
|
+
url: '/assets/upload',
|
|
195
195
|
data: formData,
|
|
196
196
|
cache: false,
|
|
197
197
|
});
|
|
@@ -250,7 +250,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
250
250
|
const body: any = { app, entityType, entityId };
|
|
251
251
|
if (visibility) body.visibility = visibility;
|
|
252
252
|
if (webhookUrl) body.webhookUrl = webhookUrl;
|
|
253
|
-
return await this.makeRequest('POST', `/
|
|
253
|
+
return await this.makeRequest('POST', `/assets/${fileId}/links`, body, { cache: false });
|
|
254
254
|
} catch (error) {
|
|
255
255
|
throw this.handleError(error);
|
|
256
256
|
}
|
|
@@ -261,7 +261,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
261
261
|
*/
|
|
262
262
|
async assetUnlink(fileId: string, app: string, entityType: string, entityId: string): Promise<any> {
|
|
263
263
|
try {
|
|
264
|
-
return await this.makeRequest('DELETE', `/
|
|
264
|
+
return await this.makeRequest('DELETE', `/assets/${fileId}/links`, {
|
|
265
265
|
app,
|
|
266
266
|
entityType,
|
|
267
267
|
entityId
|
|
@@ -276,7 +276,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
276
276
|
*/
|
|
277
277
|
async assetGet(fileId: string): Promise<any> {
|
|
278
278
|
try {
|
|
279
|
-
return await this.makeRequest('GET', `/
|
|
279
|
+
return await this.makeRequest('GET', `/assets/${fileId}`, undefined, {
|
|
280
280
|
cache: true,
|
|
281
281
|
cacheTTL: 5 * 60 * 1000,
|
|
282
282
|
});
|
|
@@ -294,7 +294,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
294
294
|
if (variant) params.variant = variant;
|
|
295
295
|
if (expiresIn) params.expiresIn = expiresIn;
|
|
296
296
|
|
|
297
|
-
return await this.makeRequest<AssetUrlResponse>('GET', `/
|
|
297
|
+
return await this.makeRequest<AssetUrlResponse>('GET', `/assets/${fileId}/url`, params, {
|
|
298
298
|
cache: true,
|
|
299
299
|
cacheTTL: 10 * 60 * 1000,
|
|
300
300
|
});
|
|
@@ -308,7 +308,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
308
308
|
*/
|
|
309
309
|
async assetRestore(fileId: string): Promise<any> {
|
|
310
310
|
try {
|
|
311
|
-
return await this.makeRequest('POST', `/
|
|
311
|
+
return await this.makeRequest('POST', `/assets/${fileId}/restore`, undefined, { cache: false });
|
|
312
312
|
} catch (error) {
|
|
313
313
|
throw this.handleError(error);
|
|
314
314
|
}
|
|
@@ -320,7 +320,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
320
320
|
async assetDelete(fileId: string, force: boolean = false): Promise<any> {
|
|
321
321
|
try {
|
|
322
322
|
const params: any = force ? { force: 'true' } : undefined;
|
|
323
|
-
return await this.makeRequest('DELETE', `/
|
|
323
|
+
return await this.makeRequest('DELETE', `/assets/${fileId}`, params, { cache: false });
|
|
324
324
|
} catch (error) {
|
|
325
325
|
throw this.handleError(error);
|
|
326
326
|
}
|
|
@@ -343,7 +343,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
343
343
|
*/
|
|
344
344
|
async assetUpdateVisibility(fileId: string, visibility: 'private' | 'public' | 'unlisted'): Promise<any> {
|
|
345
345
|
try {
|
|
346
|
-
return await this.makeRequest('PATCH', `/
|
|
346
|
+
return await this.makeRequest('PATCH', `/assets/${fileId}/visibility`, {
|
|
347
347
|
visibility
|
|
348
348
|
}, { cache: false });
|
|
349
349
|
} catch (error) {
|
|
@@ -388,7 +388,7 @@ export function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T
|
|
|
388
388
|
|
|
389
389
|
const urlRes = await this.makeRequest<{ url: string }>(
|
|
390
390
|
'GET',
|
|
391
|
-
`/
|
|
391
|
+
`/assets/${encodeURIComponent(fileId)}/url`,
|
|
392
392
|
Object.keys(params).length ? params : undefined,
|
|
393
393
|
{
|
|
394
394
|
cache: true,
|