Files
NetMesh/electron/plugins/terminalInterceptorTransport.cjs

71 lines
2.7 KiB
JavaScript
Raw Normal View History

"use strict";
const schema = require("./generated/plugin-contract.schema.json");
const { assertTerminalInterceptorFrameSchema } = require("./contractValidator.cjs");
const limits = schema.$defs.TerminalInterceptorLimits.const;
const TERMINAL_INTERCEPTOR_MAX_CHUNK_BYTES = limits.maxChunkBytes;
const TERMINAL_INTERCEPTOR_MAX_WINDOW_BYTES = limits.maxWindowBytes;
const arrayBufferByteLength = Object.getOwnPropertyDescriptor(
ArrayBuffer.prototype,
"byteLength",
)?.get;
function copyOwnDataRecord(value, label) {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
throw new TypeError(`${label} must be a plain object`);
}
const copy = {};
for (const key of Object.keys(value)) {
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (!descriptor || !("value" in descriptor)) {
throw new TypeError(`${label} must contain data properties only`);
}
copy[key] = descriptor.value;
}
return copy;
}
function transferredByteLength(value) {
if (!arrayBufferByteLength) throw new TypeError("ArrayBuffer byteLength getter is unavailable");
try {
const byteLength = arrayBufferByteLength.call(value);
// The intrinsic getter reports zero for a detached zero-length buffer as
// well as for a valid empty buffer. Constructing a view distinguishes the
// two and also works for ArrayBuffers created in another realm.
new Uint8Array(value);
return byteLength;
} catch {
throw new TypeError("Terminal interceptor transfer requires a real, attached ArrayBuffer");
}
}
function createTerminalInterceptorEnvelope(frameValue, transfer) {
const frame = copyOwnDataRecord(frameValue, "Terminal interceptor frame");
assertTerminalInterceptorFrameSchema(frame);
const requiresTransfer = frame.type === "netcatty:terminal-interceptor:chunk"
|| (frame.type === "netcatty:terminal-interceptor:result" && frame.status === "ok");
if (!requiresTransfer) {
if (transfer !== undefined) {
throw new TypeError("This terminal interceptor frame must not include a transferred buffer");
}
return Object.freeze({ frame: Object.freeze(frame) });
}
if (transfer === undefined) {
throw new TypeError("This terminal interceptor frame requires a transferred ArrayBuffer");
}
const byteLength = transferredByteLength(transfer);
if (byteLength !== frame.byteLength) {
throw new RangeError(
`Terminal interceptor byteLength mismatch: declared ${frame.byteLength}, received ${byteLength}`,
);
}
return Object.freeze({ frame: Object.freeze(frame), transfer });
}
module.exports = {
TERMINAL_INTERCEPTOR_MAX_CHUNK_BYTES,
TERMINAL_INTERCEPTOR_MAX_WINDOW_BYTES,
createTerminalInterceptorEnvelope,
};