create-request 0.0.1-beta.6 → 0.0.1-beta.8
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 +354 -98
- package/dist/library/BaseRequest.d.ts +23 -0
- package/dist/library/ResponseWrapper.d.ts +1 -0
- package/dist/library/enums.d.ts +13 -0
- package/dist/library/index.d.ts +4 -0
- package/dist/library/index.esm.js +436 -3
- package/dist/library/index.js +439 -2
- package/dist/library/types/cache.d.ts +62 -0
- package/dist/library/utils/CacheManager.d.ts +35 -0
- package/dist/library/utils/CacheStorage.d.ts +57 -0
- package/dist/library/utils/StorageProvider.d.ts +33 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,27 +8,58 @@
|
|
|
8
8
|
|
|
9
9
|
## Core Features
|
|
10
10
|
|
|
11
|
-
- ⛓️ **Chainable API** - Build and execute requests with a fluent interface
|
|
12
11
|
- ⏱️ **Timeout Support** - Set timeouts for any request
|
|
13
|
-
-
|
|
14
|
-
- 🔐 **Auth Helpers** - Simple methods for common authentication patterns
|
|
15
|
-
- 📦 **Typed Responses** - Full TypeScript support for response data
|
|
12
|
+
- 🚀 **Performance** - Tiny bundle size with zero dependencies
|
|
16
13
|
- 🚧 **Error Handling** - Detailed error info with custom error class
|
|
14
|
+
- 🔐 **Auth Helpers** - Simple methods for common authentication patterns
|
|
15
|
+
- ⛓️ **Chainable API** - Build and execute requests with a fluent interface
|
|
16
|
+
- 📉 **Reduced Boilerplate** - Write 60% less code for common API operations
|
|
17
|
+
- 🔁 **Automatic Retries** - Retry failed requests with customizable settings
|
|
18
|
+
- 🛡️ **Type Safety** - Full TypeScript support with intelligent type inference
|
|
19
|
+
- 📝 **Response Caching** - Flexible caching system with multiple storage options
|
|
17
20
|
|
|
18
21
|
## Why create-request?
|
|
19
22
|
|
|
20
|
-
**
|
|
23
|
+
**API interactions often require repetitive code patterns** - handling HTTP status checks, parsing responses, managing errors, and dealing with TypeScript types. `create-request` provides a clean, efficient solution with an elegant API that separates request building from execution:
|
|
21
24
|
|
|
22
25
|
```typescript
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// Before: Regular fetch
|
|
27
|
+
async function createUser(userData) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch('https://api.example.com/users', {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
'Authorization': 'Basic ' + btoa('username:password')
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(userData)
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
40
|
+
}
|
|
41
|
+
const data = await response.json();
|
|
42
|
+
return data;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Fetch error:', error);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// After: With create-request
|
|
50
|
+
import create from 'create-request';
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
function createUser(userData) {
|
|
53
|
+
return create.post()
|
|
54
|
+
.withBasicAuth('username', 'password')
|
|
55
|
+
.withBody(userData)
|
|
56
|
+
.sendTo('https://api.example.com/users')
|
|
57
|
+
.getData<User>()
|
|
58
|
+
.catch(error => {
|
|
59
|
+
console.error('Fetch error:', error);
|
|
60
|
+
throw error;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
32
63
|
```
|
|
33
64
|
|
|
34
65
|
## Installation
|
|
@@ -43,67 +74,6 @@ Or with yarn:
|
|
|
43
74
|
yarn add create-request
|
|
44
75
|
```
|
|
45
76
|
|
|
46
|
-
## 🔄 Fetch API Comparison
|
|
47
|
-
|
|
48
|
-
### Fetch API
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
// Basic GET request with fetch
|
|
52
|
-
try {
|
|
53
|
-
const response = await fetch('https://api.example.com/users');
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
throw new Error(`HTTP error ${response.status}`);
|
|
56
|
-
}
|
|
57
|
-
const users = await response.json();
|
|
58
|
-
console.log(users);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.error('Error fetching users:', error);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// POST request with fetch
|
|
64
|
-
try {
|
|
65
|
-
const response = await fetch('https://api.example.com/users', {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: {
|
|
68
|
-
'Content-Type': 'application/json',
|
|
69
|
-
'Authorization': 'Bearer token123'
|
|
70
|
-
},
|
|
71
|
-
body: JSON.stringify({ name: 'John Doe', email: 'john@example.com' })
|
|
72
|
-
});
|
|
73
|
-
if (!response.ok) {
|
|
74
|
-
throw new Error(`HTTP error ${response.status}`);
|
|
75
|
-
}
|
|
76
|
-
const result = await response.json();
|
|
77
|
-
console.log(result);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
console.error('Error creating user:', error);
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### create-request API
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
// Basic GET request with create-request
|
|
87
|
-
try {
|
|
88
|
-
const users = await create.get().sendTo('https://api.example.com/users').getJson();
|
|
89
|
-
console.log(users);
|
|
90
|
-
} catch (error) {
|
|
91
|
-
console.error('Error fetching users:', error.message);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// POST request with create-request
|
|
95
|
-
const createUserRequest = create.post()
|
|
96
|
-
.withBody({ name: 'John Doe', email: 'john@example.com' })
|
|
97
|
-
.withBearerToken('token123');
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const result = await createUserRequest.sendTo('https://api.example.com/users').getJson();
|
|
101
|
-
console.log(result);
|
|
102
|
-
} catch (error) {
|
|
103
|
-
console.error('Error creating user:', error.message);
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
77
|
## Basic Usage
|
|
108
78
|
|
|
109
79
|
### Creating Requests
|
|
@@ -123,30 +93,78 @@ const optionsRequest = create.options();
|
|
|
123
93
|
|
|
124
94
|
### Request Configuration
|
|
125
95
|
|
|
96
|
+
The library provides a comprehensive set of configuration methods that can be chained together to customize your requests:
|
|
97
|
+
|
|
126
98
|
```typescript
|
|
127
|
-
import { RequestPriority, CredentialsPolicy } from 'create-request';
|
|
99
|
+
import create, { RequestPriority, CredentialsPolicy, RequestMode, CachePolicy, RedirectMode, ReferrerPolicy } from 'create-request';
|
|
128
100
|
|
|
129
101
|
// Configure request options
|
|
130
102
|
const request = create.get()
|
|
131
|
-
//
|
|
132
|
-
.withHeaders({ 'X-API-Key': 'abc123' })
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.
|
|
137
|
-
|
|
138
|
-
|
|
103
|
+
// Basic headers
|
|
104
|
+
.withHeaders({ 'X-API-Key': 'abc123', 'Accept-Language': 'en-US' })
|
|
105
|
+
.withHeader('Custom-Header', 'value') // Add a single header
|
|
106
|
+
|
|
107
|
+
// Timeout settings
|
|
108
|
+
.withTimeout(5000) // Request will abort after 5 seconds
|
|
109
|
+
|
|
110
|
+
// Automatic retry configuration
|
|
111
|
+
.withRetries(3) // Retry up to 3 times on failure
|
|
112
|
+
.withRetryDelay(1000) // Wait 1 second between retries
|
|
113
|
+
.withExponentialBackoff(true) // Use exponential backoff (increases wait time between retries)
|
|
114
|
+
.onRetry(({ attempt, error, delay }) => {
|
|
115
|
+
console.log(`Attempt ${attempt} failed: ${error.message}. Retrying in ${delay}ms`);
|
|
139
116
|
})
|
|
140
117
|
|
|
141
|
-
// Authentication
|
|
142
|
-
.withBearerToken('your-token')
|
|
118
|
+
// Authentication methods
|
|
119
|
+
.withBearerToken('your-token') // Adds Authorization: Bearer your-token
|
|
120
|
+
.withBasicAuth('username', 'password') // HTTP Basic Authentication
|
|
121
|
+
|
|
122
|
+
// URL parameters
|
|
123
|
+
.withQueryParams({ search: 'term', page: 1, limit: 20 })
|
|
124
|
+
.withQueryParam('filter', 'active') // Add a single query parameter
|
|
125
|
+
|
|
126
|
+
// Request body configuration (for POST/PUT/PATCH)
|
|
127
|
+
.withContentType('application/json') // Set specific content type
|
|
128
|
+
|
|
129
|
+
// Fetch API options
|
|
130
|
+
.withCredentials(CredentialsPolicy.INCLUDE) // Includes cookies with cross-origin requests
|
|
131
|
+
.withMode(RequestMode.CORS) // Controls CORS behavior
|
|
132
|
+
.withCache(CachePolicy.NO_STORE) // Controls how request interacts with browser cache
|
|
133
|
+
.withRedirect(RedirectMode.FOLLOW) // Controls redirect behavior (follow, error, manual)
|
|
134
|
+
.withReferrer('https://example.com') // Sets request referrer
|
|
135
|
+
.withReferrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) // Controls referrer policy
|
|
136
|
+
.withPriority(RequestPriority.HIGH) // Sets request priority
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Each configuration method returns the request object, allowing for a fluent interface where methods can be chained together. You can configure only what you need for a specific request:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Simple example with just what's needed
|
|
143
|
+
const searchUsers = create.get()
|
|
144
|
+
.withBearerToken(userToken)
|
|
145
|
+
.withQueryParams({ q: searchTerm, limit: 20 })
|
|
146
|
+
.withTimeout(3000);
|
|
147
|
+
|
|
148
|
+
// Now execute the request
|
|
149
|
+
const users = await searchUsers.sendTo('https://api.example.com/users').getData();
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
You can also create reusable base requests with common settings:
|
|
143
153
|
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
```typescript
|
|
155
|
+
// Create base authenticated request
|
|
156
|
+
const apiBase = create.get()
|
|
157
|
+
.withHeaders({
|
|
158
|
+
'X-API-Version': '1.2',
|
|
159
|
+
'Accept-Language': 'en-US'
|
|
160
|
+
})
|
|
161
|
+
.withBearerToken(authToken)
|
|
162
|
+
.withTimeout(5000)
|
|
163
|
+
.withRetries(2);
|
|
146
164
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
// Use the base request for different endpoints
|
|
166
|
+
const users = await apiBase.sendTo('https://api.example.com/users').getData();
|
|
167
|
+
const products = await apiBase.sendTo('https://api.example.com/products').getData();
|
|
150
168
|
```
|
|
151
169
|
|
|
152
170
|
### Request Bodies (POST/PUT/PATCH)
|
|
@@ -181,6 +199,58 @@ const textData = await request.sendTo('https://api.example.com/endpoint').getTex
|
|
|
181
199
|
const blobData = await request.sendTo('https://api.example.com/endpoint').getBlob();
|
|
182
200
|
const bodyStream = await request.sendTo('https://api.example.com/endpoint').getBody();
|
|
183
201
|
const arrayBuffer = await request.sendTo('https://api.example.com/endpoint').getArrayBuffer();
|
|
202
|
+
|
|
203
|
+
// Using the data selector API to extract specific data
|
|
204
|
+
const userData = await request.sendTo('https://api.example.com/users')
|
|
205
|
+
.getData(data => data.results.users);
|
|
206
|
+
|
|
207
|
+
// Using the data selector without a selector function just returns the full JSON response
|
|
208
|
+
const fullData = await request.sendTo('https://api.example.com/data').getData();
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Data Selection
|
|
212
|
+
|
|
213
|
+
The `getData` method provides a powerful way to extract and transform specific data from API responses:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// Extract specific properties from nested structures
|
|
217
|
+
const posts = await request
|
|
218
|
+
.sendTo('https://api.example.com/feed')
|
|
219
|
+
.getData(data => data.feed.posts);
|
|
220
|
+
|
|
221
|
+
// Transform data in the selector function
|
|
222
|
+
const usernames = await request
|
|
223
|
+
.sendTo('https://api.example.com/users')
|
|
224
|
+
.getData(data => data.users.map(user => user.username));
|
|
225
|
+
|
|
226
|
+
// Apply filtering in the selector
|
|
227
|
+
const activeUsers = await request
|
|
228
|
+
.sendTo('https://api.example.com/users')
|
|
229
|
+
.getData(data => data.users.filter(user => user.isActive));
|
|
230
|
+
|
|
231
|
+
// Combine data from complex nested structures
|
|
232
|
+
const combinedData = await request
|
|
233
|
+
.sendTo('https://api.example.com/dashboard')
|
|
234
|
+
.getData(data => ({
|
|
235
|
+
userCount: data.stats.users.total,
|
|
236
|
+
recentPosts: data.content.recent.slice(0, 5),
|
|
237
|
+
notifications: data.user.notifications.unread
|
|
238
|
+
}));
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
When a selector fails, the error message will contain the original response data to help diagnose the issue:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
try {
|
|
245
|
+
const result = await request
|
|
246
|
+
.sendTo('https://api.example.com/data')
|
|
247
|
+
.getData(data => data.results.items); // Will fail if structure is different
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(error);
|
|
250
|
+
// Error message includes the original response data:
|
|
251
|
+
// "Data selector failed: Cannot read properties of undefined (reading 'items').
|
|
252
|
+
// Original data: { "status": "error", "message": "Invalid request" }"
|
|
253
|
+
}
|
|
184
254
|
```
|
|
185
255
|
|
|
186
256
|
### Error Handling
|
|
@@ -206,6 +276,39 @@ try {
|
|
|
206
276
|
}
|
|
207
277
|
```
|
|
208
278
|
|
|
279
|
+
### Caching Requests
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import create, { createMemoryStorage, createLocalStorageStorage, createSessionStorageStorage } from 'create-request';
|
|
283
|
+
|
|
284
|
+
// Simple in-memory caching
|
|
285
|
+
const request = create.get()
|
|
286
|
+
.withCache({
|
|
287
|
+
storage: createMemoryStorage(),
|
|
288
|
+
ttl: 60000 // 1 minute in milliseconds
|
|
289
|
+
})
|
|
290
|
+
.sendTo('https://api.example.com/data');
|
|
291
|
+
|
|
292
|
+
// Using localStorage storage
|
|
293
|
+
const request = create.get()
|
|
294
|
+
.withCache({
|
|
295
|
+
storage: createLocalStorageStorage(),
|
|
296
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
297
|
+
maxSize: '1MB',
|
|
298
|
+
keyPrefix: 'user-data'
|
|
299
|
+
})
|
|
300
|
+
.sendTo('https://api.example.com/users');
|
|
301
|
+
|
|
302
|
+
// Using sessionStorage for per-session caching
|
|
303
|
+
const request = create.get()
|
|
304
|
+
.withCache({
|
|
305
|
+
storage: createSessionStorageStorage(),
|
|
306
|
+
maxEntries: 50,
|
|
307
|
+
varyByHeaders: ['x-api-version']
|
|
308
|
+
})
|
|
309
|
+
.sendTo('https://api.example.com/data');
|
|
310
|
+
```
|
|
311
|
+
|
|
209
312
|
## Advanced Usage
|
|
210
313
|
|
|
211
314
|
### Request Reuse
|
|
@@ -251,12 +354,33 @@ interface User {
|
|
|
251
354
|
email: string;
|
|
252
355
|
}
|
|
253
356
|
|
|
357
|
+
interface ApiResponse {
|
|
358
|
+
users: User[];
|
|
359
|
+
pagination: {
|
|
360
|
+
total: number;
|
|
361
|
+
page: number;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
254
365
|
const userRequest = create.get();
|
|
255
|
-
const user = await userRequest.sendTo('https://api.example.com/user/123')
|
|
256
|
-
.getJson<User>();
|
|
257
366
|
|
|
258
|
-
//
|
|
259
|
-
|
|
367
|
+
// Type the full response
|
|
368
|
+
const response = await userRequest
|
|
369
|
+
.sendTo('https://api.example.com/users')
|
|
370
|
+
.getJson<ApiResponse>();
|
|
371
|
+
|
|
372
|
+
// Or use getData with type parameters
|
|
373
|
+
const users = await userRequest
|
|
374
|
+
.sendTo('https://api.example.com/users')
|
|
375
|
+
.getData<ApiResponse, User[]>(data => data.users);
|
|
376
|
+
|
|
377
|
+
// Or just get the full typed response
|
|
378
|
+
const fullData = await userRequest
|
|
379
|
+
.sendTo('https://api.example.com/users')
|
|
380
|
+
.getData<ApiResponse>();
|
|
381
|
+
|
|
382
|
+
// TypeScript knows the types
|
|
383
|
+
console.log(users[0].name);
|
|
260
384
|
```
|
|
261
385
|
|
|
262
386
|
### Promise Chaining
|
|
@@ -277,26 +401,158 @@ request.sendTo('https://api.example.com/data')
|
|
|
277
401
|
});
|
|
278
402
|
```
|
|
279
403
|
|
|
404
|
+
### Advanced Caching
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import create, { createMemoryStorage, StorageProvider } from 'create-request';
|
|
408
|
+
|
|
409
|
+
// Custom key generation
|
|
410
|
+
const request = create.get()
|
|
411
|
+
.withCache({
|
|
412
|
+
storage: createMemoryStorage(),
|
|
413
|
+
keyGenerator: (url, method, headers) => {
|
|
414
|
+
// Include user ID from authorization header in the cache key
|
|
415
|
+
// Get from your custom storage
|
|
416
|
+
return localStorage.getItem(`my-app:${key}`);
|
|
417
|
+
},
|
|
418
|
+
set(key: string, value: string): void {
|
|
419
|
+
// Set in your custom storage
|
|
420
|
+
localStorage.setItem(`my-app:${key}`, value);
|
|
421
|
+
},
|
|
422
|
+
has(key: string): boolean {
|
|
423
|
+
// Check if exists in your custom storage
|
|
424
|
+
return localStorage.getItem(`my-app:${key}`) !== null;
|
|
425
|
+
},
|
|
426
|
+
delete(key: string): void {
|
|
427
|
+
// Remove from your custom storage
|
|
428
|
+
localStorage.removeItem(`my-app:${key}`);
|
|
429
|
+
},
|
|
430
|
+
clear(): void {
|
|
431
|
+
// Clear all items from your namespace
|
|
432
|
+
Object.keys(localStorage)
|
|
433
|
+
.filter(key => key.startsWith('my-app:'))
|
|
434
|
+
.forEach(key => localStorage.removeItem(key));
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const request = create.get()
|
|
439
|
+
.withCache({ storage: customStorage })
|
|
440
|
+
.sendTo('https://api.example.com/data');
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Implementing Custom Storage Providers
|
|
444
|
+
|
|
445
|
+
You can create your own storage provider by implementing the `StorageProvider` interface:
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
import { StorageProvider } from 'create-request';
|
|
449
|
+
|
|
450
|
+
// Create a simple custom storage provider
|
|
451
|
+
export function createNamespacedStorage(namespace: string): StorageProvider {
|
|
452
|
+
return {
|
|
453
|
+
get(key: string): string | null {
|
|
454
|
+
return localStorage.getItem(`${namespace}:${key}`);
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
set(key: string, value: string): void {
|
|
458
|
+
try {
|
|
459
|
+
localStorage.setItem(`${namespace}:${key}`, value);
|
|
460
|
+
} catch (e) {
|
|
461
|
+
// Handle quota exceeded errors
|
|
462
|
+
console.warn("Storage quota exceeded, clearing cache");
|
|
463
|
+
this.clear();
|
|
464
|
+
try {
|
|
465
|
+
localStorage.setItem(`${namespace}:${key}`, value);
|
|
466
|
+
} catch (e) {
|
|
467
|
+
console.error("Failed to set cache item even after clearing cache");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
has(key: string): boolean {
|
|
473
|
+
return localStorage.getItem(`${namespace}:${key}`) !== null;
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
delete(key: string): void {
|
|
477
|
+
localStorage.removeItem(`${namespace}:${key}`);
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
clear(): void {
|
|
481
|
+
// Only clear items in our namespace
|
|
482
|
+
Object.keys(localStorage)
|
|
483
|
+
.filter(key => key.startsWith(`${namespace}:`))
|
|
484
|
+
.forEach(key => localStorage.removeItem(key));
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Usage:
|
|
490
|
+
const request = create.get()
|
|
491
|
+
.withCache({
|
|
492
|
+
storage: createNamespacedStorage('api-cache'),
|
|
493
|
+
ttl: 24 * 60 * 60 * 1000 // 24 hours
|
|
494
|
+
})
|
|
495
|
+
.sendTo('https://api.example.com/data');
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
With custom storage providers, you can integrate with any storage system, such as:
|
|
499
|
+
|
|
500
|
+
- Custom browser storage solutions
|
|
501
|
+
- Memory caches with expiration policies
|
|
502
|
+
- Session-based storage mechanisms
|
|
503
|
+
- Encrypted storage providers
|
|
504
|
+
- Third-party storage libraries
|
|
505
|
+
|
|
280
506
|
## CSRF Protection
|
|
281
507
|
|
|
508
|
+
Cross-Site Request Forgery (CSRF) is a type of security vulnerability where unauthorized commands are executed on behalf of an authenticated user. `create-request` provides built-in protection mechanisms to help prevent CSRF attacks.
|
|
509
|
+
|
|
510
|
+
### How CSRF Protection Works
|
|
511
|
+
|
|
512
|
+
The library employs multiple strategies to protect against CSRF attacks:
|
|
513
|
+
|
|
514
|
+
1. **Automatic X-Requested-With Header**: By default, all requests include the `X-Requested-With: XMLHttpRequest` header, which helps servers identify legitimate AJAX requests.
|
|
515
|
+
|
|
516
|
+
2. **CSRF Token Support**: The library can automatically include CSRF tokens in request headers, which servers can validate to ensure the request came from your application.
|
|
517
|
+
|
|
518
|
+
3. **XSRF Cookie Reading**: For frameworks that use the double-submit cookie pattern (like Laravel, Rails, or Django), the library can automatically read XSRF tokens from cookies and include them in request headers.
|
|
519
|
+
|
|
282
520
|
### Global CSRF Configuration
|
|
283
521
|
|
|
522
|
+
You can configure CSRF settings globally for all requests:
|
|
523
|
+
|
|
284
524
|
```typescript
|
|
285
525
|
// Configure CSRF settings for all requests
|
|
286
526
|
create.config.setCsrfToken('your-csrf-token');
|
|
287
|
-
create.config.setCsrfHeaderName('X-CSRF-Token');
|
|
288
|
-
create.config.setXsrfCookieName('XSRF-TOKEN');
|
|
527
|
+
create.config.setCsrfHeaderName('X-CSRF-Token'); // Default header name
|
|
528
|
+
create.config.setXsrfCookieName('XSRF-TOKEN'); // Default cookie name
|
|
529
|
+
create.config.enableAntiCsrf(true); // Enable/disable X-Requested-With header
|
|
530
|
+
create.config.enableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
|
|
289
531
|
```
|
|
290
532
|
|
|
291
533
|
### Per-Request CSRF Settings
|
|
292
534
|
|
|
535
|
+
You can also configure CSRF protection on individual requests:
|
|
536
|
+
|
|
293
537
|
```typescript
|
|
294
538
|
// Configure CSRF for a specific request
|
|
295
539
|
const request = create.post()
|
|
296
|
-
.withCsrfToken('request-specific-token')
|
|
297
|
-
.
|
|
540
|
+
.withCsrfToken('request-specific-token') // Set a specific token
|
|
541
|
+
.withAntiCsrfHeaders() // Explicitly add X-Requested-With header
|
|
542
|
+
.withoutCsrfProtection(); // Or disable all automatic CSRF protection
|
|
298
543
|
```
|
|
299
544
|
|
|
545
|
+
### Integrating with Backend Frameworks
|
|
546
|
+
|
|
547
|
+
Most modern frameworks support CSRF protection out of the box. The library works seamlessly with:
|
|
548
|
+
|
|
549
|
+
- **Laravel**: Automatically reads XSRF-TOKEN cookie and sends X-XSRF-TOKEN header
|
|
550
|
+
- **Rails**: Works with the Rails CSRF token system
|
|
551
|
+
- **Django**: Compatible with Django's CSRF middleware
|
|
552
|
+
- **Express.js + csurf**: Works with the csurf middleware token pattern
|
|
553
|
+
|
|
554
|
+
When your server sends a CSRF token in a cookie or response header, `create-request` can automatically extract and include it in subsequent requests.
|
|
555
|
+
|
|
300
556
|
## Browser Support
|
|
301
557
|
|
|
302
558
|
This library works with all browsers that support the Fetch API:
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { type HttpMethod, type RedirectMode, type RequestMode, type RequestPriority, CredentialsPolicy } from "./enums";
|
|
2
2
|
import { type ResponsePromise } from "./ResponseWrapper";
|
|
3
3
|
import type { RequestOptions, RetryCallback, CookiesRecord, CookieOptions } from "./types";
|
|
4
|
+
import type { CacheOptions } from "./types/cache";
|
|
5
|
+
import { CacheManager } from "./utils/CacheManager";
|
|
4
6
|
export declare abstract class BaseRequest {
|
|
5
7
|
protected abstract method: HttpMethod;
|
|
6
8
|
protected requestOptions: RequestOptions;
|
|
7
9
|
protected abortController?: AbortController;
|
|
8
10
|
protected queryParams: URLSearchParams;
|
|
9
11
|
protected autoApplyCsrfProtection: boolean;
|
|
12
|
+
protected cacheManager?: CacheManager;
|
|
13
|
+
protected cacheEnabled: boolean;
|
|
10
14
|
constructor();
|
|
11
15
|
withHeaders(headers: Record<string, string>): this;
|
|
12
16
|
withTimeout(timeout: number): this;
|
|
@@ -30,6 +34,12 @@ export declare abstract class BaseRequest {
|
|
|
30
34
|
* @returns The instance for chaining
|
|
31
35
|
*/
|
|
32
36
|
withReferrer(referrer: string): this;
|
|
37
|
+
/**
|
|
38
|
+
* Sets the referrer policy for the request
|
|
39
|
+
* @param policy - The referrer policy to use
|
|
40
|
+
* @returns The instance for chaining
|
|
41
|
+
*/
|
|
42
|
+
withReferrerPolicy(policy: ReferrerPolicy): this;
|
|
33
43
|
/**
|
|
34
44
|
* Sets the redirect policy for the request
|
|
35
45
|
* @param redirect - Can be 'follow', 'error', or 'manual'
|
|
@@ -135,6 +145,12 @@ export declare abstract class BaseRequest {
|
|
|
135
145
|
* @returns The instance for chaining
|
|
136
146
|
*/
|
|
137
147
|
withAntiCsrfHeaders(): this;
|
|
148
|
+
/**
|
|
149
|
+
* Enables caching for the request with the specified options
|
|
150
|
+
* @param options The cache configuration options
|
|
151
|
+
* @returns The instance for chaining
|
|
152
|
+
*/
|
|
153
|
+
withCache(options?: CacheOptions): this;
|
|
138
154
|
/**
|
|
139
155
|
* Send the request to the specified URL
|
|
140
156
|
* @param url The URL to send the request to
|
|
@@ -154,4 +170,11 @@ export declare abstract class BaseRequest {
|
|
|
154
170
|
*/
|
|
155
171
|
private executeWithRetries;
|
|
156
172
|
private executeRequest;
|
|
173
|
+
/**
|
|
174
|
+
* Executes a request using cache when possible
|
|
175
|
+
* @param url The formatted URL to send the request to
|
|
176
|
+
* @param fetchOptions The fetch options to use
|
|
177
|
+
* @param cacheKey The cache key for this request
|
|
178
|
+
*/
|
|
179
|
+
private executeWithCache;
|
|
157
180
|
}
|
|
@@ -29,4 +29,5 @@ export interface ResponsePromise<T = ResponseWrapper> extends Promise<T> {
|
|
|
29
29
|
getBlob(): Promise<Blob>;
|
|
30
30
|
getArrayBuffer(): Promise<ArrayBuffer>;
|
|
31
31
|
getBody(): Promise<ReadableStream<Uint8Array> | null>;
|
|
32
|
+
getData<T = unknown, R = T>(selector?: (data: T) => R): Promise<T | R>;
|
|
32
33
|
}
|
package/dist/library/enums.d.ts
CHANGED
|
@@ -70,3 +70,16 @@ export declare enum BodyType {
|
|
|
70
70
|
STRING = "string",
|
|
71
71
|
BINARY = "binary"
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Referrer policies for fetch requests
|
|
75
|
+
*/
|
|
76
|
+
export declare enum ReferrerPolicy {
|
|
77
|
+
ORIGIN = "origin",
|
|
78
|
+
UNSAFE_URL = "unsafe-url",
|
|
79
|
+
SAME_ORIGIN = "same-origin",
|
|
80
|
+
NO_REFERRER = "no-referrer",
|
|
81
|
+
STRICT_ORIGIN = "strict-origin",
|
|
82
|
+
ORIGIN_WHEN_CROSS_ORIGIN = "origin-when-cross-origin",
|
|
83
|
+
NO_REFERRER_WHEN_DOWNGRADE = "no-referrer-when-downgrade",
|
|
84
|
+
STRICT_ORIGIN_WHEN_CROSS_ORIGIN = "strict-origin-when-cross-origin"
|
|
85
|
+
}
|
package/dist/library/index.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export { RequestError } from "./RequestError";
|
|
|
6
6
|
export { ResponseWrapper } from "./ResponseWrapper";
|
|
7
7
|
export { CookieUtils } from "./utils/CookieUtils";
|
|
8
8
|
export { GetRequest, PostRequest, PutRequest, DeleteRequest, PatchRequest, HeadRequest, OptionsRequest } from "./requestMethods";
|
|
9
|
+
export { createMemoryStorage, createLocalStorageStorage, createSessionStorageStorage } from "./utils/CacheStorage";
|
|
10
|
+
export { CacheManager } from "./utils/CacheManager";
|
|
11
|
+
export type { CacheOptions } from "./types/cache";
|
|
12
|
+
export type { StorageProvider } from "./utils/StorageProvider";
|
|
9
13
|
declare const create: {
|
|
10
14
|
readonly get: typeof get;
|
|
11
15
|
readonly post: typeof post;
|