@bcjordan/node-datachannel
Version:
libdatachannel node bindings
98 lines (81 loc) • 2.99 kB
JavaScript
// eslint-disable-next-line @typescript-eslint/no-var-requires
const stream = require('stream');
/**
* Turns a node-datachannel DataChannel into a real Node.js stream, complete with buffering,
* backpressure (up to a point - if the buffer fills up, messages are dropped), and
* support for piping data elsewhere.
*
* Read & written data may be either UTF-8 strings or Buffers - this difference exists at
* the protocol level, and is preserved here throughout.
*/
module.exports = class DataChannelStream extends stream.Duplex {
constructor(rawChannel, streamOptions) {
super({
allowHalfOpen: false, // Default to autoclose on end().
...streamOptions,
objectMode: true, // Preserve the string/buffer distinction (WebRTC treats them differently)
});
this._rawChannel = rawChannel;
this._readActive = true;
rawChannel.onMessage((msg) => {
if (!this._readActive) return; // If the buffer is full, drop messages.
// If the push is rejected, we pause reading until the next call to _read().
this._readActive = this.push(msg);
});
// When the DataChannel closes, the readable & writable ends close
rawChannel.onClosed(() => {
this.push(null);
this.destroy();
});
rawChannel.onError((errMsg) => {
this.destroy(new Error(`DataChannel error: ${errMsg}`));
});
// Buffer all writes until the DataChannel opens
if (!rawChannel.isOpen()) {
this.cork();
rawChannel.onOpen(() => this.uncork());
}
}
_read() {
// Stop dropping messages, if the buffer filling up meant we were doing so before.
this._readActive = true;
}
_write(chunk, encoding, callback) {
let sentOk;
try {
if (Buffer.isBuffer(chunk)) {
sentOk = this._rawChannel.sendMessageBinary(chunk);
} else if (typeof chunk === 'string') {
sentOk = this._rawChannel.sendMessage(chunk);
} else {
const typeName = chunk.constructor.name || typeof chunk;
throw new Error(`Cannot write ${typeName} to DataChannel stream`);
}
} catch (err) {
return callback(err);
}
if (sentOk) {
callback(null);
} else {
callback(new Error('Failed to write to DataChannel'));
}
}
_final(callback) {
if (!this.allowHalfOpen) this.destroy();
callback(null);
}
_destroy(maybeErr, callback) {
// When the stream is destroyed, we close the DataChannel.
this._rawChannel.close();
callback(maybeErr);
}
get label() {
return this._rawChannel.getLabel();
}
get id() {
return this._rawChannel.getId();
}
get protocol() {
return this._rawChannel.getProtocol();
}
};