add mame modem proxy for rockwell -> usrobotics

This commit is contained in:
zefie
2025-08-13 22:29:44 -04:00
parent a6f29d6b6a
commit 9a9d161dc1
3 changed files with 671 additions and 2 deletions

View File

@@ -0,0 +1,361 @@
// Rockwell to USRobotics Modem Proxy for MAME Bitbanger
const net = require('net');
const { SerialPort } = require('serialport');
const { ReadlineParser } = require('@serialport/parser-readline');
// Configuration
const TCP_IP = '127.0.0.1';
const TCP_PORT = 57388;
const SERIAL_PORT = 'COM3';
const SERIAL_BAUDRATE = 115200;
let NEXT_RECV_IS_LAST_ASCII = false;
let DATA_MODE = false;
const THINGS_TO_STRIP = ["S95=36", "&Q5", "S51=31"];
const THINGS_TO_REPLACE = [
["M0", "M1"], // M1 = Speaker on
["S11=110", "S11=50"], // S11 = Dial speed
["S11=200", "S11=50"], // S11 = Dial speed
["18004653537", "5736666"],
["18006138199", "5736666"]
];
// Global variables
let serialPort = null;
let server = null;
let currentClient = null;
// Initialize serial port
function initSerial() {
return new Promise((resolve, reject) => {
try {
serialPort = new SerialPort({
path: SERIAL_PORT,
baudRate: SERIAL_BAUDRATE,
autoOpen: false,
// Disable buffering for immediate data flow
highWaterMark: 1,
// Set minimal timeouts
dataBits: 8,
stopBits: 1,
parity: 'none',
rtscts: false,
xon: false,
xoff: false,
xany: false
});
serialPort.open((err) => {
if (err) {
console.error('Error opening serial port:', err.message);
reject(err);
return;
}
console.log(`Serial port ${SERIAL_PORT} opened at ${SERIAL_BAUDRATE} baud`);
// Disable any internal buffering
serialPort.set({
brk: false,
cts: false,
dtr: true,
rts: true
});
// Add a small delay to ensure port is ready
setTimeout(() => {
resolve(serialPort);
}, 100);
});
} catch (error) {
console.error('Error initializing serial port:', error);
reject(error);
}
});
}
// Reset modem to command mode and hang up
function resetModemToCommandMode() {
try {
// Send escape sequence to exit data mode
serialPort.write(Buffer.from('+++', 'ascii'));
setTimeout(() => {
// Send hang up command
serialPort.write(Buffer.from('ATH\r', 'ascii'));
console.log("Sent modem reset commands: +++ and ATH");
}, 500);
} catch (error) {
console.error('Error resetting modem:', error);
}
}
// Handle data from socket to serial
function handleSocketToSerial(socket) {
let buffer = Buffer.alloc(0);
socket.on('data', (data) => {
if (!DATA_MODE) {
buffer = Buffer.concat([buffer, data]);
// Process commands only after receiving a complete command ending in '\r'
// Use Buffer.indexOf to find CR byte (0x0D) for binary safety
const crIndex = buffer.indexOf(0x0D); // '\r' = 0x0D
if (crIndex === -1) {
return; // Wait for complete command
}
// Extract command as buffer first, then convert to string only for processing
const commandBuffer = buffer.slice(0, crIndex);
let command = commandBuffer.toString('ascii').trim();
buffer = buffer.slice(crIndex + 1);
// Apply string stripping and replacement
THINGS_TO_STRIP.forEach(s => {
command = command.replace(s, "");
});
THINGS_TO_REPLACE.forEach(([find, replace]) => {
command = command.replace(find, replace);
});
const commandBytes = Buffer.from(command + '\r', 'ascii');
console.log("WEBTV COMMAND:", commandBytes.toString('ascii').trim());
if (command + '\r' === 'ATD\r') {
NEXT_RECV_IS_LAST_ASCII = true;
console.log("ATD detected - next serial response will trigger data mode");
}
try {
serialPort.write(commandBytes, (err) => {
if (err) {
console.error('Error writing command to serial port:', err);
} else {
// Force immediate transmission of commands too
serialPort.drain();
}
});
} catch (error) {
console.error('Error writing to serial port:', error);
}
} else {
// In data mode, pass through binary data unchanged with immediate write
try {
serialPort.write(data, (err) => {
if (err) {
console.error('Error writing to serial port:', err);
} else {
// Force immediate transmission
serialPort.drain((drainErr) => {
if (drainErr) {
console.error('Error draining serial port:', drainErr);
}
});
}
});
} catch (error) {
console.error('Error writing to serial port:', error);
}
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
if (DATA_MODE) {
console.log("Exiting data mode due to socket error");
DATA_MODE = false;
NEXT_RECV_IS_LAST_ASCII = false;
resetModemToCommandMode();
}
});
socket.on('close', () => {
console.log("Socket closed by remote");
if (DATA_MODE) {
console.log("Exiting data mode due to socket disconnect");
DATA_MODE = false;
NEXT_RECV_IS_LAST_ASCII = false;
resetModemToCommandMode();
}
currentClient = null;
});
}
// Handle data from serial to socket
function handleSerialToSocket(socket) {
const dataHandler = (data) => {
// Check if we're switching to data mode
if (!DATA_MODE && NEXT_RECV_IS_LAST_ASCII) {
DATA_MODE = true;
NEXT_RECV_IS_LAST_ASCII = false;
// Provide connection result and enable data mode
data = Buffer.from('79\r\n67\r\n19\r\n', 'ascii');
console.log("MODEM CONNECT RESULT:", data.toString('ascii'));
console.log("Data mode enabled");
} else if (!DATA_MODE) {
console.log("MODEM COMMAND RESPONSE:", data.toString('ascii'));
}
// Check for disabling data mode if unsupported or exit flags are received
// Use Buffer.equals for binary-safe comparison
const escapeSeq = Buffer.from("+++\r", 'ascii');
const exitCode = Buffer.from("3\r", 'ascii');
if (data.equals(escapeSeq) || data.equals(exitCode)) {
console.log("Data mode disabled by serial response");
DATA_MODE = false;
}
try {
if (socket && !socket.destroyed) {
socket.write(data);
} else {
console.log("[SERIAL->SOCKET] Cannot send - socket destroyed or null");
}
} catch (error) {
console.error('Send error:', error);
if (DATA_MODE) {
console.log("Exiting data mode due to send error");
DATA_MODE = false;
NEXT_RECV_IS_LAST_ASCII = false;
resetModemToCommandMode();
}
}
};
const errorHandler = (err) => {
console.error('Serial port error:', err);
if (DATA_MODE) {
console.log("Exiting data mode due to serial error");
DATA_MODE = false;
NEXT_RECV_IS_LAST_ASCII = false;
resetModemToCommandMode();
}
};
console.log("[SERIAL] Setting up data and error handlers");
serialPort.on('data', dataHandler);
serialPort.on('error', errorHandler);
// Return cleanup function
return () => {
console.log("[SERIAL] Cleaning up event handlers");
serialPort.removeListener('data', dataHandler);
serialPort.removeListener('error', errorHandler);
};
}
// Handle new client connection
function handleClient(socket) {
// Reset state for new connection
DATA_MODE = false;
NEXT_RECV_IS_LAST_ASCII = false;
console.log("Reset modem state for new connection");
// Disable TCP buffering for immediate data flow
socket.setNoDelay(true);
socket.setTimeout(0);
currentClient = socket;
handleSocketToSerial(socket);
const cleanupSerial = handleSerialToSocket(socket);
socket.on('close', () => {
// Ensure data mode is reset when client disconnects
if (DATA_MODE) {
console.log("Client disconnected, exiting data mode");
DATA_MODE = false;
NEXT_RECV_IS_LAST_ASCII = false;
resetModemToCommandMode();
}
// Clean up serial event listeners
cleanupSerial();
currentClient = null;
});
socket.on('error', (err) => {
console.error('Client socket error:', err);
cleanupSerial();
currentClient = null;
});
}
// Clean shutdown procedure
function cleanup() {
console.log("Cleaning up...");
// Reset modem if we're in data mode
if (DATA_MODE) {
console.log("Resetting modem before shutdown");
DATA_MODE = false;
NEXT_RECV_IS_LAST_ASCII = false;
resetModemToCommandMode();
}
if (currentClient) {
currentClient.destroy();
}
if (server) {
server.close();
}
if (serialPort && serialPort.isOpen) {
serialPort.close();
}
}
// Signal handlers
process.on('SIGINT', () => {
console.log('\nReceived interrupt signal, shutting down...');
cleanup();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('\nReceived terminate signal, shutting down...');
cleanup();
process.exit(0);
});
// Main execution
async function main() {
try {
// Initialize serial port and wait for it to be ready
await initSerial();
// Start TCP server
server = net.createServer((socket) => {
console.log(`Connection from ${socket.remoteAddress}:${socket.remotePort}`);
handleClient(socket);
});
server.listen(TCP_PORT, TCP_IP, () => {
console.log(`Listening on ${TCP_IP}:${TCP_PORT}...`);
});
server.on('error', (err) => {
console.error('Server error:', err);
cleanup();
process.exit(1);
});
} catch (error) {
console.error('Failed to initialize:', error);
process.exit(1);
}
}
// Start the proxy
if (require.main === module) {
main();
}
module.exports = {
main,
cleanup
};

View File

@@ -1,14 +1,15 @@
{
"name": "zefie_wtvp_minisrv",
"version": "0.9.64-dev",
"version": "0.9.66-dev",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "zefie_wtvp_minisrv",
"version": "0.9.64-dev",
"version": "0.9.66-dev",
"license": "GPL3",
"dependencies": {
"@serialport/parser-readline": "^13.0.0",
"adm-zip": "^0.5.12",
"cross-env": "^7.0.3",
"crypto-js": "^4.2.0",
@@ -28,6 +29,7 @@
"proxy-agent": "^6.4.0",
"rc4-crypto": "^1.5.0",
"sanitize-html": "^2.13.0",
"serialport": "^13.0.0",
"sharp": "^0.34.3",
"starttls": "^1.0.1",
"strftime": "^0.10.2",
@@ -608,6 +610,245 @@
"node": ">= 8"
}
},
"node_modules/@serialport/binding-mock": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
"integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==",
"license": "MIT",
"dependencies": {
"@serialport/bindings-interface": "^1.2.1",
"debug": "^4.3.3"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@serialport/bindings-cpp": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-13.0.0.tgz",
"integrity": "sha512-r25o4Bk/vaO1LyUfY/ulR6hCg/aWiN6Wo2ljVlb4Pj5bqWGcSRC4Vse4a9AcapuAu/FeBzHCbKMvRQeCuKjzIQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@serialport/bindings-interface": "1.2.2",
"@serialport/parser-readline": "12.0.0",
"debug": "4.4.0",
"node-addon-api": "8.3.0",
"node-gyp-build": "4.8.4"
},
"engines": {
"node": ">=18.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz",
"integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz",
"integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==",
"license": "MIT",
"dependencies": {
"@serialport/parser-delimiter": "12.0.0"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/bindings-cpp/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@serialport/bindings-interface": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz",
"integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==",
"license": "MIT",
"engines": {
"node": "^12.22 || ^14.13 || >=16"
}
},
"node_modules/@serialport/parser-byte-length": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-13.0.0.tgz",
"integrity": "sha512-32yvqeTAqJzAEtX5zCrN1Mej56GJ5h/cVFsCDPbF9S1ZSC9FWjOqNAgtByseHfFTSTs/4ZBQZZcZBpolt8sUng==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-cctalk": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-13.0.0.tgz",
"integrity": "sha512-RErAe57g9gvnlieVYGIn1xymb1bzNXb2QtUQd14FpmbQQYlcrmuRnJwKa1BgTCujoCkhtaTtgHlbBWOxm8U2uA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-delimiter": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-13.0.0.tgz",
"integrity": "sha512-Qqyb0FX1avs3XabQqNaZSivyVbl/yl0jywImp7ePvfZKLwx7jBZjvL+Hawt9wIG6tfq6zbFM24vzCCK7REMUig==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-inter-byte-timeout": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-13.0.0.tgz",
"integrity": "sha512-a0w0WecTW7bD2YHWrpTz1uyiWA2fDNym0kjmPeNSwZ2XCP+JbirZt31l43m2ey6qXItTYVuQBthm75sPVeHnGA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-packet-length": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-13.0.0.tgz",
"integrity": "sha512-60ZDDIqYRi0Xs2SPZUo4Jr5LLIjtb+rvzPKMJCohrO6tAqSDponcNpcB1O4W21mKTxYjqInSz+eMrtk0LLfZIg==",
"license": "MIT",
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/@serialport/parser-readline": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-13.0.0.tgz",
"integrity": "sha512-dov3zYoyf0dt1Sudd1q42VVYQ4WlliF0MYvAMA3MOyiU1IeG4hl0J6buBA2w4gl3DOCC05tGgLDN/3yIL81gsA==",
"license": "MIT",
"dependencies": {
"@serialport/parser-delimiter": "13.0.0"
},
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-ready": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-13.0.0.tgz",
"integrity": "sha512-JNUQA+y2Rfs4bU+cGYNqOPnNMAcayhhW+XJZihSLQXOHcZsFnOa2F9YtMg9VXRWIcnHldHYtisp62Etjlw24bw==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-regex": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-13.0.0.tgz",
"integrity": "sha512-m7HpIf56G5XcuDdA3DB34Z0pJiwxNRakThEHjSa4mG05OnWYv0IG8l2oUyYfuGMowQWaVnQ+8r+brlPxGVH+eA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-slip-encoder": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-13.0.0.tgz",
"integrity": "sha512-fUHZEExm6izJ7rg0A1yjXwu4sOzeBkPAjDZPfb+XQoqgtKAk+s+HfICiYn7N2QU9gyaeCO8VKgWwi+b/DowYOg==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-spacepacket": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-13.0.0.tgz",
"integrity": "sha512-DoXJ3mFYmyD8X/8931agJvrBPxqTaYDsPoly9/cwQSeh/q4EjQND9ySXBxpWz5WcpyCU4jOuusqCSAPsbB30Eg==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/stream": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-13.0.0.tgz",
"integrity": "sha512-F7xLJKsjGo2WuEWMSEO1SimRcOA+WtWICsY13r0ahx8s2SecPQH06338g28OT7cW7uRXI7oEQAk62qh5gHJW3g==",
"license": "MIT",
"dependencies": {
"@serialport/bindings-interface": "1.2.2",
"debug": "4.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/stream/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
@@ -2364,6 +2605,26 @@
"split2": "^4.1.0"
}
},
"node_modules/node-addon-api": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz",
"integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/nunjucks": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz",
@@ -2945,6 +3206,51 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/serialport": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/serialport/-/serialport-13.0.0.tgz",
"integrity": "sha512-PHpnTd8isMGPfFTZNCzOZp9m4mAJSNWle9Jxu6BPTcWq7YXl5qN7tp8Sgn0h+WIGcD6JFz5QDgixC2s4VW7vzg==",
"license": "MIT",
"dependencies": {
"@serialport/binding-mock": "10.2.2",
"@serialport/bindings-cpp": "13.0.0",
"@serialport/parser-byte-length": "13.0.0",
"@serialport/parser-cctalk": "13.0.0",
"@serialport/parser-delimiter": "13.0.0",
"@serialport/parser-inter-byte-timeout": "13.0.0",
"@serialport/parser-packet-length": "13.0.0",
"@serialport/parser-readline": "13.0.0",
"@serialport/parser-ready": "13.0.0",
"@serialport/parser-regex": "13.0.0",
"@serialport/parser-slip-encoder": "13.0.0",
"@serialport/parser-spacepacket": "13.0.0",
"@serialport/stream": "13.0.0",
"debug": "4.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/serialport/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",

View File

@@ -29,6 +29,7 @@
"url": "https://github.com/zefie/zefie_wtvp_minisrv.git"
},
"dependencies": {
"@serialport/parser-readline": "^13.0.0",
"adm-zip": "^0.5.12",
"cross-env": "^7.0.3",
"crypto-js": "^4.2.0",
@@ -48,6 +49,7 @@
"proxy-agent": "^6.4.0",
"rc4-crypto": "^1.5.0",
"sanitize-html": "^2.13.0",
"serialport": "^13.0.0",
"sharp": "^0.34.3",
"starttls": "^1.0.1",
"strftime": "^0.10.2",