@oxyhq/core 1.4.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.
Files changed (87) hide show
  1. package/dist/cjs/AuthManager.js +1 -1
  2. package/dist/cjs/HttpService.js +87 -69
  3. package/dist/cjs/OxyServices.base.js +5 -4
  4. package/dist/cjs/crypto/keyManager.js +1 -13
  5. package/dist/cjs/crypto/signatureService.js +7 -20
  6. package/dist/cjs/mixins/OxyServices.analytics.js +2 -2
  7. package/dist/cjs/mixins/OxyServices.assets.js +14 -14
  8. package/dist/cjs/mixins/OxyServices.auth.js +20 -18
  9. package/dist/cjs/mixins/OxyServices.developer.js +6 -6
  10. package/dist/cjs/mixins/OxyServices.devices.js +7 -7
  11. package/dist/cjs/mixins/OxyServices.features.js +23 -23
  12. package/dist/cjs/mixins/OxyServices.fedcm.js +1 -1
  13. package/dist/cjs/mixins/OxyServices.karma.js +6 -6
  14. package/dist/cjs/mixins/OxyServices.location.js +2 -2
  15. package/dist/cjs/mixins/OxyServices.payment.js +6 -6
  16. package/dist/cjs/mixins/OxyServices.popup.js +1 -1
  17. package/dist/cjs/mixins/OxyServices.privacy.js +6 -6
  18. package/dist/cjs/mixins/OxyServices.security.js +3 -3
  19. package/dist/cjs/mixins/OxyServices.user.js +22 -22
  20. package/dist/cjs/mixins/OxyServices.utility.js +16 -7
  21. package/dist/cjs/utils/asyncUtils.js +0 -1
  22. package/dist/cjs/utils/platform.js +14 -0
  23. package/dist/esm/AuthManager.js +1 -1
  24. package/dist/esm/HttpService.js +87 -69
  25. package/dist/esm/OxyServices.base.js +5 -4
  26. package/dist/esm/crypto/keyManager.js +1 -13
  27. package/dist/esm/crypto/signatureService.js +2 -15
  28. package/dist/esm/mixins/OxyServices.analytics.js +2 -2
  29. package/dist/esm/mixins/OxyServices.assets.js +14 -14
  30. package/dist/esm/mixins/OxyServices.auth.js +20 -18
  31. package/dist/esm/mixins/OxyServices.developer.js +6 -6
  32. package/dist/esm/mixins/OxyServices.devices.js +7 -7
  33. package/dist/esm/mixins/OxyServices.features.js +23 -23
  34. package/dist/esm/mixins/OxyServices.fedcm.js +1 -1
  35. package/dist/esm/mixins/OxyServices.karma.js +6 -6
  36. package/dist/esm/mixins/OxyServices.location.js +2 -2
  37. package/dist/esm/mixins/OxyServices.payment.js +6 -6
  38. package/dist/esm/mixins/OxyServices.popup.js +1 -1
  39. package/dist/esm/mixins/OxyServices.privacy.js +6 -6
  40. package/dist/esm/mixins/OxyServices.security.js +3 -3
  41. package/dist/esm/mixins/OxyServices.user.js +22 -22
  42. package/dist/esm/mixins/OxyServices.utility.js +16 -7
  43. package/dist/esm/utils/asyncUtils.js +0 -1
  44. package/dist/esm/utils/platform.js +12 -0
  45. package/dist/types/HttpService.d.ts +4 -1
  46. package/dist/types/OxyServices.base.d.ts +1 -1
  47. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
  48. package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
  49. package/dist/types/mixins/OxyServices.auth.d.ts +1 -1
  50. package/dist/types/mixins/OxyServices.developer.d.ts +1 -1
  51. package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
  52. package/dist/types/mixins/OxyServices.features.d.ts +1 -1
  53. package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -1
  54. package/dist/types/mixins/OxyServices.karma.d.ts +1 -1
  55. package/dist/types/mixins/OxyServices.language.d.ts +1 -1
  56. package/dist/types/mixins/OxyServices.location.d.ts +1 -1
  57. package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
  58. package/dist/types/mixins/OxyServices.popup.d.ts +1 -1
  59. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
  60. package/dist/types/mixins/OxyServices.redirect.d.ts +1 -1
  61. package/dist/types/mixins/OxyServices.security.d.ts +1 -1
  62. package/dist/types/mixins/OxyServices.user.d.ts +1 -1
  63. package/dist/types/mixins/OxyServices.utility.d.ts +7 -6
  64. package/dist/types/utils/platform.d.ts +8 -0
  65. package/package.json +1 -1
  66. package/src/AuthManager.ts +1 -1
  67. package/src/HttpService.ts +85 -67
  68. package/src/OxyServices.base.ts +5 -4
  69. package/src/crypto/keyManager.ts +1 -15
  70. package/src/crypto/signatureService.ts +2 -17
  71. package/src/mixins/OxyServices.analytics.ts +2 -2
  72. package/src/mixins/OxyServices.assets.ts +14 -14
  73. package/src/mixins/OxyServices.auth.ts +26 -24
  74. package/src/mixins/OxyServices.developer.ts +6 -6
  75. package/src/mixins/OxyServices.devices.ts +7 -7
  76. package/src/mixins/OxyServices.features.ts +23 -23
  77. package/src/mixins/OxyServices.fedcm.ts +1 -1
  78. package/src/mixins/OxyServices.karma.ts +6 -6
  79. package/src/mixins/OxyServices.location.ts +2 -2
  80. package/src/mixins/OxyServices.payment.ts +6 -6
  81. package/src/mixins/OxyServices.popup.ts +1 -1
  82. package/src/mixins/OxyServices.privacy.ts +6 -6
  83. package/src/mixins/OxyServices.security.ts +3 -3
  84. package/src/mixins/OxyServices.user.ts +22 -22
  85. package/src/mixins/OxyServices.utility.ts +18 -8
  86. package/src/utils/asyncUtils.ts +1 -2
  87. package/src/utils/platform.ts +14 -0
@@ -247,7 +247,7 @@ class AuthManager {
247
247
  // Use session-based token endpoint which handles auto-refresh server-side
248
248
  const response = await httpService.request({
249
249
  method: 'GET',
250
- url: `/api/session/token/${sessionId}`,
250
+ url: `/session/token/${sessionId}`,
251
251
  cache: false,
252
252
  retry: false,
253
253
  });
@@ -28,7 +28,9 @@ const platform_1 = require("./utils/platform");
28
28
  */
29
29
  const isNativeApp = (0, platform_1.isNative)();
30
30
  /**
31
- * Token store for authentication (singleton)
31
+ * Token store for authentication (instance-based)
32
+ * Each HttpService gets its own TokenStore to prevent conflicts
33
+ * when multiple OxyServices instances coexist server-side.
32
34
  */
33
35
  class TokenStore {
34
36
  constructor() {
@@ -37,12 +39,6 @@ class TokenStore {
37
39
  this.csrfToken = null;
38
40
  this.csrfTokenFetchPromise = null;
39
41
  }
40
- static getInstance() {
41
- if (!TokenStore.instance) {
42
- TokenStore.instance = new TokenStore();
43
- }
44
- return TokenStore.instance;
45
- }
46
42
  setTokens(accessToken, refreshToken = '') {
47
43
  this.accessToken = accessToken;
48
44
  this.refreshToken = refreshToken;
@@ -86,6 +82,7 @@ class TokenStore {
86
82
  class HttpService {
87
83
  constructor(config) {
88
84
  this.tokenRefreshPromise = null;
85
+ this.tokenRefreshCooldownUntil = 0;
89
86
  this._onTokenRefreshed = null;
90
87
  // Performance monitoring
91
88
  this.requestMetrics = {
@@ -98,7 +95,7 @@ class HttpService {
98
95
  };
99
96
  this.config = config;
100
97
  this.baseURL = config.baseURL;
101
- this.tokenStore = TokenStore.getInstance();
98
+ this.tokenStore = new TokenStore();
102
99
  this.logger = new requestUtils_1.SimpleLogger(config.enableLogging || false, config.logLevel || 'error', 'HttpService');
103
100
  // Initialize performance infrastructure
104
101
  this.cache = new cache_1.TTLCache(config.cacheTTL || 5 * 60 * 1000);
@@ -250,6 +247,20 @@ class HttpService {
250
247
  // Refresh failed or no token — clear tokens
251
248
  this.tokenStore.clearTokens();
252
249
  }
250
+ // On 403 with CSRF error, clear cached token and retry once
251
+ if (response.status === 403 && !config._isCsrfRetry) {
252
+ try {
253
+ const clonedResponse = response.clone();
254
+ const errBody = await clonedResponse.json();
255
+ if (errBody?.code === 'CSRF_TOKEN_INVALID' || errBody?.code === 'CSRF_TOKEN_MISSING') {
256
+ this.tokenStore.clearCsrfToken();
257
+ return this.request({ ...config, _isCsrfRetry: true, retry: false });
258
+ }
259
+ }
260
+ catch {
261
+ // Failed to parse error body — not a CSRF error
262
+ }
263
+ }
253
264
  // Try to parse error response (handle empty/malformed JSON)
254
265
  let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
255
266
  const contentType = response.headers.get('content-type');
@@ -406,52 +417,57 @@ class HttpService {
406
417
  return existingPromise;
407
418
  }
408
419
  const fetchPromise = (async () => {
409
- try {
410
- if ((0, debugUtils_1.isDev)())
411
- console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/api/csrf-token`);
412
- // Use AbortController for timeout (more compatible than AbortSignal.timeout)
413
- const controller = new AbortController();
414
- const timeoutId = setTimeout(() => controller.abort(), 5000);
415
- const response = await fetch(`${this.baseURL}/api/csrf-token`, {
416
- method: 'GET',
417
- headers: { 'Accept': 'application/json' },
418
- credentials: 'include', // Required to receive and send cookies
419
- signal: controller.signal,
420
- });
421
- clearTimeout(timeoutId);
422
- if ((0, debugUtils_1.isDev)())
423
- console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
424
- if (response.ok) {
425
- const data = await response.json();
420
+ const maxAttempts = 2;
421
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
422
+ try {
423
+ if ((0, debugUtils_1.isDev)())
424
+ console.log('[HttpService] Fetching CSRF token from:', `${this.baseURL}/csrf-token`, `(attempt ${attempt})`);
425
+ // Use AbortController for timeout (more compatible than AbortSignal.timeout)
426
+ const controller = new AbortController();
427
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
428
+ const response = await fetch(`${this.baseURL}/csrf-token`, {
429
+ method: 'GET',
430
+ headers: { 'Accept': 'application/json' },
431
+ credentials: 'include', // Required to receive and send cookies
432
+ signal: controller.signal,
433
+ });
434
+ clearTimeout(timeoutId);
435
+ if ((0, debugUtils_1.isDev)())
436
+ console.log('[HttpService] CSRF fetch response:', response.status, response.ok);
437
+ if (response.ok) {
438
+ const data = await response.json();
439
+ if ((0, debugUtils_1.isDev)())
440
+ console.log('[HttpService] CSRF response data:', data);
441
+ const token = data.csrfToken || null;
442
+ this.tokenStore.setCsrfToken(token);
443
+ this.logger.debug('CSRF token fetched');
444
+ return token;
445
+ }
446
+ // Also check response header for CSRF token
447
+ const headerToken = response.headers.get('X-CSRF-Token');
448
+ if (headerToken) {
449
+ this.tokenStore.setCsrfToken(headerToken);
450
+ this.logger.debug('CSRF token from header');
451
+ return headerToken;
452
+ }
426
453
  if ((0, debugUtils_1.isDev)())
427
- console.log('[HttpService] CSRF response data:', data);
428
- const token = data.csrfToken || null;
429
- this.tokenStore.setCsrfToken(token);
430
- this.logger.debug('CSRF token fetched');
431
- return token;
454
+ console.log('[HttpService] CSRF fetch failed with status:', response.status);
455
+ this.logger.warn('Failed to fetch CSRF token:', response.status);
432
456
  }
433
- // Also check response header for CSRF token
434
- const headerToken = response.headers.get('X-CSRF-Token');
435
- if (headerToken) {
436
- this.tokenStore.setCsrfToken(headerToken);
437
- this.logger.debug('CSRF token from header');
438
- return headerToken;
457
+ catch (error) {
458
+ if ((0, debugUtils_1.isDev)())
459
+ console.log('[HttpService] CSRF fetch error:', error);
460
+ this.logger.warn('CSRF token fetch error:', error);
461
+ }
462
+ // Wait before retry (500ms)
463
+ if (attempt < maxAttempts) {
464
+ await new Promise(resolve => setTimeout(resolve, 500));
439
465
  }
440
- if ((0, debugUtils_1.isDev)())
441
- console.log('[HttpService] CSRF fetch failed with status:', response.status);
442
- this.logger.warn('Failed to fetch CSRF token:', response.status);
443
- return null;
444
- }
445
- catch (error) {
446
- if ((0, debugUtils_1.isDev)())
447
- console.log('[HttpService] CSRF fetch error:', error);
448
- this.logger.warn('CSRF token fetch error:', error);
449
- return null;
450
- }
451
- finally {
452
- this.tokenStore.setCsrfTokenFetchPromise(null);
453
466
  }
454
- })();
467
+ return null;
468
+ })().finally(() => {
469
+ this.tokenStore.setCsrfTokenFetchPromise(null);
470
+ });
455
471
  this.tokenStore.setCsrfTokenFetchPromise(fetchPromise);
456
472
  return fetchPromise;
457
473
  }
@@ -468,18 +484,25 @@ class HttpService {
468
484
  const currentTime = Math.floor(Date.now() / 1000);
469
485
  // If token expires in less than 60 seconds, refresh it
470
486
  if (decoded.exp && decoded.exp - currentTime < 60 && decoded.sessionId) {
471
- // Deduplicate concurrent refresh attempts
472
- if (!this.tokenRefreshPromise) {
473
- this.tokenRefreshPromise = this._refreshTokenFromSession(decoded.sessionId);
487
+ // Skip if we recently failed a refresh (5s cooldown to prevent storms)
488
+ if (Date.now() < this.tokenRefreshCooldownUntil) {
489
+ return `Bearer ${accessToken}`;
474
490
  }
475
- try {
476
- const result = await this.tokenRefreshPromise;
477
- if (result)
491
+ // Deduplicate concurrent refresh attempts. The promise is shared
492
+ // across all concurrent callers and cleared only after it settles,
493
+ // so every awaiter receives the same result.
494
+ if (!this.tokenRefreshPromise) {
495
+ this.tokenRefreshPromise = this._refreshTokenFromSession(decoded.sessionId)
496
+ .then((result) => {
497
+ if (!result)
498
+ this.tokenRefreshCooldownUntil = Date.now() + 5000;
478
499
  return result;
500
+ })
501
+ .finally(() => { this.tokenRefreshPromise = null; });
479
502
  }
480
- finally {
481
- this.tokenRefreshPromise = null;
482
- }
503
+ const result = await this.tokenRefreshPromise;
504
+ if (result)
505
+ return result;
483
506
  }
484
507
  return `Bearer ${accessToken}`;
485
508
  }
@@ -490,7 +513,7 @@ class HttpService {
490
513
  }
491
514
  async _refreshTokenFromSession(sessionId) {
492
515
  try {
493
- const refreshUrl = `${this.baseURL}/api/session/token/${sessionId}`;
516
+ const refreshUrl = `${this.baseURL}/session/token/${sessionId}`;
494
517
  const response = await fetch(refreshUrl, {
495
518
  method: 'GET',
496
519
  headers: { 'Accept': 'application/json' },
@@ -643,15 +666,10 @@ class HttpService {
643
666
  getMetrics() {
644
667
  return { ...this.requestMetrics };
645
668
  }
646
- // Test-only utility
647
- static __resetTokensForTests() {
648
- try {
649
- TokenStore.getInstance().clearTokens();
650
- }
651
- catch (error) {
652
- // Silently fail in test cleanup - this is expected behavior
653
- // TokenStore might not be initialized in some test scenarios
654
- }
669
+ // Test-only utility — clears tokens on this instance
670
+ __resetTokensForTests() {
671
+ this.tokenStore.clearTokens();
672
+ this.tokenStore.clearCsrfToken();
655
673
  }
656
674
  }
657
675
  exports.HttpService = HttpService;
@@ -26,9 +26,10 @@ class OxyServicesBase {
26
26
  // Initialize unified HTTP service (handles auth, caching, deduplication, queuing, retry)
27
27
  this.httpService = new HttpService_1.HttpService(config);
28
28
  }
29
- // Test-only utility to reset global tokens between jest tests
30
- static __resetTokensForTests() {
31
- HttpService_1.HttpService.__resetTokensForTests();
29
+ // Test-only utility to reset tokens on this instance between jest tests
30
+ // Note: tokens are now per-instance, so create new instances in tests for isolation
31
+ __resetTokensForTests() {
32
+ this.httpService.__resetTokensForTests();
32
33
  }
33
34
  /**
34
35
  * Make a request with all performance optimizations
@@ -234,7 +235,7 @@ class OxyServicesBase {
234
235
  return false;
235
236
  }
236
237
  try {
237
- const res = await this.makeRequest('GET', '/api/auth/validate', undefined, {
238
+ const res = await this.makeRequest('GET', '/auth/validate', undefined, {
238
239
  cache: false,
239
240
  retry: false,
240
241
  });
@@ -92,18 +92,6 @@ async function initSecureStore() {
92
92
  }
93
93
  return SecureStore;
94
94
  }
95
- /**
96
- * Check if we're in a React Native environment
97
- */
98
- function isReactNative() {
99
- return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
100
- }
101
- /**
102
- * Check if we're in a Node.js environment
103
- */
104
- function isNodeJS() {
105
- return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
106
- }
107
95
  /**
108
96
  * Check if we're on web platform
109
97
  * Identity storage is only available on native platforms (iOS/Android)
@@ -133,7 +121,7 @@ function uint8ArrayToHex(bytes) {
133
121
  */
134
122
  async function getSecureRandomBytes(length) {
135
123
  // In React Native, always use expo-crypto
136
- if (isReactNative() || !isNodeJS()) {
124
+ if ((0, platform_1.isReactNative)() || !(0, platform_1.isNodeJS)()) {
137
125
  const Crypto = await initExpoCrypto();
138
126
  return Crypto.getRandomBytes(length);
139
127
  }
@@ -42,21 +42,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
42
42
  exports.SignatureService = void 0;
43
43
  const elliptic_1 = require("elliptic");
44
44
  const keyManager_1 = require("./keyManager");
45
+ const platform_1 = require("../utils/platform");
45
46
  // Lazy import for expo-crypto
46
47
  let ExpoCrypto = null;
47
48
  const ec = new elliptic_1.ec('secp256k1');
48
- /**
49
- * Check if we're in a React Native environment
50
- */
51
- function isReactNative() {
52
- return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
53
- }
54
- /**
55
- * Check if we're in a Node.js environment
56
- */
57
- function isNodeJS() {
58
- return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
59
- }
60
49
  /**
61
50
  * Initialize expo-crypto module
62
51
  */
@@ -73,16 +62,14 @@ async function initExpoCrypto() {
73
62
  */
74
63
  async function sha256(message) {
75
64
  // In React Native, use expo-crypto
76
- if (isReactNative()) {
65
+ if ((0, platform_1.isReactNative)()) {
77
66
  const Crypto = await initExpoCrypto();
78
67
  return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, message);
79
68
  }
80
69
  // In Node.js, use Node's crypto module
81
- if (isNodeJS()) {
70
+ if ((0, platform_1.isNodeJS)()) {
82
71
  try {
83
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
84
- const getCrypto = new Function('return require("crypto")');
85
- const nodeCrypto = getCrypto();
72
+ const nodeCrypto = await Promise.resolve().then(() => __importStar(require('crypto')));
86
73
  return nodeCrypto.createHash('sha256').update(message).digest('hex');
87
74
  }
88
75
  catch {
@@ -103,7 +90,7 @@ class SignatureService {
103
90
  */
104
91
  static async generateChallenge() {
105
92
  // In React Native, use expo-crypto
106
- if (isReactNative()) {
93
+ if ((0, platform_1.isReactNative)()) {
107
94
  const Crypto = await initExpoCrypto();
108
95
  const randomBytes = await Crypto.getRandomBytesAsync(32);
109
96
  return Array.from(randomBytes)
@@ -111,7 +98,7 @@ class SignatureService {
111
98
  .join('');
112
99
  }
113
100
  // In Node.js, use Node's crypto module
114
- if (isNodeJS()) {
101
+ if ((0, platform_1.isNodeJS)()) {
115
102
  try {
116
103
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
117
104
  const getCrypto = new Function('return require("crypto")');
@@ -178,7 +165,7 @@ class SignatureService {
178
165
  */
179
166
  static verifySync(message, signature, publicKey) {
180
167
  try {
181
- if (!isNodeJS()) {
168
+ if (!(0, platform_1.isNodeJS)()) {
182
169
  // In React Native, use async verify instead
183
170
  throw new Error('verifySync should only be used in Node.js. Use verify() in React Native.');
184
171
  }
@@ -14,7 +14,7 @@ function OxyServicesAnalyticsMixin(Base) {
14
14
  */
15
15
  async trackEvent(eventName, properties) {
16
16
  try {
17
- await this.makeRequest('POST', '/api/analytics/events', {
17
+ await this.makeRequest('POST', '/analytics/events', {
18
18
  event: eventName,
19
19
  properties
20
20
  }, { cache: false, retry: false }); // Don't retry analytics events
@@ -36,7 +36,7 @@ function OxyServicesAnalyticsMixin(Base) {
36
36
  params.startDate = startDate;
37
37
  if (endDate)
38
38
  params.endDate = endDate;
39
- return await this.makeRequest('GET', '/api/analytics', params, {
39
+ return await this.makeRequest('GET', '/analytics', params, {
40
40
  cache: true,
41
41
  cacheTTL: mixinHelpers_1.CACHE_TIMES.LONG,
42
42
  });
@@ -11,7 +11,7 @@ function OxyServicesAssetsMixin(Base) {
11
11
  */
12
12
  async deleteFile(fileId) {
13
13
  try {
14
- return await this.makeRequest('DELETE', `/api/assets/${encodeURIComponent(fileId)}`, undefined, { cache: false });
14
+ return await this.makeRequest('DELETE', `/assets/${encodeURIComponent(fileId)}`, undefined, { cache: false });
15
15
  }
16
16
  catch (error) {
17
17
  throw this.handleError(error);
@@ -32,7 +32,7 @@ function OxyServicesAssetsMixin(Base) {
32
32
  if (token)
33
33
  params.set('token', token);
34
34
  const qs = params.toString();
35
- return `${base}/api/assets/${encodeURIComponent(fileId)}/stream${qs ? `?${qs}` : ''}`;
35
+ return `${base}/assets/${encodeURIComponent(fileId)}/stream${qs ? `?${qs}` : ''}`;
36
36
  }
37
37
  /**
38
38
  * Get file download URL asynchronously (returns signed URL directly from CDN)
@@ -56,7 +56,7 @@ function OxyServicesAssetsMixin(Base) {
56
56
  paramsObj.limit = limit;
57
57
  if (offset)
58
58
  paramsObj.offset = offset;
59
- return await this.makeRequest('GET', '/api/assets', paramsObj, {
59
+ return await this.makeRequest('GET', '/assets', paramsObj, {
60
60
  cache: false,
61
61
  });
62
62
  }
@@ -69,7 +69,7 @@ function OxyServicesAssetsMixin(Base) {
69
69
  */
70
70
  async getAccountStorageUsage() {
71
71
  try {
72
- return await this.makeRequest('GET', '/api/storage/usage', undefined, {
72
+ return await this.makeRequest('GET', '/storage/usage', undefined, {
73
73
  cache: false,
74
74
  });
75
75
  }
@@ -112,7 +112,7 @@ function OxyServicesAssetsMixin(Base) {
112
112
  */
113
113
  async getBatchFileAccess(fileIds, context) {
114
114
  try {
115
- return await this.makeRequest('POST', '/api/assets/batch-access', {
115
+ return await this.makeRequest('POST', '/assets/batch-access', {
116
116
  fileIds,
117
117
  context
118
118
  });
@@ -173,7 +173,7 @@ function OxyServicesAssetsMixin(Base) {
173
173
  }
174
174
  const response = await this.getClient().request({
175
175
  method: 'POST',
176
- url: '/api/assets/upload',
176
+ url: '/assets/upload',
177
177
  data: formData,
178
178
  cache: false,
179
179
  });
@@ -231,7 +231,7 @@ function OxyServicesAssetsMixin(Base) {
231
231
  body.visibility = visibility;
232
232
  if (webhookUrl)
233
233
  body.webhookUrl = webhookUrl;
234
- return await this.makeRequest('POST', `/api/assets/${fileId}/links`, body, { cache: false });
234
+ return await this.makeRequest('POST', `/assets/${fileId}/links`, body, { cache: false });
235
235
  }
236
236
  catch (error) {
237
237
  throw this.handleError(error);
@@ -242,7 +242,7 @@ function OxyServicesAssetsMixin(Base) {
242
242
  */
243
243
  async assetUnlink(fileId, app, entityType, entityId) {
244
244
  try {
245
- return await this.makeRequest('DELETE', `/api/assets/${fileId}/links`, {
245
+ return await this.makeRequest('DELETE', `/assets/${fileId}/links`, {
246
246
  app,
247
247
  entityType,
248
248
  entityId
@@ -257,7 +257,7 @@ function OxyServicesAssetsMixin(Base) {
257
257
  */
258
258
  async assetGet(fileId) {
259
259
  try {
260
- return await this.makeRequest('GET', `/api/assets/${fileId}`, undefined, {
260
+ return await this.makeRequest('GET', `/assets/${fileId}`, undefined, {
261
261
  cache: true,
262
262
  cacheTTL: 5 * 60 * 1000,
263
263
  });
@@ -276,7 +276,7 @@ function OxyServicesAssetsMixin(Base) {
276
276
  params.variant = variant;
277
277
  if (expiresIn)
278
278
  params.expiresIn = expiresIn;
279
- return await this.makeRequest('GET', `/api/assets/${fileId}/url`, params, {
279
+ return await this.makeRequest('GET', `/assets/${fileId}/url`, params, {
280
280
  cache: true,
281
281
  cacheTTL: 10 * 60 * 1000,
282
282
  });
@@ -290,7 +290,7 @@ function OxyServicesAssetsMixin(Base) {
290
290
  */
291
291
  async assetRestore(fileId) {
292
292
  try {
293
- return await this.makeRequest('POST', `/api/assets/${fileId}/restore`, undefined, { cache: false });
293
+ return await this.makeRequest('POST', `/assets/${fileId}/restore`, undefined, { cache: false });
294
294
  }
295
295
  catch (error) {
296
296
  throw this.handleError(error);
@@ -302,7 +302,7 @@ function OxyServicesAssetsMixin(Base) {
302
302
  async assetDelete(fileId, force = false) {
303
303
  try {
304
304
  const params = force ? { force: 'true' } : undefined;
305
- return await this.makeRequest('DELETE', `/api/assets/${fileId}`, params, { cache: false });
305
+ return await this.makeRequest('DELETE', `/assets/${fileId}`, params, { cache: false });
306
306
  }
307
307
  catch (error) {
308
308
  throw this.handleError(error);
@@ -325,7 +325,7 @@ function OxyServicesAssetsMixin(Base) {
325
325
  */
326
326
  async assetUpdateVisibility(fileId, visibility) {
327
327
  try {
328
- return await this.makeRequest('PATCH', `/api/assets/${fileId}/visibility`, {
328
+ return await this.makeRequest('PATCH', `/assets/${fileId}/visibility`, {
329
329
  visibility
330
330
  }, { cache: false });
331
331
  }
@@ -363,7 +363,7 @@ function OxyServicesAssetsMixin(Base) {
363
363
  params.variant = variant;
364
364
  if (expiresIn)
365
365
  params.expiresIn = expiresIn;
366
- const urlRes = await this.makeRequest('GET', `/api/assets/${encodeURIComponent(fileId)}/url`, Object.keys(params).length ? params : undefined, {
366
+ const urlRes = await this.makeRequest('GET', `/assets/${encodeURIComponent(fileId)}/url`, Object.keys(params).length ? params : undefined, {
367
367
  cache: true,
368
368
  cacheTTL: cacheTTL ?? 10 * 60 * 1000,
369
369
  });
@@ -43,7 +43,7 @@ function OxyServicesAuthMixin(Base) {
43
43
  if (this._serviceToken && this._serviceTokenExp > Date.now() + 60000) {
44
44
  return this._serviceToken;
45
45
  }
46
- const response = await this.makeRequest('POST', '/api/auth/service-token', { apiKey: key, apiSecret: secret }, { cache: false, retry: false });
46
+ const response = await this.makeRequest('POST', '/auth/service-token', { apiKey: key, apiSecret: secret }, { cache: false, retry: false });
47
47
  this._serviceToken = response.token;
48
48
  this._serviceTokenExp = Date.now() + response.expiresIn * 1000;
49
49
  return this._serviceToken;
@@ -77,7 +77,7 @@ function OxyServicesAuthMixin(Base) {
77
77
  */
78
78
  async register(publicKey, signature, timestamp) {
79
79
  try {
80
- const res = await this.makeRequest('POST', '/api/auth/register', {
80
+ const res = await this.makeRequest('POST', '/auth/register', {
81
81
  publicKey,
82
82
  signature,
83
83
  timestamp,
@@ -99,7 +99,7 @@ function OxyServicesAuthMixin(Base) {
99
99
  */
100
100
  async requestChallenge(publicKey) {
101
101
  try {
102
- return await this.makeRequest('POST', '/api/auth/challenge', {
102
+ return await this.makeRequest('POST', '/auth/challenge', {
103
103
  publicKey,
104
104
  }, { cache: false });
105
105
  }
@@ -119,7 +119,7 @@ function OxyServicesAuthMixin(Base) {
119
119
  */
120
120
  async verifyChallenge(publicKey, challenge, signature, timestamp, deviceName, deviceFingerprint) {
121
121
  try {
122
- return await this.makeRequest('POST', '/api/auth/verify', {
122
+ return await this.makeRequest('POST', '/auth/verify', {
123
123
  publicKey,
124
124
  challenge,
125
125
  signature,
@@ -137,7 +137,7 @@ function OxyServicesAuthMixin(Base) {
137
137
  */
138
138
  async checkPublicKeyRegistered(publicKey) {
139
139
  try {
140
- return await this.makeRequest('GET', `/api/auth/check-publickey/${encodeURIComponent(publicKey)}`, undefined, { cache: false });
140
+ return await this.makeRequest('GET', `/auth/check-publickey/${encodeURIComponent(publicKey)}`, undefined, { cache: false });
141
141
  }
142
142
  catch (error) {
143
143
  throw this.handleError(error);
@@ -148,7 +148,7 @@ function OxyServicesAuthMixin(Base) {
148
148
  */
149
149
  async getUserByPublicKey(publicKey) {
150
150
  try {
151
- return await this.makeRequest('GET', `/api/auth/user/${encodeURIComponent(publicKey)}`, undefined, { cache: true, cacheTTL: 2 * 60 * 1000 });
151
+ return await this.makeRequest('GET', `/auth/user/${encodeURIComponent(publicKey)}`, undefined, { cache: true, cacheTTL: 2 * 60 * 1000 });
152
152
  }
153
153
  catch (error) {
154
154
  throw this.handleError(error);
@@ -159,7 +159,7 @@ function OxyServicesAuthMixin(Base) {
159
159
  */
160
160
  async getUserBySession(sessionId) {
161
161
  try {
162
- return await this.makeRequest('GET', `/api/session/user/${sessionId}`, undefined, {
162
+ return await this.makeRequest('GET', `/session/user/${sessionId}`, undefined, {
163
163
  cache: true,
164
164
  cacheTTL: 2 * 60 * 1000,
165
165
  });
@@ -177,7 +177,7 @@ function OxyServicesAuthMixin(Base) {
177
177
  return [];
178
178
  }
179
179
  const uniqueSessionIds = Array.from(new Set(sessionIds)).sort();
180
- return await this.makeRequest('POST', '/api/session/users/batch', { sessionIds: uniqueSessionIds }, {
180
+ return await this.makeRequest('POST', '/session/users/batch', { sessionIds: uniqueSessionIds }, {
181
181
  cache: true,
182
182
  cacheTTL: 2 * 60 * 1000,
183
183
  deduplicate: true,
@@ -192,7 +192,7 @@ function OxyServicesAuthMixin(Base) {
192
192
  */
193
193
  async getTokenBySession(sessionId) {
194
194
  try {
195
- const res = await this.makeRequest('GET', `/api/session/token/${sessionId}`, undefined, { cache: false, retry: false });
195
+ const res = await this.makeRequest('GET', `/session/token/${sessionId}`, undefined, { cache: false, retry: false });
196
196
  this.setTokens(res.accessToken);
197
197
  return res;
198
198
  }
@@ -205,7 +205,7 @@ function OxyServicesAuthMixin(Base) {
205
205
  */
206
206
  async getSessionsBySessionId(sessionId) {
207
207
  try {
208
- return await this.makeRequest('GET', `/api/session/sessions/${sessionId}`, undefined, {
208
+ return await this.makeRequest('GET', `/session/sessions/${sessionId}`, undefined, {
209
209
  cache: false,
210
210
  });
211
211
  }
@@ -219,8 +219,8 @@ function OxyServicesAuthMixin(Base) {
219
219
  async logoutSession(sessionId, targetSessionId) {
220
220
  try {
221
221
  const url = targetSessionId
222
- ? `/api/session/logout/${sessionId}/${targetSessionId}`
223
- : `/api/session/logout/${sessionId}`;
222
+ ? `/session/logout/${sessionId}/${targetSessionId}`
223
+ : `/session/logout/${sessionId}`;
224
224
  await this.makeRequest('POST', url, undefined, { cache: false });
225
225
  }
226
226
  catch (error) {
@@ -232,7 +232,7 @@ function OxyServicesAuthMixin(Base) {
232
232
  */
233
233
  async logoutAllSessions(sessionId) {
234
234
  try {
235
- await this.makeRequest('POST', `/api/session/logout-all/${sessionId}`, undefined, { cache: false });
235
+ await this.makeRequest('POST', `/session/logout-all/${sessionId}`, undefined, { cache: false });
236
236
  }
237
237
  catch (error) {
238
238
  throw this.handleError(error);
@@ -248,9 +248,11 @@ function OxyServicesAuthMixin(Base) {
248
248
  urlParams.deviceFingerprint = options.deviceFingerprint;
249
249
  if (options.useHeaderValidation)
250
250
  urlParams.useHeaderValidation = 'true';
251
- return await this.makeRequest('GET', `/api/session/validate/${sessionId}`, urlParams, { cache: false });
251
+ return await this.makeRequest('GET', `/session/validate/${sessionId}`, urlParams, { cache: false });
252
252
  }
253
253
  catch (error) {
254
+ // Session is invalid — clear any cached user data for this session (#196)
255
+ this.clearCacheEntry(`GET:/session/user/${sessionId}`);
254
256
  throw this.handleError(error);
255
257
  }
256
258
  }
@@ -259,7 +261,7 @@ function OxyServicesAuthMixin(Base) {
259
261
  */
260
262
  async checkUsernameAvailability(username) {
261
263
  try {
262
- return await this.makeRequest('GET', `/api/auth/check-username/${username}`, undefined, { cache: false });
264
+ return await this.makeRequest('GET', `/auth/check-username/${username}`, undefined, { cache: false });
263
265
  }
264
266
  catch (error) {
265
267
  throw this.handleError(error);
@@ -270,7 +272,7 @@ function OxyServicesAuthMixin(Base) {
270
272
  */
271
273
  async checkEmailAvailability(email) {
272
274
  try {
273
- return await this.makeRequest('GET', `/api/auth/check-email/${email}`, undefined, { cache: false });
275
+ return await this.makeRequest('GET', `/auth/check-email/${email}`, undefined, { cache: false });
274
276
  }
275
277
  catch (error) {
276
278
  throw this.handleError(error);
@@ -281,7 +283,7 @@ function OxyServicesAuthMixin(Base) {
281
283
  */
282
284
  async signUp(username, email, password, deviceName, deviceFingerprint) {
283
285
  try {
284
- return await this.makeRequest('POST', '/api/auth/signup', {
286
+ return await this.makeRequest('POST', '/auth/signup', {
285
287
  username,
286
288
  email,
287
289
  password,
@@ -298,7 +300,7 @@ function OxyServicesAuthMixin(Base) {
298
300
  */
299
301
  async signIn(identifier, password, deviceName, deviceFingerprint) {
300
302
  try {
301
- return await this.makeRequest('POST', '/api/auth/login', {
303
+ return await this.makeRequest('POST', '/auth/login', {
302
304
  identifier,
303
305
  password,
304
306
  deviceName,