UNPKG

remote-web-streams

Version:

Web streams that work across web workers and iframes.

144 lines (140 loc) 4.04 kB
function fromReadablePort(port) { return new ReadableStream(new MessagePortSource(port)); } class MessagePortSource { constructor(_port) { this._port = _port; this._port.onmessage = (event) => this._onMessage(event.data); } start(controller) { this._controller = controller; } pull(controller) { const message = { type: 'pull' }; this._port.postMessage(message); } cancel(reason) { const message = { type: 'error', reason }; this._port.postMessage(message); this._port.close(); } _onMessage(message) { switch (message.type) { case 'write': // enqueue() will call pull() if needed when there's no backpressure this._controller.enqueue(message.chunk); break; case 'abort': this._controller.error(message.reason); this._port.close(); break; case 'close': this._controller.close(); this._port.close(); break; } } } function fromWritablePort(port, options) { return new WritableStream(new MessagePortSink(port, options)); } class MessagePortSink { constructor(_port, options = {}) { this._port = _port; this._transferChunk = options.transferChunk; this._resetReady(); this._port.onmessage = (event) => this._onMessage(event.data); } start(controller) { this._controller = controller; // Apply initial backpressure return this._readyPromise; } write(chunk, controller) { const message = { type: 'write', chunk }; // Send chunk, optionally transferring its contents let transferList = this._transferChunk ? this._transferChunk(chunk) : []; if (transferList.length) { this._port.postMessage(message, transferList); } else { this._port.postMessage(message); } // Assume backpressure after every write, until sender pulls this._resetReady(); // Apply backpressure return this._readyPromise; } close() { const message = { type: 'close' }; this._port.postMessage(message); this._port.close(); } abort(reason) { const message = { type: 'abort', reason }; this._port.postMessage(message); this._port.close(); } _onMessage(message) { switch (message.type) { case 'pull': this._resolveReady(); break; case 'error': this._onError(message.reason); break; } } _onError(reason) { this._controller.error(reason); this._rejectReady(reason); this._port.close(); } _resetReady() { this._readyPromise = new Promise((resolve, reject) => { this._readyResolve = resolve; this._readyReject = reject; }); this._readyPending = true; } _resolveReady() { this._readyResolve(); this._readyPending = false; } _rejectReady(reason) { if (!this._readyPending) { this._resetReady(); } this._readyPromise.catch(() => { }); this._readyReject(reason); this._readyPending = false; } } class RemoteReadableStream { constructor() { const channel = new MessageChannel(); this.writablePort = channel.port1; this.readable = fromReadablePort(channel.port2); } } class RemoteWritableStream { constructor(options) { const channel = new MessageChannel(); this.readablePort = channel.port1; this.writable = fromWritablePort(channel.port2, options); } } export { RemoteReadableStream, RemoteWritableStream, fromReadablePort, fromWritablePort };