mirror of
https://github.com/parnic/node-intellicenter.git
synced 2025-06-16 18:20:14 -05:00
Finish Finder implementation
This can correctly find and return IntelliCenter units on the local network now. Cleaned up a bunch of Finder code and added back in "debug" module logging. Fixed up types in Dns module so we can use instanceof
This commit is contained in:
23
dist/dns.d.ts
vendored
23
dist/dns.d.ts
vendored
@ -1,40 +1,31 @@
|
||||
export declare const TypeTxt = 16;
|
||||
export declare const TypePtr = 12;
|
||||
export declare const TypeSrv = 33;
|
||||
export declare const TypeA = 1;
|
||||
export declare class Question {
|
||||
name: string;
|
||||
type: number;
|
||||
class: number;
|
||||
endOffset: number;
|
||||
constructor(_name: string, _type: number, _cls: number, _endOffset: number);
|
||||
}
|
||||
export interface Record {
|
||||
interface: string | undefined;
|
||||
export declare abstract class Record {
|
||||
type: number;
|
||||
ttlSeconds: number;
|
||||
name: string;
|
||||
endOffset: number;
|
||||
}
|
||||
export interface PtrRecord extends Record {
|
||||
interface: "ptr";
|
||||
export declare class PtrRecord extends Record {
|
||||
domain: string;
|
||||
}
|
||||
export interface TxtRecord extends Record {
|
||||
interface: "txt";
|
||||
export declare class TxtRecord extends Record {
|
||||
text: string;
|
||||
}
|
||||
export interface SrvRecord extends Record {
|
||||
interface: "srv";
|
||||
export declare class SrvRecord extends Record {
|
||||
priority: number;
|
||||
weight: number;
|
||||
port: number;
|
||||
target: string;
|
||||
}
|
||||
export interface ARecord extends Record {
|
||||
interface: "a";
|
||||
export declare class ARecord extends Record {
|
||||
address: number;
|
||||
addressStr: string;
|
||||
get addressStr(): string;
|
||||
}
|
||||
export declare function ipToString(ip: number): string;
|
||||
export declare function GetDNSQuestion(msg: Buffer, startOffset: number): Question;
|
||||
export declare function GetDNSAnswer(msg: Buffer, startOffset: number): Record | undefined;
|
||||
|
121
dist/dns.js
vendored
121
dist/dns.js
vendored
@ -1,19 +1,45 @@
|
||||
export const TypeTxt = 16;
|
||||
export const TypePtr = 12;
|
||||
export const TypeSrv = 33;
|
||||
export const TypeA = 1;
|
||||
const TypeTxt = 16;
|
||||
const TypePtr = 12;
|
||||
const TypeSrv = 33;
|
||||
const TypeA = 1;
|
||||
export class Question {
|
||||
name = "";
|
||||
type = 0;
|
||||
class = 0;
|
||||
endOffset = 0;
|
||||
constructor(_name, _type, _cls, _endOffset) {
|
||||
this.name = _name;
|
||||
this.type = _type;
|
||||
this.class = _cls;
|
||||
this.endOffset = _endOffset;
|
||||
}
|
||||
export class Record {
|
||||
type = 0;
|
||||
ttlSeconds = 0;
|
||||
name = "";
|
||||
endOffset = -1;
|
||||
}
|
||||
export class PtrRecord extends Record {
|
||||
domain = "";
|
||||
}
|
||||
export class TxtRecord extends Record {
|
||||
text = "";
|
||||
}
|
||||
export class SrvRecord extends Record {
|
||||
priority = 0;
|
||||
weight = 0;
|
||||
port = 0;
|
||||
target = "";
|
||||
}
|
||||
export class ARecord extends Record {
|
||||
address = 0;
|
||||
get addressStr() {
|
||||
return ipToString(this.address);
|
||||
}
|
||||
}
|
||||
export function ipToString(ip) {
|
||||
const o1 = (ip >> 24) & 0xff;
|
||||
const o2 = (ip >> 16) & 0xff;
|
||||
const o3 = (ip >> 8) & 0xff;
|
||||
const o4 = (ip >> 0) & 0xff;
|
||||
const addressStr = `${o1.toString()}.${o2.toString()}.${o3.toString()}.${o4.toString()}`;
|
||||
return addressStr;
|
||||
}
|
||||
class dnsAnswerParseResult {
|
||||
name = "";
|
||||
endOffset = 0;
|
||||
@ -58,7 +84,12 @@ export function GetDNSQuestion(msg, startOffset) {
|
||||
offset += 2;
|
||||
const cls = msg.readUInt16BE(offset);
|
||||
offset += 2;
|
||||
return new Question(parsedResult.name, type, cls, offset);
|
||||
const ret = new Question();
|
||||
ret.name = parsedResult.name;
|
||||
ret.type = type;
|
||||
ret.class = cls;
|
||||
ret.endOffset = offset;
|
||||
return ret;
|
||||
}
|
||||
export function GetDNSAnswer(msg, startOffset) {
|
||||
let offset = startOffset;
|
||||
@ -74,26 +105,22 @@ export function GetDNSAnswer(msg, startOffset) {
|
||||
switch (type) {
|
||||
case TypePtr: {
|
||||
const domainResult = parseDnsName(msg, offset);
|
||||
const ret = {
|
||||
interface: "ptr",
|
||||
type: type,
|
||||
ttlSeconds: ttlSeconds,
|
||||
name: parsedResult.name,
|
||||
endOffset: offset + rDataLength,
|
||||
domain: domainResult.name,
|
||||
};
|
||||
const ret = new PtrRecord();
|
||||
ret.type = type;
|
||||
ret.ttlSeconds = ttlSeconds;
|
||||
ret.name = parsedResult.name;
|
||||
ret.endOffset = offset + rDataLength;
|
||||
ret.domain = domainResult.name;
|
||||
return ret;
|
||||
}
|
||||
case TypeTxt: {
|
||||
const textResult = parseDnsName(msg, offset);
|
||||
const ret = {
|
||||
interface: "txt",
|
||||
type: type,
|
||||
ttlSeconds: ttlSeconds,
|
||||
name: parsedResult.name,
|
||||
endOffset: offset + rDataLength,
|
||||
text: textResult.name,
|
||||
};
|
||||
const ret = new TxtRecord();
|
||||
ret.type = type;
|
||||
ret.ttlSeconds = ttlSeconds;
|
||||
ret.name = parsedResult.name;
|
||||
ret.endOffset = offset + rDataLength;
|
||||
ret.text = textResult.name;
|
||||
return ret;
|
||||
}
|
||||
case TypeSrv: {
|
||||
@ -101,35 +128,25 @@ export function GetDNSAnswer(msg, startOffset) {
|
||||
const weight = msg.readUInt16BE(offset + 2);
|
||||
const port = msg.readUInt16BE(offset + 4);
|
||||
const targetResult = parseDnsName(msg, offset + 6);
|
||||
const ret = {
|
||||
interface: "srv",
|
||||
type: type,
|
||||
ttlSeconds: ttlSeconds,
|
||||
name: parsedResult.name,
|
||||
endOffset: offset + rDataLength,
|
||||
priority: priority,
|
||||
weight: weight,
|
||||
port: port,
|
||||
target: targetResult.name,
|
||||
};
|
||||
const ret = new SrvRecord();
|
||||
ret.type = type;
|
||||
ret.ttlSeconds = ttlSeconds;
|
||||
ret.name = parsedResult.name;
|
||||
ret.endOffset = offset + rDataLength;
|
||||
ret.priority = priority;
|
||||
ret.weight = weight;
|
||||
ret.port = port;
|
||||
ret.target = targetResult.name;
|
||||
return ret;
|
||||
}
|
||||
case TypeA: {
|
||||
const o1 = msg.readUInt8(offset);
|
||||
const o2 = msg.readUInt8(offset + 1);
|
||||
const o3 = msg.readUInt8(offset + 2);
|
||||
const o4 = msg.readUInt8(offset + 3);
|
||||
const address = (o1 << 24) | (o2 << 16) | (o3 << 8) | (o4 << 0);
|
||||
const addressStr = `${o1.toString()}.${o2.toString()}.${o3.toString()}.${o4.toString()}`;
|
||||
const ret = {
|
||||
interface: "a",
|
||||
type: type,
|
||||
ttlSeconds: ttlSeconds,
|
||||
name: parsedResult.name,
|
||||
endOffset: offset + rDataLength,
|
||||
address: address,
|
||||
addressStr: addressStr,
|
||||
};
|
||||
const address = msg.readUInt32BE(offset);
|
||||
const ret = new ARecord();
|
||||
ret.type = type;
|
||||
ret.ttlSeconds = ttlSeconds;
|
||||
ret.name = parsedResult.name;
|
||||
ret.endOffset = offset + rDataLength;
|
||||
ret.address = address;
|
||||
return ret;
|
||||
}
|
||||
default:
|
||||
|
2
dist/dns.js.map
vendored
2
dist/dns.js.map
vendored
File diff suppressed because one or more lines are too long
27
dist/finder.d.ts
vendored
27
dist/finder.d.ts
vendored
@ -1,13 +1,32 @@
|
||||
import * as dgram from "dgram";
|
||||
import { EventEmitter } from "events";
|
||||
export declare class UnitInfo {
|
||||
name: string;
|
||||
hostname: string;
|
||||
port: number;
|
||||
address: number;
|
||||
get addressStr(): string;
|
||||
constructor(_name: string, _hostname: string, _port: number, _address: number);
|
||||
}
|
||||
export declare class FindUnits extends EventEmitter {
|
||||
constructor();
|
||||
private finder;
|
||||
private bound;
|
||||
private message;
|
||||
private units;
|
||||
/**
|
||||
* Begins a search and returns immediately. Must close the finder with close() when done with all searches.
|
||||
*/
|
||||
search(): void;
|
||||
searchAsync(searchTimeMs?: number): Promise<void>;
|
||||
foundServer(msg: Buffer, remote: dgram.RemoteInfo): void;
|
||||
sendServerBroadcast(): void;
|
||||
/**
|
||||
* Searches for the given amount of time. Must close the finder with close() when done with all searches.
|
||||
* @param searchTimeMs the number of milliseconds to search before giving up and returning found results (default: 5000)
|
||||
* @returns Promise resolving to a list of discovered units, if any.
|
||||
*/
|
||||
searchAsync(searchTimeMs?: number): Promise<UnitInfo[]>;
|
||||
private foundServer;
|
||||
private sendServerBroadcast;
|
||||
/**
|
||||
* Closes the finder socket.
|
||||
*/
|
||||
close(): void;
|
||||
}
|
||||
|
138
dist/finder.js
vendored
138
dist/finder.js
vendored
@ -1,35 +1,28 @@
|
||||
import * as dgram from "dgram";
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createSocket } from "dgram";
|
||||
import { EventEmitter } from "events";
|
||||
import { setTimeout as setTimeoutSync } from "timers";
|
||||
import os from "os";
|
||||
import { GetDNSAnswer, GetDNSQuestion } from "./dns.js";
|
||||
import debug from "debug";
|
||||
import { ARecord, GetDNSAnswer, GetDNSQuestion, ipToString, PtrRecord, SrvRecord, } from "./dns.js";
|
||||
const debugFind = debug("ic:find");
|
||||
export class UnitInfo {
|
||||
name;
|
||||
hostname;
|
||||
port;
|
||||
address;
|
||||
get addressStr() {
|
||||
return ipToString(this.address);
|
||||
}
|
||||
constructor(_name, _hostname, _port, _address) {
|
||||
this.name = _name;
|
||||
this.hostname = _hostname;
|
||||
this.port = _port;
|
||||
this.address = _address;
|
||||
}
|
||||
}
|
||||
export class FindUnits extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
const infs = os.networkInterfaces();
|
||||
const localIps = [];
|
||||
let localIp = "127.0.0.1";
|
||||
Object.keys(infs).forEach((key) => {
|
||||
infs[key]?.forEach((iface) => {
|
||||
if (iface.internal) {
|
||||
return;
|
||||
}
|
||||
if (iface.family !== "IPv4") {
|
||||
return;
|
||||
}
|
||||
localIps.push(iface.address);
|
||||
});
|
||||
});
|
||||
if (localIps.length === 0) {
|
||||
console.error(`no local interfaces found, can't search for controllers.`);
|
||||
// todo: emit error
|
||||
}
|
||||
else {
|
||||
localIp = localIps[0];
|
||||
}
|
||||
if (localIps.length > 1) {
|
||||
console.log(`found ${localIps.length.toString()} local IPs, using the first one for SSDP search (${localIp})`);
|
||||
}
|
||||
// construct mDNS packet to ping for intellicenter controllers
|
||||
this.message = Buffer.alloc(34);
|
||||
let offset = 0;
|
||||
@ -48,7 +41,7 @@ export class FindUnits extends EventEmitter {
|
||||
offset = this.message.writeUInt8(0, offset); // no more strings
|
||||
offset = this.message.writeUInt16BE(0x000c, offset); // type: ptr
|
||||
this.message.writeUInt16BE(1, offset); // class: IN
|
||||
this.finder = dgram.createSocket("udp4");
|
||||
this.finder = createSocket("udp4");
|
||||
this.finder
|
||||
.on("listening", () => {
|
||||
this.finder.setBroadcast(true);
|
||||
@ -58,23 +51,25 @@ export class FindUnits extends EventEmitter {
|
||||
this.sendServerBroadcast();
|
||||
}
|
||||
})
|
||||
.on("message", (msg, remote) => {
|
||||
this.foundServer(msg, remote);
|
||||
.on("message", (msg) => {
|
||||
this.foundServer(msg);
|
||||
})
|
||||
.on("close", () => {
|
||||
// debugFind("closed");
|
||||
console.log("closed");
|
||||
debugFind("Finder socket closed.");
|
||||
this.emit("close");
|
||||
})
|
||||
.on("error", (e) => {
|
||||
// debugFind("error: %O", e);
|
||||
console.log("errored");
|
||||
debugFind("Finder socket error: %O", e);
|
||||
this.emit("error", e);
|
||||
});
|
||||
}
|
||||
finder;
|
||||
bound = false;
|
||||
message;
|
||||
units = [];
|
||||
/**
|
||||
* Begins a search and returns immediately. Must close the finder with close() when done with all searches.
|
||||
*/
|
||||
search() {
|
||||
if (!this.bound) {
|
||||
this.finder.bind();
|
||||
@ -83,27 +78,29 @@ export class FindUnits extends EventEmitter {
|
||||
this.sendServerBroadcast();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Searches for the given amount of time. Must close the finder with close() when done with all searches.
|
||||
* @param searchTimeMs the number of milliseconds to search before giving up and returning found results (default: 5000)
|
||||
* @returns Promise resolving to a list of discovered units, if any.
|
||||
*/
|
||||
async searchAsync(searchTimeMs) {
|
||||
const p = new Promise((resolve) => {
|
||||
// debugFind("IntelliCenter finder searching for local units...");
|
||||
setTimeoutSync(() => {
|
||||
// if (units.length === 0) {
|
||||
// debugFind("No units found searching locally.");
|
||||
// }
|
||||
setTimeout(() => {
|
||||
if (this.units.length === 0) {
|
||||
debugFind("No units found searching locally.");
|
||||
}
|
||||
this.removeAllListeners();
|
||||
resolve(0);
|
||||
resolve(this.units);
|
||||
}, searchTimeMs ?? 5000);
|
||||
this.on("serverFound", () => {
|
||||
// debugFind(`IntelliCenter found unit ${JSON.stringify(unit)}`);
|
||||
console.log("found");
|
||||
// units.push(unit);
|
||||
this.on("serverFound", (unit) => {
|
||||
debugFind(" found: %o", unit);
|
||||
this.units.push(unit);
|
||||
});
|
||||
this.search();
|
||||
});
|
||||
return Promise.resolve(p);
|
||||
return p;
|
||||
}
|
||||
foundServer(msg, remote) {
|
||||
// debugFind("found something");
|
||||
foundServer(msg) {
|
||||
let flags = 0;
|
||||
if (msg.length > 4) {
|
||||
flags = msg.readUInt16BE(2);
|
||||
@ -128,6 +125,7 @@ export class FindUnits extends EventEmitter {
|
||||
if (msg.length >= 8) {
|
||||
answers = msg.readUInt16BE(6);
|
||||
}
|
||||
const records = [];
|
||||
if (answers > 0) {
|
||||
for (let i = 0; i < answers; i++) {
|
||||
if (msg.length <= nextAnswerOffset) {
|
||||
@ -138,45 +136,33 @@ export class FindUnits extends EventEmitter {
|
||||
if (!answer) {
|
||||
break;
|
||||
}
|
||||
records.push(answer);
|
||||
nextAnswerOffset = answer.endOffset;
|
||||
if (answer.interface === "a") {
|
||||
console.log("a record:", answer);
|
||||
}
|
||||
}
|
||||
}
|
||||
const str = msg.toString();
|
||||
console.log(str);
|
||||
if (msg.length >= 40) {
|
||||
const server = {
|
||||
address: remote.address,
|
||||
type: msg.readInt32LE(0),
|
||||
port: msg.readInt16LE(8),
|
||||
gatewayType: msg.readUInt8(10),
|
||||
gatewaySubtype: msg.readUInt8(11),
|
||||
gatewayName: msg.toString("utf8", 12, 29),
|
||||
};
|
||||
// debugFind(
|
||||
// " type: " +
|
||||
// server.type +
|
||||
// ", host: " +
|
||||
// server.address +
|
||||
// ":" +
|
||||
// server.port +
|
||||
// ", identified as " +
|
||||
// server.gatewayName,
|
||||
// );
|
||||
if (server.type === 2) {
|
||||
this.emit("serverFound", server);
|
||||
if (records.find((r) => r.name.startsWith("Pentair -i"))) {
|
||||
const srv = records.find((r) => r instanceof SrvRecord);
|
||||
const a = records.find((r) => r instanceof ARecord);
|
||||
if (!srv || !a) {
|
||||
return;
|
||||
}
|
||||
const unit = new UnitInfo(srv.name, a.name, srv.port, a.address);
|
||||
this.emit("serverFound", unit);
|
||||
}
|
||||
else {
|
||||
// debugFind(" unexpected message");
|
||||
debugFind(" found something that wasn't an IntelliCenter unit: %s", records
|
||||
.filter((r) => r instanceof PtrRecord)
|
||||
.map((r) => r.domain)
|
||||
.join(", "));
|
||||
}
|
||||
}
|
||||
sendServerBroadcast() {
|
||||
this.finder.send(this.message, 0, this.message.length, 5353, "224.0.0.251");
|
||||
// debugFind("Looking for IntelliCenter hosts...");
|
||||
debugFind("Looking for IntelliCenter hosts...");
|
||||
}
|
||||
/**
|
||||
* Closes the finder socket.
|
||||
*/
|
||||
close() {
|
||||
this.finder.close();
|
||||
}
|
||||
|
2
dist/finder.js.map
vendored
2
dist/finder.js.map
vendored
File diff suppressed because one or more lines are too long
18
dist/index.js
vendored
18
dist/index.js
vendored
@ -4,12 +4,20 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import { FindUnits } from "./finder.js";
|
||||
console.log("searching...");
|
||||
const f = new FindUnits();
|
||||
await f.searchAsync(5000);
|
||||
// temp. replace with the IP of your device
|
||||
const endpoint = "10.0.0.41";
|
||||
const units = await f.searchAsync(1000);
|
||||
f.close();
|
||||
console.log("Discovered units:", units);
|
||||
if (units.length === 0) {
|
||||
throw new Error("no IntelliCenter units found, exiting.");
|
||||
}
|
||||
if (units.length > 1) {
|
||||
throw new Error(`found more than one IntelliCenter unit, unsure which one to use. ${JSON.stringify(units)}`);
|
||||
}
|
||||
const endpoint = units[0].addressStr;
|
||||
const port = units[0].port;
|
||||
let pingTimeout;
|
||||
console.log("connecting to intellicenter device at", endpoint);
|
||||
const client = new WebSocket(`ws://${endpoint}:6680`);
|
||||
console.log("connecting to intellicenter device at", endpoint, "port", port);
|
||||
const client = new WebSocket(`ws://${endpoint}:${port.toString()}`);
|
||||
const heartbeat = () => {
|
||||
clearTimeout(pingTimeout);
|
||||
pingTimeout = setTimeout(() => {
|
||||
|
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAC5B,MAAM,CAAC,GAAG,IAAI,SAAS,EAAE,CAAC;AAC1B,MAAM,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AAE1B,2CAA2C;AAC3C,MAAM,QAAQ,GAAG,WAAW,CAAC;AAE7B,IAAI,WAA0C,CAAC;AAE/C,OAAO,CAAC,GAAG,CAAC,uCAAuC,EAAE,QAAQ,CAAC,CAAC;AAC/D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,QAAQ,QAAQ,OAAO,CAAC,CAAC;AAEtD,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,YAAY,CAAC,WAAW,CAAC,CAAC;IAE1B,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,MAAM,CAAC,SAAS,EAAE,CAAC;IACrB,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;AAClC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC7B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC7B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;IACtB,YAAY,CAAC,WAAW,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAW,EAAE,EAAE;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,8DAA8D;IAC1G,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AACH,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IACpC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7B,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/B,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACzB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG;QACV,SAAS,EAAE,EAAE;QACb,UAAU,EAAE;YACV;gBACE,MAAM,EAAE,OAAO;gBACf,IAAI,EAAE;oBACJ,KAAK;oBACL,MAAM;oBACN,KAAK;oBACL,QAAQ;oBACR,UAAU;oBACV,MAAM;oBACN,SAAS;oBACT,MAAM;oBACN,OAAO;oBACP,OAAO;oBACP,QAAQ;oBACR,OAAO;oBACP,QAAQ;oBACR,SAAS;oBACT,OAAO;oBACP,MAAM;oBACN,MAAM;oBACN,OAAO;oBACP,SAAS;oBACT,QAAQ;oBACR,UAAU;oBACV,IAAI;oBACJ,OAAO;oBACP,SAAS;iBACV;aACF;SACF;QACD,OAAO,EAAE,cAAc;QACvB,SAAS,EAAE,MAAM,EAAE;KACpB,CAAC;IACF,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,CAAC,KAAK,EAAE,CAAC;AACjB,CAAC,CAAC,CAAC"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
158
dns.ts
158
dns.ts
@ -1,52 +1,52 @@
|
||||
export const TypeTxt = 16;
|
||||
export const TypePtr = 12;
|
||||
export const TypeSrv = 33;
|
||||
export const TypeA = 1;
|
||||
const TypeTxt = 16;
|
||||
const TypePtr = 12;
|
||||
const TypeSrv = 33;
|
||||
const TypeA = 1;
|
||||
|
||||
export class Question {
|
||||
public name = "";
|
||||
public type = 0;
|
||||
public class = 0;
|
||||
public endOffset = 0;
|
||||
}
|
||||
|
||||
constructor(_name: string, _type: number, _cls: number, _endOffset: number) {
|
||||
this.name = _name;
|
||||
this.type = _type;
|
||||
this.class = _cls;
|
||||
this.endOffset = _endOffset;
|
||||
export abstract class Record {
|
||||
public type = 0;
|
||||
public ttlSeconds = 0;
|
||||
public name = "";
|
||||
public endOffset = -1;
|
||||
}
|
||||
|
||||
export class PtrRecord extends Record {
|
||||
public domain = "";
|
||||
}
|
||||
|
||||
export class TxtRecord extends Record {
|
||||
public text = "";
|
||||
}
|
||||
|
||||
export class SrvRecord extends Record {
|
||||
public priority = 0;
|
||||
public weight = 0;
|
||||
public port = 0;
|
||||
public target = "";
|
||||
}
|
||||
|
||||
export class ARecord extends Record {
|
||||
public address = 0;
|
||||
|
||||
public get addressStr(): string {
|
||||
return ipToString(this.address);
|
||||
}
|
||||
}
|
||||
|
||||
export interface Record {
|
||||
interface: string | undefined;
|
||||
type: number;
|
||||
ttlSeconds: number;
|
||||
name: string;
|
||||
endOffset: number;
|
||||
}
|
||||
|
||||
export interface PtrRecord extends Record {
|
||||
interface: "ptr";
|
||||
domain: string;
|
||||
}
|
||||
|
||||
export interface TxtRecord extends Record {
|
||||
interface: "txt";
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface SrvRecord extends Record {
|
||||
interface: "srv";
|
||||
priority: number;
|
||||
weight: number;
|
||||
port: number;
|
||||
target: string;
|
||||
}
|
||||
|
||||
export interface ARecord extends Record {
|
||||
interface: "a";
|
||||
address: number;
|
||||
addressStr: string;
|
||||
export function ipToString(ip: number): string {
|
||||
const o1 = (ip >> 24) & 0xff;
|
||||
const o2 = (ip >> 16) & 0xff;
|
||||
const o3 = (ip >> 8) & 0xff;
|
||||
const o4 = (ip >> 0) & 0xff;
|
||||
const addressStr = `${o1.toString()}.${o2.toString()}.${o3.toString()}.${o4.toString()}`;
|
||||
return addressStr;
|
||||
}
|
||||
|
||||
class dnsAnswerParseResult {
|
||||
@ -99,7 +99,13 @@ export function GetDNSQuestion(msg: Buffer, startOffset: number): Question {
|
||||
offset += 2;
|
||||
const cls = msg.readUInt16BE(offset);
|
||||
offset += 2;
|
||||
return new Question(parsedResult.name, type, cls, offset);
|
||||
|
||||
const ret = new Question();
|
||||
ret.name = parsedResult.name;
|
||||
ret.type = type;
|
||||
ret.class = cls;
|
||||
ret.endOffset = offset;
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function GetDNSAnswer(
|
||||
@ -121,27 +127,25 @@ export function GetDNSAnswer(
|
||||
switch (type) {
|
||||
case TypePtr: {
|
||||
const domainResult = parseDnsName(msg, offset);
|
||||
const ret: PtrRecord = {
|
||||
interface: "ptr",
|
||||
type: type,
|
||||
ttlSeconds: ttlSeconds,
|
||||
name: parsedResult.name,
|
||||
endOffset: offset + rDataLength,
|
||||
domain: domainResult.name,
|
||||
};
|
||||
|
||||
const ret = new PtrRecord();
|
||||
ret.type = type;
|
||||
ret.ttlSeconds = ttlSeconds;
|
||||
ret.name = parsedResult.name;
|
||||
ret.endOffset = offset + rDataLength;
|
||||
ret.domain = domainResult.name;
|
||||
return ret;
|
||||
}
|
||||
|
||||
case TypeTxt: {
|
||||
const textResult = parseDnsName(msg, offset);
|
||||
const ret: TxtRecord = {
|
||||
interface: "txt",
|
||||
type: type,
|
||||
ttlSeconds: ttlSeconds,
|
||||
name: parsedResult.name,
|
||||
endOffset: offset + rDataLength,
|
||||
text: textResult.name,
|
||||
};
|
||||
|
||||
const ret = new TxtRecord();
|
||||
ret.type = type;
|
||||
ret.ttlSeconds = ttlSeconds;
|
||||
ret.name = parsedResult.name;
|
||||
ret.endOffset = offset + rDataLength;
|
||||
ret.text = textResult.name;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -151,37 +155,27 @@ export function GetDNSAnswer(
|
||||
const port = msg.readUInt16BE(offset + 4);
|
||||
const targetResult = parseDnsName(msg, offset + 6);
|
||||
|
||||
const ret: SrvRecord = {
|
||||
interface: "srv",
|
||||
type: type,
|
||||
ttlSeconds: ttlSeconds,
|
||||
name: parsedResult.name,
|
||||
endOffset: offset + rDataLength,
|
||||
priority: priority,
|
||||
weight: weight,
|
||||
port: port,
|
||||
target: targetResult.name,
|
||||
};
|
||||
const ret = new SrvRecord();
|
||||
ret.type = type;
|
||||
ret.ttlSeconds = ttlSeconds;
|
||||
ret.name = parsedResult.name;
|
||||
ret.endOffset = offset + rDataLength;
|
||||
ret.priority = priority;
|
||||
ret.weight = weight;
|
||||
ret.port = port;
|
||||
ret.target = targetResult.name;
|
||||
return ret;
|
||||
}
|
||||
|
||||
case TypeA: {
|
||||
const o1 = msg.readUInt8(offset);
|
||||
const o2 = msg.readUInt8(offset + 1);
|
||||
const o3 = msg.readUInt8(offset + 2);
|
||||
const o4 = msg.readUInt8(offset + 3);
|
||||
const address = (o1 << 24) | (o2 << 16) | (o3 << 8) | (o4 << 0);
|
||||
const addressStr = `${o1.toString()}.${o2.toString()}.${o3.toString()}.${o4.toString()}`;
|
||||
const address = msg.readUInt32BE(offset);
|
||||
|
||||
const ret: ARecord = {
|
||||
interface: "a",
|
||||
type: type,
|
||||
ttlSeconds: ttlSeconds,
|
||||
name: parsedResult.name,
|
||||
endOffset: offset + rDataLength,
|
||||
address: address,
|
||||
addressStr: addressStr,
|
||||
};
|
||||
const ret = new ARecord();
|
||||
ret.type = type;
|
||||
ret.ttlSeconds = ttlSeconds;
|
||||
ret.name = parsedResult.name;
|
||||
ret.endOffset = offset + rDataLength;
|
||||
ret.address = address;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
176
finder.ts
176
finder.ts
@ -1,41 +1,48 @@
|
||||
import * as dgram from "dgram";
|
||||
import { Socket } from "dgram";
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { createSocket, Socket } from "dgram";
|
||||
import { EventEmitter } from "events";
|
||||
import { setTimeout as setTimeoutSync } from "timers";
|
||||
import os from "os";
|
||||
import { GetDNSAnswer, GetDNSQuestion } from "./dns.js";
|
||||
import debug from "debug";
|
||||
|
||||
import {
|
||||
ARecord,
|
||||
GetDNSAnswer,
|
||||
GetDNSQuestion,
|
||||
ipToString,
|
||||
PtrRecord,
|
||||
Record,
|
||||
SrvRecord,
|
||||
} from "./dns.js";
|
||||
|
||||
const debugFind = debug("ic:find");
|
||||
|
||||
export class UnitInfo {
|
||||
public name: string;
|
||||
public hostname: string;
|
||||
public port: number;
|
||||
public address: number;
|
||||
|
||||
public get addressStr(): string {
|
||||
return ipToString(this.address);
|
||||
}
|
||||
|
||||
public constructor(
|
||||
_name: string,
|
||||
_hostname: string,
|
||||
_port: number,
|
||||
_address: number,
|
||||
) {
|
||||
this.name = _name;
|
||||
this.hostname = _hostname;
|
||||
this.port = _port;
|
||||
this.address = _address;
|
||||
}
|
||||
}
|
||||
|
||||
export class FindUnits extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const infs = os.networkInterfaces();
|
||||
const localIps: string[] = [];
|
||||
let localIp = "127.0.0.1";
|
||||
Object.keys(infs).forEach((key) => {
|
||||
infs[key]?.forEach((iface) => {
|
||||
if (iface.internal) {
|
||||
return;
|
||||
}
|
||||
if (iface.family !== "IPv4") {
|
||||
return;
|
||||
}
|
||||
localIps.push(iface.address);
|
||||
});
|
||||
});
|
||||
if (localIps.length === 0) {
|
||||
console.error(`no local interfaces found, can't search for controllers.`);
|
||||
// todo: emit error
|
||||
} else {
|
||||
localIp = localIps[0];
|
||||
}
|
||||
|
||||
if (localIps.length > 1) {
|
||||
console.log(
|
||||
`found ${localIps.length.toString()} local IPs, using the first one for SSDP search (${localIp})`,
|
||||
);
|
||||
}
|
||||
|
||||
// construct mDNS packet to ping for intellicenter controllers
|
||||
this.message = Buffer.alloc(34);
|
||||
let offset = 0;
|
||||
@ -55,7 +62,7 @@ export class FindUnits extends EventEmitter {
|
||||
offset = this.message.writeUInt16BE(0x000c, offset); // type: ptr
|
||||
this.message.writeUInt16BE(1, offset); // class: IN
|
||||
|
||||
this.finder = dgram.createSocket("udp4");
|
||||
this.finder = createSocket("udp4");
|
||||
this.finder
|
||||
.on("listening", () => {
|
||||
this.finder.setBroadcast(true);
|
||||
@ -66,17 +73,15 @@ export class FindUnits extends EventEmitter {
|
||||
this.sendServerBroadcast();
|
||||
}
|
||||
})
|
||||
.on("message", (msg, remote) => {
|
||||
this.foundServer(msg, remote);
|
||||
.on("message", (msg) => {
|
||||
this.foundServer(msg);
|
||||
})
|
||||
.on("close", () => {
|
||||
// debugFind("closed");
|
||||
console.log("closed");
|
||||
debugFind("Finder socket closed.");
|
||||
this.emit("close");
|
||||
})
|
||||
.on("error", (e) => {
|
||||
// debugFind("error: %O", e);
|
||||
console.log("errored");
|
||||
debugFind("Finder socket error: %O", e);
|
||||
this.emit("error", e);
|
||||
});
|
||||
}
|
||||
@ -84,8 +89,12 @@ export class FindUnits extends EventEmitter {
|
||||
private finder: Socket;
|
||||
private bound = false;
|
||||
private message: Buffer;
|
||||
private units: UnitInfo[] = [];
|
||||
|
||||
search() {
|
||||
/**
|
||||
* Begins a search and returns immediately. Must close the finder with close() when done with all searches.
|
||||
*/
|
||||
public search() {
|
||||
if (!this.bound) {
|
||||
this.finder.bind();
|
||||
} else {
|
||||
@ -93,33 +102,34 @@ export class FindUnits extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public async searchAsync(searchTimeMs?: number): Promise<void> {
|
||||
const p = new Promise((resolve) => {
|
||||
// debugFind("IntelliCenter finder searching for local units...");
|
||||
setTimeoutSync(() => {
|
||||
// if (units.length === 0) {
|
||||
// debugFind("No units found searching locally.");
|
||||
// }
|
||||
/**
|
||||
* Searches for the given amount of time. Must close the finder with close() when done with all searches.
|
||||
* @param searchTimeMs the number of milliseconds to search before giving up and returning found results (default: 5000)
|
||||
* @returns Promise resolving to a list of discovered units, if any.
|
||||
*/
|
||||
public async searchAsync(searchTimeMs?: number): Promise<UnitInfo[]> {
|
||||
const p = new Promise<UnitInfo[]>((resolve) => {
|
||||
setTimeout(() => {
|
||||
if (this.units.length === 0) {
|
||||
debugFind("No units found searching locally.");
|
||||
}
|
||||
|
||||
this.removeAllListeners();
|
||||
resolve(0);
|
||||
resolve(this.units);
|
||||
}, searchTimeMs ?? 5000);
|
||||
|
||||
this.on("serverFound", () => {
|
||||
// debugFind(`IntelliCenter found unit ${JSON.stringify(unit)}`);
|
||||
console.log("found");
|
||||
// units.push(unit);
|
||||
this.on("serverFound", (unit: UnitInfo) => {
|
||||
debugFind(" found: %o", unit);
|
||||
this.units.push(unit);
|
||||
});
|
||||
|
||||
this.search();
|
||||
});
|
||||
|
||||
return Promise.resolve(p) as Promise<void>;
|
||||
return p;
|
||||
}
|
||||
|
||||
foundServer(msg: Buffer, remote: dgram.RemoteInfo) {
|
||||
// debugFind("found something");
|
||||
|
||||
private foundServer(msg: Buffer) {
|
||||
let flags = 0;
|
||||
if (msg.length > 4) {
|
||||
flags = msg.readUInt16BE(2);
|
||||
@ -149,6 +159,7 @@ export class FindUnits extends EventEmitter {
|
||||
answers = msg.readUInt16BE(6);
|
||||
}
|
||||
|
||||
const records: Record[] = [];
|
||||
if (answers > 0) {
|
||||
for (let i = 0; i < answers; i++) {
|
||||
if (msg.length <= nextAnswerOffset) {
|
||||
@ -163,50 +174,39 @@ export class FindUnits extends EventEmitter {
|
||||
break;
|
||||
}
|
||||
|
||||
records.push(answer);
|
||||
nextAnswerOffset = answer.endOffset;
|
||||
if (answer.interface === "a") {
|
||||
console.log("a record:", answer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const str = msg.toString();
|
||||
console.log(str);
|
||||
|
||||
if (msg.length >= 40) {
|
||||
const server = {
|
||||
address: remote.address,
|
||||
type: msg.readInt32LE(0),
|
||||
port: msg.readInt16LE(8),
|
||||
gatewayType: msg.readUInt8(10),
|
||||
gatewaySubtype: msg.readUInt8(11),
|
||||
gatewayName: msg.toString("utf8", 12, 29),
|
||||
};
|
||||
|
||||
// debugFind(
|
||||
// " type: " +
|
||||
// server.type +
|
||||
// ", host: " +
|
||||
// server.address +
|
||||
// ":" +
|
||||
// server.port +
|
||||
// ", identified as " +
|
||||
// server.gatewayName,
|
||||
// );
|
||||
|
||||
if (server.type === 2) {
|
||||
this.emit("serverFound", server);
|
||||
if (records.find((r) => r.name.startsWith("Pentair -i"))) {
|
||||
const srv = records.find((r) => r instanceof SrvRecord);
|
||||
const a = records.find((r) => r instanceof ARecord);
|
||||
if (!srv || !a) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unit = new UnitInfo(srv.name, a.name, srv.port, a.address);
|
||||
this.emit("serverFound", unit);
|
||||
} else {
|
||||
// debugFind(" unexpected message");
|
||||
debugFind(
|
||||
" found something that wasn't an IntelliCenter unit: %s",
|
||||
records
|
||||
.filter((r) => r instanceof PtrRecord)
|
||||
.map((r) => r.domain)
|
||||
.join(", "),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sendServerBroadcast() {
|
||||
private sendServerBroadcast() {
|
||||
this.finder.send(this.message, 0, this.message.length, 5353, "224.0.0.251");
|
||||
// debugFind("Looking for IntelliCenter hosts...");
|
||||
debugFind("Looking for IntelliCenter hosts...");
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the finder socket.
|
||||
*/
|
||||
public close() {
|
||||
this.finder.close();
|
||||
}
|
||||
|
22
index.ts
22
index.ts
@ -6,15 +6,27 @@ import { FindUnits } from "./finder.js";
|
||||
|
||||
console.log("searching...");
|
||||
const f = new FindUnits();
|
||||
await f.searchAsync(5000);
|
||||
const units = await f.searchAsync(1000);
|
||||
f.close();
|
||||
console.log("Discovered units:", units);
|
||||
|
||||
// temp. replace with the IP of your device
|
||||
const endpoint = "10.0.0.41";
|
||||
if (units.length === 0) {
|
||||
throw new Error("no IntelliCenter units found, exiting.");
|
||||
}
|
||||
|
||||
if (units.length > 1) {
|
||||
throw new Error(
|
||||
`found more than one IntelliCenter unit, unsure which one to use. ${JSON.stringify(units)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const endpoint = units[0].addressStr;
|
||||
const port = units[0].port;
|
||||
|
||||
let pingTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
console.log("connecting to intellicenter device at", endpoint);
|
||||
const client = new WebSocket(`ws://${endpoint}:6680`);
|
||||
console.log("connecting to intellicenter device at", endpoint, "port", port);
|
||||
const client = new WebSocket(`ws://${endpoint}:${port.toString()}`);
|
||||
|
||||
const heartbeat = () => {
|
||||
clearTimeout(pingTimeout);
|
||||
|
49
package-lock.json
generated
49
package-lock.json
generated
@ -9,14 +9,17 @@
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"uuid": "^11.0.3",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/ws": "^8.5.13",
|
||||
"eslint": "^9.17.0",
|
||||
"prettier": "3.4.2",
|
||||
"supports-color": "^10.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.19.0"
|
||||
},
|
||||
@ -255,6 +258,16 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/ms": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
@ -269,6 +282,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "0.7.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
|
||||
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
|
||||
@ -630,6 +650,19 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@ -676,7 +709,6 @@
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@ -1231,7 +1263,6 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
@ -1501,16 +1532,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.0.0.tgz",
|
||||
"integrity": "sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
|
@ -30,14 +30,17 @@
|
||||
"lint": "eslint . && prettier . --check"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"uuid": "^11.0.3",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/ws": "^8.5.13",
|
||||
"eslint": "^9.17.0",
|
||||
"prettier": "3.4.2",
|
||||
"supports-color": "^10.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.19.0"
|
||||
},
|
||||
|
Reference in New Issue
Block a user