UNPKG

@stream-toolbox/tunnel

Version:

Forward data bidirectionally between two duplexes, like `a.pipe(b).pipe(a)`, but with better handling for various cases.

144 lines (143 loc) 4.43 kB
"use strict"; var stream_1 = require("stream"); function nomalizeError(err, causedBy) { var descriptors = Object.getOwnPropertyDescriptors(err); descriptors.causedBy = { enumerable: false, configurable: false, writable: false, value: causedBy, }; return Object.create(Object.getPrototypeOf(err), descriptors); } function nomalizeDuplex(d) { if (d instanceof stream_1.Duplex) { return Object.defineProperties({ rs: d, ws: d, _native: true, }, { allowHalfOpen: { get: function () { return d.allowHalfOpen; }, }, allowPipeHalfOpen: { get: function () { return d.allowPipeHalfOpen; }, }, }); } return d; } function tunnel(a, b) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } var _a = nomalizeDuplex(a); var _b = nomalizeDuplex(b); var condition; var callback; switch (args.length) { case 0: condition = 15; break; case 1: if (typeof args[0] === "number") { condition = args[0]; } else if (typeof args[0] === "function") { condition = 15; callback = args[0]; } else { throw new Error("invalid params"); } break; case 2: if (typeof args[0] === "number" && typeof args[1] === "function") { condition = args[0]; callback = args[1]; } else { throw new Error("invalid params"); } break; default: throw new Error("invalid params"); } var start = Date.now(); var resolve; var reject; var promise; if (callback) { resolve = function () { return callback(null, Date.now() - start); }; reject = function (err) { return callback(err, Date.now() - start); }; } else { promise = new Promise(function (_resolve, _reject) { resolve = function () { return _resolve(Date.now() - start); }; reject = function (err) { return _reject(err); }; }); } var failed = 0; var constructed = 0; var settled = 0; var cleanA2B = pipe(_a, _b, 2); var cleanB2A = pipe(_b, _a, 0); var onDone = function (signal) { if ((constructed |= signal) === condition && ++settled === 1) { cleanA2B(); cleanB2A(); resolve(); } }; var onFail = function (err) { if (++failed === 1 && ++settled === 1) { _a.rs.destroyed || _a.rs.destroy(); _a.ws.destroyed || _a.ws.destroy(); _b.rs.destroyed || _b.rs.destroy(); _b.ws.destroyed || _b.ws.destroy(); cleanA2B(); cleanB2A(); reject(err); } }; function pipe(x, y, signalMove) { var onReadData = function (chunk) { y.ws.write(chunk) || x.rs.pause(); }; var onReadEnd = function () { if (!x.allowPipeHalfOpen) { y.ws.end(); } if (!x.allowHalfOpen && !x._native) { x.ws.end(); } onDone(2 << signalMove); }; var onReadError = function (err) { onFail(nomalizeError(err, x.rs)); }; var onWriteDrain = function () { y.rs.resume(); }; var onWriteFinish = function () { y.rs.push(null); onDone(1 << signalMove); }; var onWriteError = function (err) { onFail(nomalizeError(err, x.ws)); }; x.rs.on("data", onReadData).on("end", onReadEnd).on("error", onReadError); x.ws.on("drain", onWriteDrain).on("finish", onWriteFinish).on("error", onWriteError); return function () { x.rs.removeListener("data", onReadData).removeListener("end", onReadEnd).removeListener("error", onReadError); x.ws.removeListener("drain", onWriteDrain).removeListener("finish", onWriteFinish).removeListener("error", onWriteError); }; } return promise; } module.exports = tunnel;