Files
OpenTerminal/tests/.zmodem-e2e.cjs
T
954801926@qq.com 0663e5affa feat(M6): broadcast input + main-process ZMODEM + quick input panel + shortcuts
- broadcast: session target picker popover, fan-out write path, <2 targets auto-disable, per-tab target dots
- zmodem: main-process engine over ssh sessions (rz send / sz receive), progress reuses transfer panel, offer 120s / stall 90s watchdogs, e2e test with real zmodem.js peer (both directions)
- quick input panel: bottom-right overlay (command line + auto-\r + library + recent), broadcast-aware send
- shortcuts: Ctrl+=/-/0 font size (persisted), globalShowHide accelerator via globalShortcut, Ctrl+PgUp/PgDn terminal cycling
- contracts: ZMODEM_OFFER/RESPOND/DONE channels, SystemSettings.globalShowHide, TransferKind zmodem-upload/download
- review fixes: xterm-helper-textarea font hotkey pass-through, zmodem error-vs-cancelled progress state, offer timer clear, receive stream destroy
2026-09-06 20:15:49 +08:00

3372 lines
113 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// node_modules/zmodem.js/src/zmlib.js
var require_zmlib = __commonJS({
"node_modules/zmodem.js/src/zmlib.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
var ZDLE = 24;
var XON = 17;
var XOFF = 19;
var XON_HIGH = 128 | XON;
var XOFF_HIGH = 128 | XOFF;
var CAN = 24;
Zmodem2.ZMLIB = {
/**
* @property {number} The ZDLE constant, which ZMODEM uses for escaping
*/
ZDLE,
/**
* @property {number} XON - ASCII XON
*/
XON,
/**
* @property {number} XOFF - ASCII XOFF
*/
XOFF,
/**
* @property {number[]} ABORT_SEQUENCE - ZMODEM’s abort sequence
*/
ABORT_SEQUENCE: [CAN, CAN, CAN, CAN, CAN],
/**
* Remove octet values from the given array that ZMODEM always ignores.
* This will mutate the given array.
*
* @param {number[]} octets - The octet values to transform.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {number[]} The passed-in array. This is the same object that is
* passed in.
*/
strip_ignored_bytes: function strip_ignored_bytes(octets) {
for (var o = octets.length - 1; o >= 0; o--) {
switch (octets[o]) {
case XON:
case XON_HIGH:
case XOFF:
case XOFF_HIGH:
octets.splice(o, 1);
continue;
}
}
return octets;
},
/**
* Like Array.prototype.indexOf, but searches for a subarray
* rather than just a particular value.
*
* @param {Array} haystack - The array to search, i.e., the bigger.
*
* @param {Array} needle - The array whose values to find,
* i.e., the smaller.
*
* @returns {number} The position in “haystack” where “needle”
* first appears—or, -1 if “needle” doesn’t appear anywhere
* in “haystack”.
*/
find_subarray: function find_subarray(haystack, needle) {
var h = 0, n;
var start = Date.now();
HAYSTACK:
while (h !== -1) {
h = haystack.indexOf(needle[0], h);
if (h === -1) break HAYSTACK;
for (n = 1; n < needle.length; n++) {
if (haystack[h + n] !== needle[n]) {
h++;
continue HAYSTACK;
}
}
return h;
}
return -1;
}
};
}
});
// node_modules/zmodem.js/src/encode.js
var require_encode = __commonJS({
"node_modules/zmodem.js/src/encode.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
var HEX_DIGITS = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102];
var HEX_OCTET_VALUE = {};
for (hd = 0; hd < HEX_DIGITS.length; hd++) {
HEX_OCTET_VALUE[HEX_DIGITS[hd]] = hd;
}
var hd;
Zmodem2.ENCODELIB = {
/**
* Return an array with the given number as 2 big-endian bytes.
*
* @param {number} number - The number to encode.
*
* @returns {number[]} The octet values.
*/
pack_u16_be: function pack_u16_be(number) {
if (number > 65535) throw "Number cannot exceed 16 bits: " + number;
return [number >> 8, number & 255];
},
/**
* Return an array with the given number as 4 little-endian bytes.
*
* @param {number} number - The number to encode.
*
* @returns {number[]} The octet values.
*/
pack_u32_le: function pack_u32_le(number) {
var high_bytes = number / 65536;
return [
number & 255,
(number & 65535) >> 8,
high_bytes & 255,
high_bytes >> 8
];
},
/**
* The inverse of pack_u16_be() - i.e., take in 2 octet values
* and parse them as an unsigned, 2-byte big-endian number.
*
* @param {number[]} octets - The octet values (2 of them).
*
* @returns {number} The decoded number.
*/
unpack_u16_be: function unpack_u16_be(bytes_arr) {
return (bytes_arr[0] << 8) + bytes_arr[1];
},
/**
* The inverse of pack_u32_le() - i.e., take in a 4-byte sequence
* and parse it as an unsigned, 4-byte little-endian number.
*
* @param {number[]} octets - The octet values (4 of them).
*
* @returns {number} The decoded number.
*/
unpack_u32_le: function unpack_u32_le(octets) {
return octets[0] + (octets[1] << 8) + (octets[2] << 16) + octets[3] * 16777216;
},
/**
* Encode a series of octet values to be the octet values that
* correspond to the ASCII hex characters for each octet. The
* returned array is suitable for use as binary data.
*
* For example:
*
* Original Hex Returned
* 254 fe 102, 101
* 12 0c 48, 99
* 129 81 56, 49
*
* @param {number[]} octets - The original octet values.
*
* @returns {number[]} The octet values that correspond to an ASCII
* representation of the given octets.
*/
octets_to_hex: function octets_to_hex(octets) {
var hex = [];
for (var o = 0; o < octets.length; o++) {
hex.push(
HEX_DIGITS[octets[o] >> 4],
HEX_DIGITS[octets[o] & 15]
);
}
return hex;
},
/**
* The inverse of octets_to_hex(): takes an array
* of hex octet pairs and returns their octet values.
*
* @param {number[]} hex_octets - The hex octet values.
*
* @returns {number[]} The parsed octet values.
*/
parse_hex_octets: function parse_hex_octets(hex_octets) {
var octets = new Array(hex_octets.length / 2);
for (var i = 0; i < octets.length; i++) {
octets[i] = (HEX_OCTET_VALUE[hex_octets[2 * i]] << 4) + HEX_OCTET_VALUE[hex_octets[1 + 2 * i]];
}
return octets;
}
};
}
});
// node_modules/zmodem.js/src/text.js
var require_text = __commonJS({
"node_modules/zmodem.js/src/text.js"(exports2, module2) {
var _my_TextEncoder = class {
encode(text) {
text = unescape(encodeURIComponent(text));
var bytes = new Array(text.length);
for (var b = 0; b < text.length; b++) {
bytes[b] = text.charCodeAt(b);
}
return new Uint8Array(bytes);
}
};
var _my_TextDecoder = class {
decode(bytes) {
return decodeURIComponent(escape(String.fromCharCode.apply(String, bytes)));
}
};
var Zmodem2 = module2.exports;
Zmodem2.Text = {
Encoder: typeof TextEncoder !== "undefined" ? TextEncoder : _my_TextEncoder,
Decoder: typeof TextDecoder !== "undefined" ? TextDecoder : _my_TextDecoder
};
}
});
// node_modules/zmodem.js/src/zdle.js
var require_zdle = __commonJS({
"node_modules/zmodem.js/src/zdle.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
Object.assign(
Zmodem2,
require_zmlib()
);
var encode_cur;
var encode_todo;
var ZDLE = Zmodem2.ZMLIB.ZDLE;
Zmodem2.ZDLE = class ZmodemZDLE {
/**
* Create a ZDLE encoder.
*
* @param {object} [config] - The initial configuration.
* @param {object} config.escape_ctrl_chars - Whether the ZDLE encoder
* should escape control characters.
*/
constructor(config) {
this._config = {};
if (config) {
this.set_escape_ctrl_chars(!!config.escape_ctrl_chars);
}
}
/**
* Enable or disable control-character escaping.
* You should probably enable this for sender sessions.
*
* @param {boolean} value - Whether to enable (true) or disable (false).
*/
set_escape_ctrl_chars(value) {
if (typeof value !== "boolean") throw "need boolean!";
if (value !== this._config.escape_ctrl_chars) {
this._config.escape_ctrl_chars = value;
this._setup_zdle_table();
}
}
/**
* Whether or not control-character escaping is enabled.
*
* @return {boolean} Whether the escaping is on (true) or off (false).
*/
escapes_ctrl_chars() {
return !!this._config.escape_ctrl_chars;
}
//I don’t know of any Zmodem implementations that use ZESC8
//(“escape_8th_bit”)??
/*
ZMODEM software escapes ZDLE, 020, 0220, 021, 0221, 023, and 0223. If
preceded by 0100 or 0300 (@), 015 and 0215 are also escaped to protect the
Telenet command escape CR-@-CR.
*/
/**
* Encode an array of octet values and return it.
* This will mutate the given array.
*
* @param {number[]} octets - The octet values to transform.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {number[]} The passed-in array, transformed. This is the
* same object that is passed in.
*/
encode(octets) {
if (!this._zdle_table) throw "No ZDLE encode table configured!";
var zdle_table = this._zdle_table;
var last_code = this._lastcode;
var arrbuf = new ArrayBuffer(2 * octets.length);
var arrbuf_uint8 = new Uint8Array(arrbuf);
var escctl_yn = this._config.escape_ctrl_chars;
var arrbuf_i = 0;
for (encode_cur = 0; encode_cur < octets.length; encode_cur++) {
encode_todo = zdle_table[octets[encode_cur]];
if (!encode_todo) {
console.trace();
console.error("bad encode() call:", JSON.stringify(octets));
this._lastcode = last_code;
throw "Invalid octet: " + octets[encode_cur];
}
last_code = octets[encode_cur];
if (encode_todo === 1) {
} else if (escctl_yn || encode_todo === 2 || (last_code & 127) === 64) {
arrbuf_uint8[arrbuf_i] = ZDLE;
arrbuf_i++;
last_code ^= 64;
}
arrbuf_uint8[arrbuf_i] = last_code;
arrbuf_i++;
}
this._lastcode = last_code;
octets.splice(0);
octets.push.apply(octets, new Uint8Array(arrbuf, 0, arrbuf_i));
return octets;
}
/**
* Decode an array of octet values and return it.
* This will mutate the given array.
*
* @param {number[]} octets - The octet values to transform.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {number[]} The passed-in array.
* This is the same object that is passed in.
*/
static decode(octets) {
for (var o = octets.length - 1; o >= 0; o--) {
if (octets[o] === ZDLE) {
octets.splice(o, 2, octets[o + 1] - 64);
}
}
return octets;
}
/**
* Remove, ZDLE-decode, and return bytes from the passed-in array.
* If the requested number of ZDLE-encoded bytes isn’t available,
* then the passed-in array is unmodified (and the return is undefined).
*
* @param {number[]} octets - The octet values to transform.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @param {number} offset - The number of (undecoded) bytes to skip
* at the beginning of the “octets” array.
*
* @param {number} count - The number of bytes (octet values) to return.
*
* @returns {number[]|undefined} An array with the requested number of
* decoded octet values, or undefined if that number of decoded
* octets isn’t available (given the passed-in offset).
*/
static splice(octets, offset, count) {
var so_far = 0;
if (!offset) offset = 0;
for (var i = offset; i < octets.length && so_far < count; i++) {
so_far++;
if (octets[i] === ZDLE) i++;
}
if (so_far === count) {
if (octets.length === i - 1) return;
octets.splice(0, offset);
return ZmodemZDLE.decode(octets.splice(0, i - offset));
}
return;
}
_setup_zdle_table() {
var zsendline_tab = new Array(256);
for (var i = 0; i < zsendline_tab.length; i++) {
if (i & 96) {
zsendline_tab[i] = 1;
} else {
switch (i) {
case ZDLE:
//NB: no (ZDLE | 0x80)
case Zmodem2.ZMLIB.XOFF:
case Zmodem2.ZMLIB.XON:
case Zmodem2.ZMLIB.XOFF | 128:
case Zmodem2.ZMLIB.XON | 128:
zsendline_tab[i] = 2;
break;
case 16:
// 020
case 144:
zsendline_tab[i] = this._config.turbo_escape ? 1 : 2;
break;
case 13:
// 015
case 141:
zsendline_tab[i] = this._config.escape_ctrl_chars ? 2 : !this._config.turbo_escape ? 3 : 1;
break;
default:
zsendline_tab[i] = this._config.escape_ctrl_chars ? 2 : 1;
}
}
}
this._zdle_table = zsendline_tab;
}
};
}
});
// node_modules/crc-32/crc32.js
var require_crc32 = __commonJS({
"node_modules/crc-32/crc32.js"(exports2) {
var CRC32;
(function(factory) {
if (typeof DO_NOT_EXPORT_CRC === "undefined") {
if ("object" === typeof exports2) {
factory(exports2);
} else if ("function" === typeof define && define.amd) {
define(function() {
var module3 = {};
factory(module3);
return module3;
});
} else {
factory(CRC32 = {});
}
} else {
factory(CRC32 = {});
}
})(function(CRC322) {
CRC322.version = "1.2.2";
function signed_crc_table() {
var c = 0, table = new Array(256);
for (var n = 0; n != 256; ++n) {
c = n;
c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1;
c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1;
c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1;
c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1;
c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1;
c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1;
c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1;
c = c & 1 ? -306674912 ^ c >>> 1 : c >>> 1;
table[n] = c;
}
return typeof Int32Array !== "undefined" ? new Int32Array(table) : table;
}
var T0 = signed_crc_table();
function slice_by_16_tables(T) {
var c = 0, v = 0, n = 0, table = typeof Int32Array !== "undefined" ? new Int32Array(4096) : new Array(4096);
for (n = 0; n != 256; ++n) table[n] = T[n];
for (n = 0; n != 256; ++n) {
v = T[n];
for (c = 256 + n; c < 4096; c += 256) v = table[c] = v >>> 8 ^ T[v & 255];
}
var out = [];
for (n = 1; n != 16; ++n) out[n - 1] = typeof Int32Array !== "undefined" ? table.subarray(n * 256, n * 256 + 256) : table.slice(n * 256, n * 256 + 256);
return out;
}
var TT = slice_by_16_tables(T0);
var T1 = TT[0], T2 = TT[1], T3 = TT[2], T4 = TT[3], T5 = TT[4];
var T6 = TT[5], T7 = TT[6], T8 = TT[7], T9 = TT[8], Ta = TT[9];
var Tb = TT[10], Tc = TT[11], Td = TT[12], Te = TT[13], Tf = TT[14];
function crc32_bstr(bstr, seed) {
var C = seed ^ -1;
for (var i = 0, L = bstr.length; i < L; ) C = C >>> 8 ^ T0[(C ^ bstr.charCodeAt(i++)) & 255];
return ~C;
}
function crc32_buf(B, seed) {
var C = seed ^ -1, L = B.length - 15, i = 0;
for (; i < L; ) C = Tf[B[i++] ^ C & 255] ^ Te[B[i++] ^ C >> 8 & 255] ^ Td[B[i++] ^ C >> 16 & 255] ^ Tc[B[i++] ^ C >>> 24] ^ Tb[B[i++]] ^ Ta[B[i++]] ^ T9[B[i++]] ^ T8[B[i++]] ^ T7[B[i++]] ^ T6[B[i++]] ^ T5[B[i++]] ^ T4[B[i++]] ^ T3[B[i++]] ^ T2[B[i++]] ^ T1[B[i++]] ^ T0[B[i++]];
L += 15;
while (i < L) C = C >>> 8 ^ T0[(C ^ B[i++]) & 255];
return ~C;
}
function crc32_str(str, seed) {
var C = seed ^ -1;
for (var i = 0, L = str.length, c = 0, d = 0; i < L; ) {
c = str.charCodeAt(i++);
if (c < 128) {
C = C >>> 8 ^ T0[(C ^ c) & 255];
} else if (c < 2048) {
C = C >>> 8 ^ T0[(C ^ (192 | c >> 6 & 31)) & 255];
C = C >>> 8 ^ T0[(C ^ (128 | c & 63)) & 255];
} else if (c >= 55296 && c < 57344) {
c = (c & 1023) + 64;
d = str.charCodeAt(i++) & 1023;
C = C >>> 8 ^ T0[(C ^ (240 | c >> 8 & 7)) & 255];
C = C >>> 8 ^ T0[(C ^ (128 | c >> 2 & 63)) & 255];
C = C >>> 8 ^ T0[(C ^ (128 | d >> 6 & 15 | (c & 3) << 4)) & 255];
C = C >>> 8 ^ T0[(C ^ (128 | d & 63)) & 255];
} else {
C = C >>> 8 ^ T0[(C ^ (224 | c >> 12 & 15)) & 255];
C = C >>> 8 ^ T0[(C ^ (128 | c >> 6 & 63)) & 255];
C = C >>> 8 ^ T0[(C ^ (128 | c & 63)) & 255];
}
}
return ~C;
}
CRC322.table = T0;
CRC322.bstr = crc32_bstr;
CRC322.buf = crc32_buf;
CRC322.str = crc32_str;
});
}
});
// node_modules/zmodem.js/src/zerror.js
var require_zerror = __commonJS({
"node_modules/zmodem.js/src/zerror.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
function _crc_message(got, expected) {
this.got = got.slice(0);
this.expected = expected.slice(0);
return "CRC check failed! (got: " + got.join() + "; expected: " + expected.join() + ")";
}
function _pass(val) {
return val;
}
var TYPE_MESSAGE = {
aborted: "Session aborted",
peer_aborted: "Peer aborted session",
already_aborted: "Session already aborted",
crc: _crc_message,
validation: _pass
};
function _generate_message(type) {
const msg = TYPE_MESSAGE[type];
switch (typeof msg) {
case "string":
return msg;
case "function":
var args_after_type = [].slice.call(arguments).slice(1);
return msg.apply(this, args_after_type);
}
return null;
}
Zmodem2.Error = class ZmodemError extends Error {
constructor(msg_or_type) {
super();
var generated = _generate_message.apply(this, arguments);
if (generated) {
this.type = msg_or_type;
this.message = generated;
} else {
this.message = msg_or_type;
}
}
};
}
});
// node_modules/zmodem.js/src/zcrc.js
var require_zcrc = __commonJS({
"node_modules/zmodem.js/src/zcrc.js"(exports2, module2) {
"use strict";
var CRC32_MOD = require_crc32();
var Zmodem2 = module2.exports;
Object.assign(
Zmodem2,
require_zerror(),
require_encode()
);
var _crctab;
var crc_width = 16;
var crc_polynomial = 4129;
var crc_castmask = 65535;
var crc_msbmask = 1 << crc_width - 1;
function _compute_crctab() {
_crctab = new Array(256);
var divident_shift = crc_width - 8;
for (var divident = 0; divident < 256; divident++) {
var currByte = divident << divident_shift & crc_castmask;
for (var bit = 0; bit < 8; bit++) {
if ((currByte & crc_msbmask) !== 0) {
currByte <<= 1;
currByte ^= crc_polynomial;
} else {
currByte <<= 1;
}
}
_crctab[divident] = currByte & crc_castmask;
}
}
function _updcrc(cp, crc) {
if (!_crctab) _compute_crctab();
return _crctab[crc >> 8 & 255] ^ (255 & crc) << 8 ^ cp;
}
function __verify(expect, got) {
var err;
if (expect.join() !== got.join()) {
throw new Zmodem2.Error("crc", got, expect);
}
}
Zmodem2.CRC = {
//https://www.lammertbies.nl/comm/info/crc-calculation.html
//CRC-CCITT (XModem)
/**
* Deduce a given set of octet values’ CRC16, as per the CRC16
* variant that ZMODEM uses (CRC-CCITT/XModem).
*
* @param {Array} octets - The array of octet values.
* Each array member should be an 8-bit unsigned integer (0-255).
*
* @returns {Array} crc - The CRC, expressed as an array of octet values.
*/
crc16: function crc16(octet_nums) {
var crc = octet_nums[0];
for (var b = 1; b < octet_nums.length; b++) {
crc = _updcrc(octet_nums[b], crc);
}
crc = _updcrc(0, _updcrc(0, crc));
return Zmodem2.ENCODELIB.pack_u16_be(crc);
},
/**
* Deduce a given set of octet values’ CRC32.
*
* @param {Array} octets - The array of octet values.
* Each array member should be an 8-bit unsigned integer (0-255).
*
* @returns {Array} crc - The CRC, expressed as an array of octet values.
*/
crc32: function crc32(octet_nums) {
return Zmodem2.ENCODELIB.pack_u32_le(
CRC32_MOD.buf(octet_nums) >>> 0
//bit-shift to get unsigned
);
},
/**
* Verify a given set of octet values’ CRC16.
* An exception is thrown on failure.
*
* @param {Array} bytes_arr - The array of octet values.
* Each array member should be an 8-bit unsigned integer (0-255).
*
* @param {Array} crc - The CRC to check against, expressed as
* an array of octet values.
*/
verify16: function verify16(bytes_arr, got) {
return __verify(this.crc16(bytes_arr), got);
},
/**
* Verify a given set of octet values’ CRC32.
* An exception is thrown on failure.
*
* @param {Array} bytes_arr - The array of octet values.
* Each array member should be an 8-bit unsigned integer (0-255).
*
* @param {Array} crc - The CRC to check against, expressed as
* an array of octet values.
*/
verify32: function verify32(bytes_arr, crc) {
try {
__verify(this.crc32(bytes_arr), crc);
} catch (err) {
err.input = bytes_arr.slice(0);
throw err;
}
}
};
}
});
// node_modules/zmodem.js/src/zheader.js
var require_zheader = __commonJS({
"node_modules/zmodem.js/src/zheader.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
Object.assign(
Zmodem2,
require_encode(),
require_zdle(),
require_zmlib(),
require_zcrc(),
require_zerror()
);
var ZPAD = "*".charCodeAt(0);
var ZBIN = "A".charCodeAt(0);
var ZHEX = "B".charCodeAt(0);
var ZBIN32 = "C".charCodeAt(0);
var HEX_HEADER_CRLF = [13, 10];
var HEX_HEADER_CRLF_XON = HEX_HEADER_CRLF.slice(0).concat([Zmodem2.ZMLIB.XON]);
var HEX_HEADER_PREFIX = [ZPAD, ZPAD, Zmodem2.ZMLIB.ZDLE, ZHEX];
var BINARY16_HEADER_PREFIX = [ZPAD, Zmodem2.ZMLIB.ZDLE, ZBIN];
var BINARY32_HEADER_PREFIX = [ZPAD, Zmodem2.ZMLIB.ZDLE, ZBIN32];
Zmodem2.Header = class ZmodemHeader {
//lrzsz’s “sz” command sends a random (?) CR/0x0d byte
//after ZEOF. Let’s accommodate 0x0a, 0x0d, 0x8a, and 0x8d.
//
//Also, when you skip a file, sz outputs a message about it.
//
//It appears that we’re supposed to ignore anything until
//[ ZPAD, ZDLE ] when we’re looking for a header.
/**
* Weed out the leading bytes that aren’t valid to start a ZMODEM header.
*
* @param {number[]} ibuffer - The octet values to parse.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {number[]} The octet values that were removed from the start
* of “ibuffer”. Order is preserved.
*/
static trim_leading_garbage(ibuffer) {
var garbage = [];
var discard_all, parser, next_ZPAD_at_least = 0;
TRIM_LOOP:
while (ibuffer.length && !parser) {
var first_ZPAD = ibuffer.indexOf(ZPAD);
if (first_ZPAD === -1) {
discard_all = true;
break TRIM_LOOP;
} else {
garbage.push.apply(garbage, ibuffer.splice(0, first_ZPAD));
if (ibuffer.length < 2) {
break TRIM_LOOP;
} else if (ibuffer[1] === ZPAD) {
if (ibuffer.length < HEX_HEADER_PREFIX.length) {
if (ibuffer.join() === HEX_HEADER_PREFIX.slice(0, ibuffer.length).join()) {
break TRIM_LOOP;
}
} else if (ibuffer[2] === HEX_HEADER_PREFIX[2] && ibuffer[3] === HEX_HEADER_PREFIX[3]) {
parser = _parse_hex;
}
} else if (ibuffer[1] === Zmodem2.ZMLIB.ZDLE) {
if (ibuffer.length < BINARY16_HEADER_PREFIX.length) {
break TRIM_LOOP;
}
if (ibuffer[2] === BINARY16_HEADER_PREFIX[2]) {
parser = _parse_binary16;
} else if (ibuffer[2] === BINARY32_HEADER_PREFIX[2]) {
parser = _parse_binary32;
}
}
if (!parser) {
garbage.push(ibuffer.shift());
}
}
}
if (discard_all) {
garbage.push.apply(garbage, ibuffer.splice(0));
}
return garbage;
}
/**
* Parse out a Header object from a given array of octet values.
*
* An exception is thrown if the given bytes are definitively invalid
* as header values.
*
* @param {number[]} octets - The octet values to parse.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {Header|undefined} An instance of the appropriate Header
* subclass, or undefined if not enough octet values are given
* to determine whether there is a valid header here or not.
*/
static parse(octets) {
var hdr;
if (octets[1] === ZPAD) {
hdr = _parse_hex(octets);
return hdr && [hdr, 16];
} else if (octets[2] === ZBIN) {
hdr = _parse_binary16(octets, 3);
return hdr && [hdr, 16];
} else if (octets[2] === ZBIN32) {
hdr = _parse_binary32(octets);
return hdr && [hdr, 32];
}
if (octets.length < 3) return;
throw "Unrecognized/unsupported octets: " + octets.join();
}
/**
* Build a Header subclass given a name and arguments.
*
* @param {string} name - The header type name, e.g., “ZRINIT”.
*
* @param {...*} args - The arguments to pass to the appropriate
* subclass constructor. These aren’t documented currently
* but are pretty easy to glean from the code.
*
* @returns {Header} An instance of the appropriate Header subclass.
*/
static build(name2) {
var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments);
var Ctr = FRAME_NAME_CREATOR[name2];
if (!Ctr) throw "No frame class \u201C" + name2 + "\u201D is defined!";
args.shift();
var hdr = new (Ctr.bind.apply(Ctr, [null].concat(args)))();
return hdr;
}
/**
* Return the octet values array that represents the object
* in ZMODEM hex encoding.
*
* @returns {number[]} An array of octet values suitable for sending
* as binary data.
*/
to_hex() {
var to_crc = this._crc_bytes();
return HEX_HEADER_PREFIX.concat(
Zmodem2.ENCODELIB.octets_to_hex(to_crc.concat(Zmodem2.CRC.crc16(to_crc))),
this._hex_header_ending
);
}
/**
* Return the octet values array that represents the object
* in ZMODEM binary encoding with a 16-bit CRC.
*
* @param {ZDLE} zencoder - A ZDLE instance to use for
* ZDLE encoding.
*
* @returns {number[]} An array of octet values suitable for sending
* as binary data.
*/
to_binary16(zencoder) {
return this._to_binary(zencoder, BINARY16_HEADER_PREFIX, Zmodem2.CRC.crc16);
}
/**
* Return the octet values array that represents the object
* in ZMODEM binary encoding with a 32-bit CRC.
*
* @param {ZDLE} zencoder - A ZDLE instance to use for
* ZDLE encoding.
*
* @returns {number[]} An array of octet values suitable for sending
* as binary data.
*/
to_binary32(zencoder) {
return this._to_binary(zencoder, BINARY32_HEADER_PREFIX, Zmodem2.CRC.crc32);
}
//This is never called directly, but only as super().
constructor() {
if (!this._bytes4) {
this._bytes4 = [0, 0, 0, 0];
}
}
_to_binary(zencoder, prefix, crc_func) {
var to_crc = this._crc_bytes();
var octets = prefix.concat(
zencoder.encode(to_crc.concat(crc_func(to_crc)))
);
return octets;
}
_crc_bytes() {
return [this.TYPENUM].concat(this._bytes4);
}
};
Zmodem2.Header.prototype._hex_header_ending = HEX_HEADER_CRLF_XON;
var ZRQINIT_HEADER = class extends Zmodem2.Header {
};
var ZRINIT_FLAG = {
//----------------------------------------------------------------------
// Bit Masks for ZRINIT flags byte ZF0
//----------------------------------------------------------------------
CANFDX: 1,
// Rx can send and receive true FDX
CANOVIO: 2,
// Rx can receive data during disk I/O
CANBRK: 4,
// Rx can send a break signal
CANCRY: 8,
// Receiver can decrypt -- nothing does this
CANLZW: 16,
// Receiver can uncompress -- nothing does this
CANFC32: 32,
// Receiver can use 32 bit Frame Check
ESCCTL: 64,
// Receiver expects ctl chars to be escaped
ESC8: 128
// Receiver expects 8th bit to be escaped
};
function _get_ZRINIT_flag_num(fl) {
if (!ZRINIT_FLAG[fl]) {
throw new Zmodem2.Error("Invalid ZRINIT flag: " + fl);
}
return ZRINIT_FLAG[fl];
}
var ZRINIT_HEADER = class extends Zmodem2.Header {
constructor(flags_arr, bufsize) {
super();
var flags_num = 0;
if (!bufsize) bufsize = 0;
flags_arr.forEach(function(fl) {
flags_num |= _get_ZRINIT_flag_num(fl);
});
this._bytes4 = [
bufsize & 255,
bufsize >> 8,
0,
flags_num
];
}
//undefined if nonstop I/O is allowed
get_buffer_size() {
return Zmodem2.ENCODELIB.unpack_u16_be(this._bytes4.slice(0, 2)) || void 0;
}
//Unimplemented:
// can_decrypt
// can_decompress
//----------------------------------------------------------------------
//function names taken from Jacques Mattheij’s implementation,
//as used in syncterm.
can_full_duplex() {
return !!(this._bytes4[3] & ZRINIT_FLAG.CANFDX);
}
can_overlap_io() {
return !!(this._bytes4[3] & ZRINIT_FLAG.CANOVIO);
}
can_break() {
return !!(this._bytes4[3] & ZRINIT_FLAG.CANBRK);
}
can_fcs_32() {
return !!(this._bytes4[3] & ZRINIT_FLAG.CANFC32);
}
escape_ctrl_chars() {
return !!(this._bytes4[3] & ZRINIT_FLAG.ESCCTL);
}
//Is this used? I don’t see it used in lrzsz or syncterm
//Looks like it was a “foreseen” feature that Forsberg
//never implemented. (The need for it went away, maybe?)
escape_8th_bit() {
return !!(this._bytes4[3] & ZRINIT_FLAG.ESC8);
}
};
var ZSINIT_FLAG = {
ESCCTL: 64,
// Transmitter will escape ctl chars
ESC8: 128
// Transmitter will escape 8th bit
};
function _get_ZSINIT_flag_num(fl) {
if (!ZSINIT_FLAG[fl]) {
throw "Invalid ZSINIT flag: " + fl;
}
return ZSINIT_FLAG[fl];
}
var ZSINIT_HEADER = class extends Zmodem2.Header {
constructor(flags_arr, attn_seq_arr) {
super();
var flags_num = 0;
flags_arr.forEach(function(fl) {
flags_num |= _get_ZSINIT_flag_num(fl);
});
this._bytes4 = [0, 0, 0, flags_num];
if (attn_seq_arr) {
if (attn_seq_arr.length > 31) {
throw "Attn sequence must be <= 31 bytes";
}
if (attn_seq_arr.some(function(num) {
return num > 255;
})) {
throw "Attn sequence (" + attn_seq_arr + ") must be <256";
}
this._data = attn_seq_arr.concat([0]);
}
}
escape_ctrl_chars() {
return !!(this._bytes4[3] & ZSINIT_FLAG.ESCCTL);
}
//Is this used? I don’t see it used in lrzsz or syncterm
escape_8th_bit() {
return !!(this._bytes4[3] & ZSINIT_FLAG.ESC8);
}
};
var ZACK_HEADER = class extends Zmodem2.Header {
constructor(payload4) {
super();
if (payload4) {
this._bytes4 = payload4.slice();
}
}
};
ZACK_HEADER.prototype._hex_header_ending = HEX_HEADER_CRLF;
var ZFILE_VALUES = {
//ZF3 (i.e., first byte)
extended: {
sparse: 64
//ZXSPARS
},
//ZF2
transport: [
void 0,
"compress",
//ZTLZW
"encrypt",
//ZTCRYPT
"rle"
//ZTRLE
],
//ZF1
management: [
void 0,
"newer_or_longer",
//ZF1_ZMNEWL
"crc",
//ZF1_ZMCRC
"append",
//ZF1_ZMAPND
"clobber",
//ZF1_ZMCLOB
"newer",
//ZF1_ZMNEW
"mtime_or_length",
//ZF1_ZMNEW
"protect",
//ZF1_ZMPROT
"rename"
//ZF1_ZMPROT
],
//ZF0 (i.e., last byte)
conversion: [
void 0,
"binary",
//ZCBIN
"text",
//ZCNL
"resume"
//ZCRESUM
]
};
var ZFILE_ORDER = ["extended", "transport", "management", "conversion"];
var ZMSKNOLOC = 128;
var MANAGEMENT_MASK = 31;
var ZXSPARS = 64;
var ZFILE_HEADER = class extends Zmodem2.Header {
//TODO: allow options on instantiation
get_options() {
var opts = {
sparse: !!(this._bytes4[0] & ZXSPARS)
};
var bytes_copy = this._bytes4.slice(0);
ZFILE_ORDER.forEach(function(key, i) {
if (ZFILE_VALUES[key] instanceof Array) {
if (key === "management") {
opts.skip_if_absent = !!(bytes_copy[i] & ZMSKNOLOC);
bytes_copy[i] &= MANAGEMENT_MASK;
}
opts[key] = ZFILE_VALUES[key][bytes_copy[i]];
} else {
for (var extkey in ZFILE_VALUES[key]) {
opts[extkey] = !!(bytes_copy[i] & ZFILE_VALUES[key][extkey]);
if (opts[extkey]) {
bytes_copy[i] ^= ZFILE_VALUES[key][extkey];
}
}
}
if (!opts[key] && bytes_copy[i]) {
opts[key] = "unknown:" + bytes_copy[i];
}
});
return opts;
}
};
var ZSKIP_HEADER = class extends Zmodem2.Header {
};
var ZABORT_HEADER = class extends Zmodem2.Header {
};
var ZFIN_HEADER = class extends Zmodem2.Header {
};
var ZFERR_HEADER = class extends Zmodem2.Header {
};
ZFIN_HEADER.prototype._hex_header_ending = HEX_HEADER_CRLF;
var ZOffsetHeader = class extends Zmodem2.Header {
constructor(offset) {
super();
this._bytes4 = Zmodem2.ENCODELIB.pack_u32_le(offset);
}
get_offset() {
return Zmodem2.ENCODELIB.unpack_u32_le(this._bytes4);
}
};
var ZRPOS_HEADER = class extends ZOffsetHeader {
};
var ZDATA_HEADER = class extends ZOffsetHeader {
};
var ZEOF_HEADER = class extends ZOffsetHeader {
};
var FRAME_CLASS_TYPES = [
[ZRQINIT_HEADER, "ZRQINIT"],
[ZRINIT_HEADER, "ZRINIT"],
[ZSINIT_HEADER, "ZSINIT"],
[ZACK_HEADER, "ZACK"],
[ZFILE_HEADER, "ZFILE"],
[ZSKIP_HEADER, "ZSKIP"],
void 0,
// [ ZNAK_HEADER, "ZNAK" ],
[ZABORT_HEADER, "ZABORT"],
[ZFIN_HEADER, "ZFIN"],
[ZRPOS_HEADER, "ZRPOS"],
[ZDATA_HEADER, "ZDATA"],
[ZEOF_HEADER, "ZEOF"],
[ZFERR_HEADER, "ZFERR"],
//see note
void 0,
//[ ZCRC_HEADER, "ZCRC" ],
void 0,
//[ ZCHALLENGE_HEADER, "ZCHALLENGE" ],
void 0,
//[ ZCOMPL_HEADER, "ZCOMPL" ],
void 0,
//[ ZCAN_HEADER, "ZCAN" ],
void 0,
//[ ZFREECNT_HEADER, "ZFREECNT" ],
void 0,
//[ ZCOMMAND_HEADER, "ZCOMMAND" ],
void 0
//[ ZSTDERR_HEADER, "ZSTDERR" ],
];
var FRAME_NAME_CREATOR = {};
for (fc = 0; fc < FRAME_CLASS_TYPES.length; fc++) {
if (!FRAME_CLASS_TYPES[fc]) continue;
FRAME_NAME_CREATOR[FRAME_CLASS_TYPES[fc][1]] = FRAME_CLASS_TYPES[fc][0];
Object.assign(
FRAME_CLASS_TYPES[fc][0].prototype,
{
TYPENUM: fc,
NAME: FRAME_CLASS_TYPES[fc][1]
}
);
}
var fc;
var CREATORS = [
ZRQINIT_HEADER,
ZRINIT_HEADER,
ZSINIT_HEADER,
ZACK_HEADER,
ZFILE_HEADER,
ZSKIP_HEADER,
"ZNAK",
ZABORT_HEADER,
ZFIN_HEADER,
ZRPOS_HEADER,
ZDATA_HEADER,
ZEOF_HEADER,
ZFERR_HEADER,
"ZCRC",
//ZCRC_HEADER, -- leaving unimplemented?
"ZCHALLENGE",
"ZCOMPL",
"ZCAN",
"ZFREECNT",
// ZFREECNT_HEADER,
"ZCOMMAND",
"ZSTDERR"
];
function _get_blank_header(typenum) {
var creator = CREATORS[typenum];
if (typeof creator === "string") {
throw "Received unsupported header: " + creator;
}
return _get_blank_header_from_constructor(creator);
}
function _get_blank_header_from_constructor(creator) {
if (creator.prototype instanceof ZOffsetHeader) {
return new creator(0);
}
return new creator([]);
}
function _parse_binary16(bytes_arr) {
var zdle_decoded = Zmodem2.ZDLE.splice(bytes_arr, BINARY16_HEADER_PREFIX.length, 7);
return zdle_decoded && _parse_non_zdle_binary16(zdle_decoded);
}
function _parse_non_zdle_binary16(decoded) {
Zmodem2.CRC.verify16(
decoded.slice(0, 5),
decoded.slice(5)
);
var typenum = decoded[0];
var hdr = _get_blank_header(typenum);
hdr._bytes4 = decoded.slice(1, 5);
return hdr;
}
function _parse_binary32(bytes_arr) {
var zdle_decoded = Zmodem2.ZDLE.splice(
bytes_arr,
//omit the leading "*", ZDLE, and "C"
BINARY32_HEADER_PREFIX.length,
9
);
if (!zdle_decoded) return;
Zmodem2.CRC.verify32(
zdle_decoded.slice(0, 5),
zdle_decoded.slice(5)
);
var typenum = zdle_decoded[0];
var hdr = _get_blank_header(typenum);
hdr._bytes4 = zdle_decoded.slice(1, 5);
return hdr;
}
function _parse_hex(bytes_arr) {
var lf_pos = bytes_arr.indexOf(138);
if (-1 === lf_pos) {
lf_pos = bytes_arr.indexOf(10);
}
var hdr_err, hex_bytes;
if (-1 === lf_pos) {
if (bytes_arr.length > 11) {
hdr_err = "Invalid hex header - no LF detected within 12 bytes!";
}
return;
} else {
hex_bytes = bytes_arr.splice(0, lf_pos);
bytes_arr.shift();
if (hex_bytes.length === 19) {
var preceding = hex_bytes.pop();
if (preceding !== 13 && preceding !== 141) {
hdr_err = "Invalid hex header: (CR/)LF doesn\u2019t have CR!";
}
} else if (hex_bytes.length !== 18) {
hdr_err = "Invalid hex header: invalid number of bytes before LF!";
}
}
if (hdr_err) {
hdr_err += " (" + hex_bytes.length + " bytes: " + hex_bytes.join() + ")";
throw hdr_err;
}
hex_bytes.splice(0, 4);
var octets = Zmodem2.ENCODELIB.parse_hex_octets(hex_bytes);
return _parse_non_zdle_binary16(octets);
}
Zmodem2.Header.parse_hex = _parse_hex;
}
});
// node_modules/zmodem.js/src/zsubpacket.js
var require_zsubpacket = __commonJS({
"node_modules/zmodem.js/src/zsubpacket.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
Object.assign(
Zmodem2,
require_zcrc(),
require_zdle(),
require_zmlib(),
require_zerror()
);
var ZCRCE = 104;
var ZCRCG = 105;
var ZCRCQ = 106;
var ZCRCW = 107;
var SUBPACKET_BUILDER;
Zmodem2.Subpacket = class ZmodemSubpacket {
/**
* Build a Subpacket subclass given a payload and frame end string.
*
* @param {Array} octets - The octet values to parse.
* Each array member should be an 8-bit unsigned integer (0-255).
*
* @param {string} frameend - One of:
* - `no_end_no_ack`
* - `end_no_ack`
* - `no_end_ack` (unused currently)
* - `end_ack`
*
* @returns {Subpacket} An instance of the appropriate Subpacket subclass.
*/
static build(octets, frameend) {
var Ctr = SUBPACKET_BUILDER[frameend];
if (!Ctr) {
throw "No subpacket type \u201C" + frameend + "\u201D is defined! Try one of: " + Object.keys(SUBPACKET_BUILDER).join(", ");
}
return new Ctr(octets);
}
/**
* Return the octet values array that represents the object
* encoded with a 16-bit CRC.
*
* @param {ZDLE} zencoder - A ZDLE instance to use for ZDLE encoding.
*
* @returns {number[]} An array of octet values suitable for sending
* as binary data.
*/
encode16(zencoder) {
return this._encode(zencoder, Zmodem2.CRC.crc16);
}
/**
* Return the octet values array that represents the object
* encoded with a 32-bit CRC.
*
* @param {ZDLE} zencoder - A ZDLE instance to use for ZDLE encoding.
*
* @returns {number[]} An array of octet values suitable for sending
* as binary data.
*/
encode32(zencoder) {
return this._encode(zencoder, Zmodem2.CRC.crc32);
}
/**
* Return the subpacket payload’s octet values.
*
* NOTE: For speed, this returns the actual data in the subpacket;
* if you mutate this return value, you alter the Subpacket object
* internals. This is OK if you won’t need the Subpacket anymore, but
* just be careful.
*
* @returns {number[]} The subpacket’s payload, represented as an
* array of octet values. **DO NOT ALTER THIS ARRAY** unless you
* no longer need the Subpacket.
*/
get_payload() {
return this._payload;
}
/**
* Parse out a Subpacket object from a given array of octet values,
* assuming a 16-bit CRC.
*
* An exception is thrown if the given bytes are definitively invalid
* as subpacket values with 16-bit CRC.
*
* @param {number[]} octets - The octet values to parse.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {Subpacket|undefined} An instance of the appropriate Subpacket
* subclass, or undefined if not enough octet values are given
* to determine whether there is a valid subpacket here or not.
*/
static parse16(octets) {
return ZmodemSubpacket._parse(octets, 2);
}
//parse32 test:
//[102, 105, 108, 101, 110, 97, 109, 101, 119, 105, 116, 104, 115, 112, 97, 99, 101, 115, 0, 49, 55, 49, 51, 49, 52, 50, 52, 51, 50, 49, 55, 50, 49, 48, 48, 54, 52, 52, 48, 49, 49, 55, 0, 43, 8, 63, 115, 23, 17]
/**
* Same as parse16(), but assuming a 32-bit CRC.
*
* @param {number[]} octets - The octet values to parse.
* Each array member should be an 8-bit unsigned integer (0-255).
* This object is mutated in the function.
*
* @returns {Subpacket|undefined} An instance of the appropriate Subpacket
* subclass, or undefined if not enough octet values are given
* to determine whether there is a valid subpacket here or not.
*/
static parse32(octets) {
return ZmodemSubpacket._parse(octets, 4);
}
/**
* Not used directly.
*/
constructor(payload) {
this._payload = payload;
}
_encode(zencoder, crc_func) {
return zencoder.encode(this._payload.slice(0)).concat(
[Zmodem2.ZMLIB.ZDLE, this._frameend_num],
zencoder.encode(crc_func(this._payload.concat(this._frameend_num)))
);
}
//Because of ZDLE encoding, we’ll never see any of the frame-end octets
//in a stream except as the ends of data payloads.
static _parse(bytes_arr, crc_len) {
var end_at;
var creator;
var _frame_ends_lookup = {
104: ZEndNoAckSubpacket,
105: ZNoEndNoAckSubpacket,
106: ZNoEndAckSubpacket,
107: ZEndAckSubpacket
};
var zdle_at = 0;
while (zdle_at < bytes_arr.length) {
zdle_at = bytes_arr.indexOf(Zmodem2.ZMLIB.ZDLE, zdle_at);
if (zdle_at === -1) return;
var after_zdle = bytes_arr[zdle_at + 1];
creator = _frame_ends_lookup[after_zdle];
if (creator) {
end_at = zdle_at + 1;
break;
}
zdle_at++;
}
if (!creator) return;
var frameend_num = bytes_arr[end_at];
if (bytes_arr[end_at - 1] !== Zmodem2.ZMLIB.ZDLE) {
throw "Byte before frame end should be ZDLE, not " + bytes_arr[end_at - 1];
}
var zdle_encoded_payload = bytes_arr.splice(0, end_at - 1);
var got_crc = Zmodem2.ZDLE.splice(bytes_arr, 2, crc_len);
if (!got_crc) {
bytes_arr.unshift.apply(bytes_arr, zdle_encoded_payload);
return;
}
var payload = Zmodem2.ZDLE.decode(zdle_encoded_payload);
Zmodem2.CRC[crc_len === 2 ? "verify16" : "verify32"](
payload.concat([frameend_num]),
got_crc
);
return new creator(payload, got_crc);
}
};
var ZEndSubpacketBase = class extends Zmodem2.Subpacket {
frame_end() {
return true;
}
};
var ZNoEndSubpacketBase = class extends Zmodem2.Subpacket {
frame_end() {
return false;
}
};
var ZEndNoAckSubpacket = class extends ZEndSubpacketBase {
ack_expected() {
return false;
}
};
ZEndNoAckSubpacket.prototype._frameend_num = ZCRCE;
var ZEndAckSubpacket = class extends ZEndSubpacketBase {
ack_expected() {
return true;
}
};
ZEndAckSubpacket.prototype._frameend_num = ZCRCW;
var ZNoEndNoAckSubpacket = class extends ZNoEndSubpacketBase {
ack_expected() {
return false;
}
};
ZNoEndNoAckSubpacket.prototype._frameend_num = ZCRCG;
var ZNoEndAckSubpacket = class extends ZNoEndSubpacketBase {
ack_expected() {
return true;
}
};
ZNoEndAckSubpacket.prototype._frameend_num = ZCRCQ;
SUBPACKET_BUILDER = {
end_no_ack: ZEndNoAckSubpacket,
end_ack: ZEndAckSubpacket,
no_end_no_ack: ZNoEndNoAckSubpacket,
no_end_ack: ZNoEndAckSubpacket
};
}
});
// node_modules/zmodem.js/src/zvalidation.js
var require_zvalidation = __commonJS({
"node_modules/zmodem.js/src/zvalidation.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
Object.assign(
Zmodem2,
require_zerror()
);
var LOOKS_LIKE_ZMODEM_HEADER = /\*\x18[AC]|\*\*\x18B/;
function _validate_number(key, value) {
if (value < 0) {
throw new Zmodem2.Error("validation", "\u201C" + key + "\u201D (" + value + ") must be nonnegative.");
}
if (value !== Math.floor(value)) {
throw new Zmodem2.Error("validation", "\u201C" + key + "\u201D (" + value + ") must be an integer.");
}
}
Zmodem2.Validation = {
/**
* Validates and normalizes a set of parameters for an offer to send.
* NOTE: This returns “mtime” as epoch seconds, not a Date. This is
* inconsistent with the get_details() method in Session, but it’s
* more useful for sending over the wire.
*
* @param {FileDetails} params - The file details. Some fairly trivial
* variances from the specification are allowed.
*
* @return {FileDetails} The parameters that should be sent. `mtime`
* will be a Date rather than a number.
*/
offer_parameters: function offer_parameters(params) {
if (!params.name) {
throw new Zmodem2.Error("validation", "Need \u201Cname\u201D!");
}
if (typeof params.name !== "string") {
throw new Zmodem2.Error("validation", "\u201Cname\u201D (" + params.name + ") must be a string!");
}
params = Object.assign({}, params);
if (LOOKS_LIKE_ZMODEM_HEADER.test(params.name)) {
console.warn("The filename " + JSON.stringify(name) + " contains characters that look like a ZMODEM header. This could corrupt the ZMODEM session; consider renaming it so that the filename doesn\u2019t contain control characters.");
}
if (params.serial !== null && params.serial !== void 0) {
throw new Zmodem2.Error("validation", "\u201Cserial\u201D is meaningless.");
}
params.serial = null;
["size", "mode", "files_remaining", "bytes_remaining"].forEach(
function(k) {
var ok;
switch (typeof params[k]) {
case "object":
ok = params[k] === null;
break;
case "undefined":
params[k] = null;
ok = true;
break;
case "number":
_validate_number(k, params[k]);
ok = true;
break;
}
if (!ok) {
throw new Zmodem2.Error("validation", "\u201C" + k + "\u201D (" + params[k] + ") must be null, undefined, or a number.");
}
}
);
if (typeof params.mode === "number") {
params.mode |= 32768;
}
if (params.files_remaining === 0) {
throw new Zmodem2.Error("validation", "\u201Cfiles_remaining\u201D, if given, must be positive.");
}
var mtime_ok;
switch (typeof params.mtime) {
case "object":
mtime_ok = true;
if (params.mtime instanceof Date) {
var date_obj = params.mtime;
params.mtime = Math.floor(date_obj.getTime() / 1e3);
if (params.mtime < 0) {
throw new Zmodem2.Error("validation", "\u201Cmtime\u201D (" + date_obj + ") must not be earlier than 1970.");
}
} else if (params.mtime !== null) {
mtime_ok = false;
}
break;
case "undefined":
params.mtime = null;
mtime_ok = true;
break;
case "number":
_validate_number("mtime", params.mtime);
mtime_ok = true;
break;
}
if (!mtime_ok) {
throw new Zmodem2.Error("validation", "\u201Cmtime\u201D (" + params.mtime + ") must be null, undefined, a Date, or a number.");
}
return params;
}
};
}
});
// node_modules/zmodem.js/src/zsession.js
var require_zsession = __commonJS({
"node_modules/zmodem.js/src/zsession.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
Zmodem2.DEBUG = false;
Object.assign(
Zmodem2,
require_encode(),
require_text(),
require_zdle(),
require_zmlib(),
require_zheader(),
require_zsubpacket(),
require_zvalidation(),
require_zerror()
);
var KEEPALIVE_INTERVAL = 5e3;
var ZRINIT_FLAGS = [
"CANFDX",
//full duplex
"CANOVIO",
//overlap I/O
//lsz has a buffer overflow bug that shows itself when:
//
// - 16-bit CRC is used, and
// - lsz receives the abort sequence while sending a file
//
//To avoid this, we just tell lsz to use 32-bit CRC
//even though there is otherwise no reason. This ensures that
//unfixed lsz versions will avoid the buffer overflow.
"CANFC32"
];
var FORCE_ESCAPE_CTRL_CHARS = true;
var DEFAULT_RECEIVE_INPUT_MODE = "spool_uint8array";
var MAX_CHUNK_LENGTH = 8192;
var BS = 8;
var OVER_AND_OUT = [79, 79];
var ABORT_SEQUENCE = Zmodem2.ZMLIB.ABORT_SEQUENCE;
var _Eventer = class {
/**
* Not called directly.
*/
constructor() {
this._on_evt = {};
this._evt_once_index = {};
}
_Add_event(evt_name) {
this._on_evt[evt_name] = [];
this._evt_once_index[evt_name] = [];
}
_get_evt_queue(evt_name) {
if (!this._on_evt[evt_name]) {
throw "Bad event: " + evt_name;
}
return this._on_evt[evt_name];
}
/**
* Register a callback for a given event.
*
* @param {string} evt_name - The name of the event.
*
* @param {Function} todo - The function to execute when the event happens.
*/
on(evt_name, todo) {
var queue = this._get_evt_queue(evt_name);
queue.push(todo);
return this;
}
/**
* Unregister a callback for a given event.
*
* @param {string} evt_name - The name of the event.
*
* @param {Function} [todo] - The function to execute when the event
* happens. If not given, the last event registered for the event
* is unregistered.
*/
off(evt_name, todo) {
var queue = this._get_evt_queue(evt_name);
if (todo) {
var at = queue.indexOf(todo);
if (at === -1) {
throw "\u201C" + todo + "\u201D is not in the \u201C" + evt_name + "\u201D queue.";
}
queue.splice(at, 1);
} else {
queue.pop();
}
return this;
}
_Happen(evt_name) {
var queue = this._get_evt_queue(evt_name);
var args = Array.apply(null, arguments);
args.shift();
var sess = this;
queue.forEach(function(cb) {
cb.apply(sess, args);
});
return queue.length;
}
};
Zmodem2.Session = class ZmodemSession extends _Eventer {
/**
* Parse out a hex header from the given array.
* If there’s a ZRQINIT or ZRINIT at the beginning,
* we’ll return it. If the input isn’t a header,
* for whatever reason, we return undefined.
*
* @param {number[]} octets - The bytes to parse.
*
* @return {Session|undefined} A Session object if the beginning
* of a session was parsable in “octets”; otherwise undefined.
*/
static parse(octets) {
var hdr;
try {
hdr = Zmodem2.Header.parse_hex(octets);
} catch (e) {
return;
}
if (!hdr) return;
switch (hdr.NAME) {
case "ZRQINIT":
return new Zmodem2.Session.Receive();
case "ZRINIT":
return new Zmodem2.Session.Send(hdr);
}
}
/**
* Sets the sender function that a Session object will use.
*
* @param {Function} sender_func - The function to call.
* It will receive an Array with the relevant octets.
*
* @return {Session} The session object (for chaining).
*/
set_sender(sender_func) {
this._sender = sender_func;
return this;
}
/**
* Whether the current Session has ended.
*
* @returns {boolean} The ended state.
*/
has_ended() {
return this._has_ended();
}
/**
* Consumes an array of octets as ZMODEM session input.
*
* @param {number[]} octets - The input octets.
*/
consume(octets) {
this._before_consume(octets);
if (this._aborted) throw new Zmodem2.Error("already_aborted");
if (!octets.length) return;
this._strip_and_enqueue_input(octets);
if (!this._check_for_abort_sequence(octets)) {
this._consume_first();
}
return;
}
/**
* Whether the current Session has been `abort()`ed.
*
* @returns {boolean} The aborted state.
*/
aborted() {
return !!this._aborted;
}
/**
* Not called directly.
*/
constructor() {
super();
this._config = {};
this._input_buffer = [];
this._Add_event("receive");
this._Add_event("garbage");
this._Add_event("session_end");
}
/**
* Returns the Session object’s role.
*
* @returns {string} One of:
* - `receive`
* - `send`
*/
get_role() {
return this.type;
}
_trim_leading_garbage_until_header() {
var garbage = Zmodem2.Header.trim_leading_garbage(this._input_buffer);
if (garbage.length) {
if (this._Happen("garbage", garbage) === 0) {
console.debug(
"Garbage: ",
String.fromCharCode.apply(String, garbage),
garbage
);
}
}
}
_parse_and_consume_header() {
this._trim_leading_garbage_until_header();
var new_header_and_crc = Zmodem2.Header.parse(this._input_buffer);
if (!new_header_and_crc) return;
if (Zmodem2.DEBUG) {
this._log_header("RECEIVED HEADER", new_header_and_crc[0]);
}
this._consume_header(new_header_and_crc[0]);
this._last_header_name = new_header_and_crc[0].NAME;
this._last_header_crc = new_header_and_crc[1];
return new_header_and_crc[0];
}
_log_header(label, header) {
console.debug(this.type, label, header.NAME, header._bytes4.join());
}
_consume_header(new_header) {
this._on_receive(new_header);
var handler = this._next_header_handler && this._next_header_handler[new_header.NAME];
if (!handler) {
console.error("Unhandled header!", new_header, this._next_header_handler);
throw new Zmodem2.Error("Unhandled header: " + new_header.NAME);
}
this._next_header_handler = null;
handler.call(this, new_header);
}
//TODO: strip out the abort sequence
_check_for_abort_sequence() {
var abort_at = Zmodem2.ZMLIB.find_subarray(this._input_buffer, ABORT_SEQUENCE);
if (abort_at !== -1) {
this._input_buffer.splice(0, abort_at + ABORT_SEQUENCE.length);
this._aborted = true;
this._on_session_end();
throw new Zmodem2.Error("peer_aborted");
}
}
_send_header(name2) {
if (!this._sender) throw "Need sender!";
var args = Array.apply(null, arguments);
var bytes_hdr = this._create_header_bytes(args);
if (Zmodem2.DEBUG) {
this._log_header("SENDING HEADER", bytes_hdr[1]);
}
this._sender(bytes_hdr[0]);
this._last_sent_header = bytes_hdr[1];
}
_create_header_bytes(name_and_args) {
var hdr = Zmodem2.Header.build.apply(Zmodem2.Header, name_and_args);
var formatter = this._get_header_formatter(name_and_args[0]);
return [
hdr[formatter](this._zencoder),
hdr
];
}
_strip_and_enqueue_input(input) {
Zmodem2.ZMLIB.strip_ignored_bytes(input);
this._input_buffer.push.apply(this._input_buffer, input);
}
/**
* **STOP!** You probably want to `skip()` an Offer rather than
* `abort()`. See below.
*
* Abort the current session by sending the ZMODEM abort sequence.
* This function will cause the Session object to refuse to send
* any further data.
*
* Zmodem.Sentry is configured to send all output to the terminal
* after a session’s `abort()`. That could result in lots of
* ZMODEM garble being sent to the JavaScript terminal, which you
* probably don’t want.
*
* `skip()` on an Offer is better because Session will continue to
* discard data until we reach either another file or the
* sender-initiated end of the ZMODEM session. So no ZMODEM garble,
* and the session will end successfully.
*
* The behavior of `abort()` is subject to change since it’s not
* very useful as currently implemented.
*/
abort() {
this._sender(
ABORT_SEQUENCE.concat([BS, BS, BS, BS, BS])
);
this._aborted = true;
this._sender = function() {
throw new Zmodem2.Error("already_aborted");
};
this._on_session_end();
return;
}
//----------------------------------------------------------------------
_on_session_end() {
this._Happen("session_end");
}
_on_receive(hdr_or_pkt) {
this._Happen("receive", hdr_or_pkt);
}
_before_consume() {
}
};
function _trim_OO(array) {
if (0 === Zmodem2.ZMLIB.find_subarray(array, OVER_AND_OUT)) {
array.splice(0, OVER_AND_OUT.length);
} else if (array[0] === OVER_AND_OUT[OVER_AND_OUT.length - 1]) {
array.splice(0, 1);
}
return array;
}
Zmodem2.Session.Receive = class ZmodemReceiveSession extends Zmodem2.Session {
//We only get 1 file at a time, so on each consume() either
//continue state for the current file or start a new one.
/**
* Not called directly.
*/
constructor() {
super();
this._Add_event("offer");
this._Add_event("data_in");
this._Add_event("file_end");
}
/**
* Consume input bytes from the sender.
*
* @private
* @param {number[]} octets - The bytes to consume.
*/
_before_consume(octets) {
if (this._bytes_after_OO) {
throw "PROTOCOL: Session is completed!";
}
this._bytes_being_consumed = octets;
}
/**
* Return any bytes that have been `consume()`d but
* came after the end of the ZMODEM session.
*
* @returns {number[]} The trailing bytes.
*/
get_trailing_bytes() {
if (this._aborted) return [];
if (!this._bytes_after_OO) {
throw "PROTOCOL: Session is not completed!";
}
return this._bytes_after_OO.slice(0);
}
_has_ended() {
return this.aborted() || !!this._bytes_after_OO;
}
//Receiver always sends hex headers.
_get_header_formatter() {
return "to_hex";
}
_parse_and_consume_subpacket() {
var parse_func;
if (this._last_header_crc === 16) {
parse_func = "parse16";
} else {
parse_func = "parse32";
}
var subpacket = Zmodem2.Subpacket[parse_func](this._input_buffer);
if (subpacket) {
if (Zmodem2.DEBUG) {
console.debug(this.type, "RECEIVED SUBPACKET", subpacket);
}
this._consume_data(subpacket);
if (subpacket.frame_end()) {
this._next_subpacket_handler = null;
}
}
return subpacket;
}
_consume_first() {
if (this._got_ZFIN) {
if (this._input_buffer.length < 2) return;
if (Zmodem2.ZMLIB.find_subarray(this._input_buffer, OVER_AND_OUT) === 0) {
this._bytes_after_OO = _trim_OO(this._bytes_being_consumed.slice(0));
this._on_session_end();
return;
} else {
throw "PROTOCOL: Only thing after ZFIN should be \u201COO\u201D (79,79), not: " + this._input_buffer.join();
}
}
var parsed;
do {
if (this._next_subpacket_handler) {
parsed = this._parse_and_consume_subpacket();
} else {
parsed = this._parse_and_consume_header();
}
} while (parsed && this._input_buffer.length);
}
_consume_data(subpacket) {
this._on_receive(subpacket);
if (!this._next_subpacket_handler) {
throw "PROTOCOL: Received unexpected data packet after " + this._last_header_name + " header: " + subpacket.get_payload().join();
}
this._next_subpacket_handler.call(this, subpacket);
}
_octets_to_string(octets) {
if (!this._textdecoder) {
this._textdecoder = new Zmodem2.Text.Decoder();
}
return this._textdecoder.decode(new Uint8Array(octets));
}
_consume_ZFILE_data(hdr, subpacket) {
if (this._file_info) {
throw "PROTOCOL: second ZFILE data subpacket received";
}
var packet_payload = subpacket.get_payload();
var nul_at = packet_payload.indexOf(0);
var fname = this._octets_to_string(packet_payload.slice(0, nul_at));
var the_rest = this._octets_to_string(packet_payload.slice(1 + nul_at)).split(" ");
var mtime = the_rest[1] && parseInt(the_rest[1], 8) || void 0;
if (mtime) {
mtime = new Date(mtime * 1e3);
}
this._file_info = {
name: fname,
size: the_rest[0] ? parseInt(the_rest[0], 10) : null,
mtime: mtime || null,
mode: the_rest[2] && parseInt(the_rest[2], 8) || null,
serial: the_rest[3] && parseInt(the_rest[3], 10) || null,
files_remaining: the_rest[4] ? parseInt(the_rest[4], 10) : null,
bytes_remaining: the_rest[5] ? parseInt(the_rest[5], 10) : null
};
var xfer = new Offer(
hdr.get_options(),
this._file_info,
this._accept.bind(this),
this._skip.bind(this)
);
this._current_transfer = xfer;
}
_consume_ZDATA_data(subpacket) {
if (!this._accepted_offer) {
throw "PROTOCOL: Received data without accepting!";
}
if (!this._offset_ok) {
console.warn("offset not ok!");
_send_ZRPOS();
return;
}
this._file_offset += subpacket.get_payload().length;
this._on_data_in(subpacket);
if (subpacket.ack_expected() && !subpacket.frame_end()) {
this._send_header("ZACK", Zmodem2.ENCODELIB.pack_u32_le(this._file_offset));
}
}
_make_promise_for_between_files() {
var sess = this;
return new Promise(function(res) {
var between_files_handler = {
ZFILE: function(hdr) {
this._next_subpacket_handler = function(subpacket) {
this._next_subpacket_handler = null;
this._consume_ZFILE_data(hdr, subpacket);
this._Happen("offer", this._current_transfer);
res(this._current_transfer);
};
},
//We use this as a keep-alive. Maybe other
//implementations do, too?
ZSINIT: function(hdr) {
sess._next_subpacket_handler = function(spkt) {
sess._next_subpacket_handler = null;
sess._consume_ZSINIT_data(spkt);
sess._send_header("ZACK");
sess._next_header_handler = between_files_handler;
};
},
ZFIN: function() {
this._consume_ZFIN();
res();
}
};
sess._next_header_handler = between_files_handler;
});
}
_consume_ZSINIT_data(spkt) {
this._attn = spkt.get_payload();
}
/**
* Start the ZMODEM session by signaling to the sender that
* we are ready for the first file offer.
*
* @returns {Promise} A promise that resolves with an Offer object
* or, if the sender closes the session immediately without offering
* anything, nothing.
*/
start() {
if (this._started) throw "Already started!";
this._started = true;
var ret = this._make_promise_for_between_files();
this._send_ZRINIT();
return ret;
}
//Returns a promise that’s fulfilled when the file
//transfer is done.
//
// That ZEOF promise return is another promise that’s
// fulfilled when we get either ZFIN or another ZFILE.
_accept(offset) {
this._accepted_offer = true;
this._file_offset = offset || 0;
var sess = this;
var ret = new Promise(function(resolve_accept) {
var last_ZDATA;
sess._next_header_handler = {
ZDATA: function on_ZDATA(hdr) {
this._consume_ZDATA(hdr);
this._next_subpacket_handler = this._consume_ZDATA_data;
this._next_header_handler = {
ZEOF: function on_ZEOF(hdr2) {
this._consume_ZEOF(hdr2);
this._next_subpacket_handler = null;
this._make_promise_for_between_files();
resolve_accept();
this._send_ZRINIT();
}
};
}
};
});
this._send_ZRPOS();
return ret;
}
_skip() {
var ret = this._make_promise_for_between_files();
if (this._accepted_offer) {
if (!this._current_transfer) return;
var bound_make_promise_for_between_files = function() {
this._accepted_offer = false;
this._next_subpacket_handler = null;
this._make_promise_for_between_files();
}.bind(this);
Object.assign(
this._next_header_handler,
{
ZEOF: bound_make_promise_for_between_files,
ZDATA: function() {
bound_make_promise_for_between_files();
this._next_header_handler.ZEOF = bound_make_promise_for_between_files;
}.bind(this)
}
);
}
this._file_info = null;
this._send_header("ZSKIP");
return ret;
}
_send_ZRINIT() {
this._send_header("ZRINIT", ZRINIT_FLAGS);
}
_consume_ZFIN() {
this._got_ZFIN = true;
this._send_header("ZFIN");
}
_consume_ZEOF(header) {
if (this._file_offset !== header.get_offset()) {
throw "ZEOF offset mismatch; unimplemented (local: " + this._file_offset + "; ZEOF: " + header.get_offset() + ")";
}
this._on_file_end();
this._file_info = null;
this._current_transfer = null;
}
_consume_ZDATA(header) {
if (this._file_offset === header.get_offset()) {
this._offset_ok = true;
} else {
throw "Error correction is unimplemented.";
}
}
_send_ZRPOS() {
this._send_header("ZRPOS", this._file_offset);
}
//----------------------------------------------------------------------
//events
_on_file_end() {
this._Happen("file_end");
if (this._current_transfer) {
this._current_transfer._Happen("complete");
this._current_transfer = null;
}
}
_on_data_in(subpacket) {
this._Happen("data_in", subpacket);
if (this._current_transfer) {
this._current_transfer._Happen("input", subpacket.get_payload());
}
}
};
Object.assign(
Zmodem2.Session.Receive.prototype,
{
type: "receive"
}
);
var Transfer_Offer_Mixin = {
/**
* Returns the file details object.
* @returns {FileDetails} `mtime` is a Date.
*/
get_details: function get_details() {
return Object.assign({}, this._file_info);
},
/**
* Returns a parse of the ZFILE header’s payload.
*
* @returns {Object} Members are:
*
* - `conversion` (string | undefined)
* - `management` (string | undefined)
* - `transfer` (string | undefined)
* - `sparse` (boolean)
*/
get_options: function get_options() {
return Object.assign({}, this._zfile_opts);
},
/**
* Returns the offset based on the last transferred chunk.
* @returns {number} The file offset (i.e., number of bytes after
* the start of the file).
*/
get_offset: function get_offset() {
return this._file_offset;
}
};
var Transfer = class {
/**
* Not called directly.
*/
constructor(file_info, offset, send_func, end_func) {
this._file_info = file_info;
this._file_offset = offset || 0;
this._send = send_func;
this._end = end_func;
}
/**
* Send a (non-terminal) piece of the file.
*
* @param { number[] | Uint8Array } array_like - The bytes to send.
*/
send(array_like) {
this._send(array_like);
this._file_offset += array_like.length;
}
/**
* Complete the file transfer.
*
* @param { number[] | Uint8Array } [array_like] - The last bytes to send.
*
* @return { Promise } Resolves when the receiver has indicated
* acceptance of the end of the file transfer.
*/
end(array_like) {
var ret = this._end(array_like || []);
if (array_like) this._file_offset += array_like.length;
return ret;
}
};
Object.assign(Transfer.prototype, Transfer_Offer_Mixin);
var Offer = class extends _Eventer {
/**
* Not called directly.
*/
constructor(zfile_opts, file_info, accept_func, skip_func) {
super();
this._zfile_opts = zfile_opts;
this._file_info = file_info;
this._accept_func = accept_func;
this._skip_func = skip_func;
this._Add_event("input");
this._Add_event("complete");
this.on("input", this._input_handler);
}
_verify_not_skipped() {
if (this._skipped) {
throw new Zmodem2.Error("Already skipped!");
}
}
/**
* Tell the sender that you don’t want the offered file.
*
* You can send this in lieu of `accept()` or after it, e.g.,
* if you find that the transfer is taking too long. Note that,
* if you `skip()` after you `accept()`, you’ll likely have to
* wait for buffers to clear out.
*
*/
skip() {
this._verify_not_skipped();
this._skipped = true;
return this._skip_func.apply(this, arguments);
}
/**
* Tell the sender to send the offered file.
*
* @param {Object} [opts] - Can be:
* @param {string} [opts.oninput=spool_uint8array] - Can be:
*
* - `spool_uint8array`: Stores the ZMODEM
* packet payloads as Uint8Array instances.
* This makes for an easy transition to a Blob,
* which JavaScript can use to save the file to disk.
*
* - `spool_array`: Stores the ZMODEM packet payloads
* as Array instances. Each value is an octet value.
*
* - (function): A handler that receives each payload
* as it arrives. The Offer object does not store
* the payloads internally when thus configured.
*
* @return { Promise } Resolves when the file is fully received.
* If the Offer has been spooling
* the packet payloads, the promise resolves with an Array
* that contains those payloads.
*/
accept(opts) {
this._verify_not_skipped();
if (this._accepted) {
throw new Zmodem2.Error("Already accepted!");
}
this._accepted = true;
if (!opts) opts = {};
this._file_offset = opts.offset || 0;
switch (opts.on_input) {
case null:
case void 0:
case "spool_array":
case DEFAULT_RECEIVE_INPUT_MODE:
this._spool = [];
break;
default:
if (typeof opts.on_input !== "function") {
throw "Invalid \u201Con_input\u201D: " + opts.on_input;
}
}
this._input_handler_mode = opts.on_input || DEFAULT_RECEIVE_INPUT_MODE;
return this._accept_func(this._file_offset).then(this._get_spool.bind(this));
}
_input_handler(payload) {
this._file_offset += payload.length;
if (typeof this._input_handler_mode === "function") {
this._input_handler_mode(payload);
} else {
if (this._input_handler_mode === DEFAULT_RECEIVE_INPUT_MODE) {
payload = new Uint8Array(payload);
} else if (this._input_handler_mode !== "spool_array") {
throw new Zmodem2.Error("WTF?? _input_handler_mode = " + this._input_handler_mode);
}
this._spool.push(payload);
}
}
_get_spool() {
return this._spool;
}
};
Object.assign(Offer.prototype, Transfer_Offer_Mixin);
var SENDER_BINARY_HEADER = {
ZFILE: true,
ZDATA: true
};
Zmodem2.Session.Send = class ZmodemSendSession extends Zmodem2.Session {
/**
* Not called directly.
*/
constructor(zrinit_hdr) {
super();
if (!zrinit_hdr) {
throw "Need first header!";
} else if (zrinit_hdr.NAME !== "ZRINIT") {
throw "First header should be ZRINIT, not " + zrinit_hdr.NAME;
}
this._last_header_name = "ZRINIT";
this._subpacket_encode_func = "encode16";
this._zencoder = new Zmodem2.ZDLE();
this._consume_ZRINIT(zrinit_hdr);
this._file_offset = 0;
var zrqinit_count = 0;
this._start_keepalive_on_set_sender = true;
}
/**
* Sets the sender function. The first time this is called,
* it will also initiate a keepalive using ZSINIT until the
* first file is sent.
*
* @param {Function} func - The function to call.
* It will receive an Array with the relevant octets.
*
* @return {Session} The session object (for chaining).
*/
set_sender(func) {
super.set_sender(func);
if (this._start_keepalive_on_set_sender) {
this._start_keepalive_on_set_sender = false;
this._start_keepalive();
}
return this;
}
//7.3.3 .. The sender also uses hex headers when they are
//not followed by binary data subpackets.
//
//FG: … or when the header is ZSINIT? That’s what lrzsz does, anyway.
//Then it sends a single NUL byte as the payload to an end_ack subpacket.
_get_header_formatter(name2) {
return SENDER_BINARY_HEADER[name2] ? "to_binary16" : "to_hex";
}
//In order to keep lrzsz from timing out, we send ZSINIT every 5 seconds.
//Maybe make this configurable?
_start_keepalive() {
if (!this._keepalive_promise) {
var sess = this;
this._keepalive_promise = new Promise(function(resolve) {
sess._keepalive_timeout = setTimeout(resolve, KEEPALIVE_INTERVAL);
}).then(function() {
sess._next_header_handler = {
ZACK: function() {
sess._got_ZSINIT_ZACK = true;
}
};
sess._send_ZSINIT();
sess._keepalive_promise = null;
sess._start_keepalive();
});
}
}
_stop_keepalive() {
if (this._keepalive_promise) {
clearTimeout(this._keepalive_timeout);
this._keep_alive_promise = null;
}
}
_send_ZSINIT() {
var zsinit_flags = [];
if (this._zencoder.escapes_ctrl_chars()) {
zsinit_flags.push("ESCCTL");
}
this._send_header_and_data(
["ZSINIT", zsinit_flags],
[0],
"end_ack"
);
}
_consume_ZRINIT(hdr) {
this._last_ZRINIT = hdr;
if (hdr.get_buffer_size()) {
throw "Buffer size (" + hdr.get_buffer_size() + ") is unsupported!";
}
if (!hdr.can_full_duplex()) {
throw "Half-duplex I/O is unsupported!";
}
if (!hdr.can_overlap_io()) {
throw "Non-overlap I/O is unsupported!";
}
if (hdr.escape_8th_bit()) {
throw "8-bit escaping is unsupported!";
}
if (FORCE_ESCAPE_CTRL_CHARS) {
this._zencoder.set_escape_ctrl_chars(true);
if (!hdr.escape_ctrl_chars()) {
console.debug("Peer didn\u2019t request escape of all control characters. Will send ZSINIT to force recognition of escaped control characters.");
}
} else {
this._zencoder.set_escape_ctrl_chars(hdr.escape_ctrl_chars());
}
}
//https://stackoverflow.com/questions/23155939/missing-0xf-and-0x16-when-binary-data-through-virtual-serial-port-pair-created-b
//^^ Because of that, we always escape control characters.
//The alternative would be that lrz would never receive those
//two bytes from zmodem.js.
_ensure_receiver_escapes_ctrl_chars() {
var promise;
var needs_ZSINIT = !this._last_ZRINIT.escape_ctrl_chars() && !this._got_ZSINIT_ZACK;
if (needs_ZSINIT) {
var sess = this;
promise = new Promise(function(res) {
sess._next_header_handler = {
ZACK: (hdr) => {
res();
}
};
sess._send_ZSINIT();
});
} else {
promise = Promise.resolve();
}
return promise;
}
_convert_params_to_offer_payload_array(params) {
params = Zmodem2.Validation.offer_parameters(params);
var subpacket_payload = params.name + "\0";
var subpacket_space_pieces = [
(params.size || 0).toString(10),
params.mtime ? params.mtime.toString(8) : "0",
params.mode ? (32768 | params.mode).toString(8) : "0",
"0"
//serial
];
if (params.files_remaining) {
subpacket_space_pieces.push(params.files_remaining);
if (params.bytes_remaining) {
subpacket_space_pieces.push(params.bytes_remaining);
}
}
subpacket_payload += subpacket_space_pieces.join(" ");
return this._string_to_octets(subpacket_payload);
}
/**
* Send an offer to the receiver.
*
* @param {FileDetails} params - All about the file you want to transfer.
*
* @returns {Promise} If the receiver accepts the offer, then the
* resolution is a Transfer object; otherwise the resolution is
* undefined.
*/
send_offer(params) {
if (Zmodem2.DEBUG) {
console.debug("SENDING OFFER", params);
}
if (!params) throw "need file params!";
if (this._sending_file) throw "Already sending file!";
var payload_array = this._convert_params_to_offer_payload_array(params);
this._stop_keepalive();
var sess = this;
function zrpos_handler_setter_func() {
sess._next_header_handler = {
// The receiver may send ZRPOS in at least two cases:
//
// 1) A malformed subpacket arrived, so we need to
// “rewind” a bit and continue from the receiver’s
// last-successful location in the file.
//
// 2) The receiver hasn’t gotten any data for a bit,
// so it sends ZRPOS as a “ping”.
//
// Case #1 shouldn’t happen since zmodem.js requires a
// reliable transport. Case #2, though, can happen due
// to either normal network congestion or errors in
// implementation. In either case, there’s nothing for
// us to do but to ignore the ZRPOS, with an optional
// warning.
//
ZRPOS: function(hdr) {
if (Zmodem2.DEBUG) {
console.warn("Mid-transfer ZRPOS \u2026 implementation error?");
}
zrpos_handler_setter_func();
}
};
}
;
var doer_func = function() {
var handler_setter_promise = new Promise(function(res) {
sess._next_header_handler = {
ZSKIP: function() {
sess._start_keepalive();
res();
},
ZRPOS: function(hdr) {
sess._sending_file = true;
zrpos_handler_setter_func();
res(
new Transfer(
params,
hdr.get_offset(),
sess._send_interim_file_piece.bind(sess),
sess._end_file.bind(sess)
)
);
}
};
});
sess._send_header_and_data(["ZFILE"], payload_array, "end_ack");
delete sess._sent_ZDATA;
return handler_setter_promise;
};
if (FORCE_ESCAPE_CTRL_CHARS) {
return this._ensure_receiver_escapes_ctrl_chars().then(doer_func);
}
return doer_func();
}
_send_header_and_data(hdr_name_and_args, data_arr, frameend) {
var bytes_hdr = this._create_header_bytes(hdr_name_and_args);
var data_bytes = this._build_subpacket_bytes(data_arr, frameend);
bytes_hdr[0].push.apply(bytes_hdr[0], data_bytes);
if (Zmodem2.DEBUG) {
this._log_header("SENDING HEADER", bytes_hdr[1]);
console.debug(this.type, "-- HEADER PAYLOAD:", frameend, data_bytes.length);
}
this._sender(bytes_hdr[0]);
this._last_sent_header = bytes_hdr[1];
}
_build_subpacket_bytes(bytes_arr, frameend) {
var subpacket = Zmodem2.Subpacket.build(bytes_arr, frameend);
return subpacket[this._subpacket_encode_func](this._zencoder);
}
_build_and_send_subpacket(bytes_arr, frameend) {
this._sender(this._build_subpacket_bytes(bytes_arr, frameend));
}
_string_to_octets(string) {
if (!this._textencoder) {
this._textencoder = new Zmodem2.Text.Encoder();
}
var uint8arr = this._textencoder.encode(string);
return Array.prototype.slice.call(uint8arr);
}
/*
Potential future support for responding to ZRPOS:
send_file_offset(offset) {
}
*/
/*
Sending logic works thus:
- ASSUME the receiver can overlap I/O (CANOVIO)
(so fail if !CANFDX || !CANOVIO)
- Sender opens the firehose … all ZCRCG (!end/!ack)
until the end, when we send a ZCRCE (end/!ack)
NB: try 8k/32k/64k chunk sizes? Looks like there’s
no need to change the packet otherwise.
*/
//TODO: Put this on a Transfer object similar to what Receive uses?
_send_interim_file_piece(bytes_obj) {
this._send_file_part(bytes_obj, "no_end_no_ack");
return Promise.resolve();
}
_ensure_we_are_sending() {
if (!this._sending_file) throw "Not sending a file currently!";
}
//This resolves once we receive ZEOF.
_end_file(bytes_obj) {
this._ensure_we_are_sending();
this._send_file_part(bytes_obj, "end_no_ack");
var sess = this;
var ret = new Promise(function(res) {
sess._sending_file = false;
sess._prepare_to_receive_ZRINIT(res);
});
this._send_header("ZEOF", this._file_offset);
this._file_offset = 0;
return ret;
}
//Called at the beginning of our session
//and also when we’re done sending a file.
_prepare_to_receive_ZRINIT(after_consume) {
this._next_header_handler = {
ZRINIT: function(hdr) {
this._consume_ZRINIT(hdr);
if (after_consume) after_consume();
}
};
}
/**
* Signal to the receiver that the ZMODEM session is wrapping up.
*
* @returns {Promise} Resolves when the receiver has responded to
* our signal that the session is over.
*/
close() {
var ok_to_close = this._last_header_name === "ZRINIT";
if (!ok_to_close) {
ok_to_close = this._last_header_name === "ZSKIP";
}
if (!ok_to_close) {
ok_to_close = this._last_sent_header.name === "ZSINIT" && this._last_header_name === "ZACK";
}
if (!ok_to_close) {
throw "Can\u2019t close; last received header was \u201C" + this._last_header_name + "\u201D";
}
var sess = this;
var ret = new Promise(function(res, rej) {
sess._next_header_handler = {
ZFIN: function() {
sess._sender(OVER_AND_OUT);
sess._sent_OO = true;
sess._on_session_end();
res();
}
};
});
this._send_header("ZFIN");
return ret;
}
_has_ended() {
return this.aborted() || !!this._sent_OO;
}
_send_file_part(bytes_obj, final_packetend) {
if (!this._sent_ZDATA) {
this._send_header("ZDATA", this._file_offset);
this._sent_ZDATA = true;
}
var obj_offset = 0;
var bytes_count = bytes_obj.length;
while (true) {
var chunk_size = Math.min(obj_offset + MAX_CHUNK_LENGTH, bytes_count) - obj_offset;
var at_end = chunk_size + obj_offset >= bytes_count;
var chunk = bytes_obj.slice(obj_offset, obj_offset + chunk_size);
if (!(chunk instanceof Array)) {
chunk = Array.prototype.slice.call(chunk);
}
this._build_and_send_subpacket(
chunk,
at_end ? final_packetend : "no_end_no_ack"
);
this._file_offset += chunk_size;
obj_offset += chunk_size;
if (obj_offset >= bytes_count) break;
}
}
_consume_first() {
if (!this._parse_and_consume_header()) {
if (this._input_buffer.join() === "67") {
throw "Receiver has fallen back to YMODEM.";
}
}
}
_on_session_end() {
this._stop_keepalive();
super._on_session_end();
}
};
Object.assign(
Zmodem2.Session.Send.prototype,
{
type: "send"
}
);
}
});
// node_modules/zmodem.js/src/zsentry.js
var require_zsentry = __commonJS({
"node_modules/zmodem.js/src/zsentry.js"(exports2, module2) {
"use strict";
var Zmodem2 = module2.exports;
Object.assign(
Zmodem2,
require_zmlib(),
require_zsession()
);
var MAX_ZM_HEX_START_LENGTH = 21;
var COMMON_ZM_HEX_START = [42, 42, 24, 66, 48];
var SENTRY_CONSTRUCTOR_REQUIRED_ARGS = [
"to_terminal",
"on_detect",
"on_retract",
"sender"
];
var Detection = class {
/**
* Not called directly.
*/
constructor(session_type, accepter, denier, checker) {
this._confirmer = accepter;
this._denier = denier;
this._is_valid = checker;
this._session_type = session_type;
}
/**
* Confirm that the detected ZMODEM sequence indicates the
* start of a ZMODEM session.
*
* @return {Session} The ZMODEM Session object (i.e., either a
* Send or Receive instance).
*/
confirm() {
return this._confirmer.apply(this, arguments);
}
/**
* Tell the Sentry that the detected bytes sequence is
* **NOT** intended to be the start of a ZMODEM session.
*/
deny() {
return this._denier.apply(this, arguments);
}
/**
* Tells whether the Detection is still valid; i.e., whether
* the Sentry has `consume()`d bytes that invalidate the
* Detection.
*
* @returns {boolean} Whether the Detection is valid.
*/
is_valid() {
return this._is_valid.apply(this, arguments);
}
/**
* Gives the session’s role.
*
* @returns {string} One of:
* - `receive`
* - `send`
*/
get_session_role() {
return this._session_type;
}
};
Zmodem2.Sentry = class ZmodemSentry {
/**
* Invoked directly. Creates a new Sentry that inspects all
* traffic before it goes to the terminal.
*
* @param {Object} options - The Sentry parameters
*
* @param {Function} options.to_terminal - Handler that sends
* traffic to the terminal object. Receives an iterable object
* (e.g., an Array) that contains octet numbers.
*
* @param {Function} options.on_detect - Handler for new
* detection events. Receives a new Detection object.
*
* @param {Function} options.on_retract - Handler for retraction
* events. Receives no input.
*
* @param {Function} options.sender - Handler that sends traffic to
* the peer. If, for example, your application uses WebSocket to talk
* to the peer, use this to send data to the WebSocket instance.
*/
constructor(options) {
if (!options) throw "Need options!";
var sentry = this;
SENTRY_CONSTRUCTOR_REQUIRED_ARGS.forEach(function(arg) {
if (!options[arg]) {
throw "Need \u201C" + arg + "\u201D!";
}
sentry["_" + arg] = options[arg];
});
this._cache = [];
}
_after_session_end() {
this._zsession = null;
}
/**
* “Consumes” a piece of input:
*
* - If there is no active or pending ZMODEM session, the text is
* all output. (This is regardless of whether we’ve got a new
* Detection.)
*
* - If there is no active ZMODEM session and the input **ends** with
* a ZRINIT or ZRQINIT, then a new Detection object is created,
* and it is passed to the “on_detect” function.
* If there was another pending Detection object, it is retracted.
*
* - If there is no active ZMODEM session and the input does NOT end
* with a ZRINIT or ZRQINIT, then any pending Detection object is
* retracted.
*
* - If there is an active ZMODEM session, the input is passed to it.
* Any non-ZMODEM data (i.e., “garbage”) parsed from the input
* is sent to output.
* If the ZMODEM session ends, any post-ZMODEM part of the input
* is sent to output.
*
* @param {number[] | ArrayBuffer} input - Octets to parse as input.
*/
consume(input) {
if (!(input instanceof Array)) {
input = Array.prototype.slice.call(new Uint8Array(input));
}
if (this._zsession) {
var session_before_consume = this._zsession;
session_before_consume.consume(input);
if (session_before_consume.has_ended()) {
if (session_before_consume.type === "receive") {
input = session_before_consume.get_trailing_bytes();
} else {
input = [];
}
} else return;
}
var new_session = this._parse(input);
var to_terminal = input;
if (new_session) {
let checker = function() {
return sentry._parsed_session === new_session;
}, accepter = function() {
if (!this.is_valid()) {
throw "Stale ZMODEM session!";
}
new_session.on("garbage", sentry._to_terminal);
new_session.on(
"session_end",
sentry._after_session_end.bind(sentry)
);
new_session.set_sender(sentry._sender);
delete sentry._parsed_session;
return sentry._zsession = new_session;
}, denier = function() {
if (!this.is_valid()) return;
};
let replacement_detect = !!this._parsed_session;
if (replacement_detect) {
if (this._parsed_session.type === new_session.type) {
to_terminal = [];
}
this._on_retract();
}
this._parsed_session = new_session;
var sentry = this;
;
;
this._on_detect(new Detection(
new_session.type,
accepter,
this._send_abort.bind(this),
checker
));
} else {
var expired_session = this._parsed_session;
this._parsed_session = null;
if (expired_session) {
if (to_terminal.length === 1 && to_terminal[0] === 67) {
this._send_abort();
}
this._on_retract();
}
}
this._to_terminal(to_terminal);
}
/**
* @return {Session|null} The sentry’s current Session object, or
* null if there is none.
*/
get_confirmed_session() {
return this._zsession || null;
}
_send_abort() {
this._sender(Zmodem2.ZMLIB.ABORT_SEQUENCE);
}
/**
* Parse an input stream and decide how much of it goes to the
* terminal or to a new Session object.
*
* This will accommodate input strings that are fragmented
* across calls to this function; e.g., if you send the first
* two bytes at the end of one parse() call then send the rest
* at the beginning of the next, parse() will recognize it as
* the beginning of a ZMODEM session.
*
* In order to keep from blocking any actual useful data to the
* terminal in real-time, this will send on the initial
* ZRINIT/ZRQINIT bytes to the terminal. They’re meant to go to the
* terminal anyway, so that should be fine.
*
* @private
*
* @param {Array|Uint8Array} array_like - The input bytes.
* Each member should be a number between 0 and 255 (inclusive).
*
* @return {Array} A two-member list:
* 0) the bytes that should be printed on the terminal
* 1) the created Session object (if any)
*/
_parse(array_like) {
var cache = this._cache;
cache.push.apply(cache, array_like);
while (true) {
let common_hex_at = Zmodem2.ZMLIB.find_subarray(cache, COMMON_ZM_HEX_START);
if (-1 === common_hex_at) break;
let before_common_hex = cache.splice(0, common_hex_at);
let zsession;
try {
zsession = Zmodem2.Session.parse(cache);
} catch (err) {
}
if (!zsession) break;
if (cache.length === 1 && cache[0] === Zmodem2.ZMLIB.XON) {
cache.shift();
}
return cache.length ? null : zsession;
}
cache.splice(MAX_ZM_HEX_START_LENGTH);
return null;
}
};
}
});
// node_modules/zmodem.js/index.js
var require_zmodem = __commonJS({
"node_modules/zmodem.js/index.js"(exports2, module2) {
"use strict";
Object.assign(
module2.exports,
require_zsentry()
);
}
});
// src/main/zmodem.ts
var zmodem_exports = {};
__export(zmodem_exports, {
attachZmodem: () => attachZmodem,
detachZmodem: () => detachZmodem,
feedZmodem: () => feedZmodem,
isZmodemActive: () => isZmodemActive,
respondZmodem: () => respondZmodem
});
module.exports = __toCommonJS(zmodem_exports);
var import_path = require("path");
var import_fs = require("fs");
var import_promises = require("fs/promises");
var Zmodem = __toESM(require_zmodem());
// src/shared/ipc.ts
var Ipc = {
// ---- PTY session lifecycle ----
// NOTE: since M2 these channels are session-generic: `id` addresses both
// local pty sessions and ssh sessions; data plane is identical.
PTY_CREATE: "pty:create",
PTY_WRITE: "pty:write",
PTY_RESIZE: "pty:resize",
PTY_KILL: "pty:kill",
/** main -> renderer broadcast: { id, data } */
PTY_DATA: "pty:data",
/** main -> renderer broadcast: { id, exitCode } */
PTY_EXIT: "pty:exit",
// ---- settings ----
SETTINGS_GET: "settings:get",
SETTINGS_SET: "settings:set",
/** main -> renderer broadcast after any save */
SETTINGS_CHANGED: "settings:changed",
// ---- fonts ----
FONTS_LIST: "fonts:list",
// ---- layouts ----
LAYOUTS_LIST: "layouts:list",
LAYOUTS_GET: "layouts:get",
LAYOUTS_SAVE: "layouts:save",
LAYOUTS_DELETE: "layouts:delete",
// ---- sessions (openSession unifies local pty and ssh) ----
SESSION_OPEN: "session:open",
/** main keeps a short replay buffer per session so late subscribers catch up */
SESSION_REPLAY: "session:replay",
// ---- ssh connections (bookmarks) ----
CONNECTIONS_LIST: "connections:list",
CONNECTIONS_SAVE: "connections:save",
CONNECTIONS_DELETE: "connections:delete",
// ---- host key verification ----
/** main -> renderer: needs user decision before connect proceeds */
HOSTKEY_PROMPT: "hostkey:prompt",
HOSTKEY_RESPOND: "hostkey:respond",
// ---- sysinfo (M3) ----
SYSINFO_START: "sysinfo:start",
SYSINFO_STOP: "sysinfo:stop",
/** main -> renderer broadcast: { id, meta } (once per session) */
SYSINFO_META: "sysinfo:meta",
/** main -> renderer broadcast: { id, sample } (poll interval) */
SYSINFO_SAMPLE: "sysinfo:sample",
// ---- sftp (M4) ----
SFTP_LIST: "sftp:list",
SFTP_MKDIR: "sftp:mkdir",
SFTP_RENAME: "sftp:rename",
SFTP_DELETE: "sftp:delete",
SFTP_CHMOD: "sftp:chmod",
SFTP_CHOWN: "sftp:chown",
SFTP_UPLOAD: "sftp:upload",
SFTP_DOWNLOAD: "sftp:download",
TRANSFER_CANCEL: "transfer:cancel",
/** main -> renderer broadcast: TransferProgressEvent */
TRANSFER_PROGRESS: "transfer:progress",
DIALOG_PICK_FILES: "dialog:pickFiles",
DIALOG_PICK_DIR: "dialog:pickDirectory",
// ---- commands + logs (M5) ----
COMMANDS_RECORD: "commands:record",
COMMANDS_HISTORY_LIST: "commands:historyList",
COMMANDS_HISTORY_CLEAR: "commands:historyClear",
COMMANDS_LIBRARY_LIST: "commands:libraryList",
COMMANDS_LIBRARY_SAVE: "commands:librarySave",
COMMANDS_LIBRARY_DELETE: "commands:libraryDelete",
LOG_START: "log:start",
LOG_STOP: "log:stop",
LOG_LIST: "log:list",
LOG_OPEN_DIR: "log:openDir",
// ---- zmodem (M6: main-process engine, ssh sessions only) ----
/** main -> renderer: remote ran rz (mode=send) or sz (mode=receive); renderer must ask the user */
ZMODEM_OFFER: "zmodem:offer",
/** renderer -> main: user answered the zmodem offer */
ZMODEM_RESPOND: "zmodem:respond",
/** main -> renderer: zmodem session finished (ok, cancelled or failed) */
ZMODEM_DONE: "zmodem:done",
// ---- misc ----
APP_INFO: "app:info"
};
// src/main/zmodem.ts
var OFFER_TIMEOUT_MS = 12e4;
var STALL_TIMEOUT_MS = 9e4;
var ABORT_BUFFER = Buffer.from(Zmodem.ZMLIB.ABORT_SEQUENCE);
var engines = /* @__PURE__ */ new Map();
function current(sessionId) {
return engines.get(sessionId);
}
function cancelTimers(engine) {
if (engine.offerTimer) clearTimeout(engine.offerTimer);
if (engine.stallTimer) clearTimeout(engine.stallTimer);
engine.offerTimer = null;
engine.stallTimer = null;
}
function emitProgress(engine, patch) {
const evt = {
transferId: engine.transferId,
kind: engine.mode === "receive" ? "zmodem-download" : "zmodem-upload",
state: "running",
file: "",
bytes: engine.bytes,
totalBytes: engine.totalBytes,
...patch
};
try {
engine.deps.broadcast(Ipc.TRANSFER_PROGRESS, evt);
} catch {
}
}
function emitDone(engine, ok, message) {
if (engine.doneEmitted) return;
engine.doneEmitted = true;
const evt = { id: engine.id, ok, message };
try {
engine.deps.broadcast(Ipc.ZMODEM_DONE, evt);
} catch {
}
}
function finalize(engine, ok, message) {
engine.active = false;
cancelTimers(engine);
if (engine.receiveStream) {
try {
engine.receiveStream.end();
} catch {
}
engine.receiveStream = null;
}
engine.session = null;
engine.detection = null;
if (!engine.progressEmitted) {
emitProgress(engine, { state: ok ? "done" : "cancelled", file: message ?? "" });
}
engine.progressEmitted = true;
if (ok) emitDone(engine, true);
else emitDone(engine, false, message ?? "\u4F20\u8F93\u5931\u8D25");
}
function makeSessionEnd(engine) {
return () => finalize(engine, true);
}
function touchActivity(engine) {
if (engine.stallTimer) clearTimeout(engine.stallTimer);
const timer = setTimeout(() => {
if (engine.stallTimer === null) return;
finalize(engine, false, "\u4F20\u8F93\u8D85\u65F6");
}, STALL_TIMEOUT_MS);
engine.stallTimer = timer;
}
function wireSession(engine) {
const session = engine.session;
if (!session) return;
const sendToPeer = (octets) => {
try {
engine.deps.writeStream(engine.id, Buffer.from(octets));
} catch {
}
};
session.set_sender(sendToPeer);
session.on("session_end", makeSessionEnd(engine));
touchActivity(engine);
}
async function sendOneFile(engine, session, file) {
const xfer = await session.send_offer({
name: file.name,
size: file.size,
mtime: new Date(file.mtimeMs),
mode: void 0
});
if (!xfer) return;
emitProgress(engine, { file: file.name, bytes: engine.bytes, totalBytes: engine.totalBytes });
await new Promise((resolve, reject) => {
const rs = (0, import_fs.createReadStream)(file.path);
rs.on("data", (chunk) => {
rs.pause();
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
try {
xfer.send(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
} catch (err) {
rs.destroy();
reject(err instanceof Error ? err : new Error("\u53D1\u9001\u5931\u8D25"));
return;
}
engine.bytes += buf.length;
emitProgress(engine, { file: file.name, bytes: engine.bytes, totalBytes: engine.totalBytes });
touchActivity(engine);
rs.resume();
});
rs.on("end", () => {
xfer.end().then(() => resolve()).catch(reject);
});
rs.on("error", reject);
});
}
async function runSend(engine, paths) {
try {
const session = engine.session;
const files = await Promise.all(
paths.map(async (p) => {
const s = await (0, import_promises.stat)(p);
return { path: p, name: (0, import_path.basename)(p), size: s.size, mtimeMs: s.mtimeMs };
})
);
engine.totalBytes = files.reduce((acc, f) => acc + f.size, 0);
for (const f of files) {
emitProgress(engine, { file: f.name, bytes: engine.bytes, totalBytes: engine.totalBytes });
await sendOneFile(engine, session, f);
}
emitProgress(engine, { file: "", bytes: engine.totalBytes, totalBytes: engine.totalBytes });
await session.close();
} catch (err) {
finalize(engine, false, err instanceof Error ? err.message : "\u4E0A\u4F20\u5931\u8D25");
}
}
function handleOffer(engine, offer) {
const details = offer.get_details();
const name2 = (0, import_path.basename)(String(details.name ?? "file"));
const dest = (0, import_path.join)(engine.dir ?? ".", name2);
if (details.size) engine.totalBytes += details.size;
const stream = (0, import_fs.createWriteStream)(dest);
engine.receiveStream = stream;
emitProgress(engine, { file: name2, bytes: engine.bytes, totalBytes: engine.totalBytes });
offer.accept({
on_input: (payload) => {
const buf = Array.isArray(payload) ? Buffer.from(payload) : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength);
if (!stream.destroyed && !stream.closed) {
stream.write(buf);
}
engine.bytes += buf.byteLength;
emitProgress(engine, { file: name2, bytes: engine.bytes, totalBytes: engine.totalBytes });
touchActivity(engine);
}
}).then(() => {
stream.end(() => {
if (engine.receiveStream === stream) engine.receiveStream = null;
emitProgress(engine, { file: name2, bytes: engine.bytes, totalBytes: engine.totalBytes });
});
}).catch((err) => {
finalize(engine, false, err instanceof Error ? err.message : "\u63A5\u6536\u5931\u8D25");
});
}
function attachZmodem(sessionId, deps) {
const engine = {
id: sessionId,
deps,
sentry: new Zmodem.Sentry({
to_terminal: (octets) => {
if (engine.active) return;
try {
deps.toTerminal(sessionId, Buffer.from(octets).toString("utf8"));
} catch {
}
},
on_detect: (detection) => {
const role = detection.get_session_role();
engine.mode = role === "receive" ? "receive" : "send";
engine.detection = detection;
engine.active = true;
engine.transferId = `zm-${sessionId}`;
emitProgress(engine, { file: "", bytes: 0, totalBytes: 0 });
try {
deps.broadcast(Ipc.ZMODEM_OFFER, { id: sessionId, mode: engine.mode });
} catch {
}
touchActivity(engine);
engine.offerTimer = setTimeout(() => {
if (engine.detection && !engine.confirmed) abortWithoutSession(engine, "\u672A\u9009\u62E9\u6587\u4EF6\uFF0C\u5DF2\u53D6\u6D88");
}, OFFER_TIMEOUT_MS);
},
on_retract: () => {
engine.detection = null;
engine.mode = null;
engine.active = false;
cancelTimers(engine);
},
sender: (octets) => {
try {
deps.writeStream(sessionId, Buffer.from(octets));
} catch {
}
}
}),
detection: null,
session: null,
active: false,
mode: null,
dir: null,
confirmed: false,
transferId: `zm-${sessionId}`,
progressEmitted: false,
doneEmitted: false,
offerTimer: null,
stallTimer: null,
receiveStream: null,
bytes: 0,
totalBytes: 0
};
engines.set(sessionId, engine);
}
function abortWithoutSession(engine, message) {
try {
engine.deps.writeStream(engine.id, ABORT_BUFFER);
} catch {
}
engine.active = false;
cancelTimers(engine);
if (!engine.progressEmitted) emitProgress(engine, { state: "cancelled", file: message });
engine.progressEmitted = true;
emitDone(engine, false, message);
}
function abortSession(engine, message) {
try {
engine.session?.abort();
} catch {
}
try {
engine.deps.writeStream(engine.id, ABORT_BUFFER);
} catch {
}
finalize(engine, false, message);
}
function feedZmodem(sessionId, data) {
const engine = current(sessionId);
if (!engine) return;
try {
engine.sentry.consume(data);
} catch (err) {
if (!engine.doneEmitted) finalize(engine, false, err instanceof Error ? err.message : "ZMODEM \u4F20\u8F93\u5F02\u5E38");
}
}
function isZmodemActive(sessionId) {
return current(sessionId)?.active ?? false;
}
function respondZmodem(resp) {
const engine = current(resp.id);
if (!engine || engine.confirmed) return;
if (resp.cancelled) {
abortWithoutSession(engine, "\u5DF2\u53D6\u6D88");
return;
}
const detection = engine.detection;
if (!detection || !detection.is_valid()) {
abortWithoutSession(engine, "\u4F1A\u8BDD\u5DF2\u5931\u6548");
return;
}
engine.confirmed = true;
engine.session = detection.confirm();
if (!engine.session) {
abortWithoutSession(engine, "\u65E0\u6CD5\u5EFA\u7ACB ZMODEM \u4F1A\u8BDD");
return;
}
wireSession(engine);
if (engine.mode === "receive") {
if (!resp.dir) {
abortSession(engine, "\u672A\u6307\u5B9A\u4FDD\u5B58\u76EE\u5F55");
return;
}
engine.dir = resp.dir;
const session = engine.session;
session.on("offer", (offer) => handleOffer(engine, offer));
void session.start().catch((err) => finalize(engine, false, err instanceof Error ? err.message : "\u63A5\u6536\u5931\u8D25"));
} else {
const paths = resp.paths ?? [];
if (paths.length === 0) {
abortSession(engine, "\u672A\u9009\u62E9\u6587\u4EF6");
return;
}
void runSend(engine, paths);
}
}
function detachZmodem(sessionId) {
const engine = current(sessionId);
if (!engine) return;
engine.active = false;
cancelTimers(engine);
if (engine.receiveStream) {
try {
engine.receiveStream.end();
} catch {
}
engine.receiveStream = null;
}
engine.session = null;
engine.detection = null;
engines.delete(sessionId);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
attachZmodem,
detachZmodem,
feedZmodem,
isZmodemActive,
respondZmodem
});
/*! Bundled license information:
crc-32/crc32.js:
(*! crc32.js (C) 2014-present SheetJS -- http://sheetjs.com *)
*/