@rendomnet/apiservice 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  A robust TypeScript API service framework for making authenticated API calls with advanced features:
4
4
 
5
- - ✅ Authentication handling with token management
5
+ - ✅ Multiple authentication strategies (token, API key, basic auth, custom)
6
6
  - ✅ Request caching with configurable time periods
7
7
  - ✅ Advanced retry mechanisms with exponential backoff
8
8
  - ✅ Status code-specific hooks for handling errors
9
9
  - ✅ Account state tracking
10
10
  - ✅ File upload support
11
11
  - ✅ Support for multiple accounts or a single default account
12
- - ✅ Automatic token refresh for 401 errors
12
+ - ✅ Automatic token refresh for 401 errors (if supported by provider)
13
13
 
14
14
  ## Installation
15
15
 
@@ -36,15 +36,28 @@ npm run test:watch
36
36
 
37
37
  ```typescript
38
38
  import ApiService from 'apiservice';
39
+ import { TokenAuthProvider, ApiKeyAuthProvider, BasicAuthProvider } from 'apiservice';
40
+
41
+ // Token-based (OAuth2, etc.)
42
+ const tokenProvider = new TokenAuthProvider(myTokenService);
43
+
44
+ // API key in header
45
+ const apiKeyHeaderProvider = new ApiKeyAuthProvider({ apiKey: 'my-key', headerName: 'x-api-key' });
46
+
47
+ // API key in query param
48
+ const apiKeyQueryProvider = new ApiKeyAuthProvider({ apiKey: 'my-key', queryParamName: 'api_key' });
49
+
50
+ // Basic Auth
51
+ const basicProvider = new BasicAuthProvider({ username: 'user', password: 'pass' });
39
52
 
40
53
  // Create and setup the API service
41
54
  const api = new ApiService();
42
55
  api.setup({
43
- provider: 'my-service', // 'google' | 'microsoft' and etc.
44
- tokenService: myTokenService,
56
+ provider: 'my-service',
57
+ authProvider: tokenProvider, // or apiKeyHeaderProvider, apiKeyQueryProvider, basicProvider
45
58
  hooks: {
46
59
  // You can define custom hooks here,
47
- // or use the default token refresh handler for 401 errors
60
+ // or use the default token refresh handler for 401 errors (if supported)
48
61
  },
49
62
  cacheTime: 30000, // 30 seconds
50
63
  baseUrl: 'https://api.example.com' // Set default base URL
@@ -74,64 +87,86 @@ const defaultResult = await api.call({
74
87
  });
75
88
  ```
76
89
 
77
- ## Automatic Token Refresh
78
-
79
- ApiService includes a built-in handler for 401 (Unauthorized) errors that automatically refreshes OAuth tokens. This feature:
80
-
81
- 1. Detects 401 errors from the API
82
- 2. Retrieves the current token for the account
83
- 3. Uses the `refresh` method from your tokenService to obtain a new token
84
- 4. Updates the stored token with the new one
85
- 5. Retries the original API request with the new token
90
+ ## Authentication Providers
86
91
 
87
- To use this feature:
92
+ ApiService supports multiple authentication strategies via the `AuthProvider` interface. You can use built-in providers or implement your own.
88
93
 
89
- 1. Ensure your tokenService implements the `refresh` method
90
- 2. Don't specify a custom 401 hook (the default will be used automatically)
94
+ ### TokenAuthProvider (OAuth2, Bearer Token)
91
95
 
92
96
  ```typescript
93
- // Example token service with refresh capability
97
+ import { TokenAuthProvider } from 'apiservice';
98
+
94
99
  const tokenService = {
95
100
  async get(accountId = 'default') {
96
101
  // Get token from storage
97
102
  return storedToken;
98
103
  },
99
-
100
104
  async set(token, accountId = 'default') {
101
105
  // Save token to storage
102
106
  },
103
-
104
107
  async refresh(refreshToken, accountId = 'default') {
105
108
  // Refresh the token with your OAuth provider
106
- const response = await fetch('https://api.example.com/oauth/token', {
107
- method: 'POST',
108
- headers: { 'Content-Type': 'application/json' },
109
- body: JSON.stringify({
110
- grant_type: 'refresh_token',
111
- refresh_token: refreshToken,
112
- client_id: 'your-client-id'
113
- })
114
- });
115
-
116
- if (!response.ok) {
117
- throw new Error('Failed to refresh token');
118
- }
119
-
120
- return await response.json();
109
+ // ...
110
+ return newToken;
121
111
  }
122
112
  };
113
+
114
+ const tokenProvider = new TokenAuthProvider(tokenService);
123
115
  ```
124
116
 
117
+ ### ApiKeyAuthProvider (Header or Query Param)
118
+
119
+ ```typescript
120
+ import { ApiKeyAuthProvider } from 'apiservice';
121
+
122
+ // API key in header
123
+ const apiKeyHeaderProvider = new ApiKeyAuthProvider({ apiKey: 'my-key', headerName: 'x-api-key' });
124
+
125
+ // API key in query param
126
+ const apiKeyQueryProvider = new ApiKeyAuthProvider({ apiKey: 'my-key', queryParamName: 'api_key' });
127
+ ```
128
+
129
+ ### BasicAuthProvider
130
+
131
+ ```typescript
132
+ import { BasicAuthProvider } from 'apiservice';
133
+
134
+ const basicProvider = new BasicAuthProvider({ username: 'user', password: 'pass' });
135
+ ```
136
+
137
+ ### Custom AuthProvider
138
+
139
+ You can implement your own provider by implementing the `AuthProvider` interface:
140
+
141
+ ```typescript
142
+ interface AuthProvider {
143
+ getAuthHeaders(accountId?: string): Promise<Record<string, string>>;
144
+ refresh?(refreshToken: string, accountId?: string): Promise<any>;
145
+ }
146
+ ```
147
+
148
+ ## Automatic Token Refresh
149
+
150
+ If your provider supports token refresh (like `TokenAuthProvider`), ApiService includes a built-in handler for 401 (Unauthorized) errors that automatically refreshes tokens. This feature:
151
+
152
+ 1. Detects 401 errors from the API
153
+ 2. Calls the provider's `refresh` method
154
+ 3. Retries the original API request with the new token
155
+
156
+ To use this feature:
157
+
158
+ - Use a provider that implements `refresh` (like `TokenAuthProvider`)
159
+ - Don't specify a custom 401 hook (the default will be used automatically)
160
+
125
161
  If you prefer to handle token refresh yourself, you can either:
126
162
 
127
163
  1. Provide your own handler for 401 errors which will override the default
128
164
  2. Disable the default handler by setting `hooks: { 401: null }`
129
165
 
130
166
  ```typescript
131
- // Disable default 401 handler without providing a custom one
132
167
  api.setup({
133
168
  provider: 'my-service',
134
- tokenService,
169
+ authProvider: tokenProvider,
135
170
  hooks: {
136
171
  401: null // Explicitly disable the default handler
137
172
  },
@@ -159,169 +194,44 @@ const result = await api.call({
159
194
 
160
195
  If no accountId is provided, ApiService automatically uses 'default' as the account ID.
161
196
 
162
- ## Token Service
163
-
164
- ApiService requires a `tokenService` for authentication. This service manages tokens for different accounts and handles token retrieval, storage, and refresh operations.
165
-
166
- ### TokenService Interface
197
+ ## AuthProvider Interface
167
198
 
168
199
  ```typescript
169
- interface TokenService {
170
- // Get a token for an account (accountId is optional, defaults to 'default')
171
- get: (accountId?: string) => Promise<Token>;
172
-
173
- // Save a token for an account
174
- set: (token: Partial<Token>, accountId?: string) => Promise<void>;
175
-
176
- // Optional: Refresh an expired token
177
- refresh?: (refreshToken: string, accountId?: string) => Promise<OAuthToken>;
178
- }
179
-
180
- // The Token interface
181
- interface Token {
182
- accountId: string;
183
- access_token: string;
184
- refresh_token: string;
185
- provider: string;
186
- enabled?: boolean;
187
- updatedAt?: string;
188
- primary?: boolean;
189
- }
190
-
191
- // OAuth token response
192
- interface OAuthToken {
193
- access_token: string;
194
- expires_in: number;
195
- id_token: string;
196
- refresh_token: string;
197
- scope: string;
198
- token_type: string;
200
+ interface AuthProvider {
201
+ getAuthHeaders(accountId?: string): Promise<Record<string, string>>;
202
+ refresh?(refreshToken: string, accountId?: string): Promise<any>;
199
203
  }
200
204
  ```
201
205
 
202
- ### Example Implementation
203
-
204
- Here's a simple `tokenService` implementation using localStorage:
205
-
206
- ```typescript
207
- // Simple token service implementation
208
- const tokenService = {
209
- // Get token for an account
210
- async get(accountId = 'default'): Promise<Token> {
211
- const storedToken = localStorage.getItem(`token-${accountId}`);
212
- if (!storedToken) {
213
- throw new Error(`No token found for account ${accountId}`);
214
- }
215
- return JSON.parse(storedToken);
216
- },
217
-
218
- // Save token for an account
219
- async set(token: Partial<Token>, accountId = 'default'): Promise<void> {
220
- const existingToken = localStorage.getItem(`token-${accountId}`);
221
- const currentToken = existingToken ? JSON.parse(existingToken) : { accountId };
222
- const updatedToken = { ...currentToken, ...token, updatedAt: new Date().toISOString() };
223
- localStorage.setItem(`token-${accountId}`, JSON.stringify(updatedToken));
224
- },
225
-
226
- // Refresh token implementation
227
- async refresh(refreshToken: string, accountId = 'default'): Promise<OAuthToken> {
228
- // Make a request to your OAuth token endpoint
229
- const response = await fetch('https://api.example.com/oauth/token', {
230
- method: 'POST',
231
- headers: { 'Content-Type': 'application/json' },
232
- body: JSON.stringify({
233
- grant_type: 'refresh_token',
234
- refresh_token: refreshToken,
235
- client_id: 'your-client-id'
236
- })
237
- });
238
-
239
- if (!response.ok) {
240
- throw new Error('Failed to refresh token');
241
- }
242
-
243
- const newToken = await response.json();
244
-
245
- // Update the stored token
246
- await this.set({
247
- access_token: newToken.access_token,
248
- refresh_token: newToken.refresh_token
249
- }, accountId);
250
-
251
- return newToken;
252
- }
253
- };
254
- ```
255
-
256
- ### Complete Authorization Flow Example
257
-
258
- Here's a complete example showing how to use ApiService with automatic token refresh:
206
+ ## Example: Complete Authorization Flow (TokenAuthProvider)
259
207
 
260
208
  ```typescript
261
209
  import ApiService from 'apiservice';
210
+ import { TokenAuthProvider } from 'apiservice';
262
211
 
263
- // Create token service with refresh capability
264
212
  const tokenService = {
265
- // Get token from storage
266
213
  async get(accountId = 'default') {
267
- const storedToken = localStorage.getItem(`token-${accountId}`);
268
- if (!storedToken) {
269
- throw new Error(`No token found for account ${accountId}`);
270
- }
271
- return JSON.parse(storedToken);
214
+ // ...
272
215
  },
273
-
274
- // Save token to storage
275
216
  async set(token, accountId = 'default') {
276
- const existingToken = localStorage.getItem(`token-${accountId}`);
277
- const currentToken = existingToken ? JSON.parse(existingToken) : { accountId };
278
- const updatedToken = { ...currentToken, ...token, updatedAt: new Date().toISOString() };
279
- localStorage.setItem(`token-${accountId}`, JSON.stringify(updatedToken));
217
+ // ...
280
218
  },
281
-
282
- // Refresh token with OAuth provider
283
219
  async refresh(refreshToken, accountId = 'default') {
284
- // Real implementation would call your OAuth endpoint
285
- const response = await fetch('https://api.example.com/oauth/token', {
286
- method: 'POST',
287
- headers: { 'Content-Type': 'application/json' },
288
- body: JSON.stringify({
289
- grant_type: 'refresh_token',
290
- refresh_token: refreshToken,
291
- client_id: 'your-client-id'
292
- })
293
- });
294
-
295
- if (!response.ok) {
296
- throw new Error('Failed to refresh token');
297
- }
298
-
299
- return await response.json();
220
+ // ...
300
221
  }
301
222
  };
302
223
 
303
- // Create API service instance
304
224
  const api = new ApiService();
305
-
306
- // Configure API service with automatic token refresh
307
225
  api.setup({
308
226
  provider: 'example-api',
309
- tokenService,
227
+ authProvider: new TokenAuthProvider(tokenService),
310
228
  cacheTime: 30000,
311
229
  baseUrl: 'https://api.example.com',
312
-
313
- // You can still add custom hooks for other status codes
314
- // The default 401 handler will be used automatically
315
230
  hooks: {
316
- // Handle 403 Forbidden errors - typically for insufficient permissions
317
231
  403: {
318
232
  shouldRetry: false,
319
233
  handler: async (accountId, response) => {
320
- console.warn('Permission denied:', response);
321
- // You could trigger a permissions UI here
322
- window.dispatchEvent(new CustomEvent('permission:required', {
323
- detail: { accountId, resource: response.resource }
324
- }));
234
+ // ...
325
235
  return null;
326
236
  }
327
237
  }
@@ -330,19 +240,11 @@ api.setup({
330
240
 
331
241
  // Use the API service
332
242
  async function fetchUserData(userId) {
333
- try {
334
- return await api.call({
335
- // No accountId needed - will use 'default' automatically
336
- method: 'GET',
337
- route: `/users/${userId}`,
338
- useAuth: true
339
- });
340
- } catch (error) {
341
- // 401 errors with valid refresh tokens will be automatically handled
342
- // This catch will only trigger for other errors or if refresh fails
343
- console.error('Failed to fetch user data:', error);
344
- throw error;
345
- }
243
+ return await api.call({
244
+ method: 'GET',
245
+ route: `/users/${userId}`,
246
+ useAuth: true
247
+ });
346
248
  }
347
249
  ```
348
250
 
@@ -353,38 +255,24 @@ Hooks can be configured to handle specific HTTP status codes:
353
255
  ```typescript
354
256
  const hooks = {
355
257
  401: {
356
- // Core settings
357
- shouldRetry: true, // Whether to retry the API call when this hook is triggered
358
- useRetryDelay: true, // Whether to apply delay between retries
359
- maxRetries: 3, // Maximum number of retry attempts for this status code (default: 4)
360
-
361
- // Advanced options
362
- preventConcurrentCalls: true, // Wait for an existing hook to complete before starting a new one
363
- // Useful for avoiding duplicate refresh token calls
364
-
365
- // Handler functions
258
+ shouldRetry: true,
259
+ useRetryDelay: true,
260
+ maxRetries: 3,
261
+ preventConcurrentCalls: true,
366
262
  handler: async (accountId, response) => {
367
- // Main handler function called when this status code is encountered
368
- // Return an object to update the API call parameters for the retry
263
+ // ...
369
264
  return { /* updated parameters */ };
370
265
  },
371
-
372
266
  onMaxRetriesExceeded: async (accountId, error) => {
373
- // Called when all retry attempts for this status code have failed
267
+ // ...
374
268
  },
375
-
376
269
  onHandlerError: async (accountId, error) => {
377
- // Called when the handler function throws an error
270
+ // ...
378
271
  },
379
-
380
- // Delay strategy settings
381
272
  delayStrategy: {
382
- calculate: (attempt, response) => {
383
- // Custom strategy for calculating delay between retries
384
- return 1000 * Math.pow(2, attempt - 1); // Exponential backoff
385
- }
273
+ calculate: (attempt, response) => 1000 * Math.pow(2, attempt - 1)
386
274
  },
387
- maxDelay: 30000 // Maximum delay in milliseconds between retries (default: 60000)
275
+ maxDelay: 30000
388
276
  }
389
277
  }
390
278
  ```
@@ -403,18 +291,23 @@ The codebase is built around a main `ApiService` class that coordinates several
403
291
 
404
292
  ### Multiple API Providers
405
293
 
406
- ```javascript
407
- // Configure multiple API providers
294
+ ```typescript
295
+ import { ApiService, TokenAuthProvider, ApiKeyAuthProvider } from 'apiservice';
296
+
297
+ const primaryProvider = new TokenAuthProvider(primaryTokenService);
298
+ const secondaryProvider = new ApiKeyAuthProvider({ apiKey: 'secondary-key', headerName: 'x-api-key' });
299
+
300
+ const api = new ApiService();
408
301
  api.setup({
409
302
  provider: 'primary-api',
410
- tokenService: primaryTokenService,
303
+ authProvider: primaryProvider,
411
304
  cacheTime: 30000,
412
305
  baseUrl: 'https://api.primary.com'
413
306
  });
414
307
 
415
308
  api.setup({
416
309
  provider: 'secondary-api',
417
- tokenService: secondaryTokenService,
310
+ authProvider: secondaryProvider,
418
311
  cacheTime: 60000,
419
312
  baseUrl: 'https://api.secondary.com'
420
313
  });
@@ -428,15 +321,12 @@ async function fetchCombinedData() {
428
321
  route: '/data',
429
322
  useAuth: true
430
323
  }),
431
-
432
324
  api.call({
433
325
  provider: 'secondary-api',
434
326
  method: 'GET',
435
327
  route: '/data',
436
328
  useAuth: true
437
329
  }),
438
-
439
- // Override baseUrl for a specific API call
440
330
  api.call({
441
331
  provider: 'primary-api',
442
332
  method: 'GET',
@@ -445,7 +335,6 @@ async function fetchCombinedData() {
445
335
  base: 'https://special-api.primary.com'
446
336
  })
447
337
  ]);
448
-
449
338
  return { primaryData, secondaryData };
450
339
  }
451
340
  ```
@@ -0,0 +1,8 @@
1
+ import { AuthProvider, ApiKeyAuthProviderOptions } from './types';
2
+ export declare class ApiKeyAuthProvider implements AuthProvider {
3
+ private apiKey;
4
+ private headerName?;
5
+ private queryParamName?;
6
+ constructor(options: ApiKeyAuthProviderOptions);
7
+ getAuthHeaders(): Promise<Record<string, string>>;
8
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiKeyAuthProvider = void 0;
4
+ class ApiKeyAuthProvider {
5
+ constructor(options) {
6
+ this.apiKey = options.apiKey;
7
+ this.headerName = options.headerName;
8
+ this.queryParamName = options.queryParamName;
9
+ }
10
+ async getAuthHeaders() {
11
+ if (this.headerName) {
12
+ return { [this.headerName]: this.apiKey };
13
+ }
14
+ // If using query param, return empty headers (handled elsewhere)
15
+ return {};
16
+ }
17
+ }
18
+ exports.ApiKeyAuthProvider = ApiKeyAuthProvider;
@@ -0,0 +1,7 @@
1
+ import { AuthProvider, BasicAuthProviderOptions } from './types';
2
+ export declare class BasicAuthProvider implements AuthProvider {
3
+ private username;
4
+ private password;
5
+ constructor(options: BasicAuthProviderOptions);
6
+ getAuthHeaders(): Promise<Record<string, string>>;
7
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BasicAuthProvider = void 0;
4
+ class BasicAuthProvider {
5
+ constructor(options) {
6
+ this.username = options.username;
7
+ this.password = options.password;
8
+ }
9
+ async getAuthHeaders() {
10
+ const encoded = Buffer.from(`${this.username}:${this.password}`).toString('base64');
11
+ return { Authorization: `Basic ${encoded}` };
12
+ }
13
+ }
14
+ exports.BasicAuthProvider = BasicAuthProvider;
@@ -0,0 +1,12 @@
1
+ import { AuthProvider, Token, OAuthToken } from './types';
2
+ export type TokenService = {
3
+ get: (accountId?: string) => Promise<Token>;
4
+ set: (token: Partial<Token>, accountId?: string) => Promise<void>;
5
+ refresh?: (refreshToken: string, accountId?: string) => Promise<OAuthToken>;
6
+ };
7
+ export declare class TokenAuthProvider implements AuthProvider {
8
+ private tokenService;
9
+ constructor(tokenService: TokenService);
10
+ getAuthHeaders(accountId?: string): Promise<Record<string, string>>;
11
+ refresh(refreshToken: string, accountId?: string): Promise<any>;
12
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenAuthProvider = void 0;
4
+ class TokenAuthProvider {
5
+ constructor(tokenService) {
6
+ this.tokenService = tokenService;
7
+ }
8
+ async getAuthHeaders(accountId) {
9
+ const token = await this.tokenService.get(accountId);
10
+ if (!(token === null || token === void 0 ? void 0 : token.access_token))
11
+ return {};
12
+ return { Authorization: `Bearer ${token.access_token}` };
13
+ }
14
+ async refresh(refreshToken, accountId) {
15
+ if (!this.tokenService.refresh)
16
+ throw new Error('Refresh not supported');
17
+ const newToken = await this.tokenService.refresh(refreshToken, accountId);
18
+ await this.tokenService.set({
19
+ access_token: newToken.access_token,
20
+ refresh_token: newToken.refresh_token || refreshToken,
21
+ }, accountId);
22
+ return newToken;
23
+ }
24
+ }
25
+ exports.TokenAuthProvider = TokenAuthProvider;
@@ -6,3 +6,6 @@ export { RetryManager } from './RetryManager';
6
6
  export { HookManager } from './HookManager';
7
7
  export { HttpClient } from './HttpClient';
8
8
  export { AccountManager } from './AccountManager';
9
+ export { TokenAuthProvider } from './TokenAuthProvider';
10
+ export { ApiKeyAuthProvider } from './ApiKeyAuthProvider';
11
+ export { BasicAuthProvider } from './BasicAuthProvider';
@@ -3,7 +3,7 @@
3
3
  * Barrel file exporting all ApiService components
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.AccountManager = exports.HttpClient = exports.HookManager = exports.RetryManager = exports.CacheManager = void 0;
6
+ exports.BasicAuthProvider = exports.ApiKeyAuthProvider = exports.TokenAuthProvider = exports.AccountManager = exports.HttpClient = exports.HookManager = exports.RetryManager = exports.CacheManager = void 0;
7
7
  var CacheManager_1 = require("./CacheManager");
8
8
  Object.defineProperty(exports, "CacheManager", { enumerable: true, get: function () { return CacheManager_1.CacheManager; } });
9
9
  var RetryManager_1 = require("./RetryManager");
@@ -14,3 +14,9 @@ var HttpClient_1 = require("./HttpClient");
14
14
  Object.defineProperty(exports, "HttpClient", { enumerable: true, get: function () { return HttpClient_1.HttpClient; } });
15
15
  var AccountManager_1 = require("./AccountManager");
16
16
  Object.defineProperty(exports, "AccountManager", { enumerable: true, get: function () { return AccountManager_1.AccountManager; } });
17
+ var TokenAuthProvider_1 = require("./TokenAuthProvider");
18
+ Object.defineProperty(exports, "TokenAuthProvider", { enumerable: true, get: function () { return TokenAuthProvider_1.TokenAuthProvider; } });
19
+ var ApiKeyAuthProvider_1 = require("./ApiKeyAuthProvider");
20
+ Object.defineProperty(exports, "ApiKeyAuthProvider", { enumerable: true, get: function () { return ApiKeyAuthProvider_1.ApiKeyAuthProvider; } });
21
+ var BasicAuthProvider_1 = require("./BasicAuthProvider");
22
+ Object.defineProperty(exports, "BasicAuthProvider", { enumerable: true, get: function () { return BasicAuthProvider_1.BasicAuthProvider; } });
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { TokenService, ApiCallParams, HookSettings, StatusCode } from './types';
1
+ import { AuthProvider, ApiCallParams, HookSettings, StatusCode } from './types';
2
2
  /**
3
3
  * ApiService - Core API service for making authenticated API calls
4
4
  * with caching, retry, and hook support.
5
5
  */
6
6
  declare class ApiService {
7
7
  provider: string;
8
- private tokenService;
8
+ private authProvider;
9
9
  private baseUrl;
10
10
  private cacheManager;
11
11
  private retryManager;
@@ -17,18 +17,18 @@ declare class ApiService {
17
17
  /**
18
18
  * Setup the API service
19
19
  */
20
- setup({ provider, tokenService, hooks, cacheTime, baseUrl, }: {
20
+ setup({ provider, authProvider, hooks, cacheTime, baseUrl, }: {
21
21
  provider: string;
22
- tokenService: TokenService;
22
+ authProvider: AuthProvider;
23
23
  hooks?: Record<StatusCode, HookSettings | null>;
24
24
  cacheTime: number;
25
25
  baseUrl?: string;
26
26
  }): void;
27
27
  /**
28
28
  * Create a default handler for 401 (Unauthorized) errors
29
- * that implements standard token refresh behavior
29
+ * that implements standard credential refresh behavior
30
30
  */
31
- private createDefaultTokenRefreshHandler;
31
+ private createDefaultAuthRefreshHandler;
32
32
  /**
33
33
  * Set the maximum number of retry attempts
34
34
  */
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ApiKeyAuthProvider_1 = require("./ApiKeyAuthProvider");
3
4
  const components_1 = require("./components");
4
5
  /**
5
6
  * ApiService - Core API service for making authenticated API calls
@@ -11,7 +12,7 @@ class ApiService {
11
12
  // Default max attempts for API calls
12
13
  this.maxAttempts = 10;
13
14
  this.provider = '';
14
- this.tokenService = {};
15
+ this.authProvider = {};
15
16
  // Initialize component managers
16
17
  this.cacheManager = new components_1.CacheManager();
17
18
  this.retryManager = new components_1.RetryManager();
@@ -22,17 +23,17 @@ class ApiService {
22
23
  /**
23
24
  * Setup the API service
24
25
  */
25
- setup({ provider, tokenService, hooks = {}, cacheTime, baseUrl = '', }) {
26
+ setup({ provider, authProvider, hooks = {}, cacheTime, baseUrl = '', }) {
26
27
  this.provider = provider;
27
- this.tokenService = tokenService;
28
+ this.authProvider = authProvider;
28
29
  this.baseUrl = baseUrl;
29
30
  // Create a copy of hooks to avoid modifying the input
30
31
  const finalHooks = {};
31
32
  // Apply default 401 handler if:
32
33
  // 1. No 401 hook is explicitly defined (or is explicitly null)
33
- // 2. TokenService has a refresh method
34
- if (hooks[401] === undefined && typeof this.tokenService.refresh === 'function') {
35
- finalHooks[401] = this.createDefaultTokenRefreshHandler();
34
+ // 2. AuthProvider has a refresh method
35
+ if (hooks[401] === undefined && typeof this.authProvider.refresh === 'function') {
36
+ finalHooks[401] = this.createDefaultAuthRefreshHandler();
36
37
  }
37
38
  // Add user-defined hooks (skipping null/undefined values)
38
39
  for (const [statusCode, hook] of Object.entries(hooks)) {
@@ -50,9 +51,9 @@ class ApiService {
50
51
  }
51
52
  /**
52
53
  * Create a default handler for 401 (Unauthorized) errors
53
- * that implements standard token refresh behavior
54
+ * that implements standard credential refresh behavior
54
55
  */
55
- createDefaultTokenRefreshHandler() {
56
+ createDefaultAuthRefreshHandler() {
56
57
  return {
57
58
  shouldRetry: true,
58
59
  useRetryDelay: true,
@@ -60,27 +61,17 @@ class ApiService {
60
61
  maxRetries: 1,
61
62
  handler: async (accountId) => {
62
63
  try {
63
- console.log(`🔄 Using default token refresh handler for ${accountId}`);
64
- // Get current token to extract refresh token
65
- const currentToken = await this.tokenService.get(accountId);
66
- if (!currentToken.refresh_token) {
67
- throw new Error(`No refresh token available for account ${accountId}`);
64
+ console.log(`🔄 Using default auth refresh handler for ${accountId}`);
65
+ if (!this.authProvider.refresh) {
66
+ throw new Error('No refresh method available on auth provider');
68
67
  }
69
- // Refresh the token
70
- const newToken = await this.tokenService.refresh(currentToken.refresh_token, accountId);
71
- if (!newToken || !newToken.access_token) {
72
- throw new Error('Token refresh returned invalid data');
73
- }
74
- // Update the token in storage
75
- await this.tokenService.set({
76
- access_token: newToken.access_token,
77
- refresh_token: newToken.refresh_token || currentToken.refresh_token
78
- }, accountId);
79
- // Return empty object to retry with same parameters
68
+ // You may want to store refresh token in account data or pass it in another way
69
+ // For now, assume refresh token is managed internally by the provider
70
+ await this.authProvider.refresh('', accountId);
80
71
  return {};
81
72
  }
82
73
  catch (error) {
83
- console.error(`Token refresh failed for ${accountId}:`, error);
74
+ console.error(`Auth refresh failed for ${accountId}:`, error);
84
75
  throw error;
85
76
  }
86
77
  },
@@ -112,6 +103,15 @@ class ApiService {
112
103
  accountId: apiCallParams.accountId || 'default',
113
104
  base: apiCallParams.base || this.baseUrl,
114
105
  };
106
+ // If using ApiKeyAuthProvider with queryParamName, add API key to queryParams
107
+ if (this.authProvider instanceof ApiKeyAuthProvider_1.ApiKeyAuthProvider &&
108
+ this.authProvider.queryParamName) {
109
+ const queryParamName = this.authProvider.queryParamName;
110
+ const apiKey = this.authProvider.apiKey;
111
+ const urlParams = params.queryParams ? new URLSearchParams(params.queryParams) : new URLSearchParams();
112
+ urlParams.set(queryParamName, apiKey);
113
+ params.queryParams = urlParams;
114
+ }
115
115
  console.log('🔄 API call', this.provider, params.accountId, params.method, params.route);
116
116
  // Check cache first
117
117
  const cachedData = this.cacheManager.getFromCache(params);
@@ -138,22 +138,38 @@ class ApiService {
138
138
  const { accountId } = apiCallParams;
139
139
  let attempts = 0;
140
140
  const statusRetries = {};
141
- // Copy the params to avoid mutation issues
142
141
  let currentParams = { ...apiCallParams };
142
+ // If using ApiKeyAuthProvider with queryParamName, add API key to queryParams
143
+ if (this.authProvider instanceof ApiKeyAuthProvider_1.ApiKeyAuthProvider &&
144
+ this.authProvider.queryParamName) {
145
+ const queryParamName = this.authProvider.queryParamName;
146
+ const apiKey = this.authProvider.apiKey;
147
+ const urlParams = currentParams.queryParams ? new URLSearchParams(currentParams.queryParams) : new URLSearchParams();
148
+ urlParams.set(queryParamName, apiKey);
149
+ currentParams.queryParams = urlParams;
150
+ }
143
151
  // Main retry loop
144
152
  while (attempts < this.maxAttempts) {
145
153
  attempts++;
146
154
  try {
147
- // Get authentication token if needed
148
- const authToken = apiCallParams.useAuth !== false
149
- ? await this.tokenService.get(accountId)
155
+ // Get authentication headers if needed
156
+ const authHeaders = apiCallParams.useAuth !== false
157
+ ? await this.authProvider.getAuthHeaders(accountId)
150
158
  : {};
159
+ // Merge auth headers into params.headers
160
+ currentParams.headers = {
161
+ ...(currentParams.headers || {}),
162
+ ...authHeaders,
163
+ };
151
164
  // Verify we have authentication if required
152
- if (apiCallParams.useAuth !== false && !apiCallParams.accessToken && !authToken.access_token) {
165
+ if (apiCallParams.useAuth !== false &&
166
+ Object.keys(authHeaders).length === 0 &&
167
+ !(this.authProvider instanceof ApiKeyAuthProvider_1.ApiKeyAuthProvider &&
168
+ this.authProvider.queryParamName)) {
153
169
  throw new Error(`${this.provider} credentials not found for account ID ${accountId}`);
154
170
  }
155
171
  // Make the actual API call
156
- const response = await this.httpClient.makeRequest(currentParams, authToken);
172
+ const response = await this.httpClient.makeRequest(currentParams, {});
157
173
  // Success - update account status and return result
158
174
  this.accountManager.setLastRequestFailed(accountId, false);
159
175
  return response;
package/dist/types.d.ts CHANGED
@@ -80,9 +80,28 @@ interface HookSettings {
80
80
  maxDelay?: number;
81
81
  }
82
82
  type StatusCode = string | number;
83
+ interface AuthProvider {
84
+ /**
85
+ * Returns headers or other auth data for a request
86
+ */
87
+ getAuthHeaders(accountId?: string): Promise<Record<string, string>>;
88
+ /**
89
+ * Optional: refresh credentials if supported (for OAuth, etc.)
90
+ */
91
+ refresh?(refreshToken: string, accountId?: string): Promise<any>;
92
+ }
93
+ interface ApiKeyAuthProviderOptions {
94
+ apiKey: string;
95
+ headerName?: string;
96
+ queryParamName?: string;
97
+ }
98
+ interface BasicAuthProviderOptions {
99
+ username: string;
100
+ password: string;
101
+ }
83
102
  type TokenService = {
84
103
  get: (accountId?: string) => Promise<Token>;
85
104
  set: (token: Partial<Token>, accountId?: string) => Promise<void>;
86
105
  refresh?: (refreshToken: string, accountId?: string) => Promise<OAuthToken>;
87
106
  };
88
- export { OAuthToken, DelayStrategy, Token, AccountData, ApiCallParams, HookSettings, StatusCode, TokenService, };
107
+ export { OAuthToken, DelayStrategy, Token, AccountData, ApiCallParams, HookSettings, StatusCode, AuthProvider, ApiKeyAuthProviderOptions, BasicAuthProviderOptions, TokenService, };
package/package.json CHANGED
@@ -1,22 +1,12 @@
1
1
  {
2
2
  "name": "@rendomnet/apiservice",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "A robust TypeScript API service framework for making authenticated API calls",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [
8
8
  "dist"
9
9
  ],
10
- "scripts": {
11
- "build": "tsc",
12
- "clean": "rimraf dist",
13
- "prebuild": "npm run clean",
14
- "prepublishOnly": "npm run build",
15
- "test": "jest",
16
- "test:watch": "jest --watch",
17
- "test:coverage": "jest --coverage",
18
- "prepare": "npm run build"
19
- },
20
10
  "keywords": [
21
11
  "api",
22
12
  "service",
@@ -50,5 +40,13 @@
50
40
  },
51
41
  "publishConfig": {
52
42
  "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc",
46
+ "clean": "rimraf dist",
47
+ "prebuild": "npm run clean",
48
+ "test": "jest",
49
+ "test:watch": "jest --watch",
50
+ "test:coverage": "jest --coverage"
53
51
  }
54
- }
52
+ }