@9c5s/node-tcnet 0.5.1

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.mjs ADDED
@@ -0,0 +1,764 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/tcnet.ts
9
+ import { createSocket } from "dgram";
10
+
11
+ // src/utils.ts
12
+ import { networkInterfaces } from "os";
13
+ function calculateBroadcastAddress(address, netmask) {
14
+ const addrParts = address.split(".").map(Number);
15
+ const maskParts = netmask.split(".").map(Number);
16
+ return addrParts.map((a, i) => a | ~maskParts[i] & 255).join(".");
17
+ }
18
+ function interfaceAddress(ifname) {
19
+ const interfaces = networkInterfaces();
20
+ const intf = interfaces[ifname];
21
+ if (!intf) {
22
+ throw new Error(`Interface ${ifname} does not exist`);
23
+ }
24
+ const address = intf.find((el) => el.family === "IPv4");
25
+ if (!address) {
26
+ throw new Error(`Interface ${ifname} does not have IPv4 address`);
27
+ }
28
+ return calculateBroadcastAddress(address.address, address.netmask);
29
+ }
30
+ var assert = (condition, message) => {
31
+ if (!condition) {
32
+ throw new Error(`Assertion failed: ${message}`);
33
+ }
34
+ };
35
+
36
+ // src/network.ts
37
+ var TCNetMessageType = /* @__PURE__ */ ((TCNetMessageType2) => {
38
+ TCNetMessageType2[TCNetMessageType2["OptIn"] = 2] = "OptIn";
39
+ TCNetMessageType2[TCNetMessageType2["OptOut"] = 3] = "OptOut";
40
+ TCNetMessageType2[TCNetMessageType2["Status"] = 5] = "Status";
41
+ TCNetMessageType2[TCNetMessageType2["TimeSync"] = 10] = "TimeSync";
42
+ TCNetMessageType2[TCNetMessageType2["Error"] = 13] = "Error";
43
+ TCNetMessageType2[TCNetMessageType2["Request"] = 20] = "Request";
44
+ TCNetMessageType2[TCNetMessageType2["ApplicationData"] = 30] = "ApplicationData";
45
+ TCNetMessageType2[TCNetMessageType2["Control"] = 101] = "Control";
46
+ TCNetMessageType2[TCNetMessageType2["Text"] = 128] = "Text";
47
+ TCNetMessageType2[TCNetMessageType2["Keyboard"] = 132] = "Keyboard";
48
+ TCNetMessageType2[TCNetMessageType2["Data"] = 200] = "Data";
49
+ TCNetMessageType2[TCNetMessageType2["File"] = 204] = "File";
50
+ TCNetMessageType2[TCNetMessageType2["Time"] = 254] = "Time";
51
+ return TCNetMessageType2;
52
+ })(TCNetMessageType || {});
53
+ var TCNetDataPacketType = /* @__PURE__ */ ((TCNetDataPacketType2) => {
54
+ TCNetDataPacketType2[TCNetDataPacketType2["MetricsData"] = 2] = "MetricsData";
55
+ TCNetDataPacketType2[TCNetDataPacketType2["MetaData"] = 4] = "MetaData";
56
+ TCNetDataPacketType2[TCNetDataPacketType2["BeatGridData"] = 8] = "BeatGridData";
57
+ TCNetDataPacketType2[TCNetDataPacketType2["CUEData"] = 12] = "CUEData";
58
+ TCNetDataPacketType2[TCNetDataPacketType2["SmallWaveFormData"] = 16] = "SmallWaveFormData";
59
+ TCNetDataPacketType2[TCNetDataPacketType2["BigWaveFormData"] = 32] = "BigWaveFormData";
60
+ TCNetDataPacketType2[TCNetDataPacketType2["MixerData"] = 150] = "MixerData";
61
+ return TCNetDataPacketType2;
62
+ })(TCNetDataPacketType || {});
63
+ var NodeType = /* @__PURE__ */ ((NodeType2) => {
64
+ NodeType2[NodeType2["Auto"] = 1] = "Auto";
65
+ NodeType2[NodeType2["Master"] = 2] = "Master";
66
+ NodeType2[NodeType2["Slave"] = 4] = "Slave";
67
+ NodeType2[NodeType2["Repeater"] = 8] = "Repeater";
68
+ return NodeType2;
69
+ })(NodeType || {});
70
+ var TCNetPacket = class {
71
+ buffer;
72
+ header;
73
+ };
74
+ var TCNetManagementHeader = class _TCNetManagementHeader {
75
+ static MAJOR_VERSION = 3;
76
+ static MAGIC_HEADER = "TCN";
77
+ buffer;
78
+ nodeId;
79
+ minorVersion;
80
+ messageType;
81
+ nodeName;
82
+ seq;
83
+ nodeType;
84
+ nodeOptions;
85
+ timestamp;
86
+ constructor(buffer) {
87
+ this.buffer = buffer;
88
+ }
89
+ read() {
90
+ this.nodeId = this.buffer.readUInt16LE(0);
91
+ assert(this.buffer.readUInt8(2) == _TCNetManagementHeader.MAJOR_VERSION);
92
+ this.minorVersion = this.buffer.readUInt8(3);
93
+ assert(this.buffer.slice(4, 7).toString("ascii") == _TCNetManagementHeader.MAGIC_HEADER);
94
+ this.messageType = this.buffer.readUInt8(7);
95
+ this.nodeName = this.buffer.slice(8, 16).toString("ascii").replace(/\0.*$/g, "");
96
+ this.seq = this.buffer.readUInt8(16);
97
+ this.nodeType = this.buffer.readUInt8(17);
98
+ this.nodeOptions = this.buffer.readUInt16LE(18);
99
+ this.timestamp = this.buffer.readUInt32LE(20);
100
+ }
101
+ write() {
102
+ assert(Buffer.from(this.nodeName, "ascii").length <= 8);
103
+ this.buffer.writeUInt16LE(this.nodeId, 0);
104
+ this.buffer.writeUInt8(_TCNetManagementHeader.MAJOR_VERSION, 2);
105
+ this.buffer.writeUInt8(this.minorVersion, 3);
106
+ this.buffer.write(_TCNetManagementHeader.MAGIC_HEADER, 4, "ascii");
107
+ this.buffer.writeUInt8(this.messageType, 7);
108
+ this.buffer.write(this.nodeName.padEnd(8, "\0"), 8, "ascii");
109
+ this.buffer.writeUInt8(this.seq, 16);
110
+ this.buffer.writeUInt8(this.nodeType, 17);
111
+ this.buffer.writeUInt16LE(this.nodeOptions, 18);
112
+ this.buffer.writeUInt32LE(this.timestamp, 20);
113
+ }
114
+ };
115
+ var TCNetOptInPacket = class extends TCNetPacket {
116
+ nodeCount;
117
+ nodeListenerPort;
118
+ uptime;
119
+ vendorName;
120
+ appName;
121
+ majorVersion;
122
+ minorVersion;
123
+ bugVersion;
124
+ read() {
125
+ this.nodeCount = this.buffer.readUInt16LE(24);
126
+ this.nodeListenerPort = this.buffer.readUInt16LE(26);
127
+ this.uptime = this.buffer.readUInt16LE(28);
128
+ this.vendorName = this.buffer.slice(32, 48).toString("ascii").replace(/\0.*$/g, "");
129
+ this.appName = this.buffer.slice(48, 64).toString("ascii").replace(/\0.*$/g, "");
130
+ this.majorVersion = this.buffer.readUInt8(64);
131
+ this.minorVersion = this.buffer.readUInt8(65);
132
+ this.bugVersion = this.buffer.readUInt8(66);
133
+ }
134
+ write() {
135
+ assert(Buffer.from(this.vendorName, "ascii").length <= 16);
136
+ assert(Buffer.from(this.appName, "ascii").length <= 16);
137
+ this.buffer.writeUInt16LE(this.nodeCount, 24);
138
+ this.buffer.writeUInt16LE(this.nodeListenerPort, 26);
139
+ this.buffer.writeUInt16LE(this.uptime, 28);
140
+ this.buffer.write(this.vendorName.padEnd(16, "\0"), 32, "ascii");
141
+ this.buffer.write(this.appName.padEnd(16, "\0"), 48, "ascii");
142
+ this.buffer.writeUInt8(this.majorVersion, 64);
143
+ this.buffer.writeUInt8(this.minorVersion, 65);
144
+ this.buffer.writeUInt8(this.bugVersion, 66);
145
+ }
146
+ length() {
147
+ return 68;
148
+ }
149
+ type() {
150
+ return 2 /* OptIn */;
151
+ }
152
+ };
153
+ var TCNetOptOutPacket = class extends TCNetPacket {
154
+ nodeCount;
155
+ nodeListenerPort;
156
+ read() {
157
+ this.nodeCount = this.buffer.readUInt16LE(24);
158
+ this.nodeListenerPort = this.buffer.readUInt16LE(26);
159
+ }
160
+ write() {
161
+ this.buffer.writeUInt16LE(this.nodeCount, 24);
162
+ this.buffer.writeUInt16LE(this.nodeListenerPort, 26);
163
+ }
164
+ length() {
165
+ return 28;
166
+ }
167
+ type() {
168
+ return 3 /* OptOut */;
169
+ }
170
+ };
171
+ var TCNetLayerStatus = /* @__PURE__ */ ((TCNetLayerStatus2) => {
172
+ TCNetLayerStatus2[TCNetLayerStatus2["IDLE"] = 0] = "IDLE";
173
+ TCNetLayerStatus2[TCNetLayerStatus2["PLAYING"] = 3] = "PLAYING";
174
+ TCNetLayerStatus2[TCNetLayerStatus2["LOOPING"] = 4] = "LOOPING";
175
+ TCNetLayerStatus2[TCNetLayerStatus2["PAUSED"] = 5] = "PAUSED";
176
+ TCNetLayerStatus2[TCNetLayerStatus2["STOPPED"] = 6] = "STOPPED";
177
+ TCNetLayerStatus2[TCNetLayerStatus2["CUEDOWN"] = 7] = "CUEDOWN";
178
+ TCNetLayerStatus2[TCNetLayerStatus2["PLATTERDOWN"] = 8] = "PLATTERDOWN";
179
+ TCNetLayerStatus2[TCNetLayerStatus2["FFWD"] = 9] = "FFWD";
180
+ TCNetLayerStatus2[TCNetLayerStatus2["FFRV"] = 10] = "FFRV";
181
+ TCNetLayerStatus2[TCNetLayerStatus2["HOLD"] = 11] = "HOLD";
182
+ return TCNetLayerStatus2;
183
+ })(TCNetLayerStatus || {});
184
+ var TCNetStatusPacket = class extends TCNetPacket {
185
+ data = null;
186
+ layers = new Array(8);
187
+ read() {
188
+ this.data = {
189
+ nodeCount: this.buffer.readUInt16LE(24),
190
+ nodeListenerPort: this.buffer.readUInt16LE(26),
191
+ smpteMode: this.buffer.readUInt8(83),
192
+ autoMasterMode: this.buffer.readUInt8(84)
193
+ };
194
+ for (let n = 0; n < 8; n++) {
195
+ this.layers[n] = {
196
+ source: this.buffer.readUInt8(34 + n),
197
+ status: this.buffer.readUInt8(42 + n),
198
+ trackID: this.buffer.readUInt32LE(50 + n * 4),
199
+ name: this.buffer.slice(172 + n * 16, 172 + (n + 1) * 16).toString("ascii").replace(/\0.*$/g, "")
200
+ };
201
+ }
202
+ }
203
+ write() {
204
+ throw new Error("not supported!");
205
+ }
206
+ length() {
207
+ return 300;
208
+ }
209
+ type() {
210
+ return 5 /* Status */;
211
+ }
212
+ };
213
+ var TCNetRequestPacket = class extends TCNetPacket {
214
+ dataType;
215
+ layer;
216
+ read() {
217
+ this.dataType = this.buffer.readUInt8(24);
218
+ this.layer = this.buffer.readUInt8(25);
219
+ }
220
+ write() {
221
+ assert(0 <= this.dataType && this.dataType <= 255);
222
+ assert(0 <= this.layer && this.layer <= 255);
223
+ this.buffer.writeUInt8(this.dataType, 24);
224
+ this.buffer.writeUInt8(this.layer, 25);
225
+ }
226
+ length() {
227
+ return 26;
228
+ }
229
+ type() {
230
+ return 20 /* Request */;
231
+ }
232
+ };
233
+ var TCNetTimecodeState = /* @__PURE__ */ ((TCNetTimecodeState2) => {
234
+ TCNetTimecodeState2[TCNetTimecodeState2["Stopped"] = 0] = "Stopped";
235
+ TCNetTimecodeState2[TCNetTimecodeState2["Running"] = 1] = "Running";
236
+ TCNetTimecodeState2[TCNetTimecodeState2["ForceReSync"] = 2] = "ForceReSync";
237
+ return TCNetTimecodeState2;
238
+ })(TCNetTimecodeState || {});
239
+ var TCNetTimecode = class {
240
+ mode;
241
+ state;
242
+ hours;
243
+ minutes;
244
+ seconds;
245
+ frames;
246
+ read(buffer, offset) {
247
+ this.mode = buffer.readUInt8(offset + 0);
248
+ this.state = buffer.readUInt8(offset + 1);
249
+ this.hours = buffer.readUInt8(offset + 2);
250
+ this.minutes = buffer.readUInt8(offset + 3);
251
+ this.seconds = buffer.readUInt8(offset + 4);
252
+ this.frames = buffer.readUInt8(offset + 5);
253
+ }
254
+ };
255
+ var TCNetTimePacket = class extends TCNetPacket {
256
+ _layers = new Array(8);
257
+ _generalSMPTEMode = 0;
258
+ read() {
259
+ for (let n = 0; n < 8; n++) {
260
+ this._layers[n] = {
261
+ currentTimeMillis: this.buffer.readUInt32LE(24 + n * 4),
262
+ totalTimeMillis: this.buffer.readUInt32LE(56 + n * 4),
263
+ beatMarker: this.buffer.readUInt8(88 + n),
264
+ state: this.buffer.readUInt8(96 + n),
265
+ onAir: this.buffer.length > 154 ? this.buffer.readUInt8(154 + n) : 255
266
+ };
267
+ }
268
+ this._generalSMPTEMode = this.buffer.readUInt8(105);
269
+ }
270
+ write() {
271
+ throw new Error("not supported!");
272
+ }
273
+ length() {
274
+ switch (this.buffer.length) {
275
+ case 154:
276
+ case 162:
277
+ return this.buffer.length;
278
+ default:
279
+ return -1;
280
+ }
281
+ }
282
+ type() {
283
+ return 254 /* Time */;
284
+ }
285
+ get layers() {
286
+ return this._layers;
287
+ }
288
+ get generalSMPTEMode() {
289
+ return this._generalSMPTEMode;
290
+ }
291
+ };
292
+ var TCNetDataPacket = class extends TCNetPacket {
293
+ dataType;
294
+ /**
295
+ * 0-indexed layer ID (0-7)
296
+ */
297
+ layer;
298
+ read() {
299
+ this.dataType = this.buffer.readUInt8(24);
300
+ this.layer = this.buffer.readUInt8(25) - 1;
301
+ }
302
+ write() {
303
+ assert(0 <= this.dataType && this.dataType <= 255);
304
+ assert(0 <= this.layer && this.layer <= 255);
305
+ this.buffer.writeUInt8(this.dataType, 24);
306
+ this.buffer.writeUInt8(this.layer, 25);
307
+ }
308
+ length() {
309
+ return -1;
310
+ }
311
+ type() {
312
+ return 200 /* Data */;
313
+ }
314
+ };
315
+ var TCNetLayerSyncMaster = /* @__PURE__ */ ((TCNetLayerSyncMaster2) => {
316
+ TCNetLayerSyncMaster2[TCNetLayerSyncMaster2["Slave"] = 0] = "Slave";
317
+ TCNetLayerSyncMaster2[TCNetLayerSyncMaster2["Master"] = 1] = "Master";
318
+ return TCNetLayerSyncMaster2;
319
+ })(TCNetLayerSyncMaster || {});
320
+ var TCNetDataPacketMetrics = class extends TCNetDataPacket {
321
+ data = null;
322
+ read() {
323
+ this.data = {
324
+ state: this.buffer.readUInt8(27),
325
+ syncMaster: this.buffer.readUInt8(29),
326
+ beatMarker: this.buffer.readUInt8(31),
327
+ trackLength: this.buffer.readUInt32LE(32),
328
+ currentPosition: this.buffer.readUInt32LE(36),
329
+ speed: this.buffer.readUInt32LE(40),
330
+ beatNumber: this.buffer.readUInt32LE(57),
331
+ bpm: this.buffer.readUInt32LE(112),
332
+ pitchBend: this.buffer.readInt16LE(116),
333
+ trackID: this.buffer.readUInt32LE(118)
334
+ };
335
+ }
336
+ write() {
337
+ throw new Error("not supported!");
338
+ }
339
+ length() {
340
+ return 122;
341
+ }
342
+ };
343
+ var TCNetDataPacketMetadata = class extends TCNetDataPacket {
344
+ info = null;
345
+ read() {
346
+ if (this.header.minorVersion < 5) {
347
+ throw new Error("Unsupported packet version");
348
+ }
349
+ this.info = {
350
+ trackArtist: this.buffer.slice(29, 285).toString("utf16le").replace(/\0/g, ""),
351
+ trackTitle: this.buffer.slice(285, 541).toString("utf16le").replace(/\0/g, ""),
352
+ trackKey: this.buffer.readUInt16LE(541),
353
+ trackID: this.buffer.readUInt32LE(543)
354
+ };
355
+ }
356
+ write() {
357
+ throw new Error("not supported!");
358
+ }
359
+ length() {
360
+ return 548;
361
+ }
362
+ };
363
+ var TCNetPackets = {
364
+ [2 /* OptIn */]: TCNetOptInPacket,
365
+ [3 /* OptOut */]: TCNetOptOutPacket,
366
+ [5 /* Status */]: TCNetStatusPacket,
367
+ [10 /* TimeSync */]: null,
368
+ // not yet implemented
369
+ [13 /* Error */]: null,
370
+ // not yet implemented
371
+ [20 /* Request */]: TCNetRequestPacket,
372
+ [30 /* ApplicationData */]: null,
373
+ // not yet implemented
374
+ [101 /* Control */]: null,
375
+ // not yet implemented
376
+ [128 /* Text */]: null,
377
+ // not yet implemented
378
+ [132 /* Keyboard */]: null,
379
+ // not yet implemented
380
+ [200 /* Data */]: TCNetDataPacket,
381
+ [204 /* File */]: null,
382
+ // not yet implemented
383
+ [254 /* Time */]: TCNetTimePacket
384
+ };
385
+ var TCNetDataPackets = {
386
+ [2 /* MetricsData */]: TCNetDataPacketMetrics,
387
+ [4 /* MetaData */]: TCNetDataPacketMetadata,
388
+ [8 /* BeatGridData */]: null,
389
+ // not yet implemented
390
+ [12 /* CUEData */]: null,
391
+ // not yet implemented
392
+ [16 /* SmallWaveFormData */]: null,
393
+ // not yet implemented
394
+ [32 /* BigWaveFormData */]: null,
395
+ // not yet implemented
396
+ [150 /* MixerData */]: null
397
+ // not yet implemented
398
+ };
399
+
400
+ // src/tcnet.ts
401
+ var EventEmitter = __require("events");
402
+ var TCNET_BROADCAST_PORT = 6e4;
403
+ var TCNET_TIMESTAMP_PORT = 60001;
404
+ var TCNetConfiguration = class {
405
+ logger = null;
406
+ unicastPort = 65023;
407
+ applicationCode = 65535;
408
+ nodeId = Math.floor(Math.random() * 65535);
409
+ nodeName = "TCNET.JS";
410
+ vendorName = "CHDXD1";
411
+ appName = "NODE-TCNET";
412
+ broadcastInterface = null;
413
+ broadcastAddress = "255.255.255.255";
414
+ broadcastListeningAddress = "";
415
+ requestTimeout = 2e3;
416
+ };
417
+ var closeSocket = (socket) => new Promise((resolve) => socket.close(() => resolve()));
418
+ var TCNetClient = class extends EventEmitter {
419
+ config;
420
+ broadcastSocket;
421
+ unicastSocket;
422
+ timestampSocket;
423
+ server;
424
+ seq = 0;
425
+ uptime = 0;
426
+ connected = false;
427
+ connectedHandler = null;
428
+ requests = /* @__PURE__ */ new Map();
429
+ announcementInterval;
430
+ /**
431
+ *
432
+ * @param config configuration for TCNet access
433
+ */
434
+ constructor(config) {
435
+ super();
436
+ this.config = config || new TCNetConfiguration();
437
+ if (this.config.broadcastInterface && this.config.broadcastAddress == "255.255.255.255") {
438
+ this.config.broadcastAddress = interfaceAddress(this.config.broadcastInterface);
439
+ }
440
+ this.config.broadcastListeningAddress ||= "0.0.0.0";
441
+ }
442
+ get log() {
443
+ return this.config.logger;
444
+ }
445
+ /**
446
+ * Wrapper method to bind a socket with a Promise
447
+ * @param socket socket to bind
448
+ * @param port port to bind to
449
+ * @param address address to bind to
450
+ * @returns Promise which always resolves (no errors in callback)
451
+ */
452
+ bindSocket(socket, port, address) {
453
+ return new Promise((resolve, reject) => {
454
+ socket.once("error", reject);
455
+ socket.bind(port, address, () => {
456
+ socket.removeListener("error", reject);
457
+ resolve();
458
+ });
459
+ });
460
+ }
461
+ /**
462
+ * Connect to the TCNet networks
463
+ */
464
+ async connect() {
465
+ this.broadcastSocket = createSocket({ type: "udp4", reuseAddr: true }, this.receiveBroadcast.bind(this));
466
+ await this.bindSocket(this.broadcastSocket, TCNET_BROADCAST_PORT, this.config.broadcastListeningAddress);
467
+ this.broadcastSocket.setBroadcast(true);
468
+ this.timestampSocket = createSocket({ type: "udp4", reuseAddr: true }, this.receiveTimestamp.bind(this));
469
+ await this.bindSocket(this.timestampSocket, TCNET_TIMESTAMP_PORT, this.config.broadcastListeningAddress);
470
+ this.timestampSocket.setBroadcast(true);
471
+ this.unicastSocket = createSocket({ type: "udp4", reuseAddr: false }, this.receiveUnicast.bind(this));
472
+ await this.bindSocket(this.unicastSocket, this.config.unicastPort, "0.0.0.0");
473
+ await this.announceApp();
474
+ this.announcementInterval = setInterval(this.announceApp.bind(this), 1e3);
475
+ await this.waitConnected();
476
+ }
477
+ /**
478
+ * Disconnects from TCNet network
479
+ */
480
+ disconnect() {
481
+ clearInterval(this.announcementInterval);
482
+ this.removeAllListeners();
483
+ this.connected = false;
484
+ return Promise.all([
485
+ closeSocket(this.broadcastSocket),
486
+ closeSocket(this.unicastSocket),
487
+ closeSocket(this.timestampSocket)
488
+ ]).catch((err) => {
489
+ const error = new Error("Error disconnecting from TCNet");
490
+ error.cause = err instanceof Error ? err : new Error(String(err));
491
+ this.log?.error(error);
492
+ }).then(() => void 0);
493
+ }
494
+ /**
495
+ * Waiting for unicast from a master
496
+ */
497
+ waitConnected() {
498
+ return new Promise((resolve, reject) => {
499
+ this.connectedHandler = resolve;
500
+ setTimeout(() => {
501
+ if (!this.connected) {
502
+ this.disconnect();
503
+ reject(new Error("Timeout connecting to network"));
504
+ }
505
+ }, this.config.requestTimeout);
506
+ });
507
+ }
508
+ /**
509
+ * Parse a packet from a ManagementHeader
510
+ * @param header the received management header
511
+ * @returns the parsed packet
512
+ */
513
+ parsePacket(header) {
514
+ const packetClass = TCNetPackets[header.messageType];
515
+ if (packetClass !== null) {
516
+ const packet = new packetClass();
517
+ packet.buffer = header.buffer;
518
+ packet.header = header;
519
+ if (packet.length() !== -1 && packet.length() !== header.buffer.length) {
520
+ this.log?.debug(
521
+ `${TCNetMessageType[header.messageType]} packet has the wrong length (expected: ${packet.length()}, received: ${header.buffer.length})`
522
+ );
523
+ return null;
524
+ }
525
+ packet.read();
526
+ return packet;
527
+ } else {
528
+ this.log?.debug(`Unknown packet type: ${header.messageType} ${TCNetMessageType[header.messageType]}`);
529
+ }
530
+ return null;
531
+ }
532
+ /**
533
+ * Callback method to receive datagrams on the broadcast socket
534
+ *
535
+ * @param msg datagram buffer
536
+ * @param rinfo remoteinfo
537
+ */
538
+ receiveBroadcast(msg, rinfo) {
539
+ const mgmtHeader = new TCNetManagementHeader(msg);
540
+ mgmtHeader.read();
541
+ const packet = this.parsePacket(mgmtHeader);
542
+ if (packet) {
543
+ if (packet instanceof TCNetOptInPacket) {
544
+ if (mgmtHeader.nodeType == 2 /* Master */) {
545
+ this.server = rinfo;
546
+ this.server.port = packet.nodeListenerPort;
547
+ if (this.connectedHandler) {
548
+ this.connected = true;
549
+ this.announceApp().catch((err) => {
550
+ const error = err instanceof Error ? err : new Error(String(err));
551
+ this.log?.debug(`Failed to announce on connect: ${error.message}`);
552
+ });
553
+ this.connectedHandler();
554
+ this.connectedHandler = null;
555
+ }
556
+ }
557
+ }
558
+ if (packet instanceof TCNetOptOutPacket) {
559
+ if (mgmtHeader.nodeType == 2 /* Master */) {
560
+ this.log?.debug("Received optout from current Master");
561
+ if (this.server?.address == rinfo.address && this.server?.port == packet.nodeListenerPort) {
562
+ this.server = null;
563
+ }
564
+ }
565
+ }
566
+ if (this.connected) {
567
+ this.emit("broadcast", packet);
568
+ }
569
+ } else {
570
+ this.log?.debug(`Unknown broadcast packet type: ${mgmtHeader.messageType}`);
571
+ }
572
+ }
573
+ /**
574
+ * Callback method to receive datagrams on the unicast socket
575
+ *
576
+ * @param msg datagram buffer
577
+ * @param rinfo remoteinfo
578
+ */
579
+ receiveUnicast(msg, rinfo) {
580
+ const mgmtHeader = new TCNetManagementHeader(msg);
581
+ mgmtHeader.read();
582
+ const packet = this.parsePacket(mgmtHeader);
583
+ if (packet instanceof TCNetDataPacket) {
584
+ const dataPacketClass = TCNetDataPackets[packet.dataType];
585
+ if (dataPacketClass !== null) {
586
+ const dataPacket = new dataPacketClass();
587
+ dataPacket.buffer = msg;
588
+ dataPacket.header = mgmtHeader;
589
+ dataPacket.dataType = packet.dataType;
590
+ dataPacket.layer = packet.layer;
591
+ dataPacket.read();
592
+ if (this.connected) {
593
+ this.emit("data", dataPacket);
594
+ }
595
+ const key = `${dataPacket.dataType}-${dataPacket.layer}`;
596
+ const pendingRequest = this.requests.get(key);
597
+ if (pendingRequest) {
598
+ this.requests.delete(key);
599
+ clearTimeout(pendingRequest.timeout);
600
+ pendingRequest.resolve(dataPacket);
601
+ }
602
+ }
603
+ } else if (packet instanceof TCNetOptInPacket) {
604
+ if (mgmtHeader.nodeType == 2 /* Master */) {
605
+ this.server = rinfo;
606
+ this.server.port = packet.nodeListenerPort;
607
+ if (this.connectedHandler) {
608
+ this.connected = true;
609
+ this.connectedHandler();
610
+ this.connectedHandler = null;
611
+ }
612
+ }
613
+ } else {
614
+ if (this.connected) {
615
+ this.emit("broadcast", packet);
616
+ }
617
+ }
618
+ }
619
+ /**
620
+ * Callback method to receive datagrams on the timestamp socket
621
+ * @param msg datagram buffer
622
+ * @param rinfo remoteinfo
623
+ */
624
+ receiveTimestamp(msg, _rinfo) {
625
+ const mgmtHeader = new TCNetManagementHeader(msg);
626
+ mgmtHeader.read();
627
+ if (mgmtHeader.messageType !== 254 /* Time */) {
628
+ this.log?.debug("Received non Time packet on Time port");
629
+ return;
630
+ }
631
+ const packet = this.parsePacket(mgmtHeader);
632
+ this.emit("time", packet);
633
+ }
634
+ /**
635
+ * Fill headers of a packet
636
+ *
637
+ * @param packet Packet that needs header information
638
+ */
639
+ fillHeader(packet) {
640
+ packet.header = new TCNetManagementHeader(packet.buffer);
641
+ packet.header.minorVersion = 5;
642
+ packet.header.nodeId = this.config.nodeId;
643
+ packet.header.messageType = packet.type();
644
+ packet.header.nodeName = this.config.nodeName;
645
+ packet.header.seq = this.seq = (this.seq + 1) % 255;
646
+ packet.header.nodeType = 4;
647
+ packet.header.nodeOptions = 0;
648
+ packet.header.timestamp = 0;
649
+ }
650
+ /**
651
+ * Generalized method to send packets to a given destination on a given socket
652
+ *
653
+ * @param packet Packet to send
654
+ * @param socket Socket to send on
655
+ * @param port Destination Port
656
+ * @param address Destination Address
657
+ */
658
+ sendPacket(packet, socket, port, address) {
659
+ return new Promise((resolve, reject) => {
660
+ const buffer = Buffer.alloc(packet.length());
661
+ packet.buffer = buffer;
662
+ this.fillHeader(packet);
663
+ packet.header.write();
664
+ packet.write();
665
+ socket.send(buffer, port, address, (err) => {
666
+ if (err) reject(err);
667
+ resolve();
668
+ });
669
+ });
670
+ }
671
+ /**
672
+ * Sends a packet to the discovered server
673
+ * @param packet Packet to send
674
+ */
675
+ async sendServer(packet) {
676
+ if (this.server === null) {
677
+ throw new Error("Server not yet discovered");
678
+ }
679
+ await this.sendPacket(packet, this.broadcastSocket, this.server.port, this.server.address);
680
+ }
681
+ /**
682
+ * Called every second to announce our app on the network
683
+ */
684
+ async announceApp() {
685
+ const optInPacket = new TCNetOptInPacket();
686
+ optInPacket.nodeCount = 0;
687
+ optInPacket.nodeListenerPort = this.config.unicastPort;
688
+ optInPacket.uptime = this.uptime++;
689
+ if (this.uptime >= 12 * 60 * 60) {
690
+ this.uptime = 0;
691
+ }
692
+ optInPacket.vendorName = this.config.vendorName;
693
+ optInPacket.appName = this.config.appName;
694
+ optInPacket.majorVersion = 1;
695
+ optInPacket.minorVersion = 1;
696
+ optInPacket.bugVersion = 1;
697
+ await this.broadcastPacket(optInPacket);
698
+ if (this.server) {
699
+ await this.sendServer(optInPacket);
700
+ }
701
+ }
702
+ /**
703
+ * Broadcasts a packet to the network
704
+ *
705
+ * @param packet packet to broadcast
706
+ */
707
+ async broadcastPacket(packet) {
708
+ await this.sendPacket(packet, this.broadcastSocket, TCNET_BROADCAST_PORT, this.config.broadcastAddress);
709
+ }
710
+ /**
711
+ * Sends a request packet to the discovered server
712
+ *
713
+ * @param dataType requested data type
714
+ * @param layer requested layer
715
+ * @returns Promise to wait for answer on request
716
+ */
717
+ requestData(dataType, layer) {
718
+ return new Promise((resolve, reject) => {
719
+ if (!Number.isInteger(layer) || layer < 0 || layer > 7) {
720
+ reject(new RangeError("layer must be an integer between 0 and 7"));
721
+ return;
722
+ }
723
+ const request = new TCNetRequestPacket();
724
+ request.dataType = dataType;
725
+ request.layer = layer + 1;
726
+ const key = `${dataType}-${layer}`;
727
+ const timeout = setTimeout(() => {
728
+ if (this.requests.delete(key)) {
729
+ reject(new Error("Timeout while requesting data"));
730
+ }
731
+ }, this.config.requestTimeout);
732
+ this.requests.set(key, { resolve, timeout });
733
+ this.sendServer(request).catch((err) => {
734
+ if (this.requests.delete(key)) {
735
+ clearTimeout(timeout);
736
+ reject(err);
737
+ }
738
+ });
739
+ });
740
+ }
741
+ };
742
+ export {
743
+ NodeType,
744
+ TCNetClient,
745
+ TCNetConfiguration,
746
+ TCNetDataPacket,
747
+ TCNetDataPacketMetadata,
748
+ TCNetDataPacketMetrics,
749
+ TCNetDataPacketType,
750
+ TCNetDataPackets,
751
+ TCNetLayerStatus,
752
+ TCNetLayerSyncMaster,
753
+ TCNetManagementHeader,
754
+ TCNetMessageType,
755
+ TCNetOptInPacket,
756
+ TCNetOptOutPacket,
757
+ TCNetPacket,
758
+ TCNetPackets,
759
+ TCNetRequestPacket,
760
+ TCNetStatusPacket,
761
+ TCNetTimePacket,
762
+ TCNetTimecode,
763
+ TCNetTimecodeState
764
+ };