@novastar/taurus 0.1.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/index.cjs +402 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +191 -0
- package/dist/index.d.mts +191 -0
- package/dist/index.mjs +355 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrei Sarakeev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @novastar/taurus
|
|
2
|
+
|
|
3
|
+
Node.js client for the high-level management protocol used by NovaStar Taurus multimedia players.
|
|
4
|
+
It complements the register protocol on TCP port 5200: this package connects to the player service
|
|
5
|
+
on TCP port 16606 (TLS when privacy mode is enabled).
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { discoverTaurusPlayers, TaurusClient } from '@novastar/taurus';
|
|
9
|
+
|
|
10
|
+
const [player] = await discoverTaurusPlayers();
|
|
11
|
+
const client = await TaurusClient.connect({
|
|
12
|
+
host: player.address,
|
|
13
|
+
port: player.tcpPort,
|
|
14
|
+
privacy: player.privacy,
|
|
15
|
+
});
|
|
16
|
+
await client.login({sn: player.sn, password: process.env.TAURUS_PASSWORD!});
|
|
17
|
+
|
|
18
|
+
console.log(await client.getEnvironmentBrightness());
|
|
19
|
+
console.log(await client.getBrightness());
|
|
20
|
+
await client.setBrightness(50); // volatile by default; does not write flash
|
|
21
|
+
|
|
22
|
+
client.close();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The device password is never discovered or stored by the library. Pass it explicitly at login.
|
|
26
|
+
TLS certificate validation defaults to off because Taurus devices use a self-signed certificate;
|
|
27
|
+
set `rejectUnauthorized` and `ca` when the device has a trusted certificate.
|
|
28
|
+
|
|
29
|
+
Discovery sends the binary Taurus search request to UDP port 16601. Legacy NovaStar discovery on
|
|
30
|
+
UDP port 3800 only returns an `rpProMI:` marker and does not contain the serial number required for
|
|
31
|
+
login. The register protocol on TCP port 5200 remains available separately through `@novastar/net`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let crypto = require("crypto");
|
|
30
|
+
let tls = require("tls");
|
|
31
|
+
tls = __toESM(tls);
|
|
32
|
+
let typed_struct = require("typed-struct");
|
|
33
|
+
let dgram = require("dgram");
|
|
34
|
+
dgram = __toESM(dgram);
|
|
35
|
+
let os = require("os");
|
|
36
|
+
|
|
37
|
+
//#region src/packet.ts
|
|
38
|
+
const TAURUS_PACKET_FLAG = 1313822273;
|
|
39
|
+
const TAURUS_REQUEST = 21073;
|
|
40
|
+
const TAURUS_RESPONSE = 21072;
|
|
41
|
+
const TAURUS_DISCOVERY = 34901;
|
|
42
|
+
/**
|
|
43
|
+
* Taurus request argument.
|
|
44
|
+
*
|
|
45
|
+
* Wire layout:
|
|
46
|
+
* action UInt8
|
|
47
|
+
* type UInt8
|
|
48
|
+
* reserved UInt16LE
|
|
49
|
+
*/
|
|
50
|
+
const TaurusRequest = new typed_struct.Struct("TaurusRequest").UInt8("action").UInt8("type").seek(2).compile();
|
|
51
|
+
/**
|
|
52
|
+
* Taurus response argument.
|
|
53
|
+
*
|
|
54
|
+
* Wire layout:
|
|
55
|
+
* status UInt16LE
|
|
56
|
+
* action UInt8
|
|
57
|
+
* type UInt8
|
|
58
|
+
*/
|
|
59
|
+
const TaurusResponse = new typed_struct.Struct("TaurusResponse").UInt16LE("status").UInt8("action").UInt8("type").compile();
|
|
60
|
+
/**
|
|
61
|
+
* Taurus packet.
|
|
62
|
+
*
|
|
63
|
+
* The same 4 bytes are exposed as both request and response structures.
|
|
64
|
+
* packetType determines which representation should be used.
|
|
65
|
+
*/
|
|
66
|
+
const TaurusPacket = new typed_struct.Struct("TaurusPacket").UInt32LE("flag", TAURUS_PACKET_FLAG).UInt32LE("sequence").UInt16LE("packetType", (0, typed_struct.typed)()).UInt16LE("what").Struct("req", TaurusRequest).back().Struct("res", TaurusResponse).UInt32LE("length").UInt8("encodeType").seek(1).UInt16LE("checksum").Buffer("body").compile();
|
|
67
|
+
const TAURUS_HEADER_SIZE = TaurusPacket.baseSize;
|
|
68
|
+
const FLAG_OFFSET = TaurusPacket.getOffsetOf("flag");
|
|
69
|
+
const LENGTH_OFFSET = TaurusPacket.getOffsetOf("length");
|
|
70
|
+
const CHECKSUM_OFFSET = TaurusPacket.getOffsetOf("checksum");
|
|
71
|
+
const signedByte = (value) => value > 127 ? value - 256 : value;
|
|
72
|
+
/**
|
|
73
|
+
* Calculates Taurus header checksum.
|
|
74
|
+
*
|
|
75
|
+
* The checksum is the sum of all signed 8-bit values preceding
|
|
76
|
+
* the checksum field.
|
|
77
|
+
*/
|
|
78
|
+
const calculateTaurusHeaderChecksum = (header) => {
|
|
79
|
+
if (header.length < CHECKSUM_OFFSET) throw new RangeError("Taurus header is too short");
|
|
80
|
+
let checksum = 0;
|
|
81
|
+
for (let index = 0; index < CHECKSUM_OFFSET; index += 1) checksum += signedByte(header[index]);
|
|
82
|
+
return checksum & 65535;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Returns the full packet size once the complete header is available.
|
|
86
|
+
*/
|
|
87
|
+
const getTaurusPacketSize = (buffer) => {
|
|
88
|
+
if (buffer.length < TAURUS_HEADER_SIZE) return;
|
|
89
|
+
const view = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
90
|
+
if (view.readUInt32LE(FLAG_OFFSET) !== 1313822273) throw new Error("Invalid Taurus packet flag");
|
|
91
|
+
return TAURUS_HEADER_SIZE + view.readUInt32LE(LENGTH_OFFSET);
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Encodes a Taurus request.
|
|
95
|
+
*/
|
|
96
|
+
const encodeTaurusRequest = (sequence, { what, type, action }, body = Buffer.alloc(0), encodeType = 0) => {
|
|
97
|
+
const payload = typeof body === "string" ? Buffer.from(body, "utf8") : body;
|
|
98
|
+
const packet = new TaurusPacket(TAURUS_HEADER_SIZE + payload.length);
|
|
99
|
+
packet.sequence = sequence;
|
|
100
|
+
packet.packetType = TAURUS_REQUEST;
|
|
101
|
+
packet.what = what;
|
|
102
|
+
packet.req.action = action;
|
|
103
|
+
packet.req.type = type;
|
|
104
|
+
packet.length = payload.length;
|
|
105
|
+
packet.encodeType = encodeType;
|
|
106
|
+
payload.copy(packet.body);
|
|
107
|
+
const raw = TaurusPacket.raw(packet);
|
|
108
|
+
packet.checksum = calculateTaurusHeaderChecksum(raw);
|
|
109
|
+
return raw;
|
|
110
|
+
};
|
|
111
|
+
/** Encodes the UDP search request used by Taurus players on port 16601. */
|
|
112
|
+
const encodeTaurusDiscoveryRequest = (supportsEncryption = true) => {
|
|
113
|
+
const packet = new TaurusPacket(TAURUS_HEADER_SIZE);
|
|
114
|
+
packet.sequence = 4294967295;
|
|
115
|
+
packet.packetType = TAURUS_DISCOVERY;
|
|
116
|
+
packet.what = 129;
|
|
117
|
+
packet.req.action = supportsEncryption ? 1 : 0;
|
|
118
|
+
packet.req.type = 0;
|
|
119
|
+
packet.length = 0;
|
|
120
|
+
packet.checksum = calculateTaurusHeaderChecksum(TaurusPacket.raw(packet));
|
|
121
|
+
return TaurusPacket.raw(packet);
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Decodes and validates a Taurus packet.
|
|
125
|
+
*/
|
|
126
|
+
const decodeTaurusPacket = (buffer) => {
|
|
127
|
+
const view = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
128
|
+
const packetSize = getTaurusPacketSize(view);
|
|
129
|
+
if (packetSize === void 0 || view.length < packetSize) throw new Error("Incomplete Taurus packet");
|
|
130
|
+
const expectedChecksum = view.readUInt16LE(CHECKSUM_OFFSET);
|
|
131
|
+
const actualChecksum = calculateTaurusHeaderChecksum(view);
|
|
132
|
+
if (actualChecksum !== expectedChecksum) throw new Error(`Invalid Taurus header checksum: expected 0x${expectedChecksum.toString(16)}, calculated 0x${actualChecksum.toString(16)}`);
|
|
133
|
+
return new TaurusPacket(view.subarray(0, packetSize));
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/connection.ts
|
|
138
|
+
var TaurusResponseError = class extends Error {
|
|
139
|
+
status;
|
|
140
|
+
responseBody;
|
|
141
|
+
constructor(status, responseBody) {
|
|
142
|
+
super(`Taurus request failed with status 0x${status.toString(16)}${responseBody ? `: ${responseBody}` : ""}`);
|
|
143
|
+
this.status = status;
|
|
144
|
+
this.responseBody = responseBody;
|
|
145
|
+
this.name = "TaurusResponseError";
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var TaurusConnection = class {
|
|
149
|
+
stream;
|
|
150
|
+
timeout;
|
|
151
|
+
#pending = /* @__PURE__ */ new Map();
|
|
152
|
+
#buffer = Buffer.alloc(0);
|
|
153
|
+
#sequence = 0;
|
|
154
|
+
#closed = false;
|
|
155
|
+
constructor(stream, timeout = 5e3) {
|
|
156
|
+
this.stream = stream;
|
|
157
|
+
this.timeout = timeout;
|
|
158
|
+
stream.on("data", (chunk) => this.#onData(Buffer.from(chunk)));
|
|
159
|
+
stream.once("close", () => this.#close(/* @__PURE__ */ new Error("Taurus connection closed")));
|
|
160
|
+
stream.once("error", (error) => this.#close(error));
|
|
161
|
+
}
|
|
162
|
+
get closed() {
|
|
163
|
+
return this.#closed;
|
|
164
|
+
}
|
|
165
|
+
request(command, body) {
|
|
166
|
+
if (this.#closed) return Promise.reject(/* @__PURE__ */ new Error("Taurus connection is closed"));
|
|
167
|
+
const sequence = (this.#sequence += 1) >>> 0;
|
|
168
|
+
const packet = encodeTaurusRequest(sequence, command, body);
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const timer = setTimeout(() => {
|
|
171
|
+
this.#pending.delete(sequence);
|
|
172
|
+
reject(/* @__PURE__ */ new Error(`Taurus request ${sequence} timed out`));
|
|
173
|
+
}, this.timeout);
|
|
174
|
+
this.#pending.set(sequence, {
|
|
175
|
+
resolve,
|
|
176
|
+
reject,
|
|
177
|
+
timer
|
|
178
|
+
});
|
|
179
|
+
this.stream.write(packet, (error) => {
|
|
180
|
+
if (!error) return;
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
this.#pending.delete(sequence);
|
|
183
|
+
reject(error);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async requestJson(command, body) {
|
|
188
|
+
const response = await this.request(command, body === void 0 ? void 0 : JSON.stringify(body));
|
|
189
|
+
const responseBody = response.body.toString("utf8");
|
|
190
|
+
if (response.res.status !== 0) throw new TaurusResponseError(response.res.status, responseBody);
|
|
191
|
+
return responseBody ? JSON.parse(responseBody) : void 0;
|
|
192
|
+
}
|
|
193
|
+
close() {
|
|
194
|
+
if (!this.stream.destroyed) this.stream.destroy();
|
|
195
|
+
this.#close(/* @__PURE__ */ new Error("Taurus connection closed"));
|
|
196
|
+
}
|
|
197
|
+
#onData(chunk) {
|
|
198
|
+
this.#buffer = this.#buffer.length ? Buffer.concat([this.#buffer, chunk]) : chunk;
|
|
199
|
+
while (this.#buffer.length >= TAURUS_HEADER_SIZE) {
|
|
200
|
+
let packetSize;
|
|
201
|
+
try {
|
|
202
|
+
packetSize = getTaurusPacketSize(this.#buffer);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
this.#close(error);
|
|
205
|
+
this.stream.destroy(error);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (packetSize === void 0 || this.#buffer.length < packetSize) return;
|
|
209
|
+
const raw = this.#buffer.subarray(0, packetSize);
|
|
210
|
+
this.#buffer = this.#buffer.subarray(packetSize);
|
|
211
|
+
const packet = decodeTaurusPacket(raw);
|
|
212
|
+
if (packet.packetType !== 21072) continue;
|
|
213
|
+
const pending = this.#pending.get(packet.sequence);
|
|
214
|
+
if (!pending) continue;
|
|
215
|
+
clearTimeout(pending.timer);
|
|
216
|
+
this.#pending.delete(packet.sequence);
|
|
217
|
+
pending.resolve(packet);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
#close(error) {
|
|
221
|
+
if (this.#closed) return;
|
|
222
|
+
this.#closed = true;
|
|
223
|
+
for (const pending of this.#pending.values()) {
|
|
224
|
+
clearTimeout(pending.timer);
|
|
225
|
+
pending.reject(error);
|
|
226
|
+
}
|
|
227
|
+
this.#pending.clear();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/client.ts
|
|
233
|
+
const TAURUS_MANAGEMENT_PORT = 16606;
|
|
234
|
+
const numberOrUndefined = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
235
|
+
var TaurusClient = class TaurusClient {
|
|
236
|
+
connection;
|
|
237
|
+
constructor(connection) {
|
|
238
|
+
this.connection = connection;
|
|
239
|
+
}
|
|
240
|
+
static async connect(options) {
|
|
241
|
+
const { host, port = TAURUS_MANAGEMENT_PORT, timeout = 5e3, privacy = true, ...tlsOptions } = options;
|
|
242
|
+
if (!privacy) {
|
|
243
|
+
const { connect } = await import("net");
|
|
244
|
+
const socket = connect({
|
|
245
|
+
host,
|
|
246
|
+
port
|
|
247
|
+
});
|
|
248
|
+
await new Promise((resolve, reject) => {
|
|
249
|
+
socket.once("connect", resolve);
|
|
250
|
+
socket.once("error", reject);
|
|
251
|
+
});
|
|
252
|
+
return new TaurusClient(new TaurusConnection(socket, timeout));
|
|
253
|
+
}
|
|
254
|
+
const socket = tls.default.connect({
|
|
255
|
+
host,
|
|
256
|
+
port,
|
|
257
|
+
rejectUnauthorized: false,
|
|
258
|
+
...tlsOptions
|
|
259
|
+
});
|
|
260
|
+
await new Promise((resolve, reject) => {
|
|
261
|
+
socket.once("secureConnect", resolve);
|
|
262
|
+
socket.once("error", reject);
|
|
263
|
+
});
|
|
264
|
+
return new TaurusClient(new TaurusConnection(socket, timeout));
|
|
265
|
+
}
|
|
266
|
+
async login(options) {
|
|
267
|
+
const { token: _token, password: _password, ...safeResponse } = await this.connection.requestJson({
|
|
268
|
+
what: 0,
|
|
269
|
+
type: 0,
|
|
270
|
+
action: 0
|
|
271
|
+
}, {
|
|
272
|
+
sn: options.sn,
|
|
273
|
+
username: options.username ?? "admin",
|
|
274
|
+
password: options.password,
|
|
275
|
+
loginType: 0,
|
|
276
|
+
source: {
|
|
277
|
+
type: 1,
|
|
278
|
+
platform: 2,
|
|
279
|
+
platformVersion: "3.6.0"
|
|
280
|
+
},
|
|
281
|
+
clientId: options.clientId ?? (0, crypto.randomUUID)(),
|
|
282
|
+
clientName: options.clientName ?? "novastar.js"
|
|
283
|
+
});
|
|
284
|
+
return safeResponse;
|
|
285
|
+
}
|
|
286
|
+
async getBrightness() {
|
|
287
|
+
const result = await this.connection.requestJson({
|
|
288
|
+
what: 24,
|
|
289
|
+
type: 1,
|
|
290
|
+
action: 5
|
|
291
|
+
});
|
|
292
|
+
return {
|
|
293
|
+
brightness: numberOrUndefined(result.brightness),
|
|
294
|
+
ratio: numberOrUndefined(result.ratio),
|
|
295
|
+
solidity: result.solidity === true,
|
|
296
|
+
orderId: numberOrUndefined(result.orderId) ?? -1
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async setBrightness(percent, persist = false) {
|
|
300
|
+
if (!Number.isFinite(percent) || percent < 0 || percent > 100) throw new RangeError("Brightness must be between 0 and 100");
|
|
301
|
+
await this.connection.requestJson({
|
|
302
|
+
what: 24,
|
|
303
|
+
type: 1,
|
|
304
|
+
action: 4
|
|
305
|
+
}, {
|
|
306
|
+
ratio: percent,
|
|
307
|
+
solidity: persist,
|
|
308
|
+
orderId: -1
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
async getEnvironmentBrightness() {
|
|
312
|
+
const value = numberOrUndefined((await this.connection.requestJson({
|
|
313
|
+
what: 26,
|
|
314
|
+
type: 0,
|
|
315
|
+
action: 5
|
|
316
|
+
})).value);
|
|
317
|
+
if (value === void 0 || value === -1e3) throw new Error("Taurus ambient light sensor is unavailable");
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
close() {
|
|
321
|
+
this.connection.close();
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/discovery.ts
|
|
327
|
+
const TAURUS_DISCOVERY_PORT = 16601;
|
|
328
|
+
const ipv4ToUint32 = (address) => address.split(".").reduce((result, octet) => result << 8 | Number(octet), 0) >>> 0;
|
|
329
|
+
const getBroadcastAddress = (address, netmask) => {
|
|
330
|
+
const broadcast = (ipv4ToUint32(address) | ~ipv4ToUint32(netmask)) >>> 0;
|
|
331
|
+
return [
|
|
332
|
+
24,
|
|
333
|
+
16,
|
|
334
|
+
8,
|
|
335
|
+
0
|
|
336
|
+
].map((shift) => broadcast >>> shift & 255).join(".");
|
|
337
|
+
};
|
|
338
|
+
const parseTaurusDiscoveryResponse = (message, address) => {
|
|
339
|
+
try {
|
|
340
|
+
const packet = decodeTaurusPacket(message);
|
|
341
|
+
if (packet.packetType !== 34901 || packet.what !== 1) return void 0;
|
|
342
|
+
const value = JSON.parse(packet.body.toString("utf8"));
|
|
343
|
+
if (typeof value.sn !== "string" || typeof value.tcpPort !== "number") return void 0;
|
|
344
|
+
return {
|
|
345
|
+
...value,
|
|
346
|
+
address
|
|
347
|
+
};
|
|
348
|
+
} catch {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const discoverFromInterface = ({ address, netmask }, timeout, destination) => new Promise((resolve) => {
|
|
353
|
+
const socket = dgram.default.createSocket("udp4");
|
|
354
|
+
const players = /* @__PURE__ */ new Map();
|
|
355
|
+
let completed = false;
|
|
356
|
+
const complete = () => {
|
|
357
|
+
if (completed) return;
|
|
358
|
+
completed = true;
|
|
359
|
+
try {
|
|
360
|
+
socket.close();
|
|
361
|
+
} catch {}
|
|
362
|
+
resolve([...players.values()]);
|
|
363
|
+
};
|
|
364
|
+
socket.once("error", complete);
|
|
365
|
+
socket.on("message", (message, remote) => {
|
|
366
|
+
const player = parseTaurusDiscoveryResponse(message, remote.address);
|
|
367
|
+
if (player) players.set(player.sn, player);
|
|
368
|
+
});
|
|
369
|
+
socket.bind(0, address, () => {
|
|
370
|
+
socket.setBroadcast(true);
|
|
371
|
+
const target = destination ?? getBroadcastAddress(address, netmask);
|
|
372
|
+
socket.send(encodeTaurusDiscoveryRequest(), TAURUS_DISCOVERY_PORT, target, (error) => {
|
|
373
|
+
if (error) complete();
|
|
374
|
+
});
|
|
375
|
+
setTimeout(complete, timeout).unref();
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
const discoverTaurusPlayers = async (destination, timeout = 1e3) => {
|
|
379
|
+
const interfaces = Object.values((0, os.networkInterfaces)()).flat().filter((info) => Boolean(info && !info.internal && info.family === "IPv4"));
|
|
380
|
+
return [...(await Promise.all(interfaces.map((info) => discoverFromInterface(info, timeout, destination)))).flat().reduce((players, player) => players.set(player.sn, player), /* @__PURE__ */ new Map()).values()];
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
exports.TAURUS_DISCOVERY = TAURUS_DISCOVERY;
|
|
385
|
+
exports.TAURUS_DISCOVERY_PORT = TAURUS_DISCOVERY_PORT;
|
|
386
|
+
exports.TAURUS_HEADER_SIZE = TAURUS_HEADER_SIZE;
|
|
387
|
+
exports.TAURUS_MANAGEMENT_PORT = TAURUS_MANAGEMENT_PORT;
|
|
388
|
+
exports.TAURUS_PACKET_FLAG = TAURUS_PACKET_FLAG;
|
|
389
|
+
exports.TAURUS_REQUEST = TAURUS_REQUEST;
|
|
390
|
+
exports.TAURUS_RESPONSE = TAURUS_RESPONSE;
|
|
391
|
+
exports.TaurusClient = TaurusClient;
|
|
392
|
+
exports.TaurusConnection = TaurusConnection;
|
|
393
|
+
exports.TaurusPacket = TaurusPacket;
|
|
394
|
+
exports.TaurusResponseError = TaurusResponseError;
|
|
395
|
+
exports.calculateTaurusHeaderChecksum = calculateTaurusHeaderChecksum;
|
|
396
|
+
exports.decodeTaurusPacket = decodeTaurusPacket;
|
|
397
|
+
exports.discoverTaurusPlayers = discoverTaurusPlayers;
|
|
398
|
+
exports.encodeTaurusDiscoveryRequest = encodeTaurusDiscoveryRequest;
|
|
399
|
+
exports.encodeTaurusRequest = encodeTaurusRequest;
|
|
400
|
+
exports.getTaurusPacketSize = getTaurusPacketSize;
|
|
401
|
+
exports.parseTaurusDiscoveryResponse = parseTaurusDiscoveryResponse;
|
|
402
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Struct","#pending","#onData","#close","#closed","#sequence","#buffer"],"sources":["../src/packet.ts","../src/connection.ts","../src/client.ts","../src/discovery.ts"],"sourcesContent":["import { ExtractType, Struct, typed } from 'typed-struct';\n\nexport const TAURUS_PACKET_FLAG = 0x4e4f5641;\nexport const TAURUS_REQUEST = 0x5251;\nexport const TAURUS_RESPONSE = 0x5250;\nexport const TAURUS_DISCOVERY = 0x8855;\n\n/**\n * Taurus request argument.\n *\n * Wire layout:\n * action UInt8\n * type UInt8\n * reserved UInt16LE\n */\nconst TaurusRequest = new Struct('TaurusRequest')\n .UInt8('action')\n .UInt8('type')\n .seek(2) // reserved\n .compile();\n\nexport interface TaurusRequest extends ExtractType<typeof TaurusRequest, false> {}\n\n/**\n * Taurus response argument.\n *\n * Wire layout:\n * status UInt16LE\n * action UInt8\n * type UInt8\n */\nconst TaurusResponse = new Struct('TaurusResponse')\n .UInt16LE('status')\n .UInt8('action')\n .UInt8('type')\n .compile();\n\nexport interface TaurusResponse extends ExtractType<typeof TaurusResponse, false> {}\n\n/**\n * Taurus packet.\n *\n * The same 4 bytes are exposed as both request and response structures.\n * packetType determines which representation should be used.\n */\nexport const TaurusPacket = new Struct('TaurusPacket')\n .UInt32LE('flag', TAURUS_PACKET_FLAG)\n .UInt32LE('sequence')\n .UInt16LE(\n 'packetType',\n typed<typeof TAURUS_REQUEST | typeof TAURUS_RESPONSE | typeof TAURUS_DISCOVERY>(),\n )\n .UInt16LE('what')\n\n // Union: request/response occupy the same four bytes.\n .Struct('req', TaurusRequest)\n .back()\n .Struct('res', TaurusResponse)\n .UInt32LE('length')\n .UInt8('encodeType')\n .seek(1)\n .UInt16LE('checksum')\n .Buffer('body')\n .compile();\n\nexport interface TaurusPacket extends ExtractType<typeof TaurusPacket, false> {}\n\nexport interface TaurusCommand {\n what: number;\n type: number;\n action: number;\n}\n\nexport const TAURUS_HEADER_SIZE = TaurusPacket.baseSize;\n\nconst FLAG_OFFSET = TaurusPacket.getOffsetOf('flag');\nconst LENGTH_OFFSET = TaurusPacket.getOffsetOf('length');\nconst CHECKSUM_OFFSET = TaurusPacket.getOffsetOf('checksum');\n\nconst signedByte = (value: number): number => (value > 0x7f ? value - 0x100 : value);\n\n/**\n * Calculates Taurus header checksum.\n *\n * The checksum is the sum of all signed 8-bit values preceding\n * the checksum field.\n */\nexport const calculateTaurusHeaderChecksum = (header: Uint8Array): number => {\n if (header.length < CHECKSUM_OFFSET) {\n throw new RangeError('Taurus header is too short');\n }\n\n let checksum = 0;\n\n for (let index = 0; index < CHECKSUM_OFFSET; index += 1) {\n checksum += signedByte(header[index]);\n }\n\n return checksum & 0xffff;\n};\n\n/**\n * Returns the full packet size once the complete header is available.\n */\nexport const getTaurusPacketSize = (buffer: Uint8Array): number | undefined => {\n if (buffer.length < TAURUS_HEADER_SIZE) {\n return undefined;\n }\n\n const view = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n\n if (view.readUInt32LE(FLAG_OFFSET) !== TAURUS_PACKET_FLAG) {\n throw new Error('Invalid Taurus packet flag');\n }\n\n return TAURUS_HEADER_SIZE + view.readUInt32LE(LENGTH_OFFSET);\n};\n\n/**\n * Encodes a Taurus request.\n */\nexport const encodeTaurusRequest = (\n sequence: number,\n { what, type, action }: TaurusCommand,\n body: string | Buffer = Buffer.alloc(0),\n encodeType = 0,\n): Buffer => {\n const payload = typeof body === 'string' ? Buffer.from(body, 'utf8') : body;\n\n const packet = new TaurusPacket(TAURUS_HEADER_SIZE + payload.length);\n\n packet.sequence = sequence;\n packet.packetType = TAURUS_REQUEST;\n packet.what = what;\n\n packet.req.action = action;\n packet.req.type = type;\n\n packet.length = payload.length;\n packet.encodeType = encodeType;\n\n payload.copy(packet.body);\n\n const raw = TaurusPacket.raw(packet);\n\n packet.checksum = calculateTaurusHeaderChecksum(raw);\n\n return raw;\n};\n\n/** Encodes the UDP search request used by Taurus players on port 16601. */\nexport const encodeTaurusDiscoveryRequest = (supportsEncryption = true): Buffer => {\n const packet = new TaurusPacket(TAURUS_HEADER_SIZE);\n packet.sequence = 0xffffffff;\n packet.packetType = TAURUS_DISCOVERY;\n packet.what = 0x81;\n packet.req.action = supportsEncryption ? 1 : 0;\n packet.req.type = 0;\n packet.length = 0;\n packet.checksum = calculateTaurusHeaderChecksum(TaurusPacket.raw(packet));\n return TaurusPacket.raw(packet);\n};\n\n/**\n * Decodes and validates a Taurus packet.\n */\nexport const decodeTaurusPacket = (buffer: Uint8Array): TaurusPacket => {\n const view = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n\n const packetSize = getTaurusPacketSize(view);\n\n if (packetSize === undefined || view.length < packetSize) {\n throw new Error('Incomplete Taurus packet');\n }\n\n const expectedChecksum = view.readUInt16LE(CHECKSUM_OFFSET);\n const actualChecksum = calculateTaurusHeaderChecksum(view);\n\n if (actualChecksum !== expectedChecksum) {\n throw new Error(\n `Invalid Taurus header checksum: expected 0x${expectedChecksum.toString(16)}, ` +\n `calculated 0x${actualChecksum.toString(16)}`,\n );\n }\n\n return new TaurusPacket(view.subarray(0, packetSize));\n};\n","import { Duplex } from 'stream';\n\nimport {\n decodeTaurusPacket,\n encodeTaurusRequest,\n getTaurusPacketSize,\n TAURUS_HEADER_SIZE,\n TAURUS_RESPONSE,\n TaurusCommand,\n TaurusPacket,\n} from './packet';\n\ntype PendingRequest = {\n resolve: (packet: TaurusPacket) => void;\n reject: (error: Error) => void;\n timer: NodeJS.Timeout;\n};\n\nexport class TaurusResponseError extends Error {\n constructor(\n public readonly status: number,\n public readonly responseBody: string,\n ) {\n super(\n `Taurus request failed with status 0x${status.toString(16)}${responseBody ? `: ${responseBody}` : ''}`,\n );\n this.name = 'TaurusResponseError';\n }\n}\n\nexport class TaurusConnection {\n readonly #pending = new Map<number, PendingRequest>();\n #buffer = Buffer.alloc(0);\n #sequence = 0;\n #closed = false;\n\n constructor(\n public readonly stream: Duplex,\n public readonly timeout = 5000,\n ) {\n stream.on('data', (chunk: Buffer | string) => this.#onData(Buffer.from(chunk)));\n stream.once('close', () => this.#close(new Error('Taurus connection closed')));\n stream.once('error', (error: Error) => this.#close(error));\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n request(command: TaurusCommand, body?: string | Buffer): Promise<TaurusPacket> {\n if (this.#closed) return Promise.reject(new Error('Taurus connection is closed'));\n const sequence = (this.#sequence += 1) >>> 0;\n const packet = encodeTaurusRequest(sequence, command, body);\n\n return new Promise<TaurusPacket>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.#pending.delete(sequence);\n reject(new Error(`Taurus request ${sequence} timed out`));\n }, this.timeout);\n this.#pending.set(sequence, { resolve, reject, timer });\n this.stream.write(packet, (error) => {\n if (!error) return;\n clearTimeout(timer);\n this.#pending.delete(sequence);\n reject(error);\n });\n });\n }\n\n async requestJson<T>(command: TaurusCommand, body?: unknown): Promise<T> {\n const response = await this.request(\n command,\n body === undefined ? undefined : JSON.stringify(body),\n );\n const responseBody = response.body.toString('utf8');\n if (response.res.status !== 0) throw new TaurusResponseError(response.res.status, responseBody);\n return (responseBody ? JSON.parse(responseBody) : undefined) as T;\n }\n\n close(): void {\n if (!this.stream.destroyed) this.stream.destroy();\n this.#close(new Error('Taurus connection closed'));\n }\n\n #onData(chunk: Buffer): void {\n this.#buffer = this.#buffer.length ? Buffer.concat([this.#buffer, chunk]) : chunk;\n while (this.#buffer.length >= TAURUS_HEADER_SIZE) {\n let packetSize: number | undefined;\n try {\n packetSize = getTaurusPacketSize(this.#buffer);\n } catch (error) {\n this.#close(error as Error);\n this.stream.destroy(error as Error);\n return;\n }\n if (packetSize === undefined || this.#buffer.length < packetSize) return;\n const raw = this.#buffer.subarray(0, packetSize);\n this.#buffer = this.#buffer.subarray(packetSize);\n const packet = decodeTaurusPacket(raw);\n if (packet.packetType !== TAURUS_RESPONSE) continue;\n const pending = this.#pending.get(packet.sequence);\n if (!pending) continue;\n clearTimeout(pending.timer);\n this.#pending.delete(packet.sequence);\n pending.resolve(packet);\n }\n }\n\n #close(error: Error): void {\n if (this.#closed) return;\n this.#closed = true;\n for (const pending of this.#pending.values()) {\n clearTimeout(pending.timer);\n pending.reject(error);\n }\n this.#pending.clear();\n }\n}\n","import { randomUUID } from 'crypto';\nimport tls, { ConnectionOptions } from 'tls';\n\nimport { TaurusConnection } from './connection';\n\n// cspell:ignore logined validition\n\nexport const TAURUS_MANAGEMENT_PORT = 16606;\n\nexport interface TaurusConnectOptions extends Omit<ConnectionOptions, 'host' | 'port'> {\n host: string;\n port?: number;\n timeout?: number;\n privacy?: boolean;\n}\n\nexport interface TaurusLoginOptions {\n sn: string;\n password: string;\n username?: string;\n clientId?: string;\n clientName?: string;\n}\n\nexport interface TaurusLoginResult {\n logined: boolean;\n validation?: boolean;\n validition?: boolean;\n username?: string;\n sn?: string;\n}\n\nexport interface TaurusBrightness {\n brightness?: number;\n ratio?: number;\n solidity: boolean;\n orderId: number;\n}\n\nconst numberOrUndefined = (value: unknown): number | undefined =>\n typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n\nexport class TaurusClient {\n private constructor(public readonly connection: TaurusConnection) {}\n\n static async connect(options: TaurusConnectOptions): Promise<TaurusClient> {\n const {\n host,\n port = TAURUS_MANAGEMENT_PORT,\n timeout = 5000,\n privacy = true,\n ...tlsOptions\n } = options;\n if (!privacy) {\n const { connect } = await import('net');\n const socket = connect({ host, port });\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', resolve);\n socket.once('error', reject);\n });\n return new TaurusClient(new TaurusConnection(socket, timeout));\n }\n\n const socket = tls.connect({\n host,\n port,\n rejectUnauthorized: false,\n ...tlsOptions,\n });\n await new Promise<void>((resolve, reject) => {\n socket.once('secureConnect', resolve);\n socket.once('error', reject);\n });\n return new TaurusClient(new TaurusConnection(socket, timeout));\n }\n\n async login(options: TaurusLoginOptions): Promise<TaurusLoginResult> {\n const response = await this.connection.requestJson<\n TaurusLoginResult & { token?: string; password?: string }\n >(\n { what: 0, type: 0, action: 0 },\n {\n sn: options.sn,\n username: options.username ?? 'admin',\n password: options.password,\n loginType: 0,\n source: { type: 1, platform: 2, platformVersion: '3.6.0' },\n clientId: options.clientId ?? randomUUID(),\n clientName: options.clientName ?? 'novastar.js',\n },\n );\n const { token: _token, password: _password, ...safeResponse } = response;\n return safeResponse;\n }\n\n async getBrightness(): Promise<TaurusBrightness> {\n const result = await this.connection.requestJson<Record<string, unknown>>({\n what: 0x18,\n type: 1,\n action: 5,\n });\n return {\n brightness: numberOrUndefined(result.brightness),\n ratio: numberOrUndefined(result.ratio),\n solidity: result.solidity === true,\n orderId: numberOrUndefined(result.orderId) ?? -1,\n };\n }\n\n async setBrightness(percent: number, persist = false): Promise<void> {\n if (!Number.isFinite(percent) || percent < 0 || percent > 100) {\n throw new RangeError('Brightness must be between 0 and 100');\n }\n await this.connection.requestJson<unknown>(\n { what: 0x18, type: 1, action: 4 },\n { ratio: percent, solidity: persist, orderId: -1 },\n );\n }\n\n async getEnvironmentBrightness(): Promise<number> {\n const result = await this.connection.requestJson<{ value?: unknown }>({\n what: 0x1a,\n type: 0,\n action: 5,\n });\n const value = numberOrUndefined(result.value);\n if (value === undefined || value === -1000)\n throw new Error('Taurus ambient light sensor is unavailable');\n return value;\n }\n\n close(): void {\n this.connection.close();\n }\n}\n","import dgram from 'dgram';\nimport { type NetworkInterfaceInfo, networkInterfaces } from 'os';\n\nimport { decodeTaurusPacket, encodeTaurusDiscoveryRequest, TAURUS_DISCOVERY } from './packet';\n\n// cspell:ignore logined sysset\n\nexport const TAURUS_DISCOVERY_PORT = 16601;\n\nexport interface TaurusPlayerInfo {\n address: string;\n aliasName: string;\n encodeType: number;\n height: number;\n modelId: number;\n platform: string;\n privacy: boolean;\n productName: string;\n rotation: number;\n sn: string;\n tcpPort: number;\n width: number;\n ftpPort?: number;\n key?: string;\n logined?: boolean;\n loginedUsernames?: string[];\n syssetFtpPort?: number;\n syssetTcpPort?: number;\n terminalEntrancePortHttps?: number;\n terminalEntrancePortWebSocket?: number;\n}\n\nconst ipv4ToUint32 = (address: string): number =>\n address.split('.').reduce((result, octet) => (result << 8) | Number(octet), 0) >>> 0;\n\nconst getBroadcastAddress = (address: string, netmask: string): string => {\n const broadcast = (ipv4ToUint32(address) | ~ipv4ToUint32(netmask)) >>> 0;\n return [24, 16, 8, 0].map((shift) => (broadcast >>> shift) & 0xff).join('.');\n};\n\nexport const parseTaurusDiscoveryResponse = (\n message: Uint8Array,\n address: string,\n): TaurusPlayerInfo | undefined => {\n try {\n const packet = decodeTaurusPacket(message);\n if (packet.packetType !== TAURUS_DISCOVERY || packet.what !== 1) return undefined;\n const value = JSON.parse(packet.body.toString('utf8')) as Partial<TaurusPlayerInfo>;\n if (typeof value.sn !== 'string' || typeof value.tcpPort !== 'number') return undefined;\n return { ...value, address } as TaurusPlayerInfo;\n } catch {\n return undefined;\n }\n};\n\nconst discoverFromInterface = (\n { address, netmask }: NetworkInterfaceInfo,\n timeout: number,\n destination?: string,\n): Promise<TaurusPlayerInfo[]> =>\n new Promise((resolve) => {\n const socket = dgram.createSocket('udp4');\n const players = new Map<string, TaurusPlayerInfo>();\n let completed = false;\n const complete = (): void => {\n if (completed) return;\n completed = true;\n try {\n socket.close();\n } catch {\n // The socket may already be closed after an error.\n }\n resolve([...players.values()]);\n };\n socket.once('error', complete);\n socket.on('message', (message, remote) => {\n const player = parseTaurusDiscoveryResponse(message, remote.address);\n if (player) players.set(player.sn, player);\n });\n socket.bind(0, address, () => {\n socket.setBroadcast(true);\n const target = destination ?? getBroadcastAddress(address, netmask);\n socket.send(encodeTaurusDiscoveryRequest(), TAURUS_DISCOVERY_PORT, target, (error) => {\n if (error) complete();\n });\n setTimeout(complete, timeout).unref();\n });\n });\n\nexport const discoverTaurusPlayers = async (\n destination?: string,\n timeout = 1000,\n): Promise<TaurusPlayerInfo[]> => {\n const interfaces = Object.values(networkInterfaces())\n .flat()\n .filter((info): info is NetworkInterfaceInfo =>\n Boolean(info && !info.internal && info.family === 'IPv4'),\n );\n const results = await Promise.all(\n interfaces.map((info) => discoverFromInterface(info, timeout, destination)),\n );\n return [\n ...results\n .flat()\n .reduce(\n (players, player) => players.set(player.sn, player),\n new Map<string, TaurusPlayerInfo>(),\n )\n .values(),\n ];\n};\n"],"mappings":"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"}
|