@nmtjs/ws-client 0.1.8 → 0.2.0

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/dist/index.js CHANGED
@@ -1,336 +1,66 @@
1
- import { ClientTransport, ClientUpStream, Subscription, createClientDownStream, onAbort, once } from '@nmtjs/client';
2
- import { MessageType, MessageTypeName, TransportType, concat, decodeNumber, encodeNumber } from '@nmtjs/common';
3
- export class WsClientTransport extends ClientTransport {
1
+ import { EventEmitter } from '@nmtjs/protocol/client';
2
+ import { ServerMessageType, concat, decodeNumber, encodeNumber } from '@nmtjs/protocol/common';
3
+ export class WebSocketClientTransport extends EventEmitter {
4
4
  options;
5
- type;
6
- calls;
7
- subscriptions;
8
- upStreams;
9
- downStreams;
10
- upStreamId;
11
- queryParams;
12
- ws;
13
- isHealthy;
14
- checkHealthAttempts;
15
- autoreconnect;
16
- isConnected;
17
- wsFactory;
18
- encodeRpcContext;
19
- decodeRpcContext;
5
+ #webSocket;
6
+ #connecting;
20
7
  constructor(options){
21
- super(), this.options = options, this.type = TransportType.WS, this.calls = new Map(), this.subscriptions = new Map(), this.upStreams = new Map(), this.downStreams = new Map(), this.upStreamId = 0, this.queryParams = {}, this.isHealthy = false, this.checkHealthAttempts = 0, this.isConnected = false;
22
- this.autoreconnect = this.options.autoreconnect ?? true;
23
- if (options.wsFactory) {
24
- this.wsFactory = options.wsFactory;
25
- } else {
26
- this.wsFactory = (url)=>new WebSocket(url.toString());
27
- }
28
- this.on('close', (error)=>this.clear(error));
29
- this.encodeRpcContext = {
30
- addStream: (blob)=>{
31
- const id = ++this.upStreamId;
32
- const upstream = new ClientUpStream(id, blob);
33
- this.upStreams.set(id, upstream);
34
- return {
35
- id,
36
- metadata: blob.metadata
37
- };
38
- },
39
- getStream: (id)=>this.upStreams.get(id)
40
- };
41
- this.decodeRpcContext = {
42
- addStream: (id, metadata)=>{
43
- const downstream = createClientDownStream(metadata, ()=>this.send(MessageType.DownStreamPull, encodeNumber(id, 'Uint32')));
44
- this.downStreams.set(id, downstream);
45
- return downstream.blob;
46
- },
47
- getStream: (id)=>this.downStreams.get(id)
48
- };
49
- }
50
- async connect() {
51
- this.autoreconnect = this.options.autoreconnect ?? true;
52
- await this.healthCheck();
53
- this.ws = this.wsFactory(this.getURL('api', 'ws', {
54
- ...this.queryParams,
55
- accept: this.client.format.contentType,
56
- 'content-type': this.client.format.contentType,
57
- services: this.client.services
58
- }));
59
- this.ws.binaryType = 'arraybuffer';
60
- this.ws.onmessage = (event)=>{
61
- const buffer = event.data;
8
+ super(), this.options = options, this.#webSocket = null, this.#connecting = null;
9
+ }
10
+ connect(auth = undefined, contentType) {
11
+ const wsUrl = new URL(this.options.origin);
12
+ wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
13
+ wsUrl.pathname = '/api';
14
+ wsUrl.searchParams.set('content-type', contentType);
15
+ wsUrl.searchParams.set('accept', contentType);
16
+ if (auth) wsUrl.searchParams.set('auth', auth);
17
+ const ws = this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString());
18
+ ws.binaryType = 'arraybuffer';
19
+ ws.addEventListener('message', ({ data })=>{
20
+ const buffer = data;
62
21
  const type = decodeNumber(buffer, 'Uint8');
63
- const handler = this[type];
64
- if (handler) {
65
- handler.call(this, buffer.slice(Uint8Array.BYTES_PER_ELEMENT), this.ws);
66
- }
67
- };
68
- this.ws.onopen = (event)=>{
69
- if (this.options.debug) console.log(`[WS] Connected to ${this.options.origin}`);
70
- this.isConnected = true;
71
- this.emit('open');
72
- this.checkHealthAttempts = 0;
73
- };
74
- this.ws.onclose = (event)=>{
75
- this.isConnected = false;
76
- this.isHealthy = false;
77
- this.emit('close', event.code === 1000 ? undefined : new Error(`Connection closed with code ${event.code}: ${event.reason}`));
78
- if (this.autoreconnect) this.connect();
79
- };
80
- this.ws.onerror = (cause)=>{
81
- this.isHealthy = false;
82
- this.emit('close', new Error('Connection error', {
83
- cause
84
- }));
85
- };
86
- await once(this, 'open');
87
- if (this.options.debug) console.log(`2[WS] Connected to ${this.options.origin}`);
88
- this.emit('connect');
89
- }
90
- async disconnect() {
91
- this.autoreconnect = false;
92
- this.ws?.close();
93
- await once(this, 'close');
94
- }
95
- async rpc(call) {
96
- const { signal, callId, payload, procedure, service } = call;
97
- const data = this.client.format.encodeRpc({
98
- callId,
99
- service,
100
- procedure,
101
- payload
102
- }, this.encodeRpcContext);
103
- onAbort(signal, ()=>{
104
- const call = this.calls.get(callId);
105
- if (call) {
106
- const { reject } = call;
107
- reject(new Error('Request aborted'));
108
- this.calls.delete(callId);
109
- this.send(MessageType.RpcAbort, encodeNumber(callId, 'Uint32'));
22
+ if (type in ServerMessageType) {
23
+ this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT));
110
24
  }
111
25
  });
112
- if (!this.isConnected) await once(this, 'connect');
26
+ this.#webSocket = ws;
113
27
  return new Promise((resolve, reject)=>{
114
- this.calls.set(callId, {
115
- resolve,
116
- reject
28
+ ws.addEventListener('open', ()=>{
29
+ this.emit('connect');
30
+ resolve();
31
+ }, {
32
+ once: true
117
33
  });
118
- this.send(MessageType.Rpc, data);
119
- });
120
- }
121
- setQueryParams(params) {
122
- this.queryParams = params;
123
- }
124
- send(type, ...payload) {
125
- this.ws?.send(concat(encodeNumber(type, 'Uint8'), ...payload));
126
- if (this.options.debug) {
127
- console.groupCollapsed(`[WS] Sent ${MessageTypeName[type]}`);
128
- console.trace();
129
- console.groupEnd();
130
- }
131
- }
132
- async clear(error = new Error('Connection closed')) {
133
- for (const call of this.calls.values()){
134
- const { reject } = call;
135
- reject(error);
136
- }
137
- for (const stream of this.upStreams.values()){
138
- stream.reader.cancel(error);
139
- }
140
- for (const stream of this.downStreams.values()){
141
- stream.writer.abort(error);
142
- }
143
- for (const subscription of this.subscriptions.values()){
144
- subscription.unsubscribe();
145
- }
146
- this.calls.clear();
147
- this.upStreams.clear();
148
- this.downStreams.clear();
149
- this.subscriptions.clear();
150
- }
151
- async healthCheck() {
152
- while(!this.isHealthy){
153
- try {
154
- const signal = AbortSignal.timeout(10000);
155
- const url = this.getURL('healthy', 'http', {
156
- auth: this.client.auth
157
- });
158
- const { ok } = await fetch(url, {
159
- signal
160
- });
161
- this.isHealthy = ok;
162
- } catch (e) {}
163
- if (!this.isHealthy) {
164
- this.checkHealthAttempts++;
165
- const seconds = Math.min(this.checkHealthAttempts, 15);
166
- await new Promise((r)=>setTimeout(r, seconds * 1000));
167
- }
168
- }
169
- this.emit('healthy');
170
- }
171
- getURL(path = '', protocol, params = {}) {
172
- const base = new URL(this.options.origin);
173
- const secure = base.protocol === 'https:';
174
- const url = new URL(`${secure ? protocol + 's' : protocol}://${base.host}/${path}`);
175
- for (let [key, values] of Object.entries(params)){
176
- if (!Array.isArray(values)) values = [
177
- values
178
- ];
179
- for (const value of values){
180
- url.searchParams.append(key, value);
181
- }
182
- }
183
- return url;
184
- }
185
- resolveRpc(callId, value) {
186
- const call = this.calls.get(callId);
187
- if (call) {
188
- const { resolve } = call;
189
- this.calls.delete(callId);
190
- resolve(value);
191
- }
192
- }
193
- async [MessageType.Event](buffer) {
194
- const [service, event, payload] = this.client.format.decode(buffer);
195
- if (this.options.debug) {
196
- console.groupCollapsed(`[WS] Received "Event" ${service}/${event}`);
197
- console.log(payload);
198
- console.groupEnd();
199
- }
200
- this.emit('event', service, event, payload);
201
- }
202
- async [MessageType.Rpc](buffer) {
203
- const { callId, error, payload } = this.client.format.decodeRpc(buffer, this.decodeRpcContext);
204
- if (this.calls.has(callId)) {
205
- this.resolveRpc(callId, error ? {
206
- success: false,
207
- error
208
- } : {
209
- success: true,
210
- value: payload
34
+ ws.addEventListener('error', (event)=>reject(new Error('WebSocket error', {
35
+ cause: event
36
+ })), {
37
+ once: true
211
38
  });
212
- }
213
- }
214
- async [MessageType.UpStreamPull](buffer) {
215
- const id = decodeNumber(buffer, 'Uint32');
216
- const size = decodeNumber(buffer, 'Uint32', Uint32Array.BYTES_PER_ELEMENT);
217
- if (this.options.debug) {
218
- console.log(`[WS] Received "UpStreamPull" ${id}`);
219
- }
220
- const stream = this.upStreams.get(id);
221
- if (stream) {
222
- const buf = new Uint8Array(new ArrayBuffer(size));
223
- const { done, value } = await stream.reader.read(buf);
224
- if (done) {
225
- this.send(MessageType.UpStreamEnd, encodeNumber(id, 'Uint32'));
226
- } else {
227
- this.send(MessageType.UpStreamPush, concat(encodeNumber(id, 'Uint32'), value.buffer.slice(0, value.byteLength)));
228
- }
229
- }
230
- }
231
- async [MessageType.UpStreamEnd](buffer) {
232
- const id = decodeNumber(buffer, 'Uint32');
233
- if (this.options.debug) {
234
- console.log(`[WS] Received "UpStreamEnd" ${id}`);
235
- }
236
- const stream = this.upStreams.get(id);
237
- if (stream) {
238
- stream.reader.cancel();
239
- this.upStreams.delete(id);
240
- }
241
- }
242
- [MessageType.UpStreamAbort](buffer) {
243
- const id = decodeNumber(buffer, 'Uint32');
244
- if (this.options.debug) {
245
- console.log(`[WS] Received "UpStreamAbort" ${id}`);
246
- }
247
- const stream = this.upStreams.get(id);
248
- if (stream) {
249
- try {
250
- stream.reader.cancel(new Error('Aborted by server'));
251
- } finally{
252
- this.upStreams.delete(id);
253
- }
254
- }
255
- }
256
- async [MessageType.DownStreamPush](buffer) {
257
- const id = decodeNumber(buffer, 'Uint32');
258
- if (this.options.debug) {
259
- console.log(`[WS] Received "DownStreamPush" ${id}`);
260
- }
261
- const stream = this.downStreams.get(id);
262
- if (stream) {
263
- try {
264
- await stream.writer.ready;
265
- const chunk = buffer.slice(Uint32Array.BYTES_PER_ELEMENT);
266
- await stream.writer.write(new Uint8Array(chunk));
267
- await stream.writer.ready;
268
- this.send(MessageType.DownStreamPull, encodeNumber(id, 'Uint32'));
269
- } catch (e) {
270
- this.send(MessageType.DownStreamAbort, encodeNumber(id, 'Uint32'));
271
- this.downStreams.delete(id);
272
- }
273
- }
274
- }
275
- async [MessageType.DownStreamEnd](buffer) {
276
- const id = decodeNumber(buffer, 'Uint32');
277
- if (this.options.debug) {
278
- console.log(`[WS] Received "DownStreamEnd" ${id}`);
279
- }
280
- const stream = this.downStreams.get(id);
281
- if (stream) {
282
- this.downStreams.delete(id);
283
- stream.writer.close().catch(()=>{});
284
- }
285
- }
286
- async [MessageType.DownStreamAbort](buffer) {
287
- const id = decodeNumber(buffer, 'Uint32');
288
- if (this.options.debug) {
289
- console.log(`[WS] Received "DownStreamAbort" ${id}`);
290
- }
291
- const stream = this.downStreams.get(id);
292
- if (stream) {
293
- this.downStreams.delete(id);
294
- stream.writer.abort(new Error('Aborted by server')).catch(()=>{});
295
- }
296
- }
297
- [MessageType.RpcSubscription](buffer) {
298
- const { callId, payload: [key, payload] } = this.client.format.decodeRpc(buffer, this.decodeRpcContext);
299
- if (this.calls.has(callId)) {
300
- const subscription = new Subscription(key, ()=>{
301
- subscription.emit('end');
302
- this.subscriptions.delete(key);
303
- this.send(MessageType.ClientUnsubscribe, this.client.format.encode([
304
- key
305
- ]));
306
- });
307
- this.subscriptions.set(key, subscription);
308
- this.resolveRpc(callId, {
309
- success: true,
310
- value: {
311
- payload,
312
- subscription
39
+ ws.addEventListener('close', (event)=>{
40
+ this.emit('disconnect');
41
+ this.#webSocket = null;
42
+ if (this.options.autoreconnect === true) {
43
+ setTimeout(()=>this.connect(auth, contentType), 1000);
313
44
  }
45
+ }, {
46
+ once: true
314
47
  });
315
- }
48
+ });
316
49
  }
317
- [MessageType.ServerSubscriptionEvent](buffer) {
318
- const [key, event, payload] = this.client.format.decode(buffer);
319
- if (this.options.debug) {
320
- console.groupCollapsed(`[WS] Received "ServerSubscriptionEvent" ${key}/${event}`);
321
- console.log(payload);
322
- console.groupEnd();
323
- }
324
- const subscription = this.subscriptions.get(key);
325
- if (subscription) subscription.emit(event, payload);
50
+ async disconnect() {
51
+ if (this.#webSocket === null) return;
52
+ this.#webSocket.close();
53
+ return _once(this.#webSocket, 'close');
326
54
  }
327
- [MessageType.ServerUnsubscribe](buffer) {
328
- const [key] = this.client.format.decode(buffer);
329
- if (this.options.debug) {
330
- console.log(`[WS] Received "ServerUnsubscribe" ${key}`);
331
- }
332
- const subscription = this.subscriptions.get(key);
333
- subscription?.emit('end');
334
- this.subscriptions.delete(key);
55
+ async send(messageType, buffer) {
56
+ if (this.#connecting) await this.#connecting;
57
+ this.#webSocket.send(concat(encodeNumber(messageType, 'Uint8'), buffer));
335
58
  }
336
59
  }
60
+ function _once(target, event) {
61
+ return new Promise((resolve)=>{
62
+ target.addEventListener(event, ()=>resolve(), {
63
+ once: true
64
+ });
65
+ });
66
+ }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../index.ts"],"sourcesContent":["import {\n type ClientDownStreamWrapper,\n ClientTransport,\n type ClientTransportRpcCall,\n type ClientTransportRpcResult,\n ClientUpStream,\n Subscription,\n createClientDownStream,\n onAbort,\n once,\n} from '@nmtjs/client'\nimport {\n type DecodeRpcContext,\n type EncodeRpcContext,\n MessageType,\n MessageTypeName,\n TransportType,\n concat,\n decodeNumber,\n encodeNumber,\n} from '@nmtjs/common'\n\nexport type WsClientTransportOptions = {\n /**\n * The origin of the server\n * @example 'http://localhost:3000'\n */\n origin: string\n /**\n * Whether to autoreconnect on close\n * @default true\n */\n autoreconnect?: boolean\n /**\n * Custom WebSocket class\n * @default globalThis.WebSocket\n */\n wsFactory?: (url: URL) => WebSocket\n\n debug?: boolean\n}\n\ntype WsCall = {\n resolve: (value: ClientTransportRpcResult) => void\n reject: (error: Error) => void\n}\n\nexport class WsClientTransport extends ClientTransport<{\n open: []\n close: [Error?]\n connect: []\n healthy: []\n}> {\n readonly type = TransportType.WS\n\n protected readonly calls = new Map<number, WsCall>()\n protected readonly subscriptions = new Map<number, Subscription>()\n protected readonly upStreams = new Map<number, ClientUpStream>()\n protected readonly downStreams = new Map<number, ClientDownStreamWrapper>()\n protected upStreamId = 0\n protected queryParams = {}\n protected ws?: WebSocket\n protected isHealthy = false\n protected checkHealthAttempts = 0\n protected autoreconnect: boolean\n protected isConnected = false\n\n private wsFactory!: (url: URL) => WebSocket\n private encodeRpcContext: EncodeRpcContext\n private decodeRpcContext: DecodeRpcContext\n\n constructor(private readonly options: WsClientTransportOptions) {\n super()\n\n this.autoreconnect = this.options.autoreconnect ?? true\n\n if (options.wsFactory) {\n this.wsFactory = options.wsFactory\n } else {\n this.wsFactory = (url: URL) => new WebSocket(url.toString())\n }\n\n // TODO: wtf is this for?\n this.on('close', (error) => this.clear(error))\n\n this.encodeRpcContext = {\n addStream: (blob) => {\n const id = ++this.upStreamId\n const upstream = new ClientUpStream(id, blob)\n this.upStreams.set(id, upstream)\n return { id, metadata: blob.metadata }\n },\n getStream: (id) => this.upStreams.get(id),\n }\n\n this.decodeRpcContext = {\n addStream: (id, metadata) => {\n const downstream = createClientDownStream(metadata, () =>\n this.send(MessageType.DownStreamPull, encodeNumber(id, 'Uint32')),\n )\n this.downStreams.set(id, downstream)\n return downstream.blob\n },\n getStream: (id) => this.downStreams.get(id),\n }\n }\n\n async connect() {\n // reset default autoreconnect value\n this.autoreconnect = this.options.autoreconnect ?? true\n await this.healthCheck()\n\n this.ws = this.wsFactory(\n this.getURL('api', 'ws', {\n ...this.queryParams,\n accept: this.client.format.contentType,\n 'content-type': this.client.format.contentType,\n services: this.client.services,\n }),\n )\n\n this.ws.binaryType = 'arraybuffer'\n\n this.ws.onmessage = (event) => {\n const buffer: ArrayBuffer = event.data\n const type = decodeNumber(buffer, 'Uint8')\n const handler = this[type]\n if (handler) {\n handler.call(this, buffer.slice(Uint8Array.BYTES_PER_ELEMENT), this.ws)\n }\n }\n this.ws.onopen = (event) => {\n if (this.options.debug)\n console.log(`[WS] Connected to ${this.options.origin}`)\n this.isConnected = true\n this.emit('open')\n this.checkHealthAttempts = 0\n }\n this.ws.onclose = (event) => {\n this.isConnected = false\n this.isHealthy = false\n this.emit(\n 'close',\n event.code === 1000\n ? undefined\n : new Error(\n `Connection closed with code ${event.code}: ${event.reason}`,\n ),\n )\n // FIXME: cleanup calls, streams, subscriptions\n if (this.autoreconnect) this.connect()\n }\n this.ws.onerror = (cause) => {\n this.isHealthy = false\n this.emit('close', new Error('Connection error', { cause }))\n }\n await once(this, 'open')\n if (this.options.debug)\n console.log(`2[WS] Connected to ${this.options.origin}`)\n this.emit('connect')\n }\n\n async disconnect(): Promise<void> {\n this.autoreconnect = false\n this.ws?.close()\n await once(this, 'close')\n }\n\n async rpc(call: ClientTransportRpcCall): Promise<ClientTransportRpcResult> {\n const { signal, callId, payload, procedure, service } = call\n\n const data = this.client.format.encodeRpc(\n {\n callId,\n service,\n procedure,\n payload,\n },\n this.encodeRpcContext,\n )\n\n onAbort(signal, () => {\n const call = this.calls.get(callId)\n if (call) {\n const { reject } = call\n reject(new Error('Request aborted'))\n this.calls.delete(callId)\n this.send(MessageType.RpcAbort, encodeNumber(callId, 'Uint32'))\n }\n })\n\n if (!this.isConnected) await once(this, 'connect')\n\n return new Promise((resolve, reject) => {\n this.calls.set(callId, { resolve, reject })\n this.send(MessageType.Rpc, data)\n })\n }\n\n setQueryParams(params: Record<string, any>) {\n this.queryParams = params\n }\n\n protected send(type: MessageType, ...payload: ArrayBuffer[]) {\n this.ws?.send(concat(encodeNumber(type, 'Uint8'), ...payload))\n if (this.options.debug) {\n console.groupCollapsed(`[WS] Sent ${MessageTypeName[type]}`)\n console.trace()\n console.groupEnd()\n }\n }\n\n protected async clear(error: Error = new Error('Connection closed')) {\n for (const call of this.calls.values()) {\n const { reject } = call\n reject(error)\n }\n\n for (const stream of this.upStreams.values()) {\n stream.reader.cancel(error)\n }\n\n for (const stream of this.downStreams.values()) {\n stream.writer.abort(error)\n }\n\n for (const subscription of this.subscriptions.values()) {\n subscription.unsubscribe()\n }\n\n this.calls.clear()\n this.upStreams.clear()\n this.downStreams.clear()\n this.subscriptions.clear()\n }\n\n protected async healthCheck() {\n while (!this.isHealthy) {\n try {\n const signal = AbortSignal.timeout(10000)\n const url = this.getURL('healthy', 'http', {\n auth: this.client.auth,\n })\n const { ok } = await fetch(url, { signal })\n this.isHealthy = ok\n } catch (e) {}\n\n if (!this.isHealthy) {\n this.checkHealthAttempts++\n const seconds = Math.min(this.checkHealthAttempts, 15)\n await new Promise((r) => setTimeout(r, seconds * 1000))\n }\n }\n this.emit('healthy')\n }\n\n private getURL(\n path = '',\n protocol: 'ws' | 'http',\n params: Record<string, any> = {},\n ) {\n // TODO: add custom path support?\n const base = new URL(this.options.origin)\n const secure = base.protocol === 'https:'\n const url = new URL(\n `${secure ? protocol + 's' : protocol}://${base.host}/${path}`,\n )\n for (let [key, values] of Object.entries(params)) {\n if (!Array.isArray(values)) values = [values]\n for (const value of values) {\n url.searchParams.append(key, value)\n }\n }\n return url\n }\n\n protected resolveRpc(callId: number, value: ClientTransportRpcResult) {\n const call = this.calls.get(callId)\n if (call) {\n const { resolve } = call\n this.calls.delete(callId)\n resolve(value)\n }\n }\n\n protected async [MessageType.Event](buffer: ArrayBuffer) {\n const [service, event, payload] = this.client.format.decode(buffer)\n if (this.options.debug) {\n console.groupCollapsed(`[WS] Received \"Event\" ${service}/${event}`)\n console.log(payload)\n console.groupEnd()\n }\n this.emit('event', service, event, payload)\n }\n\n protected async [MessageType.Rpc](buffer: ArrayBuffer) {\n const { callId, error, payload } = this.client.format.decodeRpc(\n buffer,\n this.decodeRpcContext,\n )\n\n if (this.calls.has(callId)) {\n this.resolveRpc(\n callId,\n error ? { success: false, error } : { success: true, value: payload },\n )\n }\n }\n\n protected async [MessageType.UpStreamPull](buffer: ArrayBuffer) {\n const id = decodeNumber(buffer, 'Uint32')\n const size = decodeNumber(buffer, 'Uint32', Uint32Array.BYTES_PER_ELEMENT)\n\n if (this.options.debug) {\n console.log(`[WS] Received \"UpStreamPull\" ${id}`)\n }\n\n const stream = this.upStreams.get(id)\n if (stream) {\n const buf = new Uint8Array(new ArrayBuffer(size))\n const { done, value } = await stream.reader.read(buf)\n if (done) {\n this.send(MessageType.UpStreamEnd, encodeNumber(id, 'Uint32'))\n } else {\n this.send(\n MessageType.UpStreamPush,\n concat(\n encodeNumber(id, 'Uint32'),\n value.buffer.slice(0, value.byteLength),\n ),\n )\n }\n }\n }\n\n protected async [MessageType.UpStreamEnd](buffer: ArrayBuffer) {\n const id = decodeNumber(buffer, 'Uint32')\n if (this.options.debug) {\n console.log(`[WS] Received \"UpStreamEnd\" ${id}`)\n }\n const stream = this.upStreams.get(id)\n if (stream) {\n stream.reader.cancel()\n this.upStreams.delete(id)\n }\n }\n\n protected [MessageType.UpStreamAbort](buffer: ArrayBuffer) {\n const id = decodeNumber(buffer, 'Uint32')\n if (this.options.debug) {\n console.log(`[WS] Received \"UpStreamAbort\" ${id}`)\n }\n const stream = this.upStreams.get(id)\n if (stream) {\n try {\n stream.reader.cancel(new Error('Aborted by server'))\n } finally {\n this.upStreams.delete(id)\n }\n }\n }\n\n protected async [MessageType.DownStreamPush](buffer: ArrayBuffer) {\n const id = decodeNumber(buffer, 'Uint32')\n if (this.options.debug) {\n console.log(`[WS] Received \"DownStreamPush\" ${id}`)\n }\n const stream = this.downStreams.get(id)\n if (stream) {\n try {\n await stream.writer.ready\n const chunk = buffer.slice(Uint32Array.BYTES_PER_ELEMENT)\n await stream.writer.write(new Uint8Array(chunk))\n await stream.writer.ready\n this.send(MessageType.DownStreamPull, encodeNumber(id, 'Uint32'))\n } catch (e) {\n this.send(MessageType.DownStreamAbort, encodeNumber(id, 'Uint32'))\n this.downStreams.delete(id)\n }\n }\n }\n\n protected async [MessageType.DownStreamEnd](buffer: ArrayBuffer) {\n const id = decodeNumber(buffer, 'Uint32')\n if (this.options.debug) {\n console.log(`[WS] Received \"DownStreamEnd\" ${id}`)\n }\n const stream = this.downStreams.get(id)\n if (stream) {\n this.downStreams.delete(id)\n stream.writer.close().catch(() => {})\n }\n }\n\n protected async [MessageType.DownStreamAbort](buffer: ArrayBuffer) {\n const id = decodeNumber(buffer, 'Uint32')\n if (this.options.debug) {\n console.log(`[WS] Received \"DownStreamAbort\" ${id}`)\n }\n const stream = this.downStreams.get(id)\n if (stream) {\n this.downStreams.delete(id)\n stream.writer.abort(new Error('Aborted by server')).catch(() => {})\n }\n }\n\n protected [MessageType.RpcSubscription](buffer: ArrayBuffer) {\n const {\n callId,\n payload: [key, payload],\n } = this.client.format.decodeRpc(buffer, this.decodeRpcContext)\n if (this.calls.has(callId)) {\n const subscription = new Subscription(key, () => {\n subscription.emit('end')\n this.subscriptions.delete(key)\n this.send(\n MessageType.ClientUnsubscribe,\n this.client.format.encode([key]),\n )\n })\n this.subscriptions.set(key, subscription)\n this.resolveRpc(callId, {\n success: true,\n value: { payload, subscription },\n })\n }\n }\n\n protected [MessageType.ServerSubscriptionEvent](buffer: ArrayBuffer) {\n const [key, event, payload] = this.client.format.decode(buffer)\n if (this.options.debug) {\n console.groupCollapsed(\n `[WS] Received \"ServerSubscriptionEvent\" ${key}/${event}`,\n )\n console.log(payload)\n console.groupEnd()\n }\n const subscription = this.subscriptions.get(key)\n if (subscription) subscription.emit(event, payload)\n }\n\n protected [MessageType.ServerUnsubscribe](buffer: ArrayBuffer) {\n const [key] = this.client.format.decode(buffer)\n if (this.options.debug) {\n console.log(`[WS] Received \"ServerUnsubscribe\" ${key}`)\n }\n const subscription = this.subscriptions.get(key)\n subscription?.emit('end')\n this.subscriptions.delete(key)\n }\n}\n"],"names":["ClientTransport","ClientUpStream","Subscription","createClientDownStream","onAbort","once","MessageType","MessageTypeName","TransportType","concat","decodeNumber","encodeNumber","WsClientTransport","type","calls","subscriptions","upStreams","downStreams","upStreamId","queryParams","ws","isHealthy","checkHealthAttempts","autoreconnect","isConnected","wsFactory","encodeRpcContext","decodeRpcContext","constructor","options","WS","Map","url","WebSocket","toString","on","error","clear","addStream","blob","id","upstream","set","metadata","getStream","get","downstream","send","DownStreamPull","connect","healthCheck","getURL","accept","client","format","contentType","services","binaryType","onmessage","event","buffer","data","handler","call","slice","Uint8Array","BYTES_PER_ELEMENT","onopen","debug","console","log","origin","emit","onclose","code","undefined","Error","reason","onerror","cause","disconnect","close","rpc","signal","callId","payload","procedure","service","encodeRpc","reject","delete","RpcAbort","Promise","resolve","Rpc","setQueryParams","params","groupCollapsed","trace","groupEnd","values","stream","reader","cancel","writer","abort","subscription","unsubscribe","AbortSignal","timeout","auth","ok","fetch","e","seconds","Math","min","r","setTimeout","path","protocol","base","URL","secure","host","key","Object","entries","Array","isArray","value","searchParams","append","resolveRpc","Event","decode","decodeRpc","has","success","UpStreamPull","size","Uint32Array","buf","ArrayBuffer","done","read","UpStreamEnd","UpStreamPush","byteLength","UpStreamAbort","DownStreamPush","ready","chunk","write","DownStreamAbort","DownStreamEnd","catch","RpcSubscription","ClientUnsubscribe","encode","ServerSubscriptionEvent","ServerUnsubscribe"],"mappings":"AAAA,SAEEA,eAAe,EAGfC,cAAc,EACdC,YAAY,EACZC,sBAAsB,EACtBC,OAAO,EACPC,IAAI,QACC,gBAAe;AACtB,SAGEC,WAAW,EACXC,eAAe,EACfC,aAAa,EACbC,MAAM,EACNC,YAAY,EACZC,YAAY,QACP,gBAAe;AA2BtB,OAAO,MAAMC,0BAA0BZ;;IAM5Ba,KAAuB;IAEbC,MAAiC;IACjCC,cAA+C;IAC/CC,UAA6C;IAC7CC,YAAwD;IACjEC,WAAc;IACdC,YAAgB;IAChBC,GAAc;IACdC,UAAiB;IACjBC,oBAAuB;IACvBC,cAAsB;IACtBC,YAAmB;IAErBC,UAAmC;IACnCC,iBAAkC;IAClCC,iBAAkC;IAE1CC,YAAY,AAAiBC,OAAiC,CAAE;QAC9D,KAAK,SADsBA,UAAAA,cAlBpBhB,OAAOL,cAAcsB,EAAE,OAEbhB,QAAQ,IAAIiB,YACZhB,gBAAgB,IAAIgB,YACpBf,YAAY,IAAIe,YAChBd,cAAc,IAAIc,YAC3Bb,aAAa,QACbC,cAAc,CAAC,QAEfE,YAAY,YACZC,sBAAsB,QAEtBE,cAAc;QAStB,IAAI,CAACD,aAAa,GAAG,IAAI,CAACM,OAAO,CAACN,aAAa,IAAI;QAEnD,IAAIM,QAAQJ,SAAS,EAAE;YACrB,IAAI,CAACA,SAAS,GAAGI,QAAQJ,SAAS;QACpC,OAAO;YACL,IAAI,CAACA,SAAS,GAAG,CAACO,MAAa,IAAIC,UAAUD,IAAIE,QAAQ;QAC3D;QAGA,IAAI,CAACC,EAAE,CAAC,SAAS,CAACC,QAAU,IAAI,CAACC,KAAK,CAACD;QAEvC,IAAI,CAACV,gBAAgB,GAAG;YACtBY,WAAW,CAACC;gBACV,MAAMC,KAAK,EAAE,IAAI,CAACtB,UAAU;gBAC5B,MAAMuB,WAAW,IAAIxC,eAAeuC,IAAID;gBACxC,IAAI,CAACvB,SAAS,CAAC0B,GAAG,CAACF,IAAIC;gBACvB,OAAO;oBAAED;oBAAIG,UAAUJ,KAAKI,QAAQ;gBAAC;YACvC;YACAC,WAAW,CAACJ,KAAO,IAAI,CAACxB,SAAS,CAAC6B,GAAG,CAACL;QACxC;QAEA,IAAI,CAACb,gBAAgB,GAAG;YACtBW,WAAW,CAACE,IAAIG;gBACd,MAAMG,aAAa3C,uBAAuBwC,UAAU,IAClD,IAAI,CAACI,IAAI,CAACzC,YAAY0C,cAAc,EAAErC,aAAa6B,IAAI;gBAEzD,IAAI,CAACvB,WAAW,CAACyB,GAAG,CAACF,IAAIM;gBACzB,OAAOA,WAAWP,IAAI;YACxB;YACAK,WAAW,CAACJ,KAAO,IAAI,CAACvB,WAAW,CAAC4B,GAAG,CAACL;QAC1C;IACF;IAEA,MAAMS,UAAU;QAEd,IAAI,CAAC1B,aAAa,GAAG,IAAI,CAACM,OAAO,CAACN,aAAa,IAAI;QACnD,MAAM,IAAI,CAAC2B,WAAW;QAEtB,IAAI,CAAC9B,EAAE,GAAG,IAAI,CAACK,SAAS,CACtB,IAAI,CAAC0B,MAAM,CAAC,OAAO,MAAM;YACvB,GAAG,IAAI,CAAChC,WAAW;YACnBiC,QAAQ,IAAI,CAACC,MAAM,CAACC,MAAM,CAACC,WAAW;YACtC,gBAAgB,IAAI,CAACF,MAAM,CAACC,MAAM,CAACC,WAAW;YAC9CC,UAAU,IAAI,CAACH,MAAM,CAACG,QAAQ;QAChC;QAGF,IAAI,CAACpC,EAAE,CAACqC,UAAU,GAAG;QAErB,IAAI,CAACrC,EAAE,CAACsC,SAAS,GAAG,CAACC;YACnB,MAAMC,SAAsBD,MAAME,IAAI;YACtC,MAAMhD,OAAOH,aAAakD,QAAQ;YAClC,MAAME,UAAU,IAAI,CAACjD,KAAK;YAC1B,IAAIiD,SAAS;gBACXA,QAAQC,IAAI,CAAC,IAAI,EAAEH,OAAOI,KAAK,CAACC,WAAWC,iBAAiB,GAAG,IAAI,CAAC9C,EAAE;YACxE;QACF;QACA,IAAI,CAACA,EAAE,CAAC+C,MAAM,GAAG,CAACR;YAChB,IAAI,IAAI,CAAC9B,OAAO,CAACuC,KAAK,EACpBC,QAAQC,GAAG,CAAC,CAAC,kBAAkB,EAAE,IAAI,CAACzC,OAAO,CAAC0C,MAAM,CAAC,CAAC;YACxD,IAAI,CAAC/C,WAAW,GAAG;YACnB,IAAI,CAACgD,IAAI,CAAC;YACV,IAAI,CAAClD,mBAAmB,GAAG;QAC7B;QACA,IAAI,CAACF,EAAE,CAACqD,OAAO,GAAG,CAACd;YACjB,IAAI,CAACnC,WAAW,GAAG;YACnB,IAAI,CAACH,SAAS,GAAG;YACjB,IAAI,CAACmD,IAAI,CACP,SACAb,MAAMe,IAAI,KAAK,OACXC,YACA,IAAIC,MACF,CAAC,4BAA4B,EAAEjB,MAAMe,IAAI,CAAC,EAAE,EAAEf,MAAMkB,MAAM,CAAC,CAAC;YAIpE,IAAI,IAAI,CAACtD,aAAa,EAAE,IAAI,CAAC0B,OAAO;QACtC;QACA,IAAI,CAAC7B,EAAE,CAAC0D,OAAO,GAAG,CAACC;YACjB,IAAI,CAAC1D,SAAS,GAAG;YACjB,IAAI,CAACmD,IAAI,CAAC,SAAS,IAAII,MAAM,oBAAoB;gBAAEG;YAAM;QAC3D;QACA,MAAM1E,KAAK,IAAI,EAAE;QACjB,IAAI,IAAI,CAACwB,OAAO,CAACuC,KAAK,EACpBC,QAAQC,GAAG,CAAC,CAAC,mBAAmB,EAAE,IAAI,CAACzC,OAAO,CAAC0C,MAAM,CAAC,CAAC;QACzD,IAAI,CAACC,IAAI,CAAC;IACZ;IAEA,MAAMQ,aAA4B;QAChC,IAAI,CAACzD,aAAa,GAAG;QACrB,IAAI,CAACH,EAAE,EAAE6D;QACT,MAAM5E,KAAK,IAAI,EAAE;IACnB;IAEA,MAAM6E,IAAInB,IAA4B,EAAqC;QACzE,MAAM,EAAEoB,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAEC,SAAS,EAAEC,OAAO,EAAE,GAAGxB;QAExD,MAAMF,OAAO,IAAI,CAACR,MAAM,CAACC,MAAM,CAACkC,SAAS,CACvC;YACEJ;YACAG;YACAD;YACAD;QACF,GACA,IAAI,CAAC3D,gBAAgB;QAGvBtB,QAAQ+E,QAAQ;YACd,MAAMpB,OAAO,IAAI,CAACjD,KAAK,CAAC+B,GAAG,CAACuC;YAC5B,IAAIrB,MAAM;gBACR,MAAM,EAAE0B,MAAM,EAAE,GAAG1B;gBACnB0B,OAAO,IAAIb,MAAM;gBACjB,IAAI,CAAC9D,KAAK,CAAC4E,MAAM,CAACN;gBAClB,IAAI,CAACrC,IAAI,CAACzC,YAAYqF,QAAQ,EAAEhF,aAAayE,QAAQ;YACvD;QACF;QAEA,IAAI,CAAC,IAAI,CAAC5D,WAAW,EAAE,MAAMnB,KAAK,IAAI,EAAE;QAExC,OAAO,IAAIuF,QAAQ,CAACC,SAASJ;YAC3B,IAAI,CAAC3E,KAAK,CAAC4B,GAAG,CAAC0C,QAAQ;gBAAES;gBAASJ;YAAO;YACzC,IAAI,CAAC1C,IAAI,CAACzC,YAAYwF,GAAG,EAAEjC;QAC7B;IACF;IAEAkC,eAAeC,MAA2B,EAAE;QAC1C,IAAI,CAAC7E,WAAW,GAAG6E;IACrB;IAEUjD,KAAKlC,IAAiB,EAAE,GAAGwE,OAAsB,EAAE;QAC3D,IAAI,CAACjE,EAAE,EAAE2B,KAAKtC,OAAOE,aAAaE,MAAM,aAAawE;QACrD,IAAI,IAAI,CAACxD,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQ4B,cAAc,CAAC,CAAC,UAAU,EAAE1F,eAAe,CAACM,KAAK,CAAC,CAAC;YAC3DwD,QAAQ6B,KAAK;YACb7B,QAAQ8B,QAAQ;QAClB;IACF;IAEA,MAAgB9D,MAAMD,QAAe,IAAIwC,MAAM,oBAAoB,EAAE;QACnE,KAAK,MAAMb,QAAQ,IAAI,CAACjD,KAAK,CAACsF,MAAM,GAAI;YACtC,MAAM,EAAEX,MAAM,EAAE,GAAG1B;YACnB0B,OAAOrD;QACT;QAEA,KAAK,MAAMiE,UAAU,IAAI,CAACrF,SAAS,CAACoF,MAAM,GAAI;YAC5CC,OAAOC,MAAM,CAACC,MAAM,CAACnE;QACvB;QAEA,KAAK,MAAMiE,UAAU,IAAI,CAACpF,WAAW,CAACmF,MAAM,GAAI;YAC9CC,OAAOG,MAAM,CAACC,KAAK,CAACrE;QACtB;QAEA,KAAK,MAAMsE,gBAAgB,IAAI,CAAC3F,aAAa,CAACqF,MAAM,GAAI;YACtDM,aAAaC,WAAW;QAC1B;QAEA,IAAI,CAAC7F,KAAK,CAACuB,KAAK;QAChB,IAAI,CAACrB,SAAS,CAACqB,KAAK;QACpB,IAAI,CAACpB,WAAW,CAACoB,KAAK;QACtB,IAAI,CAACtB,aAAa,CAACsB,KAAK;IAC1B;IAEA,MAAgBa,cAAc;QAC5B,MAAO,CAAC,IAAI,CAAC7B,SAAS,CAAE;YACtB,IAAI;gBACF,MAAM8D,SAASyB,YAAYC,OAAO,CAAC;gBACnC,MAAM7E,MAAM,IAAI,CAACmB,MAAM,CAAC,WAAW,QAAQ;oBACzC2D,MAAM,IAAI,CAACzD,MAAM,CAACyD,IAAI;gBACxB;gBACA,MAAM,EAAEC,EAAE,EAAE,GAAG,MAAMC,MAAMhF,KAAK;oBAAEmD;gBAAO;gBACzC,IAAI,CAAC9D,SAAS,GAAG0F;YACnB,EAAE,OAAOE,GAAG,CAAC;YAEb,IAAI,CAAC,IAAI,CAAC5F,SAAS,EAAE;gBACnB,IAAI,CAACC,mBAAmB;gBACxB,MAAM4F,UAAUC,KAAKC,GAAG,CAAC,IAAI,CAAC9F,mBAAmB,EAAE;gBACnD,MAAM,IAAIsE,QAAQ,CAACyB,IAAMC,WAAWD,GAAGH,UAAU;YACnD;QACF;QACA,IAAI,CAAC1C,IAAI,CAAC;IACZ;IAEQrB,OACNoE,OAAO,EAAE,EACTC,QAAuB,EACvBxB,SAA8B,CAAC,CAAC,EAChC;QAEA,MAAMyB,OAAO,IAAIC,IAAI,IAAI,CAAC7F,OAAO,CAAC0C,MAAM;QACxC,MAAMoD,SAASF,KAAKD,QAAQ,KAAK;QACjC,MAAMxF,MAAM,IAAI0F,IACd,CAAC,EAAEC,SAASH,WAAW,MAAMA,SAAS,GAAG,EAAEC,KAAKG,IAAI,CAAC,CAAC,EAAEL,KAAK,CAAC;QAEhE,KAAK,IAAI,CAACM,KAAKzB,OAAO,IAAI0B,OAAOC,OAAO,CAAC/B,QAAS;YAChD,IAAI,CAACgC,MAAMC,OAAO,CAAC7B,SAASA,SAAS;gBAACA;aAAO;YAC7C,KAAK,MAAM8B,SAAS9B,OAAQ;gBAC1BpE,IAAImG,YAAY,CAACC,MAAM,CAACP,KAAKK;YAC/B;QACF;QACA,OAAOlG;IACT;IAEUqG,WAAWjD,MAAc,EAAE8C,KAA+B,EAAE;QACpE,MAAMnE,OAAO,IAAI,CAACjD,KAAK,CAAC+B,GAAG,CAACuC;QAC5B,IAAIrB,MAAM;YACR,MAAM,EAAE8B,OAAO,EAAE,GAAG9B;YACpB,IAAI,CAACjD,KAAK,CAAC4E,MAAM,CAACN;YAClBS,QAAQqC;QACV;IACF;IAEA,MAAgB,CAAC5H,YAAYgI,KAAK,CAAC,CAAC1E,MAAmB,EAAE;QACvD,MAAM,CAAC2B,SAAS5B,OAAO0B,QAAQ,GAAG,IAAI,CAAChC,MAAM,CAACC,MAAM,CAACiF,MAAM,CAAC3E;QAC5D,IAAI,IAAI,CAAC/B,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQ4B,cAAc,CAAC,CAAC,sBAAsB,EAAEV,QAAQ,CAAC,EAAE5B,MAAM,CAAC;YAClEU,QAAQC,GAAG,CAACe;YACZhB,QAAQ8B,QAAQ;QAClB;QACA,IAAI,CAAC3B,IAAI,CAAC,SAASe,SAAS5B,OAAO0B;IACrC;IAEA,MAAgB,CAAC/E,YAAYwF,GAAG,CAAC,CAAClC,MAAmB,EAAE;QACrD,MAAM,EAAEwB,MAAM,EAAEhD,KAAK,EAAEiD,OAAO,EAAE,GAAG,IAAI,CAAChC,MAAM,CAACC,MAAM,CAACkF,SAAS,CAC7D5E,QACA,IAAI,CAACjC,gBAAgB;QAGvB,IAAI,IAAI,CAACb,KAAK,CAAC2H,GAAG,CAACrD,SAAS;YAC1B,IAAI,CAACiD,UAAU,CACbjD,QACAhD,QAAQ;gBAAEsG,SAAS;gBAAOtG;YAAM,IAAI;gBAAEsG,SAAS;gBAAMR,OAAO7C;YAAQ;QAExE;IACF;IAEA,MAAgB,CAAC/E,YAAYqI,YAAY,CAAC,CAAC/E,MAAmB,EAAE;QAC9D,MAAMpB,KAAK9B,aAAakD,QAAQ;QAChC,MAAMgF,OAAOlI,aAAakD,QAAQ,UAAUiF,YAAY3E,iBAAiB;QAEzE,IAAI,IAAI,CAACrC,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQC,GAAG,CAAC,CAAC,6BAA6B,EAAE9B,GAAG,CAAC;QAClD;QAEA,MAAM6D,SAAS,IAAI,CAACrF,SAAS,CAAC6B,GAAG,CAACL;QAClC,IAAI6D,QAAQ;YACV,MAAMyC,MAAM,IAAI7E,WAAW,IAAI8E,YAAYH;YAC3C,MAAM,EAAEI,IAAI,EAAEd,KAAK,EAAE,GAAG,MAAM7B,OAAOC,MAAM,CAAC2C,IAAI,CAACH;YACjD,IAAIE,MAAM;gBACR,IAAI,CAACjG,IAAI,CAACzC,YAAY4I,WAAW,EAAEvI,aAAa6B,IAAI;YACtD,OAAO;gBACL,IAAI,CAACO,IAAI,CACPzC,YAAY6I,YAAY,EACxB1I,OACEE,aAAa6B,IAAI,WACjB0F,MAAMtE,MAAM,CAACI,KAAK,CAAC,GAAGkE,MAAMkB,UAAU;YAG5C;QACF;IACF;IAEA,MAAgB,CAAC9I,YAAY4I,WAAW,CAAC,CAACtF,MAAmB,EAAE;QAC7D,MAAMpB,KAAK9B,aAAakD,QAAQ;QAChC,IAAI,IAAI,CAAC/B,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQC,GAAG,CAAC,CAAC,4BAA4B,EAAE9B,GAAG,CAAC;QACjD;QACA,MAAM6D,SAAS,IAAI,CAACrF,SAAS,CAAC6B,GAAG,CAACL;QAClC,IAAI6D,QAAQ;YACVA,OAAOC,MAAM,CAACC,MAAM;YACpB,IAAI,CAACvF,SAAS,CAAC0E,MAAM,CAAClD;QACxB;IACF;IAEU,CAAClC,YAAY+I,aAAa,CAAC,CAACzF,MAAmB,EAAE;QACzD,MAAMpB,KAAK9B,aAAakD,QAAQ;QAChC,IAAI,IAAI,CAAC/B,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQC,GAAG,CAAC,CAAC,8BAA8B,EAAE9B,GAAG,CAAC;QACnD;QACA,MAAM6D,SAAS,IAAI,CAACrF,SAAS,CAAC6B,GAAG,CAACL;QAClC,IAAI6D,QAAQ;YACV,IAAI;gBACFA,OAAOC,MAAM,CAACC,MAAM,CAAC,IAAI3B,MAAM;YACjC,SAAU;gBACR,IAAI,CAAC5D,SAAS,CAAC0E,MAAM,CAAClD;YACxB;QACF;IACF;IAEA,MAAgB,CAAClC,YAAYgJ,cAAc,CAAC,CAAC1F,MAAmB,EAAE;QAChE,MAAMpB,KAAK9B,aAAakD,QAAQ;QAChC,IAAI,IAAI,CAAC/B,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQC,GAAG,CAAC,CAAC,+BAA+B,EAAE9B,GAAG,CAAC;QACpD;QACA,MAAM6D,SAAS,IAAI,CAACpF,WAAW,CAAC4B,GAAG,CAACL;QACpC,IAAI6D,QAAQ;YACV,IAAI;gBACF,MAAMA,OAAOG,MAAM,CAAC+C,KAAK;gBACzB,MAAMC,QAAQ5F,OAAOI,KAAK,CAAC6E,YAAY3E,iBAAiB;gBACxD,MAAMmC,OAAOG,MAAM,CAACiD,KAAK,CAAC,IAAIxF,WAAWuF;gBACzC,MAAMnD,OAAOG,MAAM,CAAC+C,KAAK;gBACzB,IAAI,CAACxG,IAAI,CAACzC,YAAY0C,cAAc,EAAErC,aAAa6B,IAAI;YACzD,EAAE,OAAOyE,GAAG;gBACV,IAAI,CAAClE,IAAI,CAACzC,YAAYoJ,eAAe,EAAE/I,aAAa6B,IAAI;gBACxD,IAAI,CAACvB,WAAW,CAACyE,MAAM,CAAClD;YAC1B;QACF;IACF;IAEA,MAAgB,CAAClC,YAAYqJ,aAAa,CAAC,CAAC/F,MAAmB,EAAE;QAC/D,MAAMpB,KAAK9B,aAAakD,QAAQ;QAChC,IAAI,IAAI,CAAC/B,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQC,GAAG,CAAC,CAAC,8BAA8B,EAAE9B,GAAG,CAAC;QACnD;QACA,MAAM6D,SAAS,IAAI,CAACpF,WAAW,CAAC4B,GAAG,CAACL;QACpC,IAAI6D,QAAQ;YACV,IAAI,CAACpF,WAAW,CAACyE,MAAM,CAAClD;YACxB6D,OAAOG,MAAM,CAACvB,KAAK,GAAG2E,KAAK,CAAC,KAAO;QACrC;IACF;IAEA,MAAgB,CAACtJ,YAAYoJ,eAAe,CAAC,CAAC9F,MAAmB,EAAE;QACjE,MAAMpB,KAAK9B,aAAakD,QAAQ;QAChC,IAAI,IAAI,CAAC/B,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQC,GAAG,CAAC,CAAC,gCAAgC,EAAE9B,GAAG,CAAC;QACrD;QACA,MAAM6D,SAAS,IAAI,CAACpF,WAAW,CAAC4B,GAAG,CAACL;QACpC,IAAI6D,QAAQ;YACV,IAAI,CAACpF,WAAW,CAACyE,MAAM,CAAClD;YACxB6D,OAAOG,MAAM,CAACC,KAAK,CAAC,IAAI7B,MAAM,sBAAsBgF,KAAK,CAAC,KAAO;QACnE;IACF;IAEU,CAACtJ,YAAYuJ,eAAe,CAAC,CAACjG,MAAmB,EAAE;QAC3D,MAAM,EACJwB,MAAM,EACNC,SAAS,CAACwC,KAAKxC,QAAQ,EACxB,GAAG,IAAI,CAAChC,MAAM,CAACC,MAAM,CAACkF,SAAS,CAAC5E,QAAQ,IAAI,CAACjC,gBAAgB;QAC9D,IAAI,IAAI,CAACb,KAAK,CAAC2H,GAAG,CAACrD,SAAS;YAC1B,MAAMsB,eAAe,IAAIxG,aAAa2H,KAAK;gBACzCnB,aAAalC,IAAI,CAAC;gBAClB,IAAI,CAACzD,aAAa,CAAC2E,MAAM,CAACmC;gBAC1B,IAAI,CAAC9E,IAAI,CACPzC,YAAYwJ,iBAAiB,EAC7B,IAAI,CAACzG,MAAM,CAACC,MAAM,CAACyG,MAAM,CAAC;oBAAClC;iBAAI;YAEnC;YACA,IAAI,CAAC9G,aAAa,CAAC2B,GAAG,CAACmF,KAAKnB;YAC5B,IAAI,CAAC2B,UAAU,CAACjD,QAAQ;gBACtBsD,SAAS;gBACTR,OAAO;oBAAE7C;oBAASqB;gBAAa;YACjC;QACF;IACF;IAEU,CAACpG,YAAY0J,uBAAuB,CAAC,CAACpG,MAAmB,EAAE;QACnE,MAAM,CAACiE,KAAKlE,OAAO0B,QAAQ,GAAG,IAAI,CAAChC,MAAM,CAACC,MAAM,CAACiF,MAAM,CAAC3E;QACxD,IAAI,IAAI,CAAC/B,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQ4B,cAAc,CACpB,CAAC,wCAAwC,EAAE4B,IAAI,CAAC,EAAElE,MAAM,CAAC;YAE3DU,QAAQC,GAAG,CAACe;YACZhB,QAAQ8B,QAAQ;QAClB;QACA,MAAMO,eAAe,IAAI,CAAC3F,aAAa,CAAC8B,GAAG,CAACgF;QAC5C,IAAInB,cAAcA,aAAalC,IAAI,CAACb,OAAO0B;IAC7C;IAEU,CAAC/E,YAAY2J,iBAAiB,CAAC,CAACrG,MAAmB,EAAE;QAC7D,MAAM,CAACiE,IAAI,GAAG,IAAI,CAACxE,MAAM,CAACC,MAAM,CAACiF,MAAM,CAAC3E;QACxC,IAAI,IAAI,CAAC/B,OAAO,CAACuC,KAAK,EAAE;YACtBC,QAAQC,GAAG,CAAC,CAAC,kCAAkC,EAAEuD,IAAI,CAAC;QACxD;QACA,MAAMnB,eAAe,IAAI,CAAC3F,aAAa,CAAC8B,GAAG,CAACgF;QAC5CnB,cAAclC,KAAK;QACnB,IAAI,CAACzD,aAAa,CAAC2E,MAAM,CAACmC;IAC5B;AACF"}
1
+ {"version":3,"sources":["../../index.ts"],"sourcesContent":["import {\n type BaseClientFormat,\n EventEmitter,\n type ProtocolTransport,\n} from '@nmtjs/protocol/client'\nimport {\n type ClientMessageType,\n ServerMessageType,\n concat,\n decodeNumber,\n encodeNumber,\n} from '@nmtjs/protocol/common'\n\nexport type WebSocketClientTransportOptions = {\n /**\n * The origin of the server\n * @example 'http://localhost:3000'\n */\n origin: string\n /**\n * Whether to autoreconnect on close\n * @default true\n */\n autoreconnect?: boolean\n /**\n * Custom WebSocket class\n * @default globalThis.WebSocket\n */\n wsFactory?: (url: URL) => WebSocket\n\n debug?: boolean\n}\n\nexport class WebSocketClientTransport\n extends EventEmitter\n implements ProtocolTransport\n{\n #webSocket: WebSocket | null = null\n #connecting: Promise<void> | null = null\n\n constructor(private readonly options: WebSocketClientTransportOptions) {\n super()\n }\n\n connect(\n auth: string | undefined = undefined,\n contentType: BaseClientFormat['contentType'],\n ): Promise<void> {\n const wsUrl = new URL(this.options.origin)\n wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:'\n wsUrl.pathname = '/api'\n wsUrl.searchParams.set('content-type', contentType)\n wsUrl.searchParams.set('accept', contentType)\n if (auth) wsUrl.searchParams.set('auth', auth)\n\n const ws =\n this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString())\n\n ws.binaryType = 'arraybuffer'\n\n ws.addEventListener('message', ({ data }) => {\n const buffer: ArrayBuffer = data\n const type = decodeNumber(buffer, 'Uint8')\n if (type in ServerMessageType) {\n this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT))\n }\n })\n\n this.#webSocket = ws\n\n return new Promise((resolve, reject) => {\n ws.addEventListener(\n 'open',\n () => {\n this.emit('connect')\n resolve()\n },\n { once: true },\n )\n\n ws.addEventListener(\n 'error',\n (event) => reject(new Error('WebSocket error', { cause: event })),\n { once: true },\n )\n\n ws.addEventListener(\n 'close',\n (event) => {\n this.emit('disconnect')\n this.#webSocket = null\n if (this.options.autoreconnect === true) {\n setTimeout(() => this.connect(auth, contentType), 1000)\n }\n },\n { once: true },\n )\n })\n }\n\n async disconnect(): Promise<void> {\n if (this.#webSocket === null) return\n this.#webSocket!.close()\n return _once(this.#webSocket, 'close')\n }\n\n async send(\n messageType: ClientMessageType,\n buffer: ArrayBuffer,\n ): Promise<void> {\n if (this.#connecting) await this.#connecting\n this.#webSocket!.send(concat(encodeNumber(messageType, 'Uint8'), buffer))\n }\n}\n\nfunction _once(target: EventTarget, event: string) {\n return new Promise<void>((resolve) => {\n target.addEventListener(event, () => resolve(), { once: true })\n })\n}\n"],"names":["EventEmitter","ServerMessageType","concat","decodeNumber","encodeNumber","WebSocketClientTransport","constructor","options","connect","auth","undefined","contentType","wsUrl","URL","origin","protocol","pathname","searchParams","set","ws","wsFactory","WebSocket","toString","binaryType","addEventListener","data","buffer","type","emit","slice","Uint8Array","BYTES_PER_ELEMENT","Promise","resolve","reject","once","event","Error","cause","autoreconnect","setTimeout","disconnect","close","_once","send","messageType","target"],"mappings":"AAAA,SAEEA,YAAY,QAEP,yBAAwB;AAC/B,SAEEC,iBAAiB,EACjBC,MAAM,EACNC,YAAY,EACZC,YAAY,QACP,yBAAwB;AAsB/B,OAAO,MAAMC,iCACHL;;IAGR,CAAA,SAAU,CAAyB;IACnC,CAAA,UAAW,CAA6B;IAExCM,YAAY,AAAiBC,OAAwC,CAAE;QACrE,KAAK,SADsBA,UAAAA,cAH7B,CAAA,SAAU,GAAqB,WAC/B,CAAA,UAAW,GAAyB;IAIpC;IAEAC,QACEC,OAA2BC,SAAS,EACpCC,WAA4C,EAC7B;QACf,MAAMC,QAAQ,IAAIC,IAAI,IAAI,CAACN,OAAO,CAACO,MAAM;QACzCF,MAAMG,QAAQ,GAAGH,MAAMG,QAAQ,KAAK,WAAW,SAAS;QACxDH,MAAMI,QAAQ,GAAG;QACjBJ,MAAMK,YAAY,CAACC,GAAG,CAAC,gBAAgBP;QACvCC,MAAMK,YAAY,CAACC,GAAG,CAAC,UAAUP;QACjC,IAAIF,MAAMG,MAAMK,YAAY,CAACC,GAAG,CAAC,QAAQT;QAEzC,MAAMU,KACJ,IAAI,CAACZ,OAAO,CAACa,SAAS,GAAGR,UAAU,IAAIS,UAAUT,MAAMU,QAAQ;QAEjEH,GAAGI,UAAU,GAAG;QAEhBJ,GAAGK,gBAAgB,CAAC,WAAW,CAAC,EAAEC,IAAI,EAAE;YACtC,MAAMC,SAAsBD;YAC5B,MAAME,OAAOxB,aAAauB,QAAQ;YAClC,IAAIC,QAAQ1B,mBAAmB;gBAC7B,IAAI,CAAC2B,IAAI,CAAC,CAAC,EAAED,KAAK,CAAC,EAAED,OAAOG,KAAK,CAACC,WAAWC,iBAAiB;YAChE;QACF;QAEA,IAAI,CAAC,CAAA,SAAU,GAAGZ;QAElB,OAAO,IAAIa,QAAQ,CAACC,SAASC;YAC3Bf,GAAGK,gBAAgB,CACjB,QACA;gBACE,IAAI,CAACI,IAAI,CAAC;gBACVK;YACF,GACA;gBAAEE,MAAM;YAAK;YAGfhB,GAAGK,gBAAgB,CACjB,SACA,CAACY,QAAUF,OAAO,IAAIG,MAAM,mBAAmB;oBAAEC,OAAOF;gBAAM,KAC9D;gBAAED,MAAM;YAAK;YAGfhB,GAAGK,gBAAgB,CACjB,SACA,CAACY;gBACC,IAAI,CAACR,IAAI,CAAC;gBACV,IAAI,CAAC,CAAA,SAAU,GAAG;gBAClB,IAAI,IAAI,CAACrB,OAAO,CAACgC,aAAa,KAAK,MAAM;oBACvCC,WAAW,IAAM,IAAI,CAAChC,OAAO,CAACC,MAAME,cAAc;gBACpD;YACF,GACA;gBAAEwB,MAAM;YAAK;QAEjB;IACF;IAEA,MAAMM,aAA4B;QAChC,IAAI,IAAI,CAAC,CAAA,SAAU,KAAK,MAAM;QAC9B,IAAI,CAAC,CAAA,SAAU,CAAEC,KAAK;QACtB,OAAOC,MAAM,IAAI,CAAC,CAAA,SAAU,EAAE;IAChC;IAEA,MAAMC,KACJC,WAA8B,EAC9BnB,MAAmB,EACJ;QACf,IAAI,IAAI,CAAC,CAAA,UAAW,EAAE,MAAM,IAAI,CAAC,CAAA,UAAW;QAC5C,IAAI,CAAC,CAAA,SAAU,CAAEkB,IAAI,CAAC1C,OAAOE,aAAayC,aAAa,UAAUnB;IACnE;AACF;AAEA,SAASiB,MAAMG,MAAmB,EAAEV,KAAa;IAC/C,OAAO,IAAIJ,QAAc,CAACC;QACxBa,OAAOtB,gBAAgB,CAACY,OAAO,IAAMH,WAAW;YAAEE,MAAM;QAAK;IAC/D;AACF"}
package/index.ts CHANGED
@@ -1,26 +1,17 @@
1
1
  import {
2
- type ClientDownStreamWrapper,
3
- ClientTransport,
4
- type ClientTransportRpcCall,
5
- type ClientTransportRpcResult,
6
- ClientUpStream,
7
- Subscription,
8
- createClientDownStream,
9
- onAbort,
10
- once,
11
- } from '@nmtjs/client'
2
+ type BaseClientFormat,
3
+ EventEmitter,
4
+ type ProtocolTransport,
5
+ } from '@nmtjs/protocol/client'
12
6
  import {
13
- type DecodeRpcContext,
14
- type EncodeRpcContext,
15
- MessageType,
16
- MessageTypeName,
17
- TransportType,
7
+ type ClientMessageType,
8
+ ServerMessageType,
18
9
  concat,
19
10
  decodeNumber,
20
11
  encodeNumber,
21
- } from '@nmtjs/common'
12
+ } from '@nmtjs/protocol/common'
22
13
 
23
- export type WsClientTransportOptions = {
14
+ export type WebSocketClientTransportOptions = {
24
15
  /**
25
16
  * The origin of the server
26
17
  * @example 'http://localhost:3000'
@@ -40,412 +31,90 @@ export type WsClientTransportOptions = {
40
31
  debug?: boolean
41
32
  }
42
33
 
43
- type WsCall = {
44
- resolve: (value: ClientTransportRpcResult) => void
45
- reject: (error: Error) => void
46
- }
47
-
48
- export class WsClientTransport extends ClientTransport<{
49
- open: []
50
- close: [Error?]
51
- connect: []
52
- healthy: []
53
- }> {
54
- readonly type = TransportType.WS
55
-
56
- protected readonly calls = new Map<number, WsCall>()
57
- protected readonly subscriptions = new Map<number, Subscription>()
58
- protected readonly upStreams = new Map<number, ClientUpStream>()
59
- protected readonly downStreams = new Map<number, ClientDownStreamWrapper>()
60
- protected upStreamId = 0
61
- protected queryParams = {}
62
- protected ws?: WebSocket
63
- protected isHealthy = false
64
- protected checkHealthAttempts = 0
65
- protected autoreconnect: boolean
66
- protected isConnected = false
34
+ export class WebSocketClientTransport
35
+ extends EventEmitter
36
+ implements ProtocolTransport
37
+ {
38
+ #webSocket: WebSocket | null = null
39
+ #connecting: Promise<void> | null = null
67
40
 
68
- private wsFactory!: (url: URL) => WebSocket
69
- private encodeRpcContext: EncodeRpcContext
70
- private decodeRpcContext: DecodeRpcContext
71
-
72
- constructor(private readonly options: WsClientTransportOptions) {
41
+ constructor(private readonly options: WebSocketClientTransportOptions) {
73
42
  super()
74
-
75
- this.autoreconnect = this.options.autoreconnect ?? true
76
-
77
- if (options.wsFactory) {
78
- this.wsFactory = options.wsFactory
79
- } else {
80
- this.wsFactory = (url: URL) => new WebSocket(url.toString())
81
- }
82
-
83
- // TODO: wtf is this for?
84
- this.on('close', (error) => this.clear(error))
85
-
86
- this.encodeRpcContext = {
87
- addStream: (blob) => {
88
- const id = ++this.upStreamId
89
- const upstream = new ClientUpStream(id, blob)
90
- this.upStreams.set(id, upstream)
91
- return { id, metadata: blob.metadata }
92
- },
93
- getStream: (id) => this.upStreams.get(id),
94
- }
95
-
96
- this.decodeRpcContext = {
97
- addStream: (id, metadata) => {
98
- const downstream = createClientDownStream(metadata, () =>
99
- this.send(MessageType.DownStreamPull, encodeNumber(id, 'Uint32')),
100
- )
101
- this.downStreams.set(id, downstream)
102
- return downstream.blob
103
- },
104
- getStream: (id) => this.downStreams.get(id),
105
- }
106
43
  }
107
44
 
108
- async connect() {
109
- // reset default autoreconnect value
110
- this.autoreconnect = this.options.autoreconnect ?? true
111
- await this.healthCheck()
45
+ connect(
46
+ auth: string | undefined = undefined,
47
+ contentType: BaseClientFormat['contentType'],
48
+ ): Promise<void> {
49
+ const wsUrl = new URL(this.options.origin)
50
+ wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:'
51
+ wsUrl.pathname = '/api'
52
+ wsUrl.searchParams.set('content-type', contentType)
53
+ wsUrl.searchParams.set('accept', contentType)
54
+ if (auth) wsUrl.searchParams.set('auth', auth)
112
55
 
113
- this.ws = this.wsFactory(
114
- this.getURL('api', 'ws', {
115
- ...this.queryParams,
116
- accept: this.client.format.contentType,
117
- 'content-type': this.client.format.contentType,
118
- services: this.client.services,
119
- }),
120
- )
56
+ const ws =
57
+ this.options.wsFactory?.(wsUrl) ?? new WebSocket(wsUrl.toString())
121
58
 
122
- this.ws.binaryType = 'arraybuffer'
59
+ ws.binaryType = 'arraybuffer'
123
60
 
124
- this.ws.onmessage = (event) => {
125
- const buffer: ArrayBuffer = event.data
61
+ ws.addEventListener('message', ({ data }) => {
62
+ const buffer: ArrayBuffer = data
126
63
  const type = decodeNumber(buffer, 'Uint8')
127
- const handler = this[type]
128
- if (handler) {
129
- handler.call(this, buffer.slice(Uint8Array.BYTES_PER_ELEMENT), this.ws)
130
- }
131
- }
132
- this.ws.onopen = (event) => {
133
- if (this.options.debug)
134
- console.log(`[WS] Connected to ${this.options.origin}`)
135
- this.isConnected = true
136
- this.emit('open')
137
- this.checkHealthAttempts = 0
138
- }
139
- this.ws.onclose = (event) => {
140
- this.isConnected = false
141
- this.isHealthy = false
142
- this.emit(
143
- 'close',
144
- event.code === 1000
145
- ? undefined
146
- : new Error(
147
- `Connection closed with code ${event.code}: ${event.reason}`,
148
- ),
149
- )
150
- // FIXME: cleanup calls, streams, subscriptions
151
- if (this.autoreconnect) this.connect()
152
- }
153
- this.ws.onerror = (cause) => {
154
- this.isHealthy = false
155
- this.emit('close', new Error('Connection error', { cause }))
156
- }
157
- await once(this, 'open')
158
- if (this.options.debug)
159
- console.log(`2[WS] Connected to ${this.options.origin}`)
160
- this.emit('connect')
161
- }
162
-
163
- async disconnect(): Promise<void> {
164
- this.autoreconnect = false
165
- this.ws?.close()
166
- await once(this, 'close')
167
- }
168
-
169
- async rpc(call: ClientTransportRpcCall): Promise<ClientTransportRpcResult> {
170
- const { signal, callId, payload, procedure, service } = call
171
-
172
- const data = this.client.format.encodeRpc(
173
- {
174
- callId,
175
- service,
176
- procedure,
177
- payload,
178
- },
179
- this.encodeRpcContext,
180
- )
181
-
182
- onAbort(signal, () => {
183
- const call = this.calls.get(callId)
184
- if (call) {
185
- const { reject } = call
186
- reject(new Error('Request aborted'))
187
- this.calls.delete(callId)
188
- this.send(MessageType.RpcAbort, encodeNumber(callId, 'Uint32'))
64
+ if (type in ServerMessageType) {
65
+ this.emit(`${type}`, buffer.slice(Uint8Array.BYTES_PER_ELEMENT))
189
66
  }
190
67
  })
191
68
 
192
- if (!this.isConnected) await once(this, 'connect')
69
+ this.#webSocket = ws
193
70
 
194
71
  return new Promise((resolve, reject) => {
195
- this.calls.set(callId, { resolve, reject })
196
- this.send(MessageType.Rpc, data)
197
- })
198
- }
199
-
200
- setQueryParams(params: Record<string, any>) {
201
- this.queryParams = params
202
- }
203
-
204
- protected send(type: MessageType, ...payload: ArrayBuffer[]) {
205
- this.ws?.send(concat(encodeNumber(type, 'Uint8'), ...payload))
206
- if (this.options.debug) {
207
- console.groupCollapsed(`[WS] Sent ${MessageTypeName[type]}`)
208
- console.trace()
209
- console.groupEnd()
210
- }
211
- }
212
-
213
- protected async clear(error: Error = new Error('Connection closed')) {
214
- for (const call of this.calls.values()) {
215
- const { reject } = call
216
- reject(error)
217
- }
218
-
219
- for (const stream of this.upStreams.values()) {
220
- stream.reader.cancel(error)
221
- }
222
-
223
- for (const stream of this.downStreams.values()) {
224
- stream.writer.abort(error)
225
- }
226
-
227
- for (const subscription of this.subscriptions.values()) {
228
- subscription.unsubscribe()
229
- }
230
-
231
- this.calls.clear()
232
- this.upStreams.clear()
233
- this.downStreams.clear()
234
- this.subscriptions.clear()
235
- }
236
-
237
- protected async healthCheck() {
238
- while (!this.isHealthy) {
239
- try {
240
- const signal = AbortSignal.timeout(10000)
241
- const url = this.getURL('healthy', 'http', {
242
- auth: this.client.auth,
243
- })
244
- const { ok } = await fetch(url, { signal })
245
- this.isHealthy = ok
246
- } catch (e) {}
247
-
248
- if (!this.isHealthy) {
249
- this.checkHealthAttempts++
250
- const seconds = Math.min(this.checkHealthAttempts, 15)
251
- await new Promise((r) => setTimeout(r, seconds * 1000))
252
- }
253
- }
254
- this.emit('healthy')
255
- }
256
-
257
- private getURL(
258
- path = '',
259
- protocol: 'ws' | 'http',
260
- params: Record<string, any> = {},
261
- ) {
262
- // TODO: add custom path support?
263
- const base = new URL(this.options.origin)
264
- const secure = base.protocol === 'https:'
265
- const url = new URL(
266
- `${secure ? protocol + 's' : protocol}://${base.host}/${path}`,
267
- )
268
- for (let [key, values] of Object.entries(params)) {
269
- if (!Array.isArray(values)) values = [values]
270
- for (const value of values) {
271
- url.searchParams.append(key, value)
272
- }
273
- }
274
- return url
275
- }
276
-
277
- protected resolveRpc(callId: number, value: ClientTransportRpcResult) {
278
- const call = this.calls.get(callId)
279
- if (call) {
280
- const { resolve } = call
281
- this.calls.delete(callId)
282
- resolve(value)
283
- }
284
- }
285
-
286
- protected async [MessageType.Event](buffer: ArrayBuffer) {
287
- const [service, event, payload] = this.client.format.decode(buffer)
288
- if (this.options.debug) {
289
- console.groupCollapsed(`[WS] Received "Event" ${service}/${event}`)
290
- console.log(payload)
291
- console.groupEnd()
292
- }
293
- this.emit('event', service, event, payload)
294
- }
295
-
296
- protected async [MessageType.Rpc](buffer: ArrayBuffer) {
297
- const { callId, error, payload } = this.client.format.decodeRpc(
298
- buffer,
299
- this.decodeRpcContext,
300
- )
301
-
302
- if (this.calls.has(callId)) {
303
- this.resolveRpc(
304
- callId,
305
- error ? { success: false, error } : { success: true, value: payload },
72
+ ws.addEventListener(
73
+ 'open',
74
+ () => {
75
+ this.emit('connect')
76
+ resolve()
77
+ },
78
+ { once: true },
306
79
  )
307
- }
308
- }
309
-
310
- protected async [MessageType.UpStreamPull](buffer: ArrayBuffer) {
311
- const id = decodeNumber(buffer, 'Uint32')
312
- const size = decodeNumber(buffer, 'Uint32', Uint32Array.BYTES_PER_ELEMENT)
313
-
314
- if (this.options.debug) {
315
- console.log(`[WS] Received "UpStreamPull" ${id}`)
316
- }
317
80
 
318
- const stream = this.upStreams.get(id)
319
- if (stream) {
320
- const buf = new Uint8Array(new ArrayBuffer(size))
321
- const { done, value } = await stream.reader.read(buf)
322
- if (done) {
323
- this.send(MessageType.UpStreamEnd, encodeNumber(id, 'Uint32'))
324
- } else {
325
- this.send(
326
- MessageType.UpStreamPush,
327
- concat(
328
- encodeNumber(id, 'Uint32'),
329
- value.buffer.slice(0, value.byteLength),
330
- ),
331
- )
332
- }
333
- }
334
- }
335
-
336
- protected async [MessageType.UpStreamEnd](buffer: ArrayBuffer) {
337
- const id = decodeNumber(buffer, 'Uint32')
338
- if (this.options.debug) {
339
- console.log(`[WS] Received "UpStreamEnd" ${id}`)
340
- }
341
- const stream = this.upStreams.get(id)
342
- if (stream) {
343
- stream.reader.cancel()
344
- this.upStreams.delete(id)
345
- }
346
- }
347
-
348
- protected [MessageType.UpStreamAbort](buffer: ArrayBuffer) {
349
- const id = decodeNumber(buffer, 'Uint32')
350
- if (this.options.debug) {
351
- console.log(`[WS] Received "UpStreamAbort" ${id}`)
352
- }
353
- const stream = this.upStreams.get(id)
354
- if (stream) {
355
- try {
356
- stream.reader.cancel(new Error('Aborted by server'))
357
- } finally {
358
- this.upStreams.delete(id)
359
- }
360
- }
361
- }
362
-
363
- protected async [MessageType.DownStreamPush](buffer: ArrayBuffer) {
364
- const id = decodeNumber(buffer, 'Uint32')
365
- if (this.options.debug) {
366
- console.log(`[WS] Received "DownStreamPush" ${id}`)
367
- }
368
- const stream = this.downStreams.get(id)
369
- if (stream) {
370
- try {
371
- await stream.writer.ready
372
- const chunk = buffer.slice(Uint32Array.BYTES_PER_ELEMENT)
373
- await stream.writer.write(new Uint8Array(chunk))
374
- await stream.writer.ready
375
- this.send(MessageType.DownStreamPull, encodeNumber(id, 'Uint32'))
376
- } catch (e) {
377
- this.send(MessageType.DownStreamAbort, encodeNumber(id, 'Uint32'))
378
- this.downStreams.delete(id)
379
- }
380
- }
381
- }
382
-
383
- protected async [MessageType.DownStreamEnd](buffer: ArrayBuffer) {
384
- const id = decodeNumber(buffer, 'Uint32')
385
- if (this.options.debug) {
386
- console.log(`[WS] Received "DownStreamEnd" ${id}`)
387
- }
388
- const stream = this.downStreams.get(id)
389
- if (stream) {
390
- this.downStreams.delete(id)
391
- stream.writer.close().catch(() => {})
392
- }
393
- }
81
+ ws.addEventListener(
82
+ 'error',
83
+ (event) => reject(new Error('WebSocket error', { cause: event })),
84
+ { once: true },
85
+ )
394
86
 
395
- protected async [MessageType.DownStreamAbort](buffer: ArrayBuffer) {
396
- const id = decodeNumber(buffer, 'Uint32')
397
- if (this.options.debug) {
398
- console.log(`[WS] Received "DownStreamAbort" ${id}`)
399
- }
400
- const stream = this.downStreams.get(id)
401
- if (stream) {
402
- this.downStreams.delete(id)
403
- stream.writer.abort(new Error('Aborted by server')).catch(() => {})
404
- }
87
+ ws.addEventListener(
88
+ 'close',
89
+ (event) => {
90
+ this.emit('disconnect')
91
+ this.#webSocket = null
92
+ if (this.options.autoreconnect === true) {
93
+ setTimeout(() => this.connect(auth, contentType), 1000)
94
+ }
95
+ },
96
+ { once: true },
97
+ )
98
+ })
405
99
  }
406
100
 
407
- protected [MessageType.RpcSubscription](buffer: ArrayBuffer) {
408
- const {
409
- callId,
410
- payload: [key, payload],
411
- } = this.client.format.decodeRpc(buffer, this.decodeRpcContext)
412
- if (this.calls.has(callId)) {
413
- const subscription = new Subscription(key, () => {
414
- subscription.emit('end')
415
- this.subscriptions.delete(key)
416
- this.send(
417
- MessageType.ClientUnsubscribe,
418
- this.client.format.encode([key]),
419
- )
420
- })
421
- this.subscriptions.set(key, subscription)
422
- this.resolveRpc(callId, {
423
- success: true,
424
- value: { payload, subscription },
425
- })
426
- }
101
+ async disconnect(): Promise<void> {
102
+ if (this.#webSocket === null) return
103
+ this.#webSocket!.close()
104
+ return _once(this.#webSocket, 'close')
427
105
  }
428
106
 
429
- protected [MessageType.ServerSubscriptionEvent](buffer: ArrayBuffer) {
430
- const [key, event, payload] = this.client.format.decode(buffer)
431
- if (this.options.debug) {
432
- console.groupCollapsed(
433
- `[WS] Received "ServerSubscriptionEvent" ${key}/${event}`,
434
- )
435
- console.log(payload)
436
- console.groupEnd()
437
- }
438
- const subscription = this.subscriptions.get(key)
439
- if (subscription) subscription.emit(event, payload)
107
+ async send(
108
+ messageType: ClientMessageType,
109
+ buffer: ArrayBuffer,
110
+ ): Promise<void> {
111
+ if (this.#connecting) await this.#connecting
112
+ this.#webSocket!.send(concat(encodeNumber(messageType, 'Uint8'), buffer))
440
113
  }
114
+ }
441
115
 
442
- protected [MessageType.ServerUnsubscribe](buffer: ArrayBuffer) {
443
- const [key] = this.client.format.decode(buffer)
444
- if (this.options.debug) {
445
- console.log(`[WS] Received "ServerUnsubscribe" ${key}`)
446
- }
447
- const subscription = this.subscriptions.get(key)
448
- subscription?.emit('end')
449
- this.subscriptions.delete(key)
450
- }
116
+ function _once(target: EventTarget, event: string) {
117
+ return new Promise<void>((resolve) => {
118
+ target.addEventListener(event, () => resolve(), { once: true })
119
+ })
451
120
  }
package/package.json CHANGED
@@ -7,13 +7,10 @@
7
7
  "types": "./index.ts"
8
8
  }
9
9
  },
10
- "peerDependencies": {
11
- "@nmtjs/client": "^0.4.2",
12
- "@nmtjs/common": "^0.4.2"
13
- },
14
- "devDependencies": {
15
- "@nmtjs/client": "^0.4.2",
16
- "@nmtjs/common": "^0.4.2"
10
+ "dependencies": {
11
+ "@nmtjs/client": "^0.6.1",
12
+ "@nmtjs/common": "^0.6.1",
13
+ "@nmtjs/protocol": "^0.6.1"
17
14
  },
18
15
  "files": [
19
16
  "index.ts",
@@ -22,7 +19,7 @@
22
19
  "LICENSE.md",
23
20
  "README.md"
24
21
  ],
25
- "version": "0.1.8",
22
+ "version": "0.2.0",
26
23
  "scripts": {
27
24
  "build": "neemata-build -p neutral ./index.ts",
28
25
  "type-check": "tsc --noEmit"