@ioka-technologies/asyncapi-ts-client-template 0.0.36 → 0.0.41
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ioka-technologies/asyncapi-ts-client-template",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"description": "TypeScript AsyncAPI client generator template compatible with rust-asyncapi patterns",
|
|
5
5
|
"main": "template/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"rust-asyncapi"
|
|
25
25
|
],
|
|
26
26
|
"author": "AsyncAPI TypeScript Generator",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/Ioka-Technologies/asyncapi-template"
|
|
30
|
+
},
|
|
27
31
|
"license": "Apache-2.0",
|
|
28
32
|
"dependencies": {
|
|
29
33
|
"@asyncapi/generator-react-sdk": "^1.0.20"
|
|
@@ -142,14 +142,23 @@ import * as Models from './models';
|
|
|
142
142
|
export class ${clientName} {
|
|
143
143
|
private transport: Transport;
|
|
144
144
|
private config: TransportConfig;
|
|
145
|
+
private connectPromise: Promise<void> | null = null;
|
|
145
146
|
|
|
146
147
|
constructor(config: TransportConfig) {
|
|
147
148
|
this.config = config;
|
|
148
149
|
this.transport = TransportFactory.create(config);
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
connect(): Promise<void> {
|
|
153
|
+
if (this.transport.isConnected()) {
|
|
154
|
+
return Promise.resolve();
|
|
155
|
+
}
|
|
156
|
+
if (!this.connectPromise) {
|
|
157
|
+
this.connectPromise = this.transport.connect().finally(() => {
|
|
158
|
+
this.connectPromise = null;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return this.connectPromise;
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
async disconnect(): Promise<void> {
|
|
@@ -60,6 +60,10 @@ export class WebSocketTransport implements Transport {
|
|
|
60
60
|
private _isConnected = false;
|
|
61
61
|
private reconnectAttempts = 0;
|
|
62
62
|
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
63
|
+
private connectPromise: Promise<void> | null = null;
|
|
64
|
+
private pendingConnectionReject: ((error: Error) => void) | null = null;
|
|
65
|
+
private connectionGeneration = 0;
|
|
66
|
+
private hasConnectedSuccessfully = false;
|
|
63
67
|
private reconnectConfig: Required<ReconnectConfig>;
|
|
64
68
|
|
|
65
69
|
// Event listener registries (for application-layer listeners beyond config callbacks)
|
|
@@ -104,37 +108,73 @@ export class WebSocketTransport implements Transport {
|
|
|
104
108
|
return () => { this.disconnectedListeners.delete(callback); };
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
connect(): Promise<void> {
|
|
112
|
+
if (this.isConnected()) return Promise.resolve();
|
|
113
|
+
if (this.connectPromise) return this.connectPromise;
|
|
114
|
+
this.intentionalDisconnect = false;
|
|
115
|
+
this.connectPromise = this.createConnection().finally(() => {
|
|
116
|
+
this.connectPromise = null;
|
|
117
|
+
if (!this.isConnected()) this.attemptReconnect();
|
|
118
|
+
});
|
|
119
|
+
return this.connectPromise;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private async createConnection(): Promise<void> {
|
|
108
123
|
if (!this.WebSocketImpl) {
|
|
109
124
|
this.WebSocketImpl = await getWebSocketImpl();
|
|
110
125
|
}
|
|
111
126
|
|
|
112
127
|
return new Promise((resolve, reject) => {
|
|
128
|
+
let settled = false;
|
|
129
|
+
this.pendingConnectionReject = reject;
|
|
130
|
+
const generation = ++this.connectionGeneration;
|
|
131
|
+
const fail = (error: Error): void => {
|
|
132
|
+
if (!settled) { settled = true; reject(error); }
|
|
133
|
+
this.pendingConnectionReject = null;
|
|
134
|
+
};
|
|
113
135
|
try {
|
|
114
|
-
|
|
136
|
+
const socket = new this.WebSocketImpl(this.config.url) as WebSocketLike;
|
|
137
|
+
this.ws = socket;
|
|
138
|
+
const isCurrent = (): boolean => generation === this.connectionGeneration && this.ws === socket;
|
|
115
139
|
|
|
116
140
|
// Handle both browser and Node.js WebSocket APIs
|
|
117
|
-
if (typeof window !== 'undefined' &&
|
|
141
|
+
if (typeof window !== 'undefined' && socket.addEventListener) {
|
|
118
142
|
// Browser WebSocket API
|
|
119
|
-
|
|
143
|
+
socket.addEventListener('open', () => {
|
|
144
|
+
if (!isCurrent()) return;
|
|
145
|
+
settled = true;
|
|
146
|
+
this.pendingConnectionReject = null;
|
|
147
|
+
const wasReconnect = this.hasConnectedSuccessfully;
|
|
148
|
+
this.hasConnectedSuccessfully = true;
|
|
120
149
|
this.reconnectAttempts = 0;
|
|
121
150
|
this._isConnected = true;
|
|
122
|
-
|
|
151
|
+
if (wasReconnect) {
|
|
152
|
+
this.config.connectionCallbacks?.onReconnected?.();
|
|
153
|
+
this.reconnectedListeners.forEach(cb => cb());
|
|
154
|
+
this.resubscribeAll();
|
|
155
|
+
} else {
|
|
156
|
+
this.config.connectionCallbacks?.onConnected?.();
|
|
157
|
+
}
|
|
123
158
|
resolve();
|
|
124
159
|
});
|
|
125
160
|
|
|
126
|
-
|
|
127
|
-
this.handleMessage(event.data);
|
|
161
|
+
socket.addEventListener('message', (event: MessageEvent) => {
|
|
162
|
+
if (isCurrent()) this.handleMessage(event.data);
|
|
128
163
|
});
|
|
129
164
|
|
|
130
|
-
|
|
165
|
+
socket.addEventListener('close', () => {
|
|
166
|
+
if (!isCurrent()) return;
|
|
167
|
+
this._isConnected = false;
|
|
168
|
+
this.ws = null;
|
|
169
|
+
fail(new ConnectionError('WebSocket connection closed'));
|
|
131
170
|
this.handleDisconnect();
|
|
132
171
|
});
|
|
133
172
|
|
|
134
|
-
|
|
173
|
+
socket.addEventListener('error', (event: Event) => {
|
|
174
|
+
if (!isCurrent()) return;
|
|
135
175
|
if (!this._isConnected) {
|
|
136
176
|
// Error during initial connection
|
|
137
|
-
|
|
177
|
+
fail(new ConnectionError(\`WebSocket connection failed: \${event.type}\`));
|
|
138
178
|
} else {
|
|
139
179
|
// Error on established connection — handleDisconnect will be called by 'close'
|
|
140
180
|
console.error('WebSocket error on established connection:', event);
|
|
@@ -143,27 +183,43 @@ export class WebSocketTransport implements Transport {
|
|
|
143
183
|
);
|
|
144
184
|
}
|
|
145
185
|
});
|
|
146
|
-
} else if (
|
|
186
|
+
} else if (socket.on) {
|
|
147
187
|
// Node.js ws library API
|
|
148
|
-
|
|
188
|
+
socket.on('open', () => {
|
|
189
|
+
if (!isCurrent()) return;
|
|
190
|
+
settled = true;
|
|
191
|
+
this.pendingConnectionReject = null;
|
|
192
|
+
const wasReconnect = this.hasConnectedSuccessfully;
|
|
193
|
+
this.hasConnectedSuccessfully = true;
|
|
149
194
|
this.reconnectAttempts = 0;
|
|
150
195
|
this._isConnected = true;
|
|
151
|
-
|
|
196
|
+
if (wasReconnect) {
|
|
197
|
+
this.config.connectionCallbacks?.onReconnected?.();
|
|
198
|
+
this.reconnectedListeners.forEach(cb => cb());
|
|
199
|
+
this.resubscribeAll();
|
|
200
|
+
} else {
|
|
201
|
+
this.config.connectionCallbacks?.onConnected?.();
|
|
202
|
+
}
|
|
152
203
|
resolve();
|
|
153
204
|
});
|
|
154
205
|
|
|
155
|
-
|
|
156
|
-
this.handleMessage(data.toString());
|
|
206
|
+
socket.on('message', (data: any) => {
|
|
207
|
+
if (isCurrent()) this.handleMessage(data.toString());
|
|
157
208
|
});
|
|
158
209
|
|
|
159
|
-
|
|
210
|
+
socket.on('close', () => {
|
|
211
|
+
if (!isCurrent()) return;
|
|
212
|
+
this._isConnected = false;
|
|
213
|
+
this.ws = null;
|
|
214
|
+
fail(new ConnectionError('WebSocket connection closed'));
|
|
160
215
|
this.handleDisconnect();
|
|
161
216
|
});
|
|
162
217
|
|
|
163
|
-
|
|
218
|
+
socket.on('error', (error: any) => {
|
|
219
|
+
if (!isCurrent()) return;
|
|
164
220
|
if (!this._isConnected) {
|
|
165
221
|
// Error during initial connection
|
|
166
|
-
|
|
222
|
+
fail(new ConnectionError(\`WebSocket connection failed: \${error.message}\`));
|
|
167
223
|
} else {
|
|
168
224
|
// Error on established connection — handleDisconnect will be called by 'close'
|
|
169
225
|
console.error('WebSocket error on established connection:', error);
|
|
@@ -173,10 +229,10 @@ export class WebSocketTransport implements Transport {
|
|
|
173
229
|
}
|
|
174
230
|
});
|
|
175
231
|
} else {
|
|
176
|
-
|
|
232
|
+
fail(new ConnectionError('WebSocket implementation does not support required event handling'));
|
|
177
233
|
}
|
|
178
234
|
} catch (error: any) {
|
|
179
|
-
|
|
235
|
+
fail(new ConnectionError(\`Failed to create WebSocket: \${error.message}\`));
|
|
180
236
|
}
|
|
181
237
|
});
|
|
182
238
|
}
|
|
@@ -191,12 +247,19 @@ export class WebSocketTransport implements Transport {
|
|
|
191
247
|
this.reconnectTimer = null;
|
|
192
248
|
}
|
|
193
249
|
|
|
250
|
+
this.connectionGeneration++;
|
|
251
|
+
if (this.pendingConnectionReject) {
|
|
252
|
+
this.pendingConnectionReject(new ConnectionError('Connection closed intentionally'));
|
|
253
|
+
this.pendingConnectionReject = null;
|
|
254
|
+
}
|
|
255
|
+
|
|
194
256
|
// Reject all pending requests
|
|
195
257
|
this.rejectPendingRequests('Connection closed intentionally');
|
|
196
258
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
259
|
+
const socket = this.ws;
|
|
260
|
+
this.ws = null;
|
|
261
|
+
if (socket) {
|
|
262
|
+
socket.close();
|
|
200
263
|
}
|
|
201
264
|
}
|
|
202
265
|
|
|
@@ -443,10 +506,7 @@ export class WebSocketTransport implements Transport {
|
|
|
443
506
|
this.rejectPendingRequests('WebSocket connection lost');
|
|
444
507
|
|
|
445
508
|
// Don't reconnect if this was intentional
|
|
446
|
-
if (this.intentionalDisconnect)
|
|
447
|
-
this.intentionalDisconnect = false;
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
509
|
+
if (this.intentionalDisconnect) return;
|
|
450
510
|
|
|
451
511
|
// Notify listeners of unexpected disconnect
|
|
452
512
|
const reason = 'WebSocket connection lost unexpectedly';
|
|
@@ -464,6 +524,9 @@ export class WebSocketTransport implements Transport {
|
|
|
464
524
|
}
|
|
465
525
|
|
|
466
526
|
private attemptReconnect(): void {
|
|
527
|
+
if (this.intentionalDisconnect || this.reconnectTimer || this.connectPromise) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
467
530
|
// Check if we've exceeded max attempts (0 = unlimited)
|
|
468
531
|
if (this.reconnectConfig.maxAttempts > 0 &&
|
|
469
532
|
this.reconnectAttempts >= this.reconnectConfig.maxAttempts) {
|
|
@@ -479,22 +542,13 @@ export class WebSocketTransport implements Transport {
|
|
|
479
542
|
this.config.connectionCallbacks?.onReconnecting?.(this.reconnectAttempts, delay);
|
|
480
543
|
|
|
481
544
|
this.reconnectTimer = setTimeout(async () => {
|
|
545
|
+
this.reconnectTimer = null;
|
|
482
546
|
try {
|
|
483
547
|
// Reset the intentional disconnect flag before reconnecting
|
|
484
548
|
this.intentionalDisconnect = false;
|
|
485
549
|
await this.connect();
|
|
486
550
|
|
|
487
|
-
// Reconnection succeeded
|
|
488
|
-
console.log('WebSocket reconnected successfully');
|
|
489
|
-
this.config.connectionCallbacks?.onReconnected?.();
|
|
490
|
-
this.reconnectedListeners.forEach(cb => {
|
|
491
|
-
try { cb(); } catch (e) { console.error('Error in reconnect listener:', e); }
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
// Re-subscribe to all channels
|
|
495
|
-
this.resubscribeAll();
|
|
496
551
|
} catch (error) {
|
|
497
|
-
console.error('WebSocket reconnection attempt failed:', error);
|
|
498
552
|
// Try again
|
|
499
553
|
this.attemptReconnect();
|
|
500
554
|
}
|