- 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
3372 lines
113 KiB
JavaScript
3372 lines
113 KiB
JavaScript
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 *)
|
||
*/
|