webserial-core
Version:
Webserial Core to easy connections with serial devices
1,226 lines (1,225 loc) • 46.6 kB
JavaScript
import { io as b } from "socket.io-client";
class g extends CustomEvent {
constructor(e, t) {
super(e, t);
}
}
class m extends EventTarget {
__listeners__ = {
debug: !1
};
__debug__ = !1;
__listenersCallbacks__ = [];
/**
* Dispatches an event with the specified type and data
* @param type - The event type to dispatch
* @param data - Optional data to attach to the event
* @example
* ```typescript
* dispatcher.dispatch('connected', { port: 'COM3' });
* ```
*/
dispatch(e, t = null) {
const n = new g(e, { detail: t });
this.dispatchEvent(n), this.__debug__ && this.dispatchEvent(new g("debug", { detail: { type: e, data: t } }));
}
/**
* Dispatches an event asynchronously after a specified delay
* @param type - The event type to dispatch
* @param data - Optional data to attach to the event
* @param ms - Delay in milliseconds (default: 100)
* @example
* ```typescript
* dispatcher.dispatchAsync('timeout', { reason: 'no response' }, 500);
* ```
*/
dispatchAsync(e, t = null, n = 100) {
const i = this;
setTimeout(() => {
i.dispatch(e, t);
}, n);
}
/**
* Registers an event listener for the specified event type
* @param type - The event type to listen to
* @param callback - The callback function to execute when the event is triggered
* @example
* ```typescript
* dispatcher.on('connected', (event) => {
* console.log('Device connected', event.detail);
* });
* ```
*/
on(e, t) {
typeof this.__listeners__[e] < "u" && !this.__listeners__[e] && (this.__listeners__[e] = !0), this.__listenersCallbacks__.push({ key: e, callback: t }), this.addEventListener(e, t);
}
/**
* Removes an event listener for the specified event type
* @param type - The event type to stop listening to
* @param callback - The callback function to remove
* @example
* ```typescript
* const handler = (event) => console.log(event.detail);
* dispatcher.on('data', handler);
* dispatcher.off('data', handler);
* ```
*/
off(e, t) {
this.__listenersCallbacks__ = this.__listenersCallbacks__.filter((n) => !(n.key === e && n.callback === t)), this.removeEventListener(e, t);
}
/**
* Registers an available listener type for tracking
* @param type - The event type to register
* @internal
*/
serialRegisterAvailableListener(e) {
this.__listeners__[e] || (this.__listeners__[e] = !1);
}
/**
* Gets the list of all available listeners and their state
* @returns Array of listener objects with type and listening status
* @example
* ```typescript
* const listeners = dispatcher.availableListeners;
* console.log(listeners); // [{ type: 'connected', listening: true }, ...]
* ```
*/
get availableListeners() {
return Object.keys(this.__listeners__).sort().map((t) => ({
type: t,
listening: this.__listeners__[t]
}));
}
/**
* Removes all event listeners except internal ones (like queue listeners)
* Resets all listener states to false
* @example
* ```typescript
* dispatcher.removeAllListeners();
* ```
*/
removeAllListeners() {
for (const e of this.__listenersCallbacks__)
["internal:queue"].includes(e.key) || (this.__listenersCallbacks__ = this.__listenersCallbacks__.filter((t) => !(t.key === e.key && t.callback === e.callback)), this.removeEventListener(e.key, e.callback));
for (const e of Object.keys(this.__listeners__))
this.__listeners__[e] = !1;
}
}
class s extends m {
static instance;
static devices = {};
constructor() {
super(), ["change"].forEach((t) => {
this.serialRegisterAvailableListener(t);
});
}
static $dispatchChange(e = null) {
e && e.$checkAndDispatchConnection(), s.instance.dispatch("change", { devices: s.devices, dispatcher: e });
}
static typeError(e) {
const t = new Error();
throw t.message = `Type ${e} is not supported`, t.name = "DeviceTypeError", t;
}
/**
* Registers a new device type in the registry
* @param type - The type name of the device (e.g., 'arduino', 'esp32')
* @internal
*/
static registerType(e) {
typeof s.devices[e] > "u" && (s.devices = { ...s.devices, [e]: {} });
}
/**
* Adds a device to the registry
* @param device - The Core device instance to add
* @returns The index of the device in its type registry
* @throws {Error} If device with the same ID already exists
* @example
* ```typescript
* const arduino = new Arduino();
* Devices.add(arduino);
* ```
*/
static add(e) {
const t = e.typeDevice;
typeof s.devices[t] > "u" && s.registerType(t);
const n = e.uuid;
if (typeof s.devices[t] > "u" && s.typeError(t), s.devices[t][n])
throw new Error(`Device with id ${n} already exists`);
return s.devices[t][n] = e, s.$dispatchChange(e), Object.keys(s.devices[t]).indexOf(n);
}
/**
* Gets a specific device by type and UUID
* @param type - The device type
* @param id - The device UUID
* @returns The device instance
* @throws {Error} If the device type is not supported
* @example
* ```typescript
* const device = Devices.get('arduino', 'uuid-123');
* ```
*/
static get(e, t) {
return typeof s.devices[e] > "u" && s.registerType(e), typeof s.devices[e] > "u" && s.typeError(e), s.devices[e][t];
}
static getAll(e = null) {
return e === null ? s.devices : (typeof s.devices[e] > "u" && s.typeError(e), s.devices[e]);
}
static getList() {
return Object.values(s.devices).map((t) => Object.values(t)).flat();
}
static getByNumber(e, t) {
return typeof s.devices[e] > "u" && s.typeError(e), Object.values(s.devices[e]).find((i) => i.deviceNumber === t) ?? null;
}
static getCustom(e, t = 1) {
return typeof s.devices[e] > "u" && s.typeError(e), Object.values(s.devices[e]).find((i) => i.deviceNumber === t) ?? null;
}
static async connectToAll() {
const e = s.getList();
for (const t of e)
t.isConnected || await t.connect().catch(console.warn);
return Promise.resolve(s.areAllConnected());
}
static async disconnectAll() {
const e = s.getList();
for (const t of e)
t.isDisconnected || await t.disconnect().catch(console.warn);
return Promise.resolve(s.areAllDisconnected());
}
static async areAllConnected() {
const e = s.getList();
for (const t of e)
if (!t.isConnected) return Promise.resolve(!1);
return Promise.resolve(!0);
}
static async areAllDisconnected() {
const e = s.getList();
for (const t of e)
if (!t.isDisconnected) return Promise.resolve(!1);
return Promise.resolve(!0);
}
static async getAllConnected() {
const e = s.getList();
return Promise.resolve(e.filter((t) => t.isConnected));
}
static async getAllDisconnected() {
const e = s.getList();
return Promise.resolve(e.filter((t) => t.isDisconnected));
}
}
s.instance || (s.instance = new s());
function y(o = 100) {
return new Promise(
(e) => setTimeout(() => e(), o)
);
}
class C {
#n = "http://localhost:3000";
#i = {
transports: ["websocket"]
};
#e = null;
#s = !1;
#a = !1;
#t;
constructor() {
this.#t = {
onResponse: this.onResponse.bind(this),
onDisconnect: () => {
this.#s = !1, window.dispatchEvent(new Event("serial:socket:disconnected"));
},
onConnect: () => {
this.#s = !0, window.dispatchEvent(new Event("serial:socket:connected"));
},
onConnectError: (e) => {
console.debug("Socket connection error", e), this.#s = !1, window.dispatchEvent(new Event("serial:socket:disconnected"));
}
};
}
set uri(e) {
const t = new URL(e);
if (!["http:", "https:", "ws:", "wss:"].includes(t.protocol))
throw new Error("URI must start with http://, https://, ws://, or wss://");
this.#n = e;
}
get uri() {
return this.#n;
}
set options(e) {
if (typeof e != "object")
throw new Error("Options must be an object");
this.#i = e;
}
get options() {
return this.#i;
}
get socketId() {
return this.#e && this.#e.id ? this.#e.id : null;
}
disconnect() {
this.#e && (this.#e.off("response", this.#t.onResponse), this.#e.off("disconnect", this.#t.onDisconnect), this.#e.off("connect", this.#t.onConnect), this.#e.off("connect_error", this.#t.onConnectError), this.#e.disconnect(), this.#e = null, this.#a = !1), this.#s = !1;
}
prepare() {
this.#s || this.#a || (this.#e = b(this.#n, this.#i), this.#a = !0, this.#e.on("disconnect", this.#t.onDisconnect), this.#e.on("response", this.#t.onResponse), this.#e.on("connect", this.#t.onConnect), this.#e.on("connect_error", this.#t.onConnectError));
}
connectDevice(e) {
if (!this.#e)
throw new Error("Socket not connected. Call prepare() first.");
this.#e.emit("connectDevice", { config: e });
}
disconnectDevice(e) {
if (!this.#e)
throw new Error("Socket not connected. Call prepare() first.");
this.#e.emit("disconnectDevice", { config: e });
}
disconnectAllDevices() {
if (!this.#e)
throw new Error("Socket not connected. Call prepare() first.");
this.#e.emit("disconnectAll");
}
write(e) {
if (!this.#e)
throw new Error("Socket not connected. Call prepare() first.");
this.#e.emit("cmd", e);
}
onResponse(e) {
let t = s.get(e.name, e.uuid);
t || (t = s.getByNumber(e.name, e.deviceNumber)), t && t.socketResponse(e);
}
isConnected() {
return this.#s;
}
isDisconnected() {
return !this.#s;
}
}
const c = new C(), p = {
baudRate: 9600,
dataBits: 8,
stopBits: 1,
parity: "none",
bufferSize: 32768,
flowControl: "none"
};
class S extends m {
__internal__ = {
bypassSerialBytesConnection: !1,
auto_response: !1,
device_number: 1,
aux_port_connector: 0,
last_error: {
message: null,
action: null,
code: null,
no_code: 0
},
serial: {
socket: !1,
portInfo: {
path: null,
vendorId: null,
productId: null,
parser: {
name: "inter-byte-timeout",
interval: 50
}
},
aux_connecting: "idle",
connecting: !1,
connected: !1,
port: null,
last_action: null,
response: {
length: null,
buffer: new Uint8Array([]),
as: "uint8",
replacer: /[\n\r]+/g,
limiter: null,
prefixLimiter: !1,
sufixLimiter: !0,
delimited: !1
},
reader: null,
input_done: null,
output_done: null,
input_stream: null,
output_stream: null,
keep_reading: !0,
time_until_send_bytes: void 0,
delay_first_connection: 200,
bytes_connection: null,
filters: [],
config_port: p,
queue: [],
running_queue: !1,
auto_response: null,
free_timeout_ms: 50,
// In previous versions 400 was used
useRTSCTS: !1
// Use RTS/CTS flow control
},
device: {
type: "unknown",
id: window.crypto.randomUUID(),
listen_on_port: null
},
time: {
response_connection: 500,
response_engines: 2e3,
response_general: 2e3
},
timeout: {
until_response: 0
},
interval: {
reconnection: 0
}
};
#n = null;
constructor({
filters: e = null,
config_port: t = p,
no_device: n = 1,
device_listen_on_channel: i = 1,
bypassSerialBytesConnection: a = !1,
socket: r = !1
} = {
filters: null,
config_port: p,
no_device: 1,
device_listen_on_channel: 1,
bypassSerialBytesConnection: !1,
socket: !1
}) {
if (super(), !("serial" in navigator))
throw new Error("Web Serial not supported");
e && (this.serialFilters = e), t && (this.serialConfigPort = t), a && (this.__internal__.bypassSerialBytesConnection = a), n && this.#b(n), i && ["number", "string"].includes(typeof i) && (this.listenOnChannel = i), this.__internal__.serial.socket = r, this.#g(), this.#y();
}
set listenOnChannel(e) {
if (typeof e == "string" && (e = parseInt(e)), isNaN(e) || e < 1 || e > 255)
throw new Error("Invalid port number");
this.__internal__.device.listen_on_port = e, !this.__internal__.bypassSerialBytesConnection && (this.__internal__.serial.bytes_connection = this.serialSetConnectionConstant(e));
}
get lastAction() {
return this.__internal__.serial.last_action;
}
get listenOnChannel() {
return this.__internal__.device.listen_on_port ?? 1;
}
set serialFilters(e) {
if (this.isConnected) throw new Error("Cannot change serial filters while connected");
this.__internal__.serial.filters = e;
}
get serialFilters() {
return this.__internal__.serial.filters;
}
set serialConfigPort(e) {
if (this.isConnected) throw new Error("Cannot change serial filters while connected");
this.__internal__.serial.config_port = e;
}
get serialConfigPort() {
return this.__internal__.serial.config_port;
}
get useRTSCTS() {
return this.__internal__.serial.useRTSCTS;
}
set useRTSCTS(e) {
this.__internal__.serial.useRTSCTS = e;
}
get isConnected() {
const e = this.__internal__.serial.connected, t = this.#i(this.__internal__.serial.port);
return e && !t && this.#e({ error: "Port is closed, not readable or writable." }), this.__internal__.serial.connected = t, this.__internal__.serial.connected;
}
get isConnecting() {
return this.__internal__.serial.connecting;
}
get isDisconnected() {
const e = this.__internal__.serial.connected, t = this.#i(this.__internal__.serial.port);
return !e && t && (this.dispatch("serial:connected"), this.#o(!1), s.$dispatchChange(this)), this.__internal__.serial.connected = t, !this.__internal__.serial.connected;
}
get deviceNumber() {
return this.__internal__.device_number;
}
get uuid() {
return this.__internal__.device.id;
}
get typeDevice() {
return this.__internal__.device.type;
}
get queue() {
return this.__internal__.serial.queue;
}
get responseDelimited() {
return this.__internal__.serial.response.delimited;
}
set responseDelimited(e) {
if (typeof e != "boolean")
throw new Error("responseDelimited must be a boolean");
this.__internal__.serial.response.delimited = e;
}
get responsePrefixLimited() {
return this.__internal__.serial.response.prefixLimiter;
}
set responsePrefixLimited(e) {
if (typeof e != "boolean")
throw new Error("responsePrefixLimited must be a boolean");
this.__internal__.serial.response.prefixLimiter = e;
}
get responseSufixLimited() {
return this.__internal__.serial.response.sufixLimiter;
}
set responseSufixLimited(e) {
if (typeof e != "boolean")
throw new Error("responseSufixLimited must be a boolean");
this.__internal__.serial.response.sufixLimiter = e;
}
get responseLimiter() {
return this.__internal__.serial.response.limiter;
}
set responseLimiter(e) {
if (typeof e != "string" && !(e instanceof RegExp))
throw new Error("responseLimiter must be a string or a RegExp");
this.__internal__.serial.response.limiter = e;
}
get fixedBytesMessage() {
return this.__internal__.serial.response.length;
}
set fixedBytesMessage(e) {
if (e !== null && (typeof e != "number" || e < 1))
throw new Error("Invalid length for fixed bytes message");
this.__internal__.serial.response.length = e;
}
get timeoutBeforeResponseBytes() {
return this.__internal__.serial.free_timeout_ms || 50;
}
set timeoutBeforeResponseBytes(e) {
if (e !== void 0 && (typeof e != "number" || e < 1))
throw new Error("Invalid timeout for response bytes");
this.__internal__.serial.free_timeout_ms = e ?? 50;
}
get bypassSerialBytesConnection() {
return this.__internal__.bypassSerialBytesConnection;
}
set bypassSerialBytesConnection(e) {
if (typeof e != "boolean")
throw new Error("bypassSerialBytesConnection must be a boolean");
this.__internal__.bypassSerialBytesConnection = e;
}
get useSocket() {
return this.__internal__.serial.socket;
}
get connectionBytes() {
const e = this.__internal__.serial.bytes_connection;
return e instanceof Uint8Array ? e : typeof e == "string" ? this.stringArrayToUint8Array(this.parseStringToBytes(e, "")) : Array.isArray(e) && typeof e[0] == "string" ? this.stringArrayToUint8Array(e) : Array.isArray(e) && typeof e[0] == "number" ? new Uint8Array(e) : new Uint8Array([]);
}
set portPath(e) {
if (this.isConnected) throw new Error("Cannot change port path while connected");
if (typeof e != "string" && e !== null)
throw new TypeError("vendorId must be string or null");
this.__internal__.serial.portInfo.path = e;
}
get portPath() {
return this.__internal__.serial.portInfo.path;
}
set portVendorId(e) {
if (this.isConnected) throw new Error("Cannot change port vendorId while connected");
if (typeof e == "number" && typeof e != "string" && e !== null)
throw new TypeError("vendorId must be a number, string or null");
this.__internal__.serial.portInfo.vendorId = e;
}
get portVendorId() {
return this.__internal__.serial.portInfo.vendorId;
}
set portProductId(e) {
if (this.isConnected) throw new Error("Cannot change port productId while connected");
if (typeof e == "number" && typeof e != "string" && e !== null)
throw new TypeError("productId must be a number, string or null");
this.__internal__.serial.portInfo.productId = e;
}
get portProductId() {
return this.__internal__.serial.portInfo.productId;
}
set socketPortParser(e) {
if (["byte-length", "inter-byte-timeout"].includes(e))
throw new TypeError("socketPortParser must be a string, either 'byte-length' or 'inter-byte-timeout'");
this.__internal__.serial.portInfo.parser.name = e;
}
get socketPortParser() {
return this.__internal__.serial.portInfo.parser.name;
}
set socketPortParserInterval(e) {
if (typeof e != "number" || e < 1)
throw new TypeError("Interval must be a positive number");
this.__internal__.serial.portInfo.parser.interval = e;
}
get socketPortParserInterval() {
return this.__internal__.serial.portInfo.parser.interval || 50;
}
set socketPortParserLength(e) {
if (typeof e != "number" || e < 1)
throw new TypeError("Length must be a positive number or null");
this.__internal__.serial.portInfo.parser.length = e;
}
get socketPortParserLength() {
return this.__internal__.serial.portInfo.parser.length || 14;
}
get parserForSocket() {
return this.socketPortParser === "byte-length" ? {
name: this.socketPortParser,
length: this.socketPortParserLength
} : {
name: this.socketPortParser,
interval: this.socketPortParserInterval
};
}
get configDeviceSocket() {
return {
uuid: this.uuid,
name: this.typeDevice,
deviceNumber: this.deviceNumber,
connectionBytes: Array.from(this.connectionBytes),
config: {
baudRate: this.__internal__.serial.config_port.baudRate,
dataBits: this.__internal__.serial.config_port.dataBits,
stopBits: this.__internal__.serial.config_port.stopBits,
parity: this.__internal__.serial.config_port.parity,
bufferSize: this.__internal__.serial.config_port.bufferSize,
flowControl: this.__internal__.serial.config_port.flowControl
},
info: {
vendorId: this.portVendorId,
// vendor ID or null for auto-detect
productId: this.portProductId,
// product ID or null for auto-detect
portName: this.portPath
// COM3, /dev/ttyUSB0, etc. null for auto-detect
},
response: {
automatic: this.__internal__.auto_response,
// true to auto-respond to commands this only for devices that doesn't respond nothing
autoResponse: this.__internal__.serial.auto_response,
// null or data to respond automatically, ie. [0x02, 0x06, 0xdd, 0xdd, 0xf0, 0xcf, 0x03] for relay
parser: this.parserForSocket,
timeout: {
general: this.__internal__.time.response_general,
engines: this.__internal__.time.response_engines,
connection: this.__internal__.time.response_connection
}
}
};
}
#i(e) {
return this.useSocket ? this.__internal__.serial.connected && c.isConnected() : !!(e && e.readable && e.writable);
}
async timeout(e, t) {
this.__internal__.last_error.message = "Operation response timed out.", this.__internal__.last_error.action = t, this.__internal__.last_error.code = e, this.__internal__.timeout.until_response && (clearTimeout(this.__internal__.timeout.until_response), this.__internal__.timeout.until_response = 0), t === "connect" ? (this.__internal__.serial.connected = !1, this.dispatch("serial:reconnect", {}), s.$dispatchChange(this)) : t === "connection:start" && (await this.serialDisconnect(), this.__internal__.serial.connected = !1, this.__internal__.aux_port_connector += 1, s.$dispatchChange(this), await this.serialConnect()), this.__internal__.serial.queue.length > 0 && this.dispatch("internal:queue", {}), this.dispatch("serial:timeout", {
...this.__internal__.last_error,
bytes: e,
action: t
});
}
async disconnect(e = null) {
await this.serialDisconnect(), this.#e(e);
}
#e(e = null) {
this.__internal__.serial.connected = !1, this.__internal__.aux_port_connector = 0, this.dispatch("serial:disconnected", e), s.$dispatchChange(this);
}
#s(e) {
this.__internal__.serial.aux_connecting = e.detail.active ? "connecting" : "finished";
}
socketResponse(e) {
const t = this.__internal__.serial.connected;
if (e.type === "disconnect" || e.type === "error" && e.data === "DISCONNECTED" ? this.__internal__.serial.connected = !1 : e.type === "success" && (this.__internal__.serial.connected = !0), s.$dispatchChange(this), !t && this.__internal__.serial.connected && (this.dispatch("serial:connected"), this.#o(!1)), e.type === "success")
this.#r(new Uint8Array(e.data));
else if (e.type === "error") {
const n = new Error("The port is closed or is not readable/writable");
this.serialErrors(n);
} else e.type === "timeout" && this.timeout(e.data.bytes ?? [], this.lastAction || "unknown");
this.__internal__.serial.last_action = null;
}
async connect() {
return this.isConnected ? !0 : (this.__internal__.serial.aux_connecting = "idle", new Promise((e, t) => {
this.#n || (this.#n = this.#s.bind(this)), this.on("internal:connecting", this.#n);
const n = setInterval(() => {
this.__internal__.serial.aux_connecting === "finished" ? (clearInterval(n), this.__internal__.serial.aux_connecting = "idle", this.#n !== null && this.off("internal:connecting", this.#n), this.isConnected ? e(!0) : t(`${this.typeDevice} device ${this.deviceNumber} not connected`)) : this.__internal__.serial.aux_connecting === "connecting" && (this.__internal__.serial.aux_connecting = "idle", this.dispatch("internal:connecting", { active: !0 }), this.dispatch("serial:connecting", { active: !0 }));
}, 100);
this.serialConnect();
}));
}
async serialDisconnect() {
try {
if (this.useSocket)
c.isConnected() && c.disconnectDevice(this.configDeviceSocket);
else {
const e = this.__internal__.serial.reader, t = this.__internal__.serial.output_stream;
e && (await e.cancel().catch((i) => this.serialErrors(i)), await this.__internal__.serial.input_done), t && (await t.getWriter().close(), await this.__internal__.serial.output_done), this.__internal__.serial.connected && this.__internal__.serial && this.__internal__.serial.port && await this.__internal__.serial.port.close();
}
} catch (e) {
this.serialErrors(e);
} finally {
this.__internal__.serial.reader = null, this.__internal__.serial.input_done = null, this.__internal__.serial.output_stream = null, this.__internal__.serial.output_done = null, this.__internal__.serial.connected = !1, this.__internal__.serial.port = null, s.$dispatchChange(this);
}
}
async #a(e) {
if (c.isDisconnected())
throw this.#e({ error: "Socket is disconnected." }), new Error("The socket is disconnected");
if (this.isDisconnected)
throw this.#e({ error: "Port is closed, not readable or writable." }), new Error("The port is closed or is not readable/writable");
const t = this.validateBytes(e);
c.write({ config: this.configDeviceSocket, bytes: Array.from(t) });
}
async #t(e) {
if (this.useSocket) {
await this.#a(e);
return;
}
const t = this.__internal__.serial.port;
if (!t || t && (!t.readable || !t.writable))
throw this.#e({ error: "Port is closed, not readable or writable." }), new Error("The port is closed or is not readable/writable");
const n = this.validateBytes(e);
if (this.useRTSCTS && await this.#l(t, 5e3), t.writable === null) return;
const i = t.writable.getWriter();
await i.write(n), i.releaseLock();
}
async #l(e, t = 5e3) {
const n = Date.now();
for (; ; ) {
if (Date.now() - n > t)
throw new Error("Timeout waiting for clearToSend signal");
const { clearToSend: i } = await e.getSignals();
if (i) return;
await y(100);
}
}
#r(e = new Uint8Array([]), t = !1) {
if (e && e.length > 0) {
const n = this.__internal__.serial.connected;
if (this.__internal__.serial.connected = this.#i(this.__internal__.serial.port), s.$dispatchChange(this), !n && this.__internal__.serial.connected && (this.dispatch("serial:connected"), this.#o(!1)), this.__internal__.interval.reconnection && (clearInterval(this.__internal__.interval.reconnection), this.__internal__.interval.reconnection = 0), this.__internal__.timeout.until_response && (clearTimeout(this.__internal__.timeout.until_response), this.__internal__.timeout.until_response = 0), this.__internal__.serial.response.as === "hex")
t ? this.serialCorruptMessage(this.parseUint8ToHex(e)) : this.serialMessage(this.parseUint8ToHex(e));
else if (this.__internal__.serial.response.as === "uint8")
t ? this.serialCorruptMessage(e) : this.serialMessage(e);
else if (this.__internal__.serial.response.as === "string") {
const i = this.parseUint8ArrayToString(e);
if (this.__internal__.serial.response.limiter !== null) {
const a = i.split(this.__internal__.serial.response.limiter);
for (const r in a)
a[r] && (t ? this.serialCorruptMessage(a[r]) : this.serialMessage(a[r]));
} else
t ? this.serialCorruptMessage(i) : this.serialMessage(i);
} else {
const i = this.stringToArrayBuffer(this.parseUint8ArrayToString(e));
t ? this.serialCorruptMessage(i) : this.serialMessage(i);
}
}
if (this.__internal__.serial.queue.length === 0) {
this.__internal__.serial.running_queue = !1;
return;
}
this.dispatch("internal:queue", {});
}
getResponseAsArrayBuffer() {
this.__internal__.serial.response.as = "arraybuffer";
}
getResponseAsArrayHex() {
this.__internal__.serial.response.as = "hex";
}
getResponseAsUint8Array() {
this.__internal__.serial.response.as = "uint8";
}
getResponseAsString() {
this.__internal__.serial.response.as = "string";
}
async #_() {
const e = this.serialFilters, t = await navigator.serial.getPorts({ filters: e });
return e.length === 0 ? t : t.filter((i) => {
const a = i.getInfo();
return e.some((r) => a.usbProductId === r.usbProductId && a.usbVendorId === r.usbVendorId);
}).filter((i) => !this.#i(i));
}
async serialPortsSaved(e) {
const t = this.serialFilters;
if (this.__internal__.aux_port_connector < e.length) {
const n = this.__internal__.aux_port_connector;
this.__internal__.serial.port = e[n];
} else
this.__internal__.aux_port_connector = 0, this.__internal__.serial.port = await navigator.serial.requestPort({
filters: t
});
if (!this.__internal__.serial.port)
throw new Error("Select another port please");
}
serialErrors(e) {
const t = e.toString().toLowerCase();
switch (!0) {
case t.includes("must be handling a user gesture to show a permission request"):
case t.includes("the port is closed."):
case t.includes("the port is closed or is not writable"):
case t.includes("the port is closed or is not readable"):
case t.includes("the port is closed or is not readable/writable"):
case t.includes("select another port please"):
case t.includes("no port selected by the user"):
case t.includes(
"this readable stream reader has been released and cannot be used to cancel its previous owner stream"
):
this.dispatch("serial:need-permission", {}), s.$dispatchChange(this);
break;
case t.includes("the port is already open."):
case t.includes("failed to open serial port"):
this.serialDisconnect().then(async () => {
this.__internal__.aux_port_connector += 1, await this.serialConnect();
});
break;
case t.includes("cannot read properties of undefined (reading 'writable')"):
case t.includes("cannot read properties of null (reading 'writable')"):
case t.includes("cannot read property 'writable' of null"):
case t.includes("cannot read property 'writable' of undefined"):
this.serialDisconnect().then(async () => {
await this.serialConnect();
});
break;
case t.includes("'close' on 'serialport': a call to close() is already in progress."):
break;
case t.includes("failed to execute 'open' on 'serialport': a call to open() is already in progress."):
break;
case t.includes("the port is already closed."):
break;
case t.includes("the device has been lost"):
this.dispatch("serial:lost", {}), s.$dispatchChange(this);
break;
case t.includes("navigator.serial is undefined"):
this.dispatch("serial:unsupported", {});
break;
default:
console.error(e);
break;
}
this.dispatch("serial:error", e);
}
#c(e) {
if (e) {
const t = this.__internal__.serial.response.buffer, n = new Uint8Array(t.length + e.byteLength);
n.set(t, 0), n.set(new Uint8Array(e), t.length), this.__internal__.serial.response.buffer = n;
}
}
async #h() {
this.__internal__.serial.time_until_send_bytes && (clearTimeout(this.__internal__.serial.time_until_send_bytes), this.__internal__.serial.time_until_send_bytes = 0), this.__internal__.serial.time_until_send_bytes = setTimeout(() => {
this.__internal__.serial.response.buffer && this.#r(this.__internal__.serial.response.buffer), this.__internal__.serial.response.buffer = new Uint8Array(0);
}, this.__internal__.serial.free_timeout_ms || 50);
}
async #u() {
const e = this.__internal__.serial.response.length;
let t = this.__internal__.serial.response.buffer;
if (this.__internal__.serial.time_until_send_bytes && (clearTimeout(this.__internal__.serial.time_until_send_bytes), this.__internal__.serial.time_until_send_bytes = 0), !(e === null || !t || t.length === 0)) {
for (; t.length >= e; ) {
const n = t.slice(0, e);
this.#r(n), t = t.slice(e);
}
this.__internal__.serial.response.buffer = t, t.length > 0 && (this.__internal__.serial.time_until_send_bytes = setTimeout(() => {
this.#r(this.__internal__.serial.response.buffer, !0);
}, this.__internal__.serial.free_timeout_ms || 50));
}
}
async #d() {
const {
limiter: e,
prefixLimiter: t = !1,
sufixLimiter: n = !0
} = this.__internal__.serial.response;
if (!e)
throw new Error("No limiter defined for delimited serial response");
const i = this.__internal__.serial.response.buffer;
if (!e || !i || i.length === 0) return;
this.__internal__.serial.time_until_send_bytes && (clearTimeout(this.__internal__.serial.time_until_send_bytes), this.__internal__.serial.time_until_send_bytes = 0);
let r = new TextDecoder().decode(i);
const u = [];
if (typeof e == "string") {
let l;
if (t && n)
l = new RegExp(`${e}([^${e}]+)${e}`, "g");
else if (t)
l = new RegExp(`${e}([^${e}]*)`, "g");
else if (n)
l = new RegExp(`([^${e}]+)${e}`, "g");
else
return;
let h, _ = 0;
for (; (h = l.exec(r)) !== null; )
u.push(new TextEncoder().encode(h[1])), _ = l.lastIndex;
r = r.slice(_);
} else if (e instanceof RegExp) {
let l, h = 0;
if (t && n) {
const _ = new RegExp(`${e.source}(.*?)${e.source}`, "g");
for (; (l = _.exec(r)) !== null; )
u.push(new TextEncoder().encode(l[1])), h = _.lastIndex;
} else if (n)
for (; (l = e.exec(r)) !== null; ) {
const _ = l.index, d = r.slice(h, _);
u.push(new TextEncoder().encode(d)), h = e.lastIndex;
}
else if (t) {
const _ = r.split(e);
_.shift();
for (const d of _)
u.push(new TextEncoder().encode(d));
r = "";
}
r = r.slice(h);
}
for (const l of u)
this.#r(l);
const f = new TextEncoder().encode(r);
this.__internal__.serial.response.buffer = f, f.length > 0 && (this.__internal__.serial.time_until_send_bytes = setTimeout(() => {
this.#r(this.__internal__.serial.response.buffer, !0), this.__internal__.serial.response.buffer = new Uint8Array(0);
}, this.__internal__.serial.free_timeout_ms ?? 50));
}
async #p() {
const e = this.__internal__.serial.port;
if (!e || !e.readable) throw new Error("Port is not readable");
const t = e.readable.getReader();
this.__internal__.serial.reader = t;
try {
for (; this.__internal__.serial.keep_reading; ) {
const { value: n, done: i } = await t.read();
if (i) break;
this.#c(n), this.__internal__.serial.response.delimited ? await this.#d() : this.__internal__.serial.response.length === null ? await this.#h() : await this.#u();
}
} catch (n) {
this.serialErrors(n);
} finally {
t.releaseLock(), this.__internal__.serial.keep_reading = !0, this.__internal__.serial.port && await this.__internal__.serial.port.close();
}
}
#o(e) {
e !== this.__internal__.serial.connecting && (this.__internal__.serial.connecting = e, this.dispatch("serial:connecting", { active: e }), this.dispatch("internal:connecting", { active: e }));
}
async serialConnect() {
try {
if (this.#o(!0), this.useSocket) {
if (c.prepare(), this.__internal__.serial.last_action = "connect", this.__internal__.timeout.until_response = setTimeout(async () => {
await this.timeout(this.__internal__.serial.bytes_connection ?? [], "connection:start");
}, this.__internal__.time.response_connection), c.isDisconnected())
return;
c.connectDevice(this.configDeviceSocket), this.dispatch("serial:sent", {
action: "connect",
bytes: this.__internal__.serial.bytes_connection
});
} else {
const e = await this.#_();
if (e.length > 0)
await this.serialPortsSaved(e);
else {
const i = this.serialFilters;
this.__internal__.serial.port = await navigator.serial.requestPort({
filters: i
});
}
const t = this.__internal__.serial.port;
if (!t)
throw new Error("No port selected by the user");
await t.open(this.serialConfigPort);
const n = this;
t.onconnect = (i) => {
n.dispatch("serial:connected", i), n.#o(!1), s.$dispatchChange(this), n.__internal__.serial.queue.length > 0 ? n.dispatch("internal:queue", {}) : n.__internal__.serial.running_queue = !1;
}, t.ondisconnect = async () => {
await n.disconnect();
}, await y(this.__internal__.serial.delay_first_connection), this.__internal__.timeout.until_response = setTimeout(async () => {
await n.timeout(n.__internal__.serial.bytes_connection ?? [], "connection:start");
}, this.__internal__.time.response_connection), this.__internal__.serial.last_action = "connect", await this.#t(this.__internal__.serial.bytes_connection ?? []), this.dispatch("serial:sent", {
action: "connect",
bytes: this.__internal__.serial.bytes_connection
}), this.__internal__.auto_response && this.#r(this.__internal__.serial.auto_response), await this.#p();
}
} catch (e) {
this.#o(!1), this.serialErrors(e);
}
}
async #f() {
return typeof window > "u" ? !1 : "serial" in navigator && "forget" in SerialPort.prototype && this.__internal__.serial.port ? (await this.__internal__.serial.port.forget(), !0) : !1;
}
async serialForget() {
return await this.#f();
}
decToHex(e) {
return typeof e == "string" && (e = parseInt(e, 10)), e.toString(16);
}
hexToDec(e) {
return parseInt(e, 16);
}
hexMaker(e = "00", t = 2) {
return e.toString().padStart(t, "0").toLowerCase();
}
add0x(e) {
const t = [];
return e.forEach((n, i) => {
t[i] = "0x" + n;
}), t;
}
bytesToHex(e) {
return this.add0x(Array.from(e, (t) => this.hexMaker(t)));
}
#g() {
[
"serial:connected",
"serial:connecting",
"serial:reconnect",
"serial:timeout",
"serial:disconnected",
"serial:sent",
"serial:soft-reload",
"serial:message",
"serial:corrupt-message",
"unknown",
"serial:need-permission",
"serial:lost",
"serial:unsupported",
"serial:error",
"debug"
].forEach((t) => {
this.serialRegisterAvailableListener(t);
});
}
#y() {
const e = this;
this.on("internal:queue", async () => {
await e.#w();
});
const t = () => {
e.isConnected && e.#e({ error: "Socket disconnected." });
}, n = () => {
e.isDisconnected && !e.isConnecting && e.serialConnect().catch(() => {
});
};
this.useSocket && (window.addEventListener("serial:socket:disconnected", t), window.addEventListener("serial:socket:connected", n)), this.#m();
}
#m() {
const e = this;
navigator.serial.addEventListener("connect", async () => {
e.isDisconnected && await e.serialConnect().catch(() => {
});
});
}
async #w() {
if (this.useSocket && c.isDisconnected())
return;
if (!this.#i(this.__internal__.serial.port)) {
this.#e({ error: "Port is closed, not readable or writable." }), await this.serialConnect();
return;
}
if (this.__internal__.timeout.until_response) return;
if (this.__internal__.serial.queue.length === 0) {
this.__internal__.serial.running_queue = !1;
return;
}
this.__internal__.serial.running_queue = !0;
const e = this.__internal__.serial.queue[0];
let t = this.__internal__.time.response_general;
if (e.action === "connect" && (t = this.__internal__.time.response_connection), this.__internal__.timeout.until_response = setTimeout(async () => {
await this.timeout(e.bytes, e.action);
}, t), this.__internal__.serial.last_action = e.action ?? "unknown", await this.#t(e.bytes), this.dispatch("serial:sent", {
action: e.action,
bytes: e.bytes
}), this.__internal__.auto_response) {
let i = new Uint8Array(0);
try {
i = this.validateBytes(this.__internal__.serial.auto_response);
} catch (a) {
this.serialErrors(a);
}
this.#r(i);
}
const n = [...this.__internal__.serial.queue];
this.__internal__.serial.queue = n.splice(1), this.__internal__.serial.queue.length > 0 && (this.__internal__.serial.running_queue = !0);
}
validateBytes(e) {
let t = new Uint8Array(0);
if (e instanceof Uint8Array)
t = e;
else if (typeof e == "string")
t = this.parseStringToTextEncoder(e);
else if (Array.isArray(e) && typeof e[0] == "string")
t = this.stringArrayToUint8Array(e);
else if (Array.isArray(e) && typeof e[0] == "number")
t = new Uint8Array(e);
else
throw new Error("Invalid data type");
return t;
}
async appendToQueue(e, t) {
const n = this.validateBytes(e);
if (["connect", "connection:start"].includes(t)) {
if (this.__internal__.serial.connected) return;
await this.serialConnect();
return;
}
this.__internal__.serial.queue.push({ bytes: n, action: t }), this.dispatch("internal:queue", {});
}
#b(e = 1) {
this.__internal__.device_number = e, !this.__internal__.bypassSerialBytesConnection && (this.__internal__.serial.bytes_connection = this.serialSetConnectionConstant(e));
}
serialSetConnectionConstant(e = 1) {
if (this.__internal__.bypassSerialBytesConnection) return this.__internal__.serial.bytes_connection;
throw new Error(`Method not implemented 'serialSetConnectionConstant' to listen on channel ${e}`);
}
serialMessage(e) {
throw console.log(e), this.dispatch("serial:message", { code: e }), new Error("Method not implemented 'serialMessage'");
}
serialCorruptMessage(e) {
throw console.log(e), this.dispatch("serial:corrupt-message", { code: e }), new Error("Method not implemented 'serialCorruptMessage'");
}
#C() {
this.__internal__.last_error = {
message: null,
action: null,
code: null,
no_code: 0
};
}
clearSerialQueue() {
this.__internal__.serial.queue = [];
}
sumHex(e) {
let t = 0;
return e.forEach((n) => {
t += parseInt(n, 16);
}), t.toString(16);
}
toString() {
return JSON.stringify({
__class: this.typeDevice,
device_number: this.deviceNumber,
uuid: this.uuid,
connected: this.isConnected,
connection: this.__internal__.serial.bytes_connection
});
}
softReload() {
this.#C(), this.dispatch("serial:soft-reload", {});
}
async sendConnect() {
if (!this.__internal__.serial.bytes_connection)
throw new Error("No connection bytes defined");
await this.appendToQueue(this.__internal__.serial.bytes_connection, "connect");
}
async sendCustomCode({ code: e = [] } = { code: [] }) {
if (!e)
throw new Error("No data to send");
this.__internal__.bypassSerialBytesConnection && (this.__internal__.serial.bytes_connection = this.validateBytes(e)), await this.appendToQueue(e, "custom");
}
stringToArrayHex(e) {
return Array.from(e).map((t) => t.charCodeAt(0).toString(16));
}
stringToArrayBuffer(e, t = `
`) {
return this.parseStringToTextEncoder(e, t).buffer;
}
parseStringToTextEncoder(e = "", t = `
`) {
const n = new TextEncoder();
return e += t, n.encode(e);
}
parseStringToBytes(e = "", t = `
`) {
const n = this.parseStringToTextEncoder(e, t);
return Array.from(n).map((i) => i.toString(16));
}
parseUint8ToHex(e) {
return Array.from(e).map((t) => t.toString(16).padStart(2, "0").toLowerCase());
}
parseHexToUint8(e) {
return new Uint8Array(e.map((t) => parseInt(t, 16)));
}
stringArrayToUint8Array(e) {
const t = [];
return typeof e == "string" ? this.parseStringToTextEncoder(e).buffer : (e.forEach((n) => {
const i = n.replace("0x", "");
t.push(parseInt(i, 16));
}), new Uint8Array(t));
}
parseUint8ArrayToString(e) {
let t = new Uint8Array(0);
e instanceof Uint8Array ? t = e : t = this.stringArrayToUint8Array(e), e = this.parseUint8ToHex(t);
const n = e.map((i) => parseInt(i, 16));
return this.__internal__.serial.response.replacer ? String.fromCharCode(...n).replace(this.__internal__.serial.response.replacer, "") : String.fromCharCode(...n);
}
hexToAscii(e) {
const t = e.toString();
let n = "";
for (let i = 0; i < t.length; i += 2)
n += String.fromCharCode(parseInt(t.substring(i, 2), 16));
return n;
}
asciiToHex(e) {
const t = [];
for (let n = 0, i = e.length; n < i; n++) {
const a = Number(e.charCodeAt(n)).toString(16);
t.push(a);
}
return t.join("");
}
$checkAndDispatchConnection() {
return this.isConnected;
}
}
var E = /* @__PURE__ */ ((o) => (o.CONNECTION_FAILED = "CONNECTION_FAILED", o.DISCONNECTION_FAILED = "DISCONNECTION_FAILED", o.WRITE_FAILED = "WRITE_FAILED", o.READ_FAILED = "READ_FAILED", o.TIMEOUT = "TIMEOUT", o.PORT_NOT_FOUND = "PORT_NOT_FOUND", o.PERMISSION_DENIED = "PERMISSION_DENIED", o.DEVICE_NOT_SUPPORTED = "DEVICE_NOT_SUPPORTED", o.INVALID_CONFIGURATION = "INVALID_CONFIGURATION", o.SOCKET_ERROR = "SOCKET_ERROR", o.UNKNOWN_ERROR = "UNKNOWN_ERROR", o))(E || {});
class w extends Error {
/**
* Error code identifying the type of error
*/
code;
/**
* Additional context about the error
*/
context;
/**
* Timestamp when the error occurred
*/
timestamp;
/**
* Creates a new SerialError
* @param message - Human-readable error message
* @param code - Error code from SerialErrorCode enum
* @param context - Additional context information
* @example
* ```typescript
* throw new SerialError(
* 'Failed to connect to device',
* SerialErrorCode.CONNECTION_FAILED,
* { port: 'COM3', baudRate: 9600 }
* );
* ```
*/
constructor(e, t = "UNKNOWN_ERROR", n) {
super(e), this.name = "SerialError", this.code = t, this.context = n, this.timestamp = /* @__PURE__ */ new Date(), Error.captureStackTrace && Error.captureStackTrace(this, w);
}
/**
* Returns a JSON representation of the error
* @returns Serialized error object
*/
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
context: this.context,
timestamp: this.timestamp.toISOString(),
stack: this.stack
};
}
/**
* Returns a formatted string representation of the error
* @returns Formatted error string
*/
toString() {
const e = this.context ? ` | Context: ${JSON.stringify(this.context)}` : "";
return `${this.name} [${this.code}]: ${this.message}${e}`;
}
}
export {
S as Core,
s as Devices,
m as Dispatcher,
w as SerialError,
E as SerialErrorCode,
c as Socket
};
//# sourceMappingURL=webserial-core.js.map