create-request 0.0.1-beta.10
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/LICENSE +21 -0
- package/README.md +547 -0
- package/dist/library/BaseRequest.d.ts +257 -0
- package/dist/library/BodyRequest.d.ts +20 -0
- package/dist/library/RequestError.d.ts +18 -0
- package/dist/library/ResponseWrapper.d.ts +93 -0
- package/dist/library/enums.d.ts +85 -0
- package/dist/library/index.d.ts +37 -0
- package/dist/library/index.esm.js +1906 -0
- package/dist/library/index.js +1930 -0
- package/dist/library/requestFactories.d.ts +89 -0
- package/dist/library/requestMethods.d.ts +84 -0
- package/dist/library/types/cache.d.ts +62 -0
- package/dist/library/types.d.ts +30 -0
- package/dist/library/utils/CacheManager.d.ts +82 -0
- package/dist/library/utils/CacheStorage.d.ts +95 -0
- package/dist/library/utils/Config.d.ts +125 -0
- package/dist/library/utils/CookieUtils.d.ts +9 -0
- package/dist/library/utils/CsrfUtils.d.ts +24 -0
- package/dist/library/utils/StorageProvider.d.ts +33 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daniel Amenou
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# create-request
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/create-request)
|
|
4
|
+
[](https://github.com/DanielAmenou/create-request/blob/main/LICENSE)
|
|
5
|
+
[](https://bundlephobia.com/package/create-request)
|
|
6
|
+
|
|
7
|
+
`create-request` is a modern TypeScript library that transforms how you make API calls. Built as an elegant wrapper around the native Fetch API, it provides a chainable, fluent interface that dramatically reduces boilerplate while adding powerful features like automatic retries, timeout handling, and comprehensive error management.
|
|
8
|
+
|
|
9
|
+
## Core Features
|
|
10
|
+
|
|
11
|
+
- 🚀 **Performance** - Tiny bundle size with zero dependencies
|
|
12
|
+
- 🚧 **Error Handling** - Detailed error info with custom error class
|
|
13
|
+
- ⛓️ **Chainable API** - Build and execute requests with a fluent interface
|
|
14
|
+
- ⏱️ **Timeout Support** - Set timeouts for requests with automatic aborts
|
|
15
|
+
- 🛡️ **Type Safety** - Full TypeScript support with intelligent type inference
|
|
16
|
+
- 🔐 **Auth Helpers** - Simple methods for common authentication patterns
|
|
17
|
+
- 🔍 **Data Selection** - Extract and transform specific data from responses
|
|
18
|
+
- 🔁 **Automatic Retries** - Retry failed requests with customizable settings
|
|
19
|
+
- 📉 **Reduced Boilerplate** - Write 60% less code for common API operations
|
|
20
|
+
- 🔒 **CSRF Protection** - Built-in safeguards against cross-site request forgery
|
|
21
|
+
- 📝 **Response Caching** - Flexible caching system with multiple storage options
|
|
22
|
+
- 🛑 **Request Cancellation** - Abort requests on demand with AbortController integration
|
|
23
|
+
|
|
24
|
+
## Why create-request?
|
|
25
|
+
|
|
26
|
+
**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:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Before: Regular fetch
|
|
30
|
+
async function createUser(userData) {
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch('https://api.example.com/users', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
'Authorization': 'Basic ' + btoa('username:password')
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(userData)
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
43
|
+
}
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
return data;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Fetch error:', error);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// After: With create-request
|
|
53
|
+
import create from 'create-request';
|
|
54
|
+
|
|
55
|
+
function createUser(userData) {
|
|
56
|
+
return create.post()
|
|
57
|
+
.withBasicAuth('username', 'password')
|
|
58
|
+
.withBody(userData)
|
|
59
|
+
.sendTo('https://api.example.com/users')
|
|
60
|
+
.getData<User>()
|
|
61
|
+
.catch(error => {
|
|
62
|
+
console.error('Fetch error:', error);
|
|
63
|
+
throw error;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install create-request
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or with yarn:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
yarn add create-request
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Basic Usage
|
|
81
|
+
|
|
82
|
+
### Creating Requests
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import create from 'create-request';
|
|
86
|
+
|
|
87
|
+
// Create different request types
|
|
88
|
+
const getRequest = create.get();
|
|
89
|
+
const postRequest = create.post();
|
|
90
|
+
const putRequest = create.put();
|
|
91
|
+
const headRequest = create.head();
|
|
92
|
+
const deleteRequest = create.del();
|
|
93
|
+
const patchRequest = create.patch();
|
|
94
|
+
const optionsRequest = create.options();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Request Configuration
|
|
98
|
+
|
|
99
|
+
The library provides a comprehensive set of configuration methods that can be chained together to customize your requests:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import create, { RequestPriority, CredentialsPolicy, RequestMode, CacheMode, RedirectMode, ReferrerPolicy } from 'create-request';
|
|
103
|
+
|
|
104
|
+
// Configure request options
|
|
105
|
+
const request = create.get()
|
|
106
|
+
// Basic headers
|
|
107
|
+
.withHeaders({ 'X-API-Key': 'abc123', 'Accept-Language': 'en-US' })
|
|
108
|
+
.withHeader('Custom-Header', 'value') // Add a single header
|
|
109
|
+
|
|
110
|
+
// Timeout settings
|
|
111
|
+
.withTimeout(5000) // Request will abort after 5 seconds
|
|
112
|
+
|
|
113
|
+
// Automatic retry configuration
|
|
114
|
+
.withRetries(3) // Retry up to 3 times on failure
|
|
115
|
+
.onRetry(({ attempt, error }) => {
|
|
116
|
+
console.log(`Attempt ${attempt} failed: ${error.message}. Retrying...`);
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Authentication methods
|
|
120
|
+
.withBearerToken('your-token') // Adds Authorization: Bearer your-token
|
|
121
|
+
.withBasicAuth('username', 'password') // HTTP Basic Authentication
|
|
122
|
+
|
|
123
|
+
// URL parameters
|
|
124
|
+
.withQueryParams({ search: 'term', page: 1, limit: 20 })
|
|
125
|
+
.withQueryParam('filter', 'active') // Add a single query parameter
|
|
126
|
+
|
|
127
|
+
// Request body configuration (for POST/PUT/PATCH)
|
|
128
|
+
.withContentType('application/json') // Set specific content type
|
|
129
|
+
|
|
130
|
+
// Fetch API options
|
|
131
|
+
.withCredentials(CredentialsPolicy.INCLUDE) // Includes cookies with cross-origin requests
|
|
132
|
+
.withMode(RequestMode.CORS) // Controls CORS behavior
|
|
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:
|
|
153
|
+
|
|
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);
|
|
164
|
+
|
|
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();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Request Bodies (POST/PUT/PATCH)
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// JSON body
|
|
174
|
+
const jsonRequest = create.post()
|
|
175
|
+
.withBody({ name: 'John', age: 30 });
|
|
176
|
+
|
|
177
|
+
// String body
|
|
178
|
+
const textRequest = create.post()
|
|
179
|
+
.withBody('Plain text content');
|
|
180
|
+
|
|
181
|
+
// Form data
|
|
182
|
+
const formData = new FormData();
|
|
183
|
+
formData.append('name', 'John');
|
|
184
|
+
formData.append('file', fileBlob);
|
|
185
|
+
|
|
186
|
+
const formRequest = create.post()
|
|
187
|
+
.withBody(formData);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Executing Requests
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Simple execution
|
|
194
|
+
const response = await request.sendTo('https://api.example.com/endpoint');
|
|
195
|
+
|
|
196
|
+
// With direct data extraction
|
|
197
|
+
const jsonData = await request.sendTo('https://api.example.com/endpoint').getJson();
|
|
198
|
+
const textData = await request.sendTo('https://api.example.com/endpoint').getText();
|
|
199
|
+
const blobData = await request.sendTo('https://api.example.com/endpoint').getBlob();
|
|
200
|
+
const bodyStream = await request.sendTo('https://api.example.com/endpoint').getBody();
|
|
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
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Error Handling
|
|
255
|
+
|
|
256
|
+
All errors from requests are instances of `RequestError` with detailed information:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
try {
|
|
260
|
+
const data = await request.sendTo('https://api.example.com/data').getJson();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
// error will always be a RequestError
|
|
263
|
+
console.log(error.message); // Error message
|
|
264
|
+
console.log(error.status); // HTTP status code (if available)
|
|
265
|
+
console.log(error.url); // Request URL
|
|
266
|
+
console.log(error.method); // HTTP method
|
|
267
|
+
console.log(error.timeoutError); // Whether it was a timeout
|
|
268
|
+
|
|
269
|
+
// Access the original response if available
|
|
270
|
+
if (error.response) {
|
|
271
|
+
// Raw Response object is available
|
|
272
|
+
console.log(error.response.status);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Caching Requests
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import create, { createMemoryStorage, createLocalStorageStorage, createSessionStorageStorage } from 'create-request';
|
|
281
|
+
|
|
282
|
+
// Simple in-memory caching
|
|
283
|
+
const request = create.get()
|
|
284
|
+
.withCache({
|
|
285
|
+
storage: createMemoryStorage(),
|
|
286
|
+
ttl: 60000 // 1 minute in milliseconds
|
|
287
|
+
})
|
|
288
|
+
.sendTo('https://api.example.com/data');
|
|
289
|
+
|
|
290
|
+
// Using localStorage storage
|
|
291
|
+
const request = create.get()
|
|
292
|
+
.withCache({
|
|
293
|
+
storage: createLocalStorageStorage(),
|
|
294
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
295
|
+
maxSize: '1MB',
|
|
296
|
+
keyPrefix: 'user-data'
|
|
297
|
+
})
|
|
298
|
+
.sendTo('https://api.example.com/users');
|
|
299
|
+
|
|
300
|
+
// Using sessionStorage for per-session caching
|
|
301
|
+
const request = create.get()
|
|
302
|
+
.withCache({
|
|
303
|
+
storage: createSessionStorageStorage(),
|
|
304
|
+
maxEntries: 50,
|
|
305
|
+
varyByHeaders: ['x-api-version']
|
|
306
|
+
})
|
|
307
|
+
.sendTo('https://api.example.com/data');
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Advanced Usage
|
|
311
|
+
|
|
312
|
+
### Request Reuse
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// Create a base authenticated request
|
|
316
|
+
const authRequest = create.get()
|
|
317
|
+
.withBearerToken('token123')
|
|
318
|
+
.withTimeout(5000)
|
|
319
|
+
.withRetries(2);
|
|
320
|
+
|
|
321
|
+
// Reuse for different endpoints
|
|
322
|
+
const users = await authRequest.sendTo('https://api.example.com/users').getJson();
|
|
323
|
+
const products = await authRequest.sendTo('https://api.example.com/products').getJson();
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Request Cancellation
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const controller = new AbortController();
|
|
330
|
+
|
|
331
|
+
const request = create.get()
|
|
332
|
+
.withTimeout(10000)
|
|
333
|
+
.withAbortController(controller);
|
|
334
|
+
|
|
335
|
+
// Later, cancel the request if needed
|
|
336
|
+
setTimeout(() => controller.abort(), 2000);
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const data = await request.sendTo('https://api.example.com/slow-endpoint').getJson();
|
|
340
|
+
} catch (error) {
|
|
341
|
+
// Check if request was cancelled
|
|
342
|
+
console.log('Request was cancelled:', error.name === 'AbortError');
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Working with Typed Responses
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
interface User {
|
|
350
|
+
id: number;
|
|
351
|
+
name: string;
|
|
352
|
+
email: string;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
interface ApiResponse {
|
|
356
|
+
users: User[];
|
|
357
|
+
pagination: {
|
|
358
|
+
total: number;
|
|
359
|
+
page: number;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const userRequest = create.get();
|
|
364
|
+
|
|
365
|
+
// Type the full response
|
|
366
|
+
const response = await userRequest
|
|
367
|
+
.sendTo('https://api.example.com/users')
|
|
368
|
+
.getJson<ApiResponse>();
|
|
369
|
+
|
|
370
|
+
// Or use getData with type parameters
|
|
371
|
+
const users = await userRequest
|
|
372
|
+
.sendTo('https://api.example.com/users')
|
|
373
|
+
.getData<ApiResponse, User[]>(data => data.users);
|
|
374
|
+
|
|
375
|
+
// Or just get the full typed response
|
|
376
|
+
const fullData = await userRequest
|
|
377
|
+
.sendTo('https://api.example.com/users')
|
|
378
|
+
.getData<ApiResponse>();
|
|
379
|
+
|
|
380
|
+
// TypeScript knows the types
|
|
381
|
+
console.log(users[0].name);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Promise Chaining
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
const request = create.get();
|
|
388
|
+
|
|
389
|
+
request.sendTo('https://api.example.com/data')
|
|
390
|
+
.then(response => {
|
|
391
|
+
console.log('Status:', response.status);
|
|
392
|
+
return response.getJson();
|
|
393
|
+
})
|
|
394
|
+
.then(data => {
|
|
395
|
+
console.log('Data:', data);
|
|
396
|
+
})
|
|
397
|
+
.catch(error => {
|
|
398
|
+
console.error('Error:', error.message);
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Advanced Caching
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import create, { createMemoryStorage, StorageProvider } from 'create-request';
|
|
406
|
+
|
|
407
|
+
// Custom key generation
|
|
408
|
+
const request = create.get()
|
|
409
|
+
.withCache({
|
|
410
|
+
storage: createMemoryStorage(),
|
|
411
|
+
keyGenerator: (url, method, headers) => {
|
|
412
|
+
// Include user ID from authorization header in the cache key
|
|
413
|
+
const authHeader = headers?.['Authorization'] || '';
|
|
414
|
+
const userMatch = authHeader.match(/User-(\d+)/);
|
|
415
|
+
const userId = userMatch ? userMatch[1] : 'anonymous';
|
|
416
|
+
return `${method}:${userId}:${url}`;
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
.sendTo('https://api.example.com/data');
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Implementing Custom Storage Providers
|
|
423
|
+
|
|
424
|
+
You can create your own storage provider by implementing the `StorageProvider` interface:
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
import { StorageProvider } from 'create-request';
|
|
428
|
+
|
|
429
|
+
// Create a simple custom storage provider
|
|
430
|
+
export function createNamespacedStorage(namespace: string): StorageProvider {
|
|
431
|
+
return {
|
|
432
|
+
get(key: string): string | null {
|
|
433
|
+
return localStorage.getItem(`${namespace}:${key}`);
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
set(key: string, value: string): void {
|
|
437
|
+
try {
|
|
438
|
+
localStorage.setItem(`${namespace}:${key}`, value);
|
|
439
|
+
} catch (e) {
|
|
440
|
+
// Handle quota exceeded errors
|
|
441
|
+
console.warn("Storage quota exceeded, clearing cache");
|
|
442
|
+
this.clear();
|
|
443
|
+
try {
|
|
444
|
+
localStorage.setItem(`${namespace}:${key}`, value);
|
|
445
|
+
} catch (e) {
|
|
446
|
+
console.error("Failed to set cache item even after clearing cache");
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
has(key: string): boolean {
|
|
452
|
+
return localStorage.getItem(`${namespace}:${key}`) !== null;
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
delete(key: string): void {
|
|
456
|
+
localStorage.removeItem(`${namespace}:${key}`);
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
clear(): void {
|
|
460
|
+
// Only clear items in our namespace
|
|
461
|
+
Object.keys(localStorage)
|
|
462
|
+
.filter(key => key.startsWith(`${namespace}:`))
|
|
463
|
+
.forEach(key => localStorage.removeItem(key));
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Usage:
|
|
469
|
+
const request = create.get()
|
|
470
|
+
.withCache({
|
|
471
|
+
storage: createNamespacedStorage('api-cache'),
|
|
472
|
+
ttl: 24 * 60 * 60 * 1000 // 24 hours
|
|
473
|
+
})
|
|
474
|
+
.sendTo('https://api.example.com/data');
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
With custom storage providers, you can integrate with any storage system, such as:
|
|
478
|
+
|
|
479
|
+
- Custom browser storage solutions
|
|
480
|
+
- Memory caches with expiration policies
|
|
481
|
+
- Session-based storage mechanisms
|
|
482
|
+
- Encrypted storage providers
|
|
483
|
+
- Third-party storage libraries
|
|
484
|
+
|
|
485
|
+
## CSRF Protection
|
|
486
|
+
|
|
487
|
+
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.
|
|
488
|
+
|
|
489
|
+
### How CSRF Protection Works
|
|
490
|
+
|
|
491
|
+
The library employs multiple strategies to protect against CSRF attacks:
|
|
492
|
+
|
|
493
|
+
1. **Automatic X-Requested-With Header**: By default, all requests include the `X-Requested-With: XMLHttpRequest` header, which helps servers identify legitimate AJAX requests.
|
|
494
|
+
|
|
495
|
+
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.
|
|
496
|
+
|
|
497
|
+
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.
|
|
498
|
+
|
|
499
|
+
### Global CSRF Configuration
|
|
500
|
+
|
|
501
|
+
You can configure CSRF settings globally for all requests:
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// Configure CSRF settings for all requests
|
|
505
|
+
create.config.setCsrfToken('your-csrf-token');
|
|
506
|
+
create.config.setCsrfHeaderName('X-CSRF-Token'); // Default header name
|
|
507
|
+
create.config.setXsrfCookieName('XSRF-TOKEN'); // Default cookie name
|
|
508
|
+
create.config.setEnableAntiCsrf(true); // Enable/disable X-Requested-With header
|
|
509
|
+
create.config.setEnableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Per-Request CSRF Settings
|
|
513
|
+
|
|
514
|
+
You can also configure CSRF protection on individual requests:
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
// Configure CSRF for a specific request
|
|
518
|
+
const request = create.post()
|
|
519
|
+
.withCsrfToken('request-specific-token') // Set a specific token
|
|
520
|
+
.withAntiCsrfHeaders() // Explicitly add X-Requested-With header
|
|
521
|
+
.withoutCsrfProtection(); // Or disable all automatic CSRF protection
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Integrating with Backend Frameworks
|
|
525
|
+
|
|
526
|
+
Most modern frameworks support CSRF protection out of the box. The library works seamlessly with:
|
|
527
|
+
|
|
528
|
+
- **Laravel**: Automatically reads XSRF-TOKEN cookie and sends X-XSRF-TOKEN header
|
|
529
|
+
- **Rails**: Works with the Rails CSRF token system
|
|
530
|
+
- **Django**: Compatible with Django's CSRF middleware
|
|
531
|
+
- **Express.js + csurf**: Works with the csurf middleware token pattern
|
|
532
|
+
|
|
533
|
+
When your server sends a CSRF token in a cookie or response header, `create-request` can automatically extract and include it in subsequent requests.
|
|
534
|
+
|
|
535
|
+
## Browser Support
|
|
536
|
+
|
|
537
|
+
This library works with all browsers that support the Fetch API:
|
|
538
|
+
|
|
539
|
+
- Chrome 42+
|
|
540
|
+
- Firefox 39+
|
|
541
|
+
- Safari 10.1+
|
|
542
|
+
- Edge 14+
|
|
543
|
+
- Opera 29+
|
|
544
|
+
|
|
545
|
+
## License
|
|
546
|
+
|
|
547
|
+
MIT
|