@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
JavaScript
;
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;