@ioka-technologies/asyncapi-ts-client-template 0.0.7 → 0.0.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/README.md +468 -122
- package/examples/auth-retry/README.md +384 -0
- package/examples/auth-retry/asyncapi.yaml +464 -0
- package/examples/auth-retry/login-flow-example.ts +375 -0
- package/package.json +3 -2
- package/template/helpers/security.js +137 -0
- package/template/src/client.ts.js +24 -2
- package/template/src/models.ts.js +157 -17
- package/template/src/runtime/auth/headers.ts.js +95 -0
- package/template/src/runtime/auth/index.ts.js +15 -0
- package/template/src/runtime/auth/types.ts.js +74 -0
- package/template/src/runtime/retry/index.ts.js +18 -0
- package/template/src/runtime/retry/manager.ts.js +150 -0
- package/template/src/runtime/retry/presets.ts.js +81 -0
- package/template/src/runtime/retry/types.ts.js +60 -0
- package/template/src/runtime/transports/http.ts.js +112 -17
- package/template/src/runtime/transports/websocket.ts.js +28 -3
- package/template/src/runtime/types.ts.js +12 -3
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# TypeScript Client Auth and Retry Example
|
|
2
|
+
|
|
3
|
+
This example demonstrates how to use the authentication and retry features in the TypeScript AsyncAPI client.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Authentication Support**: JWT, Basic Auth, and API Key authentication
|
|
8
|
+
- **Retry Logic**: Exponential backoff with configurable presets
|
|
9
|
+
- **Type Safety**: Full TypeScript support with proper error handling
|
|
10
|
+
- **Clean API**: Same method signatures regardless of auth requirements
|
|
11
|
+
|
|
12
|
+
## Basic Usage
|
|
13
|
+
|
|
14
|
+
### Simple Client Setup
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { UserServiceClient } from './generated-client';
|
|
18
|
+
|
|
19
|
+
// Basic client without auth or retry
|
|
20
|
+
const client = new UserServiceClient({
|
|
21
|
+
type: 'http',
|
|
22
|
+
url: 'https://api.example.com'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await client.connect();
|
|
26
|
+
const user = await client.userSignup({ email: 'user@example.com', password: 'secret' });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Client with JWT Authentication
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
const client = new UserServiceClient({
|
|
33
|
+
type: 'http',
|
|
34
|
+
url: 'https://api.example.com',
|
|
35
|
+
auth: {
|
|
36
|
+
jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Auth headers are automatically added to all requests
|
|
41
|
+
const user = await client.userSignup(signupData);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Client with Basic Authentication
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const client = new UserServiceClient({
|
|
48
|
+
type: 'http',
|
|
49
|
+
url: 'https://api.example.com',
|
|
50
|
+
auth: {
|
|
51
|
+
basic: {
|
|
52
|
+
username: 'myuser',
|
|
53
|
+
password: 'mypassword'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Client with API Key Authentication
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// API Key in header
|
|
63
|
+
const client = new UserServiceClient({
|
|
64
|
+
type: 'http',
|
|
65
|
+
url: 'https://api.example.com',
|
|
66
|
+
auth: {
|
|
67
|
+
apikey: {
|
|
68
|
+
key: 'my-api-key-123',
|
|
69
|
+
location: 'header',
|
|
70
|
+
name: 'X-API-Key'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// API Key in query parameter
|
|
76
|
+
const client2 = new UserServiceClient({
|
|
77
|
+
type: 'http',
|
|
78
|
+
url: 'https://api.example.com',
|
|
79
|
+
auth: {
|
|
80
|
+
apikey: {
|
|
81
|
+
key: 'my-api-key-123',
|
|
82
|
+
location: 'query',
|
|
83
|
+
name: 'apikey'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Retry Configuration
|
|
90
|
+
|
|
91
|
+
### Using Retry Presets
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// Conservative retry (3 attempts, longer delays)
|
|
95
|
+
const client = new UserServiceClient({
|
|
96
|
+
type: 'http',
|
|
97
|
+
url: 'https://api.example.com',
|
|
98
|
+
retry: 'conservative'
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Balanced retry (5 attempts, moderate delays)
|
|
102
|
+
const client2 = new UserServiceClient({
|
|
103
|
+
type: 'http',
|
|
104
|
+
url: 'https://api.example.com',
|
|
105
|
+
retry: 'balanced'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Aggressive retry (10 attempts, shorter delays)
|
|
109
|
+
const client3 = new UserServiceClient({
|
|
110
|
+
type: 'http',
|
|
111
|
+
url: 'https://api.example.com',
|
|
112
|
+
retry: 'aggressive'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// No retry
|
|
116
|
+
const client4 = new UserServiceClient({
|
|
117
|
+
type: 'http',
|
|
118
|
+
url: 'https://api.example.com',
|
|
119
|
+
retry: 'none'
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Custom Retry Configuration
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const client = new UserServiceClient({
|
|
127
|
+
type: 'http',
|
|
128
|
+
url: 'https://api.example.com',
|
|
129
|
+
retry: {
|
|
130
|
+
enabled: true,
|
|
131
|
+
maxAttempts: 3,
|
|
132
|
+
baseDelay: 1000, // 1 second initial delay
|
|
133
|
+
maxDelay: 30000, // 30 seconds max delay
|
|
134
|
+
backoffMultiplier: 2, // Double delay each attempt
|
|
135
|
+
jitter: true, // Add randomization
|
|
136
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
137
|
+
retryableErrors: ['NETWORK_ERROR', 'TIMEOUT', 'ECONNRESET']
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Advanced Features
|
|
143
|
+
|
|
144
|
+
### Auth and Retry Combined
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const client = new UserServiceClient({
|
|
148
|
+
type: 'http',
|
|
149
|
+
url: 'https://api.example.com',
|
|
150
|
+
auth: {
|
|
151
|
+
jwt: 'your-jwt-token'
|
|
152
|
+
},
|
|
153
|
+
retry: 'balanced',
|
|
154
|
+
authCallbacks: {
|
|
155
|
+
onAuthError: async () => {
|
|
156
|
+
// Handle 401 errors - refresh token, etc.
|
|
157
|
+
console.log('Authentication failed, attempting to refresh token...');
|
|
158
|
+
// Return true to retry the request with updated auth
|
|
159
|
+
return false;
|
|
160
|
+
},
|
|
161
|
+
onTokenRefresh: async (oldToken) => {
|
|
162
|
+
// Refresh the JWT token
|
|
163
|
+
const newToken = await refreshJwtToken(oldToken);
|
|
164
|
+
return newToken;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
retryCallbacks: {
|
|
168
|
+
onRetry: (attempt, error, delay) => {
|
|
169
|
+
console.log(`Retry attempt ${attempt} after ${delay}ms due to:`, error.message);
|
|
170
|
+
},
|
|
171
|
+
onRetryExhausted: (operation, finalError) => {
|
|
172
|
+
console.error(`All retry attempts exhausted for ${operation}:`, finalError);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Per-Request Retry Override
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Override retry config for specific requests
|
|
182
|
+
const user = await client.userSignup(signupData, {
|
|
183
|
+
retry: 'aggressive', // Use aggressive retry for this request only
|
|
184
|
+
timeout: 10000 // 10 second timeout
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Disable retry for a specific request
|
|
188
|
+
const quickResult = await client.getUserProfile(userId, {
|
|
189
|
+
retry: 'none'
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### WebSocket with Auth
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const client = new UserServiceClient({
|
|
197
|
+
type: 'websocket',
|
|
198
|
+
url: 'wss://api.example.com/ws',
|
|
199
|
+
auth: {
|
|
200
|
+
jwt: 'your-jwt-token'
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await client.connect();
|
|
205
|
+
|
|
206
|
+
// Subscribe to events (auth is handled automatically)
|
|
207
|
+
const unsubscribe = client.userNotifications((notification) => {
|
|
208
|
+
console.log('Received notification:', notification);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Clean up
|
|
212
|
+
unsubscribe();
|
|
213
|
+
await client.disconnect();
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Error Handling
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { AuthError, UnauthorizedError, RetryError, MaxRetriesExceededError } from './generated-client';
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const user = await client.userSignup(signupData);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (error instanceof UnauthorizedError) {
|
|
225
|
+
console.error('Authentication failed:', error.message);
|
|
226
|
+
// Redirect to login page
|
|
227
|
+
} else if (error instanceof MaxRetriesExceededError) {
|
|
228
|
+
console.error('Request failed after all retry attempts:', error.lastError);
|
|
229
|
+
// Show user-friendly error message
|
|
230
|
+
} else if (error instanceof AuthError) {
|
|
231
|
+
console.error('Auth configuration error:', error.message);
|
|
232
|
+
} else {
|
|
233
|
+
console.error('Unexpected error:', error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Login Flow and JWT Token Management
|
|
239
|
+
|
|
240
|
+
### Basic Login Flow
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// Step 1: Create client without auth for login
|
|
244
|
+
const client = new UserServiceClient({
|
|
245
|
+
type: 'http',
|
|
246
|
+
url: 'https://api.example.com',
|
|
247
|
+
retry: 'balanced'
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await client.connect();
|
|
251
|
+
|
|
252
|
+
// Step 2: Login (no auth required)
|
|
253
|
+
const loginResponse = await client.userLogin({
|
|
254
|
+
email: 'user@example.com',
|
|
255
|
+
password: 'mypassword'
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Step 3: Update client with JWT token
|
|
259
|
+
client.updateAuth({
|
|
260
|
+
jwt: loginResponse.token
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Step 4: Make authenticated requests
|
|
264
|
+
const userProfile = await client.getUserProfile({
|
|
265
|
+
userId: loginResponse.user.id
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Advanced Login with Token Refresh
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const client = new UserServiceClient({
|
|
273
|
+
type: 'http',
|
|
274
|
+
url: 'https://api.example.com',
|
|
275
|
+
retry: 'balanced',
|
|
276
|
+
authCallbacks: {
|
|
277
|
+
onAuthError: async () => {
|
|
278
|
+
// Handle 401 errors by refreshing token
|
|
279
|
+
if (refreshToken) {
|
|
280
|
+
try {
|
|
281
|
+
const refreshResponse = await refreshJwtToken(refreshToken);
|
|
282
|
+
client.updateAuth({ jwt: refreshResponse.token });
|
|
283
|
+
return true; // Retry the original request
|
|
284
|
+
} catch (error) {
|
|
285
|
+
// Redirect to login page
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Login and store tokens
|
|
295
|
+
const loginResponse = await client.userLogin(credentials);
|
|
296
|
+
client.updateAuth({ jwt: loginResponse.token });
|
|
297
|
+
refreshToken = loginResponse.refreshToken;
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### WebSocket Login Flow
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// For WebSocket, you may need to reconnect after auth update
|
|
304
|
+
const client = new UserServiceClient({
|
|
305
|
+
type: 'websocket',
|
|
306
|
+
url: 'wss://api.example.com/ws'
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await client.connect();
|
|
310
|
+
|
|
311
|
+
// Login
|
|
312
|
+
const loginResponse = await client.userLogin(credentials);
|
|
313
|
+
|
|
314
|
+
// Disconnect and reconnect with auth
|
|
315
|
+
await client.disconnect();
|
|
316
|
+
client.updateAuth({ jwt: loginResponse.token });
|
|
317
|
+
await client.connect();
|
|
318
|
+
|
|
319
|
+
// Now subscribe to authenticated events
|
|
320
|
+
const unsubscribe = client.userNotifications((notification) => {
|
|
321
|
+
console.log('Notification:', notification);
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### React Hook Example
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
function useAuthenticatedClient() {
|
|
329
|
+
const [client, setClient] = useState(null);
|
|
330
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
331
|
+
|
|
332
|
+
const login = useCallback(async (email, password) => {
|
|
333
|
+
const loginResponse = await client.userLogin({ email, password });
|
|
334
|
+
|
|
335
|
+
client.updateAuth({ jwt: loginResponse.token });
|
|
336
|
+
setIsAuthenticated(true);
|
|
337
|
+
|
|
338
|
+
return loginResponse;
|
|
339
|
+
}, [client]);
|
|
340
|
+
|
|
341
|
+
const logout = useCallback(async () => {
|
|
342
|
+
client.updateAuth({});
|
|
343
|
+
setIsAuthenticated(false);
|
|
344
|
+
}, [client]);
|
|
345
|
+
|
|
346
|
+
return { client, isAuthenticated, login, logout };
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Best Practices
|
|
351
|
+
|
|
352
|
+
1. **Configure Once**: Set up auth and retry at the client level rather than per-request
|
|
353
|
+
2. **Use Presets**: Start with retry presets (`balanced` is recommended) before customizing
|
|
354
|
+
3. **Handle Auth Errors**: Implement `onAuthError` callback for token refresh scenarios
|
|
355
|
+
4. **Monitor Retries**: Use retry callbacks to log and monitor retry behavior
|
|
356
|
+
5. **Graceful Degradation**: Handle `MaxRetriesExceededError` with user-friendly messages
|
|
357
|
+
6. **Security**: Never log auth credentials or tokens in production
|
|
358
|
+
7. **Login Flow**: Use `updateAuth()` to set JWT tokens after login
|
|
359
|
+
8. **WebSocket Auth**: Reconnect WebSocket connections after updating auth
|
|
360
|
+
|
|
361
|
+
## Environment-Specific Configuration
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// Development
|
|
365
|
+
const devClient = new UserServiceClient({
|
|
366
|
+
type: 'http',
|
|
367
|
+
url: 'http://localhost:3000',
|
|
368
|
+
retry: 'aggressive', // Fast feedback during development
|
|
369
|
+
auth: { jwt: 'dev-token' }
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Production
|
|
373
|
+
const prodClient = new UserServiceClient({
|
|
374
|
+
type: 'https',
|
|
375
|
+
url: 'https://api.production.com',
|
|
376
|
+
retry: 'balanced', // Balanced approach for production
|
|
377
|
+
auth: { jwt: process.env.JWT_TOKEN },
|
|
378
|
+
retryCallbacks: {
|
|
379
|
+
onRetryExhausted: (operation, error) => {
|
|
380
|
+
// Log to monitoring service
|
|
381
|
+
logger.error('Request failed after retries', { operation, error });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
});
|