mqtt-plus 0.9.12 → 0.9.14

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.
@@ -2,9 +2,11 @@ import { APISchema } from "./mqtt-plus-api";
2
2
  import { APIOptions, OptionsTrait } from "./mqtt-plus-options";
3
3
  export default class Codec {
4
4
  private type;
5
+ private types;
6
+ private tags;
5
7
  constructor(type: "cbor" | "json");
6
- encode(data: unknown): Buffer | string;
7
- decode(data: Buffer | string): unknown;
8
+ encode(data: unknown): Uint8Array | string;
9
+ decode(data: Uint8Array | string): unknown;
8
10
  }
9
11
  export declare class CodecTrait<T extends APISchema = APISchema> extends OptionsTrait<T> {
10
12
  protected codec: Codec;
@@ -22,18 +22,51 @@
22
22
  ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
24
  /* external requirements */
25
- import CBOR from "cbor";
25
+ import * as CBOR from "cbor2";
26
26
  import { OptionsTrait } from "./mqtt-plus-options";
27
+ /* JSON encode/decode with Uint8Array support */
28
+ class JSONX {
29
+ static uint8ArrayToBase64(arr) {
30
+ return btoa(String.fromCharCode(...arr));
31
+ }
32
+ static base64ToUint8Array(base64) {
33
+ const binary = atob(base64);
34
+ const arr = new Uint8Array(binary.length);
35
+ for (let i = 0; i < binary.length; i++)
36
+ arr[i] = binary.charCodeAt(i);
37
+ return arr;
38
+ }
39
+ static stringify(obj) {
40
+ return JSON.stringify(obj, (_, value) => value instanceof Uint8Array
41
+ ? { __Uint8Array: this.uint8ArrayToBase64(value) }
42
+ : value);
43
+ }
44
+ static parse(json) {
45
+ return JSON.parse(json, (_, value) => value?.__Uint8Array
46
+ ? this.base64ToUint8Array(value.__Uint8Array)
47
+ : value);
48
+ }
49
+ }
27
50
  /* the encoder/decoder abstraction */
28
51
  export default class Codec {
29
52
  constructor(type) {
30
53
  this.type = type;
54
+ this.types = new CBOR.TypeEncoderMap();
55
+ this.tags = new Map();
56
+ /* support direct encoding/decoding of Buffer */
57
+ const TAG_BUFFER = 64000;
58
+ this.types.registerEncoder(Buffer, (buffer) => {
59
+ return [TAG_BUFFER, new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)];
60
+ });
61
+ this.tags.set(TAG_BUFFER, (tag) => {
62
+ return Buffer.from(tag.contents);
63
+ });
31
64
  }
32
65
  encode(data) {
33
66
  let result;
34
67
  if (this.type === "cbor") {
35
68
  try {
36
- result = CBOR.encode(data);
69
+ result = CBOR.encode(data, { types: this.types });
37
70
  }
38
71
  catch (_ex) {
39
72
  throw new Error("failed to encode CBOR format");
@@ -41,7 +74,7 @@ export default class Codec {
41
74
  }
42
75
  else if (this.type === "json") {
43
76
  try {
44
- result = JSON.stringify(data);
77
+ result = JSONX.stringify(data);
45
78
  }
46
79
  catch (_ex) {
47
80
  throw new Error("failed to encode JSON format");
@@ -53,9 +86,11 @@ export default class Codec {
53
86
  }
54
87
  decode(data) {
55
88
  let result;
56
- if (this.type === "cbor" && typeof data === "object" && data instanceof Buffer) {
89
+ if (this.type === "cbor"
90
+ && typeof data === "object"
91
+ && data instanceof Uint8Array) {
57
92
  try {
58
- result = CBOR.decode(data);
93
+ result = CBOR.decode(data, { tags: this.tags });
59
94
  }
60
95
  catch (_ex) {
61
96
  throw new Error("failed to decode CBOR format");
@@ -63,7 +98,7 @@ export default class Codec {
63
98
  }
64
99
  else if (this.type === "json" && typeof data === "string") {
65
100
  try {
66
- result = JSON.parse(data);
101
+ result = JSONX.parse(data);
67
102
  }
68
103
  catch (_ex) {
69
104
  throw new Error("failed to decode JSON format");
@@ -21,6 +21,8 @@
21
21
  ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
22
  ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
+ /* built-in requirements */
25
+ import { Buffer } from "node:buffer";
24
26
  import { nanoid } from "nanoid";
25
27
  /* internal requirements */
26
28
  import { EventEmission } from "./mqtt-plus-msg";
@@ -98,7 +100,7 @@ export class EventTrait extends BaseTrait {
98
100
  /* generate corresponding MQTT topic */
99
101
  const topic = this.options.topicMake(event, "event-emission", receiver);
100
102
  /* publish message to MQTT topic */
101
- this.mqtt.publish(topic, message, { qos: 0, ...options });
103
+ this.mqtt.publish(topic, Buffer.from(message), { qos: 0, ...options });
102
104
  }
103
105
  /* dispatch message (Event pattern handling) */
104
106
  _dispatchMessage(topic, parsed) {
@@ -111,7 +113,9 @@ export class EventTrait extends BaseTrait {
111
113
  const name = parsed.event;
112
114
  const handler = this.subscriptions.get(name);
113
115
  const params = parsed.params ?? [];
114
- const info = { sender: parsed.sender ?? "", receiver: parsed.receiver };
116
+ const info = { sender: parsed.sender ?? "" };
117
+ if (parsed.receiver)
118
+ info.receiver = parsed.receiver;
115
119
  Promise.resolve()
116
120
  .then(() => handler?.(...params, info))
117
121
  .catch((err) => {
@@ -1,4 +1,4 @@
1
- import { Readable } from "stream";
1
+ import { Readable } from "node:stream";
2
2
  export interface InfoBase {
3
3
  sender: string;
4
4
  receiver?: string;
@@ -10,6 +10,6 @@ export interface InfoService extends InfoBase {
10
10
  export interface InfoResource extends InfoBase {
11
11
  meta?: Record<string, any>;
12
12
  stream?: Readable;
13
- buffer?: Promise<Buffer>;
13
+ buffer?: Promise<Uint8Array>;
14
14
  }
15
15
  export type WithInfo<F, I extends InfoBase> = F extends (...args: infer P) => infer R ? (...args: [...P, info: I]) => R : never;
@@ -30,18 +30,18 @@ export declare class ResourceTransferRequest extends Base {
30
30
  export declare class ResourceTransferResponse extends Base {
31
31
  resource?: string | undefined;
32
32
  params?: any[] | undefined;
33
- chunk?: Buffer | undefined;
33
+ chunk?: Uint8Array | undefined;
34
34
  meta?: Record<string, any> | undefined;
35
35
  error?: string | undefined;
36
36
  final?: boolean | undefined;
37
- constructor(id: string, resource?: string | undefined, params?: any[] | undefined, chunk?: Buffer | undefined, meta?: Record<string, any> | undefined, error?: string | undefined, final?: boolean | undefined, sender?: string, receiver?: string);
37
+ constructor(id: string, resource?: string | undefined, params?: any[] | undefined, chunk?: Uint8Array | undefined, meta?: Record<string, any> | undefined, error?: string | undefined, final?: boolean | undefined, sender?: string, receiver?: string);
38
38
  }
39
39
  declare class Msg {
40
40
  makeEventEmission(id: string, event: string, params?: any[], sender?: string, receiver?: string): EventEmission;
41
41
  makeServiceCallRequest(id: string, service: string, params?: any[], sender?: string, receiver?: string): ServiceCallRequest;
42
42
  makeServiceCallResponse(id: string, result?: any, error?: string, sender?: string, receiver?: string): ServiceCallResponse;
43
43
  makeResourceTransferRequest(id: string, resource: string, params?: any[], sender?: string, receiver?: string): ResourceTransferRequest;
44
- makeResourceTransferResponse(id: string, resource?: string, params?: any[], chunk?: Buffer, meta?: Record<string, any>, error?: string, final?: boolean, sender?: string, receiver?: string): ResourceTransferResponse;
44
+ makeResourceTransferResponse(id: string, resource?: string, params?: any[], chunk?: Uint8Array, meta?: Record<string, any>, error?: string, final?: boolean, sender?: string, receiver?: string): ResourceTransferResponse;
45
45
  parse(obj: any): EventEmission | ServiceCallRequest | ServiceCallResponse | ResourceTransferRequest | ResourceTransferResponse;
46
46
  }
47
47
  export declare class MsgTrait<T extends APISchema = APISchema> extends CodecTrait<T> {
@@ -1,4 +1,4 @@
1
- import { Readable } from "stream";
1
+ import { Readable } from "node:stream";
2
2
  import { IClientPublishOptions, IClientSubscribeOptions } from "mqtt";
3
3
  import { APISchema, ResourceKeys } from "./mqtt-plus-api";
4
4
  import type { WithInfo, InfoResource } from "./mqtt-plus-info";
@@ -13,10 +13,10 @@ export declare class ResourceTrait<T extends APISchema = APISchema> extends Serv
13
13
  private pushTimers;
14
14
  provision<K extends ResourceKeys<T> & string>(resource: K, callback: WithInfo<T[K], InfoResource>): Promise<Provisioning>;
15
15
  provision<K extends ResourceKeys<T> & string>(resource: K, options: Partial<IClientSubscribeOptions>, callback: WithInfo<T[K], InfoResource>): Promise<Provisioning>;
16
- push<K extends ResourceKeys<T> & string>(resource: K, data: Readable | Buffer, ...params: Parameters<T[K]>): Promise<void>;
16
+ push<K extends ResourceKeys<T> & string>(resource: K, data: Readable | Uint8Array, ...params: Parameters<T[K]>): Promise<void>;
17
17
  push<K extends ResourceKeys<T> & string>(config: {
18
18
  resource: K;
19
- data: Readable | Buffer;
19
+ data: Readable | Uint8Array;
20
20
  params: Parameters<T[K]>;
21
21
  meta?: Record<string, any>;
22
22
  receiver?: string;
@@ -24,7 +24,7 @@ export declare class ResourceTrait<T extends APISchema = APISchema> extends Serv
24
24
  }): Promise<void>;
25
25
  fetch<K extends ResourceKeys<T> & string>(resource: K, ...params: Parameters<T[K]>): Promise<{
26
26
  stream: Readable;
27
- buffer: Promise<Buffer>;
27
+ buffer: Promise<Uint8Array>;
28
28
  meta: Promise<Record<string, any> | undefined>;
29
29
  }>;
30
30
  fetch<K extends ResourceKeys<T> & string>(config: {
@@ -34,7 +34,7 @@ export declare class ResourceTrait<T extends APISchema = APISchema> extends Serv
34
34
  options?: IClientPublishOptions;
35
35
  }): Promise<{
36
36
  stream: Readable;
37
- buffer: Promise<Buffer>;
37
+ buffer: Promise<Uint8Array>;
38
38
  meta: Promise<Record<string, any> | undefined>;
39
39
  }>;
40
40
  protected _dispatchMessage(topic: string, parsed: any): void;
@@ -22,7 +22,8 @@
22
22
  ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
24
  /* built-in requirements */
25
- import { Readable } from "stream";
25
+ import { Buffer } from "node:buffer";
26
+ import { Readable } from "node:stream";
26
27
  import { nanoid } from "nanoid";
27
28
  /* internal requirements */
28
29
  import { streamToBuffer, sendBufferAsChunks, sendStreamAsChunks } from "./mqtt-plus-util";
@@ -121,7 +122,7 @@ export class ResourceTrait extends ServiceTrait {
121
122
  firstChunk = false;
122
123
  const request = this.msg.makeResourceTransferResponse(rid, resource, params, chunk, chunkMeta, error, final, this.options.id, receiver);
123
124
  const message = this.codec.encode(request);
124
- this.mqtt.publish(topic, message, { qos: 2, ...options });
125
+ this.mqtt.publish(topic, Buffer.from(message), { qos: 2, ...options });
125
126
  };
126
127
  /* iterate over all chunks of the buffer */
127
128
  return new Promise((resolve, reject) => {
@@ -129,7 +130,7 @@ export class ResourceTrait extends ServiceTrait {
129
130
  /* attach to the readable */
130
131
  sendStreamAsChunks(streamOrBuffer, this.options.chunkSize, sendChunk, () => resolve(), (err) => reject(err));
131
132
  }
132
- else if (streamOrBuffer instanceof Buffer) {
133
+ else if (streamOrBuffer instanceof Uint8Array) {
133
134
  /* split buffer into chunks and send them */
134
135
  sendBufferAsChunks(streamOrBuffer, this.options.chunkSize, sendChunk);
135
136
  resolve();
@@ -171,13 +172,15 @@ export class ResourceTrait extends ServiceTrait {
171
172
  /* define timer */
172
173
  let timer = null;
173
174
  /* utility function for cleanup */
174
- const cleanup = () => {
175
+ const cleanup = (resolveMeta = false) => {
175
176
  if (timer !== null) {
176
177
  clearTimeout(timer);
177
178
  timer = null;
178
179
  }
179
180
  this._unsubscribeTopic(responseTopic).catch(() => { });
180
181
  this.callbacks.delete(requestId);
182
+ if (resolveMeta)
183
+ metaResolve?.(undefined);
181
184
  };
182
185
  /* start timeout handler */
183
186
  timer = setTimeout(() => {
@@ -195,7 +198,7 @@ export class ResourceTrait extends ServiceTrait {
195
198
  metaResolve?.(meta);
196
199
  }
197
200
  if (error !== undefined) {
198
- cleanup();
201
+ cleanup(true);
199
202
  stream.destroy(error);
200
203
  }
201
204
  else {
@@ -214,7 +217,7 @@ export class ResourceTrait extends ServiceTrait {
214
217
  /* generate corresponding MQTT topic */
215
218
  const topic = this.options.topicMake(resource, "resource-transfer-request", receiver);
216
219
  /* publish message to MQTT topic */
217
- this.mqtt.publish(topic, message, { qos: 2, ...options });
220
+ this.mqtt.publish(topic, Buffer.from(message), { qos: 2, ...options });
218
221
  /* produce result */
219
222
  return { stream, buffer, meta };
220
223
  }
@@ -235,7 +238,9 @@ export class ResourceTrait extends ServiceTrait {
235
238
  const params = parsed.params ?? [];
236
239
  const sender = parsed.sender ?? "";
237
240
  const receiver = parsed.receiver;
238
- const info = { sender, receiver };
241
+ const info = { sender };
242
+ if (receiver)
243
+ info.receiver = receiver;
239
244
  /* generate corresponding MQTT topic */
240
245
  const responseTopic = this.options.topicMake(resource, "resource-transfer-response", sender);
241
246
  /* callback for creating and sending a chunk message */
@@ -244,7 +249,7 @@ export class ResourceTrait extends ServiceTrait {
244
249
  const chunkMeta = firstChunk ? info.meta : undefined;
245
250
  firstChunk = false;
246
251
  const request = this.msg.makeResourceTransferResponse(requestId, resource, undefined, chunk, chunkMeta, error, final, this.options.id, sender);
247
- const message = this.codec.encode(request);
252
+ const message = Buffer.from(this.codec.encode(request));
248
253
  this.mqtt.publish(responseTopic, message, { qos: 2 });
249
254
  };
250
255
  /* call the handler callback */
@@ -276,8 +281,8 @@ export class ResourceTrait extends ServiceTrait {
276
281
  const error = parsed.error;
277
282
  const meta = parsed.meta;
278
283
  const final = parsed.final;
279
- const chunk = (parsed.chunk !== undefined && !Buffer.isBuffer(parsed.chunk))
280
- ? Buffer.from(parsed.chunk) : parsed.chunk;
284
+ const chunk = (parsed.chunk !== undefined && !(parsed.chunk instanceof Uint8Array))
285
+ ? Uint8Array.from(parsed.chunk) : parsed.chunk;
281
286
  /* case 1: response on fetch */
282
287
  const handler = this.callbacks.get(requestId);
283
288
  if (handler !== undefined)
@@ -304,13 +309,13 @@ export class ResourceTrait extends ServiceTrait {
304
309
  /* prepare info object */
305
310
  const promise = streamToBuffer(readable);
306
311
  const params = parsed.params ?? [];
307
- const info = {
308
- sender: parsed.sender ?? "",
309
- receiver: parsed.receiver,
310
- meta: meta,
311
- stream: readable,
312
- buffer: promise
313
- };
312
+ const info = { sender: parsed.sender ?? "" };
313
+ if (parsed.receiver)
314
+ info.receiver = parsed.receiver;
315
+ if (parsed.meta)
316
+ info.meta = meta;
317
+ info.stream = readable;
318
+ info.buffer = promise;
314
319
  /* call handler */
315
320
  Promise.resolve()
316
321
  .then(() => handler(...params, info))
@@ -21,6 +21,8 @@
21
21
  ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
22
  ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
+ /* built-in requirements */
25
+ import { Buffer } from "node:buffer";
24
26
  import { nanoid } from "nanoid";
25
27
  /* internal requirements */
26
28
  import { ServiceCallRequest, ServiceCallResponse } from "./mqtt-plus-msg";
@@ -124,7 +126,7 @@ export class ServiceTrait extends EventTrait {
124
126
  /* generate corresponding MQTT topic */
125
127
  const topic = this.options.topicMake(service, "service-call-request", receiver);
126
128
  /* publish message to MQTT topic */
127
- this.mqtt.publish(topic, message, { qos: 2, ...options }, (err) => {
129
+ this.mqtt.publish(topic, Buffer.from(message), { qos: 2, ...options }, (err) => {
128
130
  /* handle request failure (only if not already handled) */
129
131
  if (err) {
130
132
  const pendingRequest = this.responseCallback.get(rid);
@@ -185,7 +187,9 @@ export class ServiceTrait extends EventTrait {
185
187
  if (handler !== undefined) {
186
188
  /* execute service handler */
187
189
  const params = parsed.params ?? [];
188
- const info = { sender: parsed.sender ?? "", receiver: parsed.receiver };
190
+ const info = { sender: parsed.sender ?? "" };
191
+ if (parsed.receiver)
192
+ info.receiver = parsed.receiver;
189
193
  response = Promise.resolve().then(() => handler(...params, info));
190
194
  }
191
195
  else
@@ -212,7 +216,7 @@ export class ServiceTrait extends EventTrait {
212
216
  throw new Error("invalid request: missing sender");
213
217
  const encoded = this.codec.encode(rpcResponse);
214
218
  const topic = this.options.topicMake(name, "service-call-response", senderPeerId);
215
- this.mqtt.publish(topic, encoded, { qos: 2 });
219
+ this.mqtt.publish(topic, Buffer.from(encoded), { qos: 2 });
216
220
  }).catch((err) => {
217
221
  this.mqtt.emit("error", err);
218
222
  });
@@ -1,5 +1,5 @@
1
- import { Readable } from "stream";
2
- export declare function streamToBuffer(stream: Readable): Promise<Buffer>;
3
- export type SendChunkCallback = (chunk: Buffer | undefined, error: string | undefined, final: boolean) => void;
4
- export declare function sendBufferAsChunks(buffer: Buffer, chunkSize: number, sendChunk: SendChunkCallback): void;
1
+ import { Readable } from "node:stream";
2
+ export declare function streamToBuffer(stream: Readable): Promise<Uint8Array>;
3
+ export type SendChunkCallback = (chunk: Uint8Array | undefined, error: string | undefined, final: boolean) => void;
4
+ export declare function sendBufferAsChunks(buffer: Uint8Array, chunkSize: number, sendChunk: SendChunkCallback): void;
5
5
  export declare function sendStreamAsChunks(readable: Readable, chunkSize: number, sendChunk: SendChunkCallback, onEnd: () => void, onError: (err: Error) => void): void;
@@ -23,35 +23,47 @@
23
23
  */
24
24
  /* external requirements */
25
25
  import PLazy from "p-lazy";
26
- /* utility function for collecting stream chunks into a Buffer */
26
+ /* concatenate elements of an Uint8Array array */
27
+ function uint8ArrayConcat(arrays) {
28
+ const totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
29
+ const result = new Uint8Array(totalLength);
30
+ let offset = 0;
31
+ for (const array of arrays) {
32
+ result.set(array, offset);
33
+ offset += array.length;
34
+ }
35
+ return result;
36
+ }
37
+ /* utility function for collecting stream chunks into a buffer */
27
38
  export function streamToBuffer(stream) {
28
39
  return new PLazy((resolve, reject) => {
29
40
  const chunks = [];
30
- stream.on("data", (data) => {
41
+ stream.on("data", (_data) => {
42
+ const data = chunkToBuffer(_data);
31
43
  chunks.push(data);
32
44
  });
33
45
  stream.on("end", () => {
34
- resolve(Buffer.concat(chunks));
46
+ resolve(uint8ArrayConcat(chunks));
35
47
  });
36
48
  stream.on("error", (err) => {
37
49
  reject(err);
38
50
  });
39
51
  });
40
52
  }
41
- /* utility function for converting a chunk to a Buffer */
53
+ /* utility function for converting a chunk to a buffer */
42
54
  function chunkToBuffer(chunk) {
43
55
  let buffer;
44
- if (Buffer.isBuffer(chunk))
56
+ if (chunk instanceof Buffer)
57
+ buffer = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.length);
58
+ else if (chunk instanceof Uint8Array)
45
59
  buffer = chunk;
46
60
  else if (typeof chunk === "string")
47
- buffer = Buffer.from(chunk);
48
- else if (chunk instanceof Uint8Array)
49
- buffer = Buffer.from(chunk);
61
+ buffer = new TextEncoder().encode(chunk);
50
62
  else
51
- buffer = Buffer.from(String(chunk));
63
+ buffer = new TextEncoder().encode(String(chunk));
52
64
  return buffer;
53
65
  }
54
- /* utility function for sending a Buffer as chunks */
66
+ /* utility function for sending a buffer as chunks */
55
67
  export function sendBufferAsChunks(buffer, chunkSize, sendChunk) {
56
68
  if (buffer.byteLength === 0) {
57
69
  /* handle empty buffer by sending final chunk */