@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.
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Login Flow Example - JWT Token Management
3
+ *
4
+ * This example demonstrates how to handle a typical authentication flow:
5
+ * 1. Start with no authentication (or basic auth for login)
6
+ * 2. Call userLogin operation
7
+ * 3. Extract JWT token from response
8
+ * 4. Update client auth configuration
9
+ * 5. Make authenticated requests with the JWT
10
+ */
11
+
12
+ import React, { useState, useCallback, useEffect } from 'react';
13
+
14
+ // These would be imported from your generated client
15
+ // import { UserServiceClient } from './generated-client';
16
+ // import { AuthError, UnauthorizedError, AuthCredentials } from './generated-client';
17
+
18
+ // For this example, we'll use placeholder types and classes
19
+ interface AuthCredentials {
20
+ jwt?: string;
21
+ basic?: {
22
+ username: string;
23
+ password: string;
24
+ };
25
+ apikey?: {
26
+ key: string;
27
+ location: 'header' | 'query';
28
+ name: string;
29
+ };
30
+ }
31
+
32
+ class UserServiceClient {
33
+ constructor(config: any) { }
34
+ async connect(): Promise<void> { }
35
+ async disconnect(): Promise<void> { }
36
+ async userLogin(payload: any): Promise<any> { return {}; }
37
+ async getUserProfile(payload: any): Promise<any> { return {}; }
38
+ async updateUserPreferences(payload: any): Promise<void> { }
39
+ userNotifications(callback: (notification: any) => void): () => void { return () => { }; }
40
+ updateAuth(auth: AuthCredentials): void { }
41
+ getAuth(): AuthCredentials | undefined { return {}; }
42
+ }
43
+
44
+ class AuthError extends Error {
45
+ constructor(message: string) {
46
+ super(message);
47
+ this.name = 'AuthError';
48
+ }
49
+ }
50
+
51
+ class UnauthorizedError extends Error {
52
+ constructor(message: string) {
53
+ super(message);
54
+ this.name = 'UnauthorizedError';
55
+ }
56
+ }
57
+
58
+ // Example login response type (this would be generated from your AsyncAPI spec)
59
+ interface LoginResponse {
60
+ token: string;
61
+ user: {
62
+ id: string;
63
+ email: string;
64
+ name: string;
65
+ };
66
+ expiresAt: string;
67
+ }
68
+
69
+ // Example user profile type
70
+ interface UserProfile {
71
+ id: string;
72
+ email: string;
73
+ name: string;
74
+ preferences: {
75
+ theme: string;
76
+ notifications: boolean;
77
+ };
78
+ lastLoginAt: string;
79
+ }
80
+
81
+ async function loginFlowExample() {
82
+ // Step 1: Create client without authentication (for login)
83
+ const client = new UserServiceClient({
84
+ type: 'http',
85
+ url: 'https://api.example.com',
86
+ retry: 'balanced'
87
+ // No auth configuration initially
88
+ });
89
+
90
+ await client.connect();
91
+
92
+ try {
93
+ // Step 2: Perform login (this operation doesn't require auth)
94
+ console.log('Logging in...');
95
+ const loginResponse: LoginResponse = await client.userLogin({
96
+ email: 'user@example.com',
97
+ password: 'mypassword'
98
+ });
99
+
100
+ console.log('Login successful!', loginResponse.user);
101
+
102
+ // Step 3: Update client auth configuration with JWT token
103
+ client.updateAuth({
104
+ jwt: loginResponse.token
105
+ });
106
+
107
+ console.log('Auth updated with JWT token');
108
+
109
+ // Step 4: Now make authenticated requests
110
+ // The JWT token will be automatically included in all subsequent requests
111
+ const userProfile: UserProfile = await client.getUserProfile({
112
+ userId: loginResponse.user.id
113
+ });
114
+
115
+ console.log('User profile retrieved:', userProfile);
116
+
117
+ // Step 5: Make other authenticated operations
118
+ await client.updateUserPreferences({
119
+ userId: loginResponse.user.id,
120
+ preferences: {
121
+ theme: 'dark',
122
+ notifications: true
123
+ }
124
+ });
125
+
126
+ console.log('User preferences updated');
127
+
128
+ } catch (error) {
129
+ if (error instanceof UnauthorizedError) {
130
+ console.error('Authentication failed:', error.message);
131
+ // Handle login failure - show login form again
132
+ } else if (error instanceof AuthError) {
133
+ console.error('Auth configuration error:', error.message);
134
+ } else {
135
+ console.error('Unexpected error:', error);
136
+ }
137
+ } finally {
138
+ await client.disconnect();
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Advanced Login Flow with Token Refresh
144
+ *
145
+ * This example shows how to handle token refresh scenarios
146
+ */
147
+ async function advancedLoginFlowExample() {
148
+ let currentToken: string | null = null;
149
+ let refreshToken: string | null = null;
150
+
151
+ const client = new UserServiceClient({
152
+ type: 'http',
153
+ url: 'https://api.example.com',
154
+ retry: 'balanced',
155
+ authCallbacks: {
156
+ onAuthError: async () => {
157
+ // Handle 401 errors by attempting token refresh
158
+ console.log('Auth error detected, attempting token refresh...');
159
+
160
+ if (refreshToken) {
161
+ try {
162
+ const refreshResponse = await refreshJwtToken(refreshToken);
163
+ currentToken = refreshResponse.token;
164
+ refreshToken = refreshResponse.refreshToken;
165
+
166
+ // Update the client's auth configuration
167
+ client.updateAuth({
168
+ jwt: currentToken || undefined
169
+ });
170
+
171
+ console.log('Token refreshed successfully');
172
+ return true; // Retry the original request
173
+ } catch (refreshError) {
174
+ console.error('Token refresh failed:', refreshError);
175
+ // Redirect to login page
176
+ return false;
177
+ }
178
+ }
179
+
180
+ return false; // No refresh token available
181
+ }
182
+ }
183
+ });
184
+
185
+ await client.connect();
186
+
187
+ try {
188
+ // Initial login
189
+ const loginResponse = await client.userLogin({
190
+ email: 'user@example.com',
191
+ password: 'mypassword'
192
+ });
193
+
194
+ currentToken = loginResponse.token;
195
+ refreshToken = loginResponse.refreshToken; // Assuming your API provides refresh tokens
196
+
197
+ // Set initial auth
198
+ client.updateAuth({
199
+ jwt: currentToken || undefined
200
+ });
201
+
202
+ // Make authenticated requests - token refresh will be handled automatically
203
+ const userProfile = await client.getUserProfile({
204
+ userId: loginResponse.user.id
205
+ });
206
+
207
+ console.log('User profile:', userProfile);
208
+
209
+ } catch (error) {
210
+ console.error('Login flow failed:', error);
211
+ } finally {
212
+ await client.disconnect();
213
+ }
214
+ }
215
+
216
+ /**
217
+ * WebSocket Login Flow Example
218
+ *
219
+ * For WebSocket connections, you typically need to reconnect after updating auth
220
+ */
221
+ async function websocketLoginFlowExample() {
222
+ // Step 1: Create WebSocket client without auth
223
+ const client = new UserServiceClient({
224
+ type: 'websocket',
225
+ url: 'wss://api.example.com/ws',
226
+ retry: 'balanced'
227
+ });
228
+
229
+ await client.connect();
230
+
231
+ try {
232
+ // Step 2: Login via WebSocket
233
+ const loginResponse: LoginResponse = await client.userLogin({
234
+ email: 'user@example.com',
235
+ password: 'mypassword'
236
+ });
237
+
238
+ console.log('Login successful via WebSocket');
239
+
240
+ // Step 3: Disconnect and reconnect with auth
241
+ await client.disconnect();
242
+
243
+ client.updateAuth({
244
+ jwt: loginResponse.token
245
+ });
246
+
247
+ await client.connect(); // Reconnect with auth headers
248
+
249
+ // Step 4: Subscribe to authenticated events
250
+ const unsubscribe = client.userNotifications((notification) => {
251
+ console.log('Received notification:', notification);
252
+ });
253
+
254
+ // Step 5: Make authenticated requests
255
+ const userProfile = await client.getUserProfile({
256
+ userId: loginResponse.user.id
257
+ });
258
+
259
+ console.log('User profile:', userProfile);
260
+
261
+ // Clean up
262
+ setTimeout(() => {
263
+ unsubscribe();
264
+ client.disconnect();
265
+ }, 30000);
266
+
267
+ } catch (error) {
268
+ console.error('WebSocket login flow failed:', error);
269
+ await client.disconnect();
270
+ }
271
+ }
272
+
273
+ /**
274
+ * React Hook Example for Login Flow
275
+ */
276
+ function useAuthenticatedClient() {
277
+ const [client, setClient] = useState(null as UserServiceClient | null);
278
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
279
+ const [user, setUser] = useState(null as any);
280
+
281
+ const initializeClient = useCallback(() => {
282
+ const newClient = new UserServiceClient({
283
+ type: 'http',
284
+ url: process.env.REACT_APP_API_URL || 'https://api.example.com',
285
+ retry: 'balanced',
286
+ authCallbacks: {
287
+ onAuthError: async () => {
288
+ // Handle auth errors by clearing auth state
289
+ setIsAuthenticated(false);
290
+ setUser(null);
291
+ return false;
292
+ }
293
+ }
294
+ });
295
+
296
+ newClient.connect();
297
+ setClient(newClient);
298
+ return newClient;
299
+ }, []);
300
+
301
+ const login = useCallback(async (email: string, password: string) => {
302
+ const currentClient = client || initializeClient();
303
+
304
+ try {
305
+ const loginResponse = await currentClient.userLogin({ email, password });
306
+
307
+ // Update auth configuration
308
+ currentClient.updateAuth({
309
+ jwt: loginResponse.token
310
+ });
311
+
312
+ setIsAuthenticated(true);
313
+ setUser(loginResponse.user);
314
+
315
+ return loginResponse;
316
+ } catch (error) {
317
+ console.error('Login failed:', error);
318
+ throw error;
319
+ }
320
+ }, [client, initializeClient]);
321
+
322
+ const logout = useCallback(async () => {
323
+ if (client) {
324
+ // Clear auth
325
+ client.updateAuth({});
326
+ await client.disconnect();
327
+ }
328
+
329
+ setIsAuthenticated(false);
330
+ setUser(null);
331
+ setClient(null);
332
+ }, [client]);
333
+
334
+ useEffect(() => {
335
+ return () => {
336
+ if (client) {
337
+ client.disconnect();
338
+ }
339
+ };
340
+ }, [client]);
341
+
342
+ return {
343
+ client,
344
+ isAuthenticated,
345
+ user,
346
+ login,
347
+ logout,
348
+ initializeClient
349
+ };
350
+ }
351
+
352
+ // Helper function for token refresh (implement based on your API)
353
+ async function refreshJwtToken(refreshToken: string): Promise<{ token: string; refreshToken: string }> {
354
+ const response = await fetch('/api/auth/refresh', {
355
+ method: 'POST',
356
+ headers: {
357
+ 'Content-Type': 'application/json',
358
+ },
359
+ body: JSON.stringify({ refreshToken })
360
+ });
361
+
362
+ if (!response.ok) {
363
+ throw new Error('Token refresh failed');
364
+ }
365
+
366
+ return response.json();
367
+ }
368
+
369
+ // Export examples
370
+ export {
371
+ loginFlowExample,
372
+ advancedLoginFlowExample,
373
+ websocketLoginFlowExample,
374
+ useAuthenticatedClient
375
+ };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@ioka-technologies/asyncapi-ts-client-template",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "description": "TypeScript AsyncAPI client generator template compatible with rust-asyncapi patterns",
5
5
  "main": "template/index.js",
6
6
  "scripts": {
7
7
  "test": "npm run test:generate",
8
- "test:generate": "asyncapi generate fromTemplate examples/simple/asyncapi.yaml . -o test-output-simple --force-write -p enableAuth=true",
8
+ "test:generate": "asyncapi generate fromTemplate examples/auth-retry/asyncapi.yaml . -o test-output-auth-retry --force-write -p enableAuth=true && cd test-output-auth-retry && npm install && npm run build",
9
+ "test:realworld": "asyncapi generate fromTemplate ../examples/realworld/asyncapi.yaml . -o test-output-realworld --force-write -p enableAuth=true",
9
10
  "clean": "rm -rf test-output-*"
10
11
  },
11
12
  "keywords": [
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Security analysis helper functions for TypeScript client template
3
+ * Adapted from rust-server template helpers
4
+ */
5
+
6
+ /**
7
+ * Analyzes operation security requirements from AsyncAPI specification
8
+ *
9
+ * @param {object} operation - AsyncAPI operation object
10
+ * @returns {object} Security analysis result
11
+ */
12
+ export function analyzeOperationSecurity(operation) {
13
+ try {
14
+ // Check AsyncAPI security field
15
+ const security = operation.security && operation.security();
16
+ if (security && Array.isArray(security) && security.length > 0) {
17
+ return {
18
+ hasSecurityRequirements: true,
19
+ securitySchemes: security,
20
+ requiresAuthentication: true
21
+ };
22
+ }
23
+
24
+ // Check if operation has security defined in AsyncAPI spec
25
+ const operationJson = operation._json || operation;
26
+ if (operationJson.security && Array.isArray(operationJson.security) && operationJson.security.length > 0) {
27
+ return {
28
+ hasSecurityRequirements: true,
29
+ securitySchemes: operationJson.security,
30
+ requiresAuthentication: true
31
+ };
32
+ }
33
+
34
+ return {
35
+ hasSecurityRequirements: false,
36
+ securitySchemes: [],
37
+ requiresAuthentication: false
38
+ };
39
+ } catch (e) {
40
+ return {
41
+ hasSecurityRequirements: false,
42
+ securitySchemes: [],
43
+ requiresAuthentication: false
44
+ };
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Checks if an operation has security requirements
50
+ *
51
+ * @param {object} operation - AsyncAPI operation object
52
+ * @returns {boolean} True if operation has security requirements
53
+ */
54
+ export function operationRequiresAuth(operation) {
55
+ const analysis = analyzeOperationSecurity(operation);
56
+ return analysis.hasSecurityRequirements;
57
+ }
58
+
59
+ /**
60
+ * Checks if the AsyncAPI specification has security schemes defined
61
+ *
62
+ * @param {object} asyncapi - AsyncAPI specification object
63
+ * @returns {boolean} True if security schemes are present
64
+ */
65
+ export function hasSecuritySchemes(asyncapi) {
66
+ try {
67
+ const components = asyncapi.components();
68
+ if (!components) return false;
69
+
70
+ const securitySchemes = components.securitySchemes();
71
+ return securitySchemes && Object.keys(securitySchemes).length > 0;
72
+ } catch (e) {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Get security scheme type from AsyncAPI security scheme definition
79
+ *
80
+ * @param {object} securityScheme - AsyncAPI security scheme object
81
+ * @returns {string} Security scheme type ('jwt', 'basic', 'apikey', etc.)
82
+ */
83
+ export function getSecuritySchemeType(securityScheme) {
84
+ try {
85
+ if (securityScheme.type && typeof securityScheme.type === 'function') {
86
+ return securityScheme.type();
87
+ }
88
+ if (securityScheme.type) {
89
+ return securityScheme.type;
90
+ }
91
+ if (securityScheme._json && securityScheme._json.type) {
92
+ return securityScheme._json.type;
93
+ }
94
+ return 'unknown';
95
+ } catch (e) {
96
+ return 'unknown';
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Extract security requirements for all operations in the AsyncAPI spec
102
+ *
103
+ * @param {object} asyncapi - AsyncAPI specification object
104
+ * @returns {object} Map of operation names to their security requirements
105
+ */
106
+ export function extractOperationSecurityMap(asyncapi) {
107
+ const securityMap = {};
108
+
109
+ try {
110
+ const operations = asyncapi.operations && asyncapi.operations();
111
+ if (operations) {
112
+ // Handle AsyncAPI parser collection - use .all() method to get array
113
+ const operationArray = operations.all ? operations.all() : Object.values(operations);
114
+
115
+ operationArray.forEach((operation) => {
116
+ // Get operation ID
117
+ let operationId = null;
118
+ if (operation._meta && operation._meta.id) {
119
+ operationId = operation._meta.id;
120
+ } else if (operation.id && typeof operation.id === 'function') {
121
+ operationId = operation.id();
122
+ } else if (operation.id) {
123
+ operationId = operation.id;
124
+ }
125
+
126
+ if (operationId) {
127
+ const securityAnalysis = analyzeOperationSecurity(operation);
128
+ securityMap[operationId] = securityAnalysis;
129
+ }
130
+ });
131
+ }
132
+ } catch (e) {
133
+ console.warn('Error extracting operation security map:', e.message);
134
+ }
135
+
136
+ return securityMap;
137
+ }
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-unused-vars */
2
2
  import { File } from '@asyncapi/generator-react-sdk';
3
+ import { operationRequiresAuth, extractOperationSecurityMap } from '../helpers/security.js';
3
4
 
4
5
  function generateClient(asyncapi, clientName) {
5
6
  // Method name sanitization function
@@ -9,7 +10,7 @@ function generateClient(asyncapi, clientName) {
9
10
  // Convert to camelCase and remove invalid characters
10
11
  let sanitized = operationId
11
12
  // Replace dots, hyphens, underscores, spaces, and forward slashes with camelCase
12
- .replace(/[.\-_\s\/]+(.)/g, (_, char) => char.toUpperCase())
13
+ .replace(/[.\-_\s/]+(.)/g, (_, char) => char.toUpperCase())
13
14
  // Remove any remaining invalid characters
14
15
  .replace(/[^a-zA-Z0-9]/g, '')
15
16
  // Ensure it starts with a lowercase letter if it starts with a number
@@ -42,6 +43,7 @@ function generateClient(asyncapi, clientName) {
42
43
 
43
44
  let content = `import { TransportFactory } from './runtime/transports/factory';
44
45
  import { Transport, TransportConfig, RequestOptions, MessageEnvelope } from './runtime/types';
46
+ import { AuthCredentials } from './runtime/auth/types';
45
47
  import * as Models from './models';
46
48
 
47
49
  export class ${clientName} {
@@ -70,6 +72,26 @@ export class ${clientName} {
70
72
  this.transport.unsubscribe(channel, callback);
71
73
  }
72
74
 
75
+ /**
76
+ * Update authentication configuration
77
+ * @param auth New authentication configuration
78
+ */
79
+ updateAuth(auth: AuthCredentials): void {
80
+ this.config.auth = auth;
81
+ // If transport supports auth updates, update it
82
+ if (this.transport && typeof (this.transport as any).updateAuth === 'function') {
83
+ (this.transport as any).updateAuth(auth);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get current authentication configuration
89
+ * @returns Current auth configuration
90
+ */
91
+ getAuth(): AuthCredentials | undefined {
92
+ return this.config.auth;
93
+ }
94
+
73
95
  // Generated operation methods
74
96
  `;
75
97
 
@@ -374,4 +396,4 @@ module.exports = function ({ asyncapi, params }) {
374
396
  {generatedContent}
375
397
  </File>
376
398
  );
377
- }
399
+ };