@litert/televoke 1.0.0-0 → 1.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.
Files changed (77) hide show
  1. package/docs/Televoke_v2.md +378 -0
  2. package/lib/client/JsonApiClient.impl.js +2 -2
  3. package/lib/client/JsonApiClient.impl.js.map +1 -1
  4. package/lib/client/LegacyHttpClient.impl.d.ts +17 -16
  5. package/lib/client/LegacyHttpClient.impl.d.ts.map +1 -1
  6. package/lib/client/LegacyHttpClient.impl.js +25 -11
  7. package/lib/client/LegacyHttpClient.impl.js.map +1 -1
  8. package/lib/server/Server.decl.d.ts +2 -1
  9. package/lib/server/Server.decl.d.ts.map +1 -1
  10. package/lib/server/Server.impl.d.ts +1 -0
  11. package/lib/server/Server.impl.d.ts.map +1 -1
  12. package/lib/server/Server.impl.js +8 -1
  13. package/lib/server/Server.impl.js.map +1 -1
  14. package/lib/server/SimpleJsonApiRouter.d.ts +3 -1
  15. package/lib/server/SimpleJsonApiRouter.d.ts.map +1 -1
  16. package/lib/server/SimpleJsonApiRouter.js +18 -8
  17. package/lib/server/SimpleJsonApiRouter.js.map +1 -1
  18. package/lib/shared/BinaryStream.d.ts +5 -1
  19. package/lib/shared/BinaryStream.d.ts.map +1 -1
  20. package/lib/shared/BinaryStream.js +9 -2
  21. package/lib/shared/BinaryStream.js.map +1 -1
  22. package/lib/shared/BinaryStreamManager.js.map +1 -1
  23. package/lib/shared/Channel.impl.d.ts +1 -1
  24. package/lib/shared/Channel.impl.d.ts.map +1 -1
  25. package/lib/shared/Channel.impl.js +21 -6
  26. package/lib/shared/Channel.impl.js.map +1 -1
  27. package/lib/shared/Encodings/v1/EncoderV1.js.map +1 -1
  28. package/lib/shared/Encodings/v2/DecoderV2.d.ts.map +1 -1
  29. package/lib/shared/Encodings/v2/DecoderV2.js +3 -1
  30. package/lib/shared/Encodings/v2/DecoderV2.js.map +1 -1
  31. package/lib/shared/Encodings/v2/EncoderV2.d.ts.map +1 -1
  32. package/lib/shared/Encodings/v2/EncoderV2.js +7 -6
  33. package/lib/shared/Encodings/v2/EncoderV2.js.map +1 -1
  34. package/lib/shared/Encodings/v2/Encoding.v2.decl.d.ts +4 -0
  35. package/lib/shared/Encodings/v2/Encoding.v2.decl.d.ts.map +1 -1
  36. package/lib/shared/Errors.d.ts +3 -0
  37. package/lib/shared/Errors.d.ts.map +1 -1
  38. package/lib/shared/Errors.js +3 -0
  39. package/lib/shared/Errors.js.map +1 -1
  40. package/lib/shared/Shared.decl.d.ts +21 -4
  41. package/lib/shared/Shared.decl.d.ts.map +1 -1
  42. package/lib/shared/Utils.js.map +1 -1
  43. package/lib/transporters/http-listener/HttpListener.d.ts.map +1 -1
  44. package/lib/transporters/http-listener/HttpListener.js +1 -0
  45. package/lib/transporters/http-listener/HttpListener.js.map +1 -1
  46. package/lib/transporters/http-listener/HttpUnixSocketListener.js.map +1 -1
  47. package/lib/transporters/http-listener/HttpsListener.js.map +1 -1
  48. package/lib/transporters/legacy-http/LegacyHttp.Server.d.ts.map +1 -1
  49. package/lib/transporters/legacy-http/LegacyHttp.Server.js +22 -15
  50. package/lib/transporters/legacy-http/LegacyHttp.Server.js.map +1 -1
  51. package/lib/transporters/legacy-http/LegacyHttp.Transporter.js.map +1 -1
  52. package/lib/transporters/lwdfx/LwDFX.Tcp.Server.js.map +1 -1
  53. package/lib/transporters/lwdfx/LwDFX.Tls.Server.js.map +1 -1
  54. package/lib/transporters/lwdfx/LwDFX.Transporter.js.map +1 -1
  55. package/lib/transporters/lwdfx/LwDFX.UnixSocket.Server.js.map +1 -1
  56. package/lib/transporters/memory/Memory.Impl.js.map +1 -1
  57. package/lib/transporters/websocket/WebSocket.Server.d.ts +11 -1
  58. package/lib/transporters/websocket/WebSocket.Server.d.ts.map +1 -1
  59. package/lib/transporters/websocket/WebSocket.Server.js +4 -3
  60. package/lib/transporters/websocket/WebSocket.Server.js.map +1 -1
  61. package/lib/transporters/websocket/WebSocket.Transporter.js.map +1 -1
  62. package/package.json +8 -8
  63. package/src/lib/client/JsonApiClient.impl.ts +2 -2
  64. package/src/lib/client/LegacyHttpClient.impl.ts +51 -31
  65. package/src/lib/server/Server.decl.ts +3 -1
  66. package/src/lib/server/Server.impl.ts +10 -1
  67. package/src/lib/server/SimpleJsonApiRouter.ts +22 -10
  68. package/src/lib/shared/BinaryStream.ts +12 -2
  69. package/src/lib/shared/Channel.impl.ts +24 -7
  70. package/src/lib/shared/Encodings/v2/DecoderV2.ts +3 -1
  71. package/src/lib/shared/Encodings/v2/EncoderV2.ts +7 -6
  72. package/src/lib/shared/Encodings/v2/Encoding.v2.decl.ts +5 -0
  73. package/src/lib/shared/Errors.ts +3 -0
  74. package/src/lib/shared/Shared.decl.ts +23 -4
  75. package/src/lib/transporters/http-listener/HttpListener.ts +1 -0
  76. package/src/lib/transporters/legacy-http/LegacyHttp.Server.ts +38 -21
  77. package/src/lib/transporters/websocket/WebSocket.Server.ts +16 -1
@@ -36,7 +36,8 @@ class TvLegacyHttpClient extends EventEmitter implements C.IClient<any> {
36
36
  private readonly _request: (opts: any, cb: (resp: $Http.IncomingMessage) => void) => $Http.ClientRequest,
37
37
  private readonly _opts: $Https.RequestOptions,
38
38
  private readonly _agent: $Http.Agent,
39
- private readonly _apiNameWrapper?: (name: string) => string
39
+ private readonly _retryOnConnReset: boolean = true,
40
+ private readonly _apiNameWrapper?: (name: string) => string,
40
41
  ) {
41
42
 
42
43
  super();
@@ -75,7 +76,26 @@ class TvLegacyHttpClient extends EventEmitter implements C.IClient<any> {
75
76
  return Promise.reject(new Shared.errors.cmd_not_impl());
76
77
  }
77
78
 
78
- public invoke(api: any, ...args: any[]): Promise<any> {
79
+ public async invoke(api: any, ...args: any[]): Promise<any> {
80
+
81
+ try {
82
+
83
+ return await this._invoke(api, ...args);
84
+ }
85
+ catch (e) {
86
+
87
+ if (this._retryOnConnReset && (e as any)?.code === 'ECONNRESET') {
88
+
89
+ return this._invoke(api, ...args);
90
+ }
91
+ else {
92
+
93
+ throw e;
94
+ }
95
+ }
96
+ }
97
+
98
+ private _invoke(api: any, ...args: any[]): Promise<any> {
79
99
 
80
100
  const CST = Date.now();
81
101
 
@@ -84,7 +104,7 @@ class TvLegacyHttpClient extends EventEmitter implements C.IClient<any> {
84
104
  rid: 0,
85
105
  cst: CST,
86
106
  args,
87
- api: this._apiNameWrapper ? this._apiNameWrapper(api) : api
107
+ api: this._apiNameWrapper?.(api) ?? api
88
108
  });
89
109
 
90
110
  return new Promise((resolve, reject) => {
@@ -174,7 +194,7 @@ class TvLegacyHttpClient extends EventEmitter implements C.IClient<any> {
174
194
  case v1.EResponseCode.MALFORMED_ARGUMENTS:
175
195
  case v1.EResponseCode.SYSTEM_ERROR:
176
196
  default:
177
- reject(new Shared.errors.unknown({
197
+ reject(new Shared.errors.server_internal_error({
178
198
  api, ...data
179
199
  }));
180
200
  break;
@@ -207,7 +227,7 @@ class TvLegacyHttpClient extends EventEmitter implements C.IClient<any> {
207
227
  }
208
228
  }
209
229
 
210
- export interface IHttpClientNetworkOptions extends $Https.RequestOptions {
230
+ interface IHttpClientOptionsBase extends $Https.RequestOptions {
211
231
 
212
232
  /**
213
233
  * Whether to use HTTPS.
@@ -217,44 +237,43 @@ export interface IHttpClientNetworkOptions extends $Https.RequestOptions {
217
237
  https?: boolean;
218
238
 
219
239
  /**
220
- * The hostname of HTTP server.
240
+ * Wrap the API name before sending to server.
221
241
  *
222
- * @default 'localhost'
242
+ * @default null
223
243
  */
224
- hostname: string;
244
+ apiNameWrapper?: (name: string) => string;
225
245
 
226
246
  /**
227
- * The port of HTTP server.
247
+ * Whether to retry on connection reset.
228
248
  *
229
- * @default 80 or 443
249
+ * @default true
230
250
  */
231
- port?: number;
251
+ retryOnConnReset?: boolean;
252
+ }
253
+
254
+ export interface IHttpClientNetworkOptions extends IHttpClientOptionsBase {
232
255
 
233
256
  /**
234
- * The path to the RPC entry.
257
+ * The hostname of HTTP server.
235
258
  *
236
- * @default '/'
259
+ * @default 'localhost'
237
260
  */
238
- path?: string;
239
-
240
- apiNameWrapper?: (name: string) => string;
241
- }
242
-
243
- export interface IHttpClientUnixSocketOptions extends $Https.RequestOptions {
261
+ hostname: string;
244
262
 
245
263
  /**
246
- * Whether to use HTTPS.
264
+ * The port of HTTP server.
247
265
  *
248
- * > Always `false` for Unix domain socket.
266
+ * @default 80 or 443
249
267
  */
250
- https?: false;
268
+ port?: number;
269
+ }
270
+
271
+ export interface IHttpClientUnixSocketOptions extends IHttpClientOptionsBase {
251
272
 
252
273
  /**
253
274
  * The path of Unix domain socket to connect to.
254
275
  */
255
276
  socketPath: string;
256
-
257
- apiNameWrapper?: (name: string) => string;
258
277
  }
259
278
 
260
279
  export type IHttpClientOptions = IHttpClientNetworkOptions | IHttpClientUnixSocketOptions;
@@ -265,14 +284,15 @@ export function createLegacyHttpClient<TAPIs extends Shared.IObject>(opts: IHttp
265
284
  opts.https ? $Https.request : $Http.request,
266
285
  opts,
267
286
  opts.https ? new $Https.Agent({
268
- maxSockets: 0,
269
- keepAlive: true,
270
- keepAliveMsecs: DEFAULT_TIMEOUT
287
+ 'maxSockets': 0,
288
+ 'keepAlive': true,
289
+ 'keepAliveMsecs': DEFAULT_TIMEOUT
271
290
  }) : new $Http.Agent({
272
- maxSockets: 0,
273
- keepAlive: true,
274
- keepAliveMsecs: DEFAULT_TIMEOUT
291
+ 'maxSockets': 0,
292
+ 'keepAlive': true,
293
+ 'keepAliveMsecs': DEFAULT_TIMEOUT
275
294
  }),
276
- opts.apiNameWrapper
295
+ opts.retryOnConnReset,
296
+ opts.apiNameWrapper,
277
297
  );
278
298
  }
@@ -41,7 +41,7 @@ export type IRouteApiCallback = (response: string | Buffer | Array<string | Buff
41
41
 
42
42
  export type IRouteLegacyApiCallback = (response: unknown | Shared.TvErrorResponse | Shared.TelevokeError) => void;
43
43
 
44
- export interface IRouter {
44
+ export interface IRouter extends Shared.IEventListener<Shared.IDefaultEvents> {
45
45
 
46
46
  /**
47
47
  * The encoding of the API request/response.
@@ -82,6 +82,8 @@ export interface IRouter {
82
82
  export interface IServerEvents extends Shared.IDefaultEvents {
83
83
 
84
84
  channel(channel: IChannel): void;
85
+
86
+ warning(error: unknown, channel?: IChannel): void;
85
87
  }
86
88
 
87
89
  export interface IServer extends Shared.IEventListener<IServerEvents> {
@@ -35,11 +35,19 @@ export class TvServer extends EventEmitter implements dS.IServer, dT.IServer {
35
35
  private readonly _streamManagerFactory: Shared.IStreamManagerFactory = Shared.createSharedStreamManagerFactory()
36
36
  ) {
37
37
  super();
38
+ this.router.on('error', this._onRouterError);
38
39
  }
39
40
 
41
+ private readonly _onRouterError = (e: unknown): void => {
42
+
43
+ this.emit('error', e);
44
+ };
45
+
40
46
  public setRouter(router: dS.IRouter): void {
41
47
 
42
- this.router = router;
48
+ this.router?.off('error', this._onRouterError);
49
+ this.router = router
50
+ .on('error', this._onRouterError);
43
51
  }
44
52
 
45
53
  public registerChannel(transporter: dT.ITransporter): void {
@@ -60,6 +68,7 @@ export class TvServer extends EventEmitter implements dS.IServer, dT.IServer {
60
68
  });
61
69
  })
62
70
  .on('error', (err) => this.emit('error', err))
71
+ .on('warning', (err) => this.emit('warning', err, this._channels[id]))
63
72
  .on('close', () => { delete this._channels[id]; });
64
73
  }
65
74
 
@@ -17,12 +17,13 @@
17
17
  import type * as dS from './Server.decl';
18
18
  import * as Shared from '../shared';
19
19
  import { buffer2json } from '../shared/Utils';
20
+ import { EventEmitter } from 'node:events';
20
21
 
21
22
  export type ISimpleJsonApiHandler = (ctx: dS.IRequestContext, ...args: any[]) => unknown;
22
23
 
23
24
  export type ISimpleJsonMessageHandler = (ctx: dS.IRequestContext, msg: unknown) => unknown;
24
25
 
25
- export class SimpleJsonApiRouter implements dS.IRouter {
26
+ export class SimpleJsonApiRouter extends EventEmitter implements dS.IRouter {
26
27
 
27
28
  public get encoding(): string {
28
29
 
@@ -65,8 +66,18 @@ export class SimpleJsonApiRouter implements dS.IRouter {
65
66
  if (result instanceof Promise) {
66
67
 
67
68
  result.then(
68
- (data) => { cb(JSON.stringify(data)); },
69
- (e) => { this._processError(cb, e); },
69
+ (data) => {
70
+
71
+ try {
72
+
73
+ cb(JSON.stringify(data ?? null));
74
+ }
75
+ catch (e) {
76
+
77
+ this._processError(cb, e, name);
78
+ }
79
+ },
80
+ (e) => { this._processError(cb, e, name); },
70
81
  );
71
82
  }
72
83
  else {
@@ -76,11 +87,11 @@ export class SimpleJsonApiRouter implements dS.IRouter {
76
87
  }
77
88
  catch (e) {
78
89
 
79
- this._processError(cb, e);
90
+ this._processError(cb, e, name);
80
91
  }
81
92
  }
82
93
 
83
- private _processError(cb: dS.IRouteApiCallback, e: unknown): void {
94
+ private _processError(cb: dS.IRouteApiCallback, e: unknown, name: string): void {
84
95
 
85
96
  if (e instanceof Shared.TelevokeError) {
86
97
 
@@ -90,11 +101,12 @@ export class SimpleJsonApiRouter implements dS.IRouter {
90
101
 
91
102
  if (e instanceof Shared.TvErrorResponse) {
92
103
 
93
- cb(new Shared.errors.app_error(JSON.stringify(e.data)));
104
+ cb(new Shared.errors.app_error(JSON.stringify(e.data ?? null)));
94
105
  return;
95
106
  }
96
107
 
97
- cb(new Shared.errors.app_error('null'));
108
+ cb(new Shared.errors.server_internal_error());
109
+ this.emit('error', new Shared.errors.unprocessable_error({ api: name }, e));
98
110
  }
99
111
 
100
112
  public processLegacyApi(
@@ -118,8 +130,8 @@ export class SimpleJsonApiRouter implements dS.IRouter {
118
130
  if (result instanceof Promise) {
119
131
 
120
132
  result.then(
121
- (data) => { cb(data); },
122
- (e) => { this._processError(cb, e); },
133
+ (data) => { cb(data ?? null); },
134
+ (e) => { this._processError(cb, e, name); },
123
135
  );
124
136
  }
125
137
  else {
@@ -129,7 +141,7 @@ export class SimpleJsonApiRouter implements dS.IRouter {
129
141
  }
130
142
  catch (e) {
131
143
 
132
- this._processError(cb, e);
144
+ this._processError(cb, e, name);
133
145
  }
134
146
  }
135
147
  }
@@ -20,6 +20,11 @@ import { Readable } from 'node:stream';
20
20
 
21
21
  export class TvBinaryReadStream extends Readable implements Shared.IBinaryReadStream {
22
22
 
23
+ /**
24
+ * The index of next chunk to read.
25
+ */
26
+ public nextIndex: number = 0;
27
+
23
28
  private _timer: NodeJS.Timeout | null = null;
24
29
 
25
30
  public constructor(
@@ -87,7 +92,7 @@ export class TvBinaryReadStream extends Readable implements Shared.IBinaryReadSt
87
92
  }, this._timeout);
88
93
  }
89
94
 
90
- public push(buf: Buffer | null): boolean {
95
+ public append(chunkSegments: Buffer[]): void {
91
96
 
92
97
  if (this.readableEnded) {
93
98
 
@@ -99,7 +104,12 @@ export class TvBinaryReadStream extends Readable implements Shared.IBinaryReadSt
99
104
  this._resetTimer();
100
105
  }
101
106
 
102
- return super.push(buf);
107
+ for (const s of chunkSegments) {
108
+
109
+ super.push(s);
110
+ }
111
+
112
+ this.nextIndex++;
103
113
  }
104
114
 
105
115
  public close(): void {
@@ -438,18 +438,34 @@ export abstract class AbstractTvChannelV2
438
438
  break;
439
439
  }
440
440
 
441
- const chunks = (packet as v2.IBinaryChunkRequestPacket).ct.body as Buffer[];
441
+ const chunkSegments = (packet as v2.IBinaryChunkRequestPacket).ct.body as Buffer[];
442
442
 
443
- if (!chunks[0]?.byteLength) {
443
+ const chunkIndex = (packet as v2.IBinaryChunkRequestPacket).ct.index;
444
+
445
+ if (chunkIndex === 0xFFFFFFFF) {
446
+
447
+ stream.abort();
448
+ }
449
+ else if (chunkIndex !== stream.nextIndex) {
450
+
451
+ this._reply({
452
+ 'cmd': v2.ECommand.BINARY_CHUNK,
453
+ 'typ': v2.EPacketType.ERROR_RESPONSE,
454
+ 'seq': packet.seq,
455
+ 'ct': new E.errors.stream_index_mismatch({
456
+ 'sid': (packet as v2.IBinaryChunkRequestPacket).ct.streamId,
457
+ 'chId': this.id,
458
+ }),
459
+ });
460
+ break;
461
+ }
462
+ else if (!chunkSegments[0]?.byteLength) {
444
463
 
445
464
  stream.close();
446
465
  }
447
466
  else {
448
467
 
449
- for (const chunk of chunks) {
450
-
451
- stream.push(chunk);
452
- }
468
+ stream.append(chunkSegments);
453
469
  }
454
470
 
455
471
  this._reply({
@@ -546,7 +562,7 @@ export abstract class AbstractTvChannelV2
546
562
  });
547
563
  }
548
564
 
549
- public sendBinaryChunk(streamId: number, chunk: Buffer | null): Promise<void> {
565
+ public sendBinaryChunk(streamId: number, index: number | false, chunk: Buffer | null): Promise<void> {
550
566
 
551
567
  if (!this.writable) {
552
568
 
@@ -561,6 +577,7 @@ export abstract class AbstractTvChannelV2
561
577
  'seq': seq,
562
578
  'ct': {
563
579
  streamId,
580
+ 'index': index === false ? 0xFFFFFFFF : index,
564
581
  'body': chunk ?? [],
565
582
  }
566
583
  } satisfies v2.IBinaryChunkRequestPacket));
@@ -234,7 +234,7 @@ class TvPushMessageRequestDecoder implements IPacketDecoder {
234
234
  'cmd': CSv2.ECommand.PUSH_MESSAGE,
235
235
  'typ': CSv2.EPacketType.REQUEST,
236
236
  'seq': ctx.seq,
237
- 'ct': readVarBuffer16(ctx)
237
+ 'ct': readVarBuffer(ctx)
238
238
  } satisfies dEnc2.IPushMessageRequestPacket;
239
239
  }
240
240
  }
@@ -244,12 +244,14 @@ class TvBinaryChunkRequestDecoder implements IPacketDecoder {
244
244
  public decode(ctx: IDecodeContext): dEnc2.ICommandPacket {
245
245
 
246
246
  const streamId = readSmallBytes(4, B4, ctx).readUInt32LE(0);
247
+ const chunkIndex = readSmallBytes(4, B4, ctx).readUInt32LE(0);
247
248
  return {
248
249
  'cmd': CSv2.ECommand.BINARY_CHUNK,
249
250
  'typ': CSv2.EPacketType.REQUEST,
250
251
  'seq': ctx.seq,
251
252
  'ct': {
252
253
  'streamId': streamId,
254
+ 'index': chunkIndex,
253
255
  'body': readVarBuffer(ctx),
254
256
  }
255
257
  } satisfies dEnc2.IBinaryChunkRequestPacket;
@@ -104,10 +104,11 @@ class BinaryChunkRequestEncoder implements IPacketEncoder {
104
104
 
105
105
  public encode(packet: dEnc2.IBinaryChunkRequestPacket): v2.IDataChunkArray {
106
106
 
107
- const leadSeg = Buffer.allocUnsafe(CS.HEADER_SIZE + 4 + 4);
107
+ const leadSeg = Buffer.allocUnsafe(CS.HEADER_SIZE + 12);
108
108
 
109
109
  writePacketHeader(leadSeg, packet.cmd, packet.typ, packet.seq);
110
110
  leadSeg.writeUInt32LE(packet.ct.streamId, CS.HEADER_SIZE);
111
+ leadSeg.writeUInt32LE(packet.ct.index, CS.HEADER_SIZE + 4);
111
112
 
112
113
  if (Array.isArray(packet.ct.body)) {
113
114
 
@@ -118,12 +119,12 @@ class BinaryChunkRequestEncoder implements IPacketEncoder {
118
119
  bodyLen += Buffer.byteLength(p);
119
120
  }
120
121
 
121
- leadSeg.writeUInt32LE(bodyLen, CS.HEADER_SIZE + 4);
122
+ leadSeg.writeUInt32LE(bodyLen, CS.HEADER_SIZE + 8);
122
123
 
123
124
  return [leadSeg, ...packet.ct.body];
124
125
  }
125
126
 
126
- leadSeg.writeUInt32LE(Buffer.byteLength(packet.ct.body), CS.HEADER_SIZE + 4);
127
+ leadSeg.writeUInt32LE(Buffer.byteLength(packet.ct.body), CS.HEADER_SIZE + 8);
127
128
 
128
129
  return [leadSeg, packet.ct.body];
129
130
  }
@@ -173,7 +174,7 @@ class PushMessageRequestEncoder implements IPacketEncoder {
173
174
 
174
175
  public encode(packet: dEnc2.IPushMessageRequestPacket): v2.IDataChunkArray {
175
176
 
176
- const leadSeg = Buffer.allocUnsafe(CS.HEADER_SIZE + 2);
177
+ const leadSeg = Buffer.allocUnsafe(CS.HEADER_SIZE + 4);
177
178
 
178
179
  writePacketHeader(leadSeg, packet.cmd, packet.typ, packet.seq);
179
180
 
@@ -186,12 +187,12 @@ class PushMessageRequestEncoder implements IPacketEncoder {
186
187
  bodyLen += Buffer.byteLength(p);
187
188
  }
188
189
 
189
- leadSeg.writeUInt16LE(bodyLen, CS.HEADER_SIZE);
190
+ leadSeg.writeUInt32LE(bodyLen, CS.HEADER_SIZE);
190
191
 
191
192
  return [leadSeg, ...packet.ct];
192
193
  }
193
194
 
194
- leadSeg.writeUInt16LE(Buffer.byteLength(packet.ct), CS.HEADER_SIZE);
195
+ leadSeg.writeUInt32LE(Buffer.byteLength(packet.ct), CS.HEADER_SIZE);
195
196
 
196
197
  return [leadSeg, packet.ct];
197
198
  }
@@ -111,6 +111,11 @@ export interface IBinaryChunkRequestPacket extends IRequestPacket {
111
111
  */
112
112
  readonly streamId: number;
113
113
 
114
+ /**
115
+ * The index of this chunk in the stream.
116
+ */
117
+ readonly index: number;
118
+
114
119
  /**
115
120
  * The chunk data.
116
121
  */
@@ -73,9 +73,12 @@ const errorCtors = {
73
73
  ...defineProtocolError('stream_not_found'),
74
74
  ...defineProtocolError('stream_aborted'),
75
75
  ...defineProtocolError('stream_closed'),
76
+ ...defineProtocolError('stream_index_mismatch'),
76
77
  ...defineProtocolError('system_busy'),
78
+ ...defineProtocolError('server_internal_error'),
77
79
  ...defineProtocolError('timeout'),
78
80
  ...defineProtocolError('unknown'),
81
+ ...defineProtocolError('unprocessable_error'),
79
82
 
80
83
  ['app_error']: class AppError extends TelevokeError {
81
84
 
@@ -197,13 +197,28 @@ export interface ITransporter {
197
197
  getAllProperties(): Record<string, unknown>;
198
198
  }
199
199
 
200
- export interface IBinaryReadStream extends Readable {
200
+ export interface IBinaryReadStream extends Omit<Readable, 'push'> {
201
201
 
202
202
  /**
203
203
  * The unique id of binary stream.
204
204
  */
205
205
  readonly id: number;
206
206
 
207
+ /**
208
+ * The expected index of next chunk to read.
209
+ */
210
+ readonly nextIndex: number;
211
+
212
+ /**
213
+ * Append a binary data chunk to this binary stream, with the given index.
214
+ *
215
+ * @param chunk The binary data chunk to be appended.
216
+ * @param index The index of the chunk in the stream.
217
+ *
218
+ * @returns Whether the chunk is appended successfully.
219
+ */
220
+ append(chunk: Buffer[]): void;
221
+
207
222
  /**
208
223
  * Abort the binary stream before it's completed, and release the resource.
209
224
  *
@@ -332,13 +347,17 @@ export interface IChannelBase<TEvents extends IDefaultEvents> extends IEventList
332
347
  /**
333
348
  * Send a binary data chunk to an opened binary stream on the remote side.
334
349
  *
335
- * > **NOTE: It is not recommended to send a large binary data chunk at once. A large binary chunk should be split**
336
- * > **into several small chunks, and then send them one by one, in order.**
350
+ * > **NOTE**
351
+ * > - It is not recommended to send a large binary data chunk at once. A large binary chunk should be split
352
+ * > into several small chunks, and then send them one by one, in order.
353
+ * > - Set the `index` to `false` to close the binary stream by aborting.
354
+ * > - Set the `chunk` to `null` to close the binary stream by finishing.
337
355
  *
338
356
  * @param id The `id` field in `IBinaryReadStream`, opened by remote side.
357
+ * @param index The index of this chunk in the stream.
339
358
  * @param chunks The binary data chunks to be sent.
340
359
  */
341
- sendBinaryChunk(id: number, chunk: Buffer | null): Promise<void>;
360
+ sendBinaryChunk(id: number, index: number | false, chunk: Buffer | null): Promise<void>;
342
361
 
343
362
  /**
344
363
  * Close the channel.
@@ -74,6 +74,7 @@ class HttpListener extends AbstractHttpListener {
74
74
  }
75
75
 
76
76
  const gateway = this._gateway;
77
+ this._gateway = null;
77
78
 
78
79
  return new Promise<void>((resolve, reject) => {
79
80
 
@@ -27,7 +27,7 @@ const encoder = new v1.TvEncoderV1();
27
27
  const INVALID_REQUEST_RESPONSE = Buffer.from(encoder.encodeApiErrorResponse(
28
28
  'null',
29
29
  Shared.Encodings.v1.EResponseCode.MALFORMED_ARGUMENTS,
30
- 'INVALID REQUEST',
30
+ '"INVALID REQUEST"',
31
31
  0
32
32
  ));
33
33
 
@@ -76,6 +76,19 @@ class LegacyHttpGateway extends EventEmitter implements dT.IGateway {
76
76
  .setLegacyApiProcessor(this._onRequest);
77
77
  }
78
78
 
79
+ private _sendResponse(resp: Http.ServerResponse, data: string): void {
80
+
81
+ try {
82
+
83
+ resp.setHeader('content-length', Buffer.byteLength(data));
84
+ resp.end(data);
85
+ }
86
+ catch (e) {
87
+
88
+ this.emit('error', e);
89
+ }
90
+ }
91
+
79
92
  public get running(): boolean {
80
93
 
81
94
  return this._listener.running;
@@ -159,7 +172,6 @@ class LegacyHttpGateway extends EventEmitter implements dT.IGateway {
159
172
  refuseBadRequest(resp);
160
173
  this.emit('error', new Shared.errors.invalid_packet({
161
174
  reason: 'invalid_json',
162
- data: buf,
163
175
  }, e));
164
176
  return;
165
177
  }
@@ -169,7 +181,6 @@ class LegacyHttpGateway extends EventEmitter implements dT.IGateway {
169
181
  refuseBadRequest(resp);
170
182
  this.emit('error', new Shared.errors.invalid_packet({
171
183
  reason: 'malformed_json',
172
- data: input,
173
184
  }));
174
185
  return;
175
186
  }
@@ -185,48 +196,54 @@ class LegacyHttpGateway extends EventEmitter implements dT.IGateway {
185
196
 
186
197
  if (result instanceof Shared.errors.app_error) {
187
198
 
188
- const data = encoder.encodeApiErrorResponse(
199
+ this._sendResponse(resp, encoder.encodeApiErrorResponse(
189
200
  input.rid,
190
201
  v1.EResponseCode.FAILURE,
191
202
  result.message,
192
203
  recvAt
193
- );
194
-
195
- resp.setHeader('content-length', Buffer.byteLength(data));
196
- resp.end(data);
204
+ ));
197
205
  }
198
206
  else if (result instanceof Shared.errors.api_not_found) {
199
207
 
200
- const data = encoder.encodeApiErrorResponse(
208
+ this._sendResponse(resp, encoder.encodeApiErrorResponse(
201
209
  input.rid,
202
210
  v1.EResponseCode.API_NOT_FOUND,
203
211
  'null',
204
212
  recvAt
205
- );
206
-
207
- resp.setHeader('content-length', Buffer.byteLength(data));
208
- resp.end(data);
213
+ ));
209
214
  }
210
215
  else {
211
216
 
212
- const data = encoder.encodeApiErrorResponse(
217
+ this._sendResponse(resp, encoder.encodeApiErrorResponse(
213
218
  input.rid,
214
219
  v1.EResponseCode.SYSTEM_ERROR,
215
220
  'null',
216
221
  recvAt
217
- );
218
-
219
- resp.setHeader('content-length', Buffer.byteLength(data));
220
- resp.end(data);
222
+ ));
221
223
  }
222
224
 
223
225
  return;
224
226
  }
225
227
 
226
- const data = encoder.encodeApiOkResponse(input.rid, result ?? null, recvAt);
228
+ let data: string;
229
+
230
+ try {
231
+
232
+ data = encoder.encodeApiOkResponse(input.rid, result ?? null, recvAt);
233
+ }
234
+ catch (e) {
235
+
236
+ data = encoder.encodeApiErrorResponse(
237
+ input.rid,
238
+ v1.EResponseCode.SYSTEM_ERROR,
239
+ 'null',
240
+ recvAt
241
+ );
242
+
243
+ this.emit('error', new Shared.errors.unprocessable_error({ api: input.api }, e));
244
+ }
227
245
 
228
- resp.setHeader('content-length', Buffer.byteLength(data));
229
- resp.end(data);
246
+ this._sendResponse(resp, data);
230
247
 
231
248
  }, input.api, input.args, new LegacyHttpTransporter(req));
232
249
  });