@oxyhq/core 1.5.0 → 1.6.1

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 (92) hide show
  1. package/dist/cjs/AuthManager.js +14 -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/index.js +9 -1
  7. package/dist/cjs/mixins/OxyServices.analytics.js +2 -2
  8. package/dist/cjs/mixins/OxyServices.assets.js +14 -14
  9. package/dist/cjs/mixins/OxyServices.auth.js +19 -19
  10. package/dist/cjs/mixins/OxyServices.developer.js +6 -6
  11. package/dist/cjs/mixins/OxyServices.devices.js +7 -7
  12. package/dist/cjs/mixins/OxyServices.features.js +23 -23
  13. package/dist/cjs/mixins/OxyServices.fedcm.js +1 -1
  14. package/dist/cjs/mixins/OxyServices.karma.js +6 -6
  15. package/dist/cjs/mixins/OxyServices.location.js +2 -2
  16. package/dist/cjs/mixins/OxyServices.payment.js +6 -6
  17. package/dist/cjs/mixins/OxyServices.popup.js +1 -1
  18. package/dist/cjs/mixins/OxyServices.privacy.js +6 -6
  19. package/dist/cjs/mixins/OxyServices.security.js +3 -3
  20. package/dist/cjs/mixins/OxyServices.user.js +22 -22
  21. package/dist/cjs/mixins/OxyServices.utility.js +39 -10
  22. package/dist/cjs/utils/authHelpers.js +114 -0
  23. package/dist/cjs/utils/platform.js +14 -0
  24. package/dist/esm/AuthManager.js +14 -1
  25. package/dist/esm/HttpService.js +87 -69
  26. package/dist/esm/OxyServices.base.js +5 -4
  27. package/dist/esm/crypto/keyManager.js +1 -13
  28. package/dist/esm/crypto/signatureService.js +2 -15
  29. package/dist/esm/index.js +2 -0
  30. package/dist/esm/mixins/OxyServices.analytics.js +2 -2
  31. package/dist/esm/mixins/OxyServices.assets.js +14 -14
  32. package/dist/esm/mixins/OxyServices.auth.js +19 -19
  33. package/dist/esm/mixins/OxyServices.developer.js +6 -6
  34. package/dist/esm/mixins/OxyServices.devices.js +7 -7
  35. package/dist/esm/mixins/OxyServices.features.js +23 -23
  36. package/dist/esm/mixins/OxyServices.fedcm.js +1 -1
  37. package/dist/esm/mixins/OxyServices.karma.js +6 -6
  38. package/dist/esm/mixins/OxyServices.location.js +2 -2
  39. package/dist/esm/mixins/OxyServices.payment.js +6 -6
  40. package/dist/esm/mixins/OxyServices.popup.js +1 -1
  41. package/dist/esm/mixins/OxyServices.privacy.js +6 -6
  42. package/dist/esm/mixins/OxyServices.security.js +3 -3
  43. package/dist/esm/mixins/OxyServices.user.js +22 -22
  44. package/dist/esm/mixins/OxyServices.utility.js +39 -10
  45. package/dist/esm/utils/authHelpers.js +105 -0
  46. package/dist/esm/utils/platform.js +12 -0
  47. package/dist/types/HttpService.d.ts +4 -1
  48. package/dist/types/OxyServices.base.d.ts +1 -1
  49. package/dist/types/index.d.ts +2 -0
  50. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
  51. package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
  52. package/dist/types/mixins/OxyServices.auth.d.ts +1 -1
  53. package/dist/types/mixins/OxyServices.developer.d.ts +1 -1
  54. package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
  55. package/dist/types/mixins/OxyServices.features.d.ts +1 -1
  56. package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -1
  57. package/dist/types/mixins/OxyServices.karma.d.ts +1 -1
  58. package/dist/types/mixins/OxyServices.language.d.ts +1 -1
  59. package/dist/types/mixins/OxyServices.location.d.ts +1 -1
  60. package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
  61. package/dist/types/mixins/OxyServices.popup.d.ts +1 -1
  62. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
  63. package/dist/types/mixins/OxyServices.redirect.d.ts +1 -1
  64. package/dist/types/mixins/OxyServices.security.d.ts +1 -1
  65. package/dist/types/mixins/OxyServices.user.d.ts +1 -1
  66. package/dist/types/mixins/OxyServices.utility.d.ts +20 -6
  67. package/dist/types/utils/authHelpers.d.ts +57 -0
  68. package/dist/types/utils/platform.d.ts +8 -0
  69. package/package.json +1 -1
  70. package/src/AuthManager.ts +14 -1
  71. package/src/HttpService.ts +85 -67
  72. package/src/OxyServices.base.ts +5 -4
  73. package/src/crypto/keyManager.ts +1 -15
  74. package/src/crypto/signatureService.ts +2 -17
  75. package/src/index.ts +11 -0
  76. package/src/mixins/OxyServices.analytics.ts +2 -2
  77. package/src/mixins/OxyServices.assets.ts +14 -14
  78. package/src/mixins/OxyServices.auth.ts +19 -19
  79. package/src/mixins/OxyServices.developer.ts +6 -6
  80. package/src/mixins/OxyServices.devices.ts +7 -7
  81. package/src/mixins/OxyServices.features.ts +23 -23
  82. package/src/mixins/OxyServices.fedcm.ts +1 -1
  83. package/src/mixins/OxyServices.karma.ts +6 -6
  84. package/src/mixins/OxyServices.location.ts +2 -2
  85. package/src/mixins/OxyServices.payment.ts +6 -6
  86. package/src/mixins/OxyServices.popup.ts +1 -1
  87. package/src/mixins/OxyServices.privacy.ts +6 -6
  88. package/src/mixins/OxyServices.security.ts +3 -3
  89. package/src/mixins/OxyServices.user.ts +22 -22
  90. package/src/mixins/OxyServices.utility.ts +41 -11
  91. package/src/utils/authHelpers.ts +140 -0
  92. package/src/utils/platform.ts +14 -0
@@ -9,7 +9,7 @@ export function OxyServicesUserMixin(Base) {
9
9
  */
10
10
  async getProfileByUsername(username) {
11
11
  try {
12
- return await this.makeRequest('GET', `/api/profiles/username/${username}`, undefined, {
12
+ return await this.makeRequest('GET', `/profiles/username/${username}`, undefined, {
13
13
  cache: true,
14
14
  cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
15
15
  });
@@ -26,7 +26,7 @@ export function OxyServicesUserMixin(Base) {
26
26
  const params = { query, ...pagination };
27
27
  const searchParams = buildSearchParams(params);
28
28
  const paramsObj = Object.fromEntries(searchParams.entries());
29
- const response = await this.makeRequest('GET', '/api/profiles/search', paramsObj, {
29
+ const response = await this.makeRequest('GET', '/profiles/search', paramsObj, {
30
30
  cache: true,
31
31
  cacheTTL: 2 * 60 * 1000, // 2 minutes cache
32
32
  });
@@ -71,7 +71,7 @@ export function OxyServicesUserMixin(Base) {
71
71
  */
72
72
  async getProfileRecommendations() {
73
73
  return this.withAuthRetry(async () => {
74
- return await this.makeRequest('GET', '/api/profiles/recommendations', undefined, { cache: true });
74
+ return await this.makeRequest('GET', '/profiles/recommendations', undefined, { cache: true });
75
75
  }, 'getProfileRecommendations');
76
76
  }
77
77
  /**
@@ -79,7 +79,7 @@ export function OxyServicesUserMixin(Base) {
79
79
  */
80
80
  async getUserById(userId) {
81
81
  try {
82
- return await this.makeRequest('GET', `/api/users/${userId}`, undefined, {
82
+ return await this.makeRequest('GET', `/users/${userId}`, undefined, {
83
83
  cache: true,
84
84
  cacheTTL: 5 * 60 * 1000, // 5 minutes cache
85
85
  });
@@ -93,7 +93,7 @@ export function OxyServicesUserMixin(Base) {
93
93
  */
94
94
  async getCurrentUser() {
95
95
  return this.withAuthRetry(async () => {
96
- return await this.makeRequest('GET', '/api/users/me', undefined, {
96
+ return await this.makeRequest('GET', '/users/me', undefined, {
97
97
  cache: true,
98
98
  cacheTTL: 1 * 60 * 1000, // 1 minute cache for current user
99
99
  });
@@ -105,7 +105,7 @@ export function OxyServicesUserMixin(Base) {
105
105
  */
106
106
  async updateProfile(updates) {
107
107
  try {
108
- return await this.makeRequest('PUT', '/api/users/me', updates, { cache: false });
108
+ return await this.makeRequest('PUT', '/users/me', updates, { cache: false });
109
109
  }
110
110
  catch (error) {
111
111
  const errorAny = error;
@@ -131,7 +131,7 @@ export function OxyServicesUserMixin(Base) {
131
131
  async getPrivacySettings(userId) {
132
132
  try {
133
133
  const id = userId || (await this.getCurrentUser()).id;
134
- return await this.makeRequest('GET', `/api/privacy/${id}/privacy`, undefined, {
134
+ return await this.makeRequest('GET', `/privacy/${id}/privacy`, undefined, {
135
135
  cache: true,
136
136
  cacheTTL: 2 * 60 * 1000, // 2 minutes cache
137
137
  });
@@ -148,7 +148,7 @@ export function OxyServicesUserMixin(Base) {
148
148
  async updatePrivacySettings(settings, userId) {
149
149
  try {
150
150
  const id = userId || (await this.getCurrentUser()).id;
151
- return await this.makeRequest('PATCH', `/api/privacy/${id}/privacy`, settings, {
151
+ return await this.makeRequest('PATCH', `/privacy/${id}/privacy`, settings, {
152
152
  cache: false,
153
153
  });
154
154
  }
@@ -161,7 +161,7 @@ export function OxyServicesUserMixin(Base) {
161
161
  */
162
162
  async requestAccountVerification(reason, evidence) {
163
163
  try {
164
- return await this.makeRequest('POST', '/api/users/verify/request', {
164
+ return await this.makeRequest('POST', '/users/verify/request', {
165
165
  reason,
166
166
  evidence,
167
167
  }, { cache: false });
@@ -178,7 +178,7 @@ export function OxyServicesUserMixin(Base) {
178
178
  // Use httpService for blob responses (it handles blob responses automatically)
179
179
  const result = await this.getClient().request({
180
180
  method: 'GET',
181
- url: `/api/users/me/data`,
181
+ url: `/users/me/data`,
182
182
  params: { format },
183
183
  cache: false,
184
184
  });
@@ -195,7 +195,7 @@ export function OxyServicesUserMixin(Base) {
195
195
  */
196
196
  async deleteAccount(password, confirmText) {
197
197
  try {
198
- return await this.makeRequest('DELETE', '/api/users/me', {
198
+ return await this.makeRequest('DELETE', '/users/me', {
199
199
  password,
200
200
  confirmText,
201
201
  }, { cache: false });
@@ -209,7 +209,7 @@ export function OxyServicesUserMixin(Base) {
209
209
  */
210
210
  async followUser(userId) {
211
211
  try {
212
- return await this.makeRequest('POST', `/api/users/${userId}/follow`, undefined, { cache: false });
212
+ return await this.makeRequest('POST', `/users/${userId}/follow`, undefined, { cache: false });
213
213
  }
214
214
  catch (error) {
215
215
  throw this.handleError(error);
@@ -220,7 +220,7 @@ export function OxyServicesUserMixin(Base) {
220
220
  */
221
221
  async unfollowUser(userId) {
222
222
  try {
223
- return await this.makeRequest('DELETE', `/api/users/${userId}/follow`, undefined, { cache: false });
223
+ return await this.makeRequest('DELETE', `/users/${userId}/follow`, undefined, { cache: false });
224
224
  }
225
225
  catch (error) {
226
226
  throw this.handleError(error);
@@ -231,7 +231,7 @@ export function OxyServicesUserMixin(Base) {
231
231
  */
232
232
  async getFollowStatus(userId) {
233
233
  try {
234
- return await this.makeRequest('GET', `/api/users/${userId}/follow-status`, undefined, {
234
+ return await this.makeRequest('GET', `/users/${userId}/follow-status`, undefined, {
235
235
  cache: true,
236
236
  cacheTTL: 1 * 60 * 1000, // 1 minute cache
237
237
  });
@@ -246,7 +246,7 @@ export function OxyServicesUserMixin(Base) {
246
246
  async getUserFollowers(userId, pagination) {
247
247
  try {
248
248
  const params = buildPaginationParams(pagination || {});
249
- const response = await this.makeRequest('GET', `/api/users/${userId}/followers`, params, {
249
+ const response = await this.makeRequest('GET', `/users/${userId}/followers`, params, {
250
250
  cache: true,
251
251
  cacheTTL: 2 * 60 * 1000, // 2 minutes cache
252
252
  });
@@ -266,7 +266,7 @@ export function OxyServicesUserMixin(Base) {
266
266
  async getUserFollowing(userId, pagination) {
267
267
  try {
268
268
  const params = buildPaginationParams(pagination || {});
269
- const response = await this.makeRequest('GET', `/api/users/${userId}/following`, params, {
269
+ const response = await this.makeRequest('GET', `/users/${userId}/following`, params, {
270
270
  cache: true,
271
271
  cacheTTL: 2 * 60 * 1000, // 2 minutes cache
272
272
  });
@@ -285,7 +285,7 @@ export function OxyServicesUserMixin(Base) {
285
285
  */
286
286
  async getNotifications() {
287
287
  return this.withAuthRetry(async () => {
288
- return await this.makeRequest('GET', '/api/notifications', undefined, {
288
+ return await this.makeRequest('GET', '/notifications', undefined, {
289
289
  cache: false, // Don't cache notifications - always get fresh data
290
290
  });
291
291
  }, 'getNotifications');
@@ -295,7 +295,7 @@ export function OxyServicesUserMixin(Base) {
295
295
  */
296
296
  async getUnreadCount() {
297
297
  try {
298
- const res = await this.makeRequest('GET', '/api/notifications/unread-count', undefined, {
298
+ const res = await this.makeRequest('GET', '/notifications/unread-count', undefined, {
299
299
  cache: false, // Don't cache unread count - always get fresh data
300
300
  });
301
301
  return res.count;
@@ -309,7 +309,7 @@ export function OxyServicesUserMixin(Base) {
309
309
  */
310
310
  async createNotification(data) {
311
311
  try {
312
- return await this.makeRequest('POST', '/api/notifications', data, { cache: false });
312
+ return await this.makeRequest('POST', '/notifications', data, { cache: false });
313
313
  }
314
314
  catch (error) {
315
315
  throw this.handleError(error);
@@ -320,7 +320,7 @@ export function OxyServicesUserMixin(Base) {
320
320
  */
321
321
  async markNotificationAsRead(notificationId) {
322
322
  try {
323
- await this.makeRequest('PUT', `/api/notifications/${notificationId}/read`, undefined, { cache: false });
323
+ await this.makeRequest('PUT', `/notifications/${notificationId}/read`, undefined, { cache: false });
324
324
  }
325
325
  catch (error) {
326
326
  throw this.handleError(error);
@@ -331,7 +331,7 @@ export function OxyServicesUserMixin(Base) {
331
331
  */
332
332
  async markAllNotificationsAsRead() {
333
333
  try {
334
- await this.makeRequest('PUT', '/api/notifications/read-all', undefined, { cache: false });
334
+ await this.makeRequest('PUT', '/notifications/read-all', undefined, { cache: false });
335
335
  }
336
336
  catch (error) {
337
337
  throw this.handleError(error);
@@ -342,7 +342,7 @@ export function OxyServicesUserMixin(Base) {
342
342
  */
343
343
  async deleteNotification(notificationId) {
344
344
  try {
345
- await this.makeRequest('DELETE', `/api/notifications/${notificationId}`, undefined, { cache: false });
345
+ await this.makeRequest('DELETE', `/notifications/${notificationId}`, undefined, { cache: false });
346
346
  }
347
347
  catch (error) {
348
348
  throw this.handleError(error);
@@ -16,7 +16,7 @@ export function OxyServicesUtilityMixin(Base) {
16
16
  */
17
17
  async fetchLinkMetadata(url) {
18
18
  try {
19
- return await this.makeRequest('GET', '/api/link-metadata', { url }, {
19
+ return await this.makeRequest('GET', '/link-metadata', { url }, {
20
20
  cache: true,
21
21
  cacheTTL: CACHE_TIMES.EXTRA_LONG,
22
22
  });
@@ -31,25 +31,36 @@ export function OxyServicesUtilityMixin(Base) {
31
31
  * Validates JWT tokens against the Oxy API and attaches user data to requests.
32
32
  * Uses server-side session validation for security (not just JWT decode).
33
33
  *
34
+ * **Design note — jwtDecode vs jwt.verify:**
35
+ * This middleware intentionally uses `jwtDecode()` (decode-only, no signature
36
+ * verification) for user tokens. This is by design, NOT a security gap:
37
+ * - Third-party apps using `oxy.auth()` don't have the Oxy JWT secret
38
+ * - Security comes from API-based session validation (`validateSession()`)
39
+ * which checks the session server-side on every request
40
+ * - Service tokens (type: 'service') DO use cryptographic HMAC verification
41
+ * via the `jwtSecret` option, since they are stateless
42
+ * - The backend's own `authMiddleware` uses `jwt.verify()` because it has
43
+ * direct access to `ACCESS_TOKEN_SECRET`
44
+ *
34
45
  * @example
35
46
  * ```typescript
36
47
  * import { OxyServices } from '@oxyhq/core';
37
48
  *
38
49
  * const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
39
50
  *
40
- * // Protect all routes under /api/protected
41
- * app.use('/api/protected', oxy.auth());
51
+ * // Protect all routes under /protected
52
+ * app.use('/protected', oxy.auth());
42
53
  *
43
54
  * // Access user in route handler
44
- * app.get('/api/protected/me', (req, res) => {
55
+ * app.get('/protected/me', (req, res) => {
45
56
  * res.json({ userId: req.userId, user: req.user });
46
57
  * });
47
58
  *
48
59
  * // Load full user profile from API
49
- * app.use('/api/admin', oxy.auth({ loadUser: true }));
60
+ * app.use('/admin', oxy.auth({ loadUser: true }));
50
61
  *
51
62
  * // Optional auth - attach user if present, don't block if absent
52
- * app.use('/api/public', oxy.auth({ optional: true }));
63
+ * app.use('/public', oxy.auth({ optional: true }));
53
64
  * ```
54
65
  *
55
66
  * @param options Optional configuration
@@ -83,6 +94,7 @@ export function OxyServicesUtilityMixin(Base) {
83
94
  return next();
84
95
  }
85
96
  const error = {
97
+ error: 'MISSING_TOKEN',
86
98
  message: 'Access token required',
87
99
  code: 'MISSING_TOKEN',
88
100
  status: 401
@@ -103,6 +115,7 @@ export function OxyServicesUtilityMixin(Base) {
103
115
  return next();
104
116
  }
105
117
  const error = {
118
+ error: 'INVALID_TOKEN_FORMAT',
106
119
  message: 'Invalid token format',
107
120
  code: 'INVALID_TOKEN_FORMAT',
108
121
  status: 401
@@ -122,6 +135,7 @@ export function OxyServicesUtilityMixin(Base) {
122
135
  return next();
123
136
  }
124
137
  const error = {
138
+ error: 'SERVICE_TOKEN_NOT_CONFIGURED',
125
139
  message: 'Service token verification not configured',
126
140
  code: 'SERVICE_TOKEN_NOT_CONFIGURED',
127
141
  status: 403
@@ -151,13 +165,22 @@ export function OxyServicesUtilityMixin(Base) {
151
165
  throw new Error('Invalid signature');
152
166
  }
153
167
  }
154
- catch {
168
+ catch (verifyError) {
169
+ const isSignatureError = verifyError instanceof Error &&
170
+ (verifyError.message === 'Invalid signature' || verifyError.message === 'Invalid token structure');
171
+ if (!isSignatureError) {
172
+ console.error('[oxy.auth] Unexpected error during service token verification:', verifyError);
173
+ const error = { error: 'AUTH_INTERNAL_ERROR', message: 'Internal authentication error', code: 'AUTH_INTERNAL_ERROR', status: 500 };
174
+ if (onError)
175
+ return onError(error);
176
+ return res.status(500).json(error);
177
+ }
155
178
  if (optional) {
156
179
  req.userId = null;
157
180
  req.user = null;
158
181
  return next();
159
182
  }
160
- const error = { message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
183
+ const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
161
184
  if (onError)
162
185
  return onError(error);
163
186
  return res.status(401).json(error);
@@ -169,7 +192,7 @@ export function OxyServicesUtilityMixin(Base) {
169
192
  req.user = null;
170
193
  return next();
171
194
  }
172
- const error = { message: 'Service token expired', code: 'TOKEN_EXPIRED', status: 401 };
195
+ const error = { error: 'TOKEN_EXPIRED', message: 'Service token expired', code: 'TOKEN_EXPIRED', status: 401 };
173
196
  if (onError)
174
197
  return onError(error);
175
198
  return res.status(401).json(error);
@@ -181,7 +204,7 @@ export function OxyServicesUtilityMixin(Base) {
181
204
  req.user = null;
182
205
  return next();
183
206
  }
184
- const error = { message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
207
+ const error = { error: 'INVALID_SERVICE_TOKEN', message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
185
208
  if (onError)
186
209
  return onError(error);
187
210
  return res.status(401).json(error);
@@ -208,6 +231,7 @@ export function OxyServicesUtilityMixin(Base) {
208
231
  return next();
209
232
  }
210
233
  const error = {
234
+ error: 'INVALID_TOKEN_PAYLOAD',
211
235
  message: 'Token missing user ID',
212
236
  code: 'INVALID_TOKEN_PAYLOAD',
213
237
  status: 401
@@ -224,6 +248,7 @@ export function OxyServicesUtilityMixin(Base) {
224
248
  return next();
225
249
  }
226
250
  const error = {
251
+ error: 'TOKEN_EXPIRED',
227
252
  message: 'Token expired',
228
253
  code: 'TOKEN_EXPIRED',
229
254
  status: 401
@@ -246,6 +271,7 @@ export function OxyServicesUtilityMixin(Base) {
246
271
  return next();
247
272
  }
248
273
  const error = {
274
+ error: 'INVALID_SESSION',
249
275
  message: 'Session invalid or expired',
250
276
  code: 'INVALID_SESSION',
251
277
  status: 401
@@ -280,6 +306,7 @@ export function OxyServicesUtilityMixin(Base) {
280
306
  return next();
281
307
  }
282
308
  const error = {
309
+ error: 'SESSION_VALIDATION_ERROR',
283
310
  message: 'Session validation failed',
284
311
  code: 'SESSION_VALIDATION_ERROR',
285
312
  status: 401
@@ -337,6 +364,8 @@ export function OxyServicesUtilityMixin(Base) {
337
364
  * Returns a middleware function for Socket.IO that validates JWT tokens
338
365
  * from the handshake auth object and attaches user data to the socket.
339
366
  *
367
+ * Uses `jwtDecode()` + API session validation (same rationale as `auth()`).
368
+ *
340
369
  * @example
341
370
  * ```typescript
342
371
  * import { OxyServices } from '@oxyhq/core';
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Authentication helper utilities for common token validation
3
+ * and authentication error handling patterns.
4
+ */
5
+ /**
6
+ * Error thrown when session sync is required
7
+ */
8
+ export class SessionSyncRequiredError extends Error {
9
+ constructor(message = 'Session needs to be synced. Please try again.') {
10
+ super(message);
11
+ this.name = 'SessionSyncRequiredError';
12
+ }
13
+ }
14
+ /**
15
+ * Error thrown when authentication fails
16
+ */
17
+ export class AuthenticationFailedError extends Error {
18
+ constructor(message = 'Authentication failed. Please sign in again.') {
19
+ super(message);
20
+ this.name = 'AuthenticationFailedError';
21
+ }
22
+ }
23
+ /**
24
+ * Ensures a valid token exists before making authenticated API calls.
25
+ * If no valid token exists and an active session ID is available,
26
+ * attempts to refresh the token using the session.
27
+ *
28
+ * @throws {SessionSyncRequiredError} If the session needs to be synced (offline session)
29
+ */
30
+ export async function ensureValidToken(oxyServices, activeSessionId) {
31
+ if (oxyServices.hasValidToken() || !activeSessionId) {
32
+ return;
33
+ }
34
+ try {
35
+ await oxyServices.getTokenBySession(activeSessionId);
36
+ }
37
+ catch (tokenError) {
38
+ const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
39
+ if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
40
+ throw new SessionSyncRequiredError();
41
+ }
42
+ throw tokenError;
43
+ }
44
+ }
45
+ /**
46
+ * Checks if an error is an authentication error (401 or auth-related message)
47
+ */
48
+ export function isAuthenticationError(error) {
49
+ if (!error || typeof error !== 'object') {
50
+ return false;
51
+ }
52
+ const errorObj = error;
53
+ const errorMessage = errorObj.message || '';
54
+ const status = errorObj.status || errorObj.response?.status;
55
+ return (status === 401 ||
56
+ errorMessage.includes('Authentication required') ||
57
+ errorMessage.includes('Invalid or missing authorization header'));
58
+ }
59
+ /**
60
+ * Wraps an API call with authentication error handling.
61
+ * On auth failure, optionally attempts to sync the session and retry.
62
+ *
63
+ * @throws {AuthenticationFailedError} If authentication fails and cannot be recovered
64
+ */
65
+ export async function withAuthErrorHandling(apiCall, options) {
66
+ try {
67
+ return await apiCall();
68
+ }
69
+ catch (error) {
70
+ if (!isAuthenticationError(error)) {
71
+ throw error;
72
+ }
73
+ if (options?.syncSession && options?.activeSessionId && options?.oxyServices) {
74
+ try {
75
+ await options.syncSession();
76
+ await options.oxyServices.getTokenBySession(options.activeSessionId);
77
+ return await apiCall();
78
+ }
79
+ catch {
80
+ throw new AuthenticationFailedError();
81
+ }
82
+ }
83
+ throw new AuthenticationFailedError();
84
+ }
85
+ }
86
+ /**
87
+ * Combines token validation and auth error handling for a complete authenticated API call.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * return await authenticatedApiCall(
92
+ * oxyServices,
93
+ * activeSessionId,
94
+ * () => oxyServices.updateProfile(updates)
95
+ * );
96
+ * ```
97
+ */
98
+ export async function authenticatedApiCall(oxyServices, activeSessionId, apiCall, syncSession) {
99
+ await ensureValidToken(oxyServices, activeSessionId);
100
+ return withAuthErrorHandling(apiCall, {
101
+ syncSession,
102
+ activeSessionId,
103
+ oxyServices,
104
+ });
105
+ }
@@ -74,6 +74,18 @@ export function isIOS() {
74
74
  export function isAndroid() {
75
75
  return getPlatformOS() === 'android';
76
76
  }
77
+ /**
78
+ * Check if running in React Native
79
+ */
80
+ export function isReactNative() {
81
+ return typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
82
+ }
83
+ /**
84
+ * Check if running in Node.js
85
+ */
86
+ export function isNodeJS() {
87
+ return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
88
+ }
77
89
  /**
78
90
  * Set the platform OS explicitly
79
91
  * Called by React Native entry point to register the platform
@@ -30,6 +30,8 @@ interface RequestConfig extends RequestOptions {
30
30
  params?: Record<string, unknown>;
31
31
  /** @internal Used to prevent infinite auth retry loops */
32
32
  _isAuthRetry?: boolean;
33
+ /** @internal Used to prevent infinite CSRF retry loops */
34
+ _isCsrfRetry?: boolean;
33
35
  }
34
36
  /**
35
37
  * Unified HTTP Service
@@ -46,6 +48,7 @@ export declare class HttpService {
46
48
  private logger;
47
49
  private config;
48
50
  private tokenRefreshPromise;
51
+ private tokenRefreshCooldownUntil;
49
52
  private _onTokenRefreshed;
50
53
  private requestMetrics;
51
54
  constructor(config: OxyConfig);
@@ -164,6 +167,6 @@ export declare class HttpService {
164
167
  cacheMisses: number;
165
168
  averageResponseTime: number;
166
169
  };
167
- static __resetTokensForTests(): void;
170
+ __resetTokensForTests(): void;
168
171
  }
169
172
  export {};
@@ -11,7 +11,7 @@ export declare class OxyServicesBase {
11
11
  cloudURL: string;
12
12
  config: OxyConfig;
13
13
  constructor(...args: any[]);
14
- static __resetTokensForTests(): void;
14
+ __resetTokensForTests(): void;
15
15
  /**
16
16
  * Make a request with all performance optimizations
17
17
  * This is the main method for all API calls - ensures authentication and performance features
@@ -43,6 +43,8 @@ export { DEFAULT_CIRCUIT_BREAKER_CONFIG, createCircuitBreakerState, calculateBac
43
43
  export type { CircuitBreakerState, CircuitBreakerConfig } from './shared/utils/networkUtils';
44
44
  export { isDev, debugLog, debugWarn, debugError, createDebugLogger, } from './shared/utils/debugUtils';
45
45
  export { translate } from './i18n';
46
+ export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers';
47
+ export type { HandleApiErrorOptions } from './utils/authHelpers';
46
48
  export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual } from './utils/sessionUtils';
47
49
  export { packageInfo } from './constants/version';
48
50
  export * from './utils/apiUtils';
@@ -22,6 +22,7 @@ export declare function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBa
22
22
  httpService: import("../HttpService").HttpService;
23
23
  cloudURL: string;
24
24
  config: import("../OxyServices.base").OxyConfig;
25
+ __resetTokensForTests(): void;
25
26
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
26
27
  getBaseURL(): string;
27
28
  getClient(): import("../HttpService").HttpService;
@@ -64,5 +65,4 @@ export declare function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBa
64
65
  [key: string]: any;
65
66
  }>;
66
67
  };
67
- __resetTokensForTests(): void;
68
68
  } & T;
@@ -91,6 +91,7 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
91
91
  httpService: import("../HttpService").HttpService;
92
92
  cloudURL: string;
93
93
  config: import("../OxyServices.base").OxyConfig;
94
+ __resetTokensForTests(): void;
94
95
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
95
96
  getBaseURL(): string;
96
97
  getClient(): import("../HttpService").HttpService;
@@ -133,5 +134,4 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
133
134
  [key: string]: any;
134
135
  }>;
135
136
  };
136
- __resetTokensForTests(): void;
137
137
  } & T;
@@ -178,6 +178,7 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
178
178
  httpService: import("../HttpService").HttpService;
179
179
  cloudURL: string;
180
180
  config: import("../OxyServices.base").OxyConfig;
181
+ __resetTokensForTests(): void;
181
182
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
182
183
  getBaseURL(): string;
183
184
  getClient(): import("../HttpService").HttpService;
@@ -220,5 +221,4 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
220
221
  [key: string]: any;
221
222
  }>;
222
223
  };
223
- __resetTokensForTests(): void;
224
224
  } & T;
@@ -55,6 +55,7 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
55
55
  httpService: import("../HttpService").HttpService;
56
56
  cloudURL: string;
57
57
  config: import("../OxyServices.base").OxyConfig;
58
+ __resetTokensForTests(): void;
58
59
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
59
60
  getBaseURL(): string;
60
61
  getClient(): import("../HttpService").HttpService;
@@ -97,5 +98,4 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
97
98
  [key: string]: any;
98
99
  }>;
99
100
  };
100
- __resetTokensForTests(): void;
101
101
  } & T;
@@ -52,6 +52,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
52
52
  httpService: import("../HttpService").HttpService;
53
53
  cloudURL: string;
54
54
  config: import("../OxyServices.base").OxyConfig;
55
+ __resetTokensForTests(): void;
55
56
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
56
57
  getBaseURL(): string;
57
58
  getClient(): import("../HttpService").HttpService;
@@ -94,5 +95,4 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
94
95
  [key: string]: any;
95
96
  }>;
96
97
  };
97
- __resetTokensForTests(): void;
98
98
  } & T;
@@ -184,6 +184,7 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
184
184
  httpService: import("../HttpService").HttpService;
185
185
  cloudURL: string;
186
186
  config: import("../OxyServices.base").OxyConfig;
187
+ __resetTokensForTests(): void;
187
188
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
188
189
  getBaseURL(): string;
189
190
  getClient(): import("../HttpService").HttpService;
@@ -226,5 +227,4 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
226
227
  [key: string]: any;
227
228
  }>;
228
229
  };
229
- __resetTokensForTests(): void;
230
230
  } & T;
@@ -148,6 +148,7 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
148
148
  httpService: import("../HttpService").HttpService;
149
149
  cloudURL: string;
150
150
  config: import("../OxyServices.base").OxyConfig;
151
+ __resetTokensForTests(): void;
151
152
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
152
153
  getBaseURL(): string;
153
154
  getClient(): import("../HttpService").HttpService;
@@ -197,6 +198,5 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
197
198
  * Check if FedCM is supported in the current browser
198
199
  */
199
200
  isFedCMSupported(): boolean;
200
- __resetTokensForTests(): void;
201
201
  } & T;
202
202
  export { OxyServicesFedCMMixin as FedCMMixin };
@@ -41,6 +41,7 @@ export declare function OxyServicesKarmaMixin<T extends typeof OxyServicesBase>(
41
41
  httpService: import("../HttpService").HttpService;
42
42
  cloudURL: string;
43
43
  config: import("../OxyServices.base").OxyConfig;
44
+ __resetTokensForTests(): void;
44
45
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
45
46
  getBaseURL(): string;
46
47
  getClient(): import("../HttpService").HttpService;
@@ -83,5 +84,4 @@ export declare function OxyServicesKarmaMixin<T extends typeof OxyServicesBase>(
83
84
  [key: string]: any;
84
85
  }>;
85
86
  };
86
- __resetTokensForTests(): void;
87
87
  } & T;
@@ -37,6 +37,7 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
37
37
  httpService: import("../HttpService").HttpService;
38
38
  cloudURL: string;
39
39
  config: import("../OxyServices.base").OxyConfig;
40
+ __resetTokensForTests(): void;
40
41
  makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
41
42
  getBaseURL(): string;
42
43
  getClient(): import("../HttpService").HttpService;
@@ -79,5 +80,4 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
79
80
  [key: string]: any;
80
81
  }>;
81
82
  };
82
- __resetTokensForTests(): void;
83
83
  } & T;