UNPKG

data-transport

Version:
150 lines (141 loc) 4.57 kB
import type { Instance } from 'simple-peer'; import type { BaseInteraction, ListenerOptions, SendOptions, TransportOptions, } from '../interface'; import { Transport } from '../transport'; const MAX_CHUNK_SIZE = 1024 * 60; const BUFFER_FULL_THRESHOLD = 1024 * 64; const EXPIRED_TIME = 1000 * 60; export interface WebRTCTransportOptions extends Partial<TransportOptions> { peer: Instance; } interface WebRTCTransportSendOptions extends SendOptions<{}> { chunkId?: number; length?: number; } abstract class WebRTCTransport< T extends BaseInteraction = any > extends Transport<T> { private receiveBuffer = new Map<string, { data: any[]; timestamp: number }>(); constructor(_options: WebRTCTransportOptions) { const { peer, listener = (callback) => { const handler = (data: string) => { const message: WebRTCTransportSendOptions = JSON.parse(data); const key = Object.prototype.hasOwnProperty.call(message, 'request') ? 'request' : 'response'; const buffer = this.receiveBuffer.get( message.__DATA_TRANSPORT_UUID__ ) ?? { data: [], timestamp: Date.now(), }; this.receiveBuffer.set(message.__DATA_TRANSPORT_UUID__, buffer); buffer.data[message.chunkId!] = message[key]; buffer.data.length = message.length!; buffer.timestamp = Date.now(); const isComplete = buffer.data.filter((item) => item).length === message.length; if (isComplete) { const data = JSON.parse(buffer.data.join('')); message[key] = key === 'request' ? data : data[0]; delete message.length; callback(message as ListenerOptions); this.receiveBuffer.delete(message.__DATA_TRANSPORT_UUID__); for (const [id, item] of this.receiveBuffer) { if (Date.now() - item.timestamp > EXPIRED_TIME) { this.receiveBuffer.delete(id); } } } }; peer.on('data', handler); return () => { peer.off('data', handler); }; }, sender = (message: WebRTCTransportSendOptions) => { const key = Object.prototype.hasOwnProperty.call(message, 'request') ? 'request' : 'response'; message[key] = JSON.stringify( key === 'request' ? message.request : typeof message.response !== 'undefined' ? [message.response] : [] ); let chunkId = 0; const allChunksSize = Math.ceil( (message[key] as string).length / MAX_CHUNK_SIZE ); while ((message[key] as string).length > 0) { const data = { ...message, [key]: (message[key] as string).slice(0, MAX_CHUNK_SIZE), chunkId, length: allChunksSize, }; peer.send(JSON.stringify(data)); message[key] = (message[key] as string).slice(MAX_CHUNK_SIZE); chunkId += 1; } }, ...options } = _options; super({ ...options, listener, sender, }); if (peer) { let webRTCPaused = false; const webRTCMessageQueue: any[] = []; const peerSend = peer.send.bind(peer); const sendMessageQueued = () => { webRTCPaused = false; let message = webRTCMessageQueue.shift(); while (message) { if ( (peer as any)._channel.bufferedAmount && (peer as any)._channel.bufferedAmount > BUFFER_FULL_THRESHOLD ) { webRTCPaused = true; webRTCMessageQueue.unshift(message); const listener = () => { (peer as any)._channel.removeEventListener( 'bufferedamountlow', listener ); sendMessageQueued(); }; (peer as any)._channel.addEventListener( 'bufferedamountlow', listener ); return; } try { peerSend(message); message = webRTCMessageQueue.shift(); } catch (error: any) { throw new Error(`Error send message to peer: ${error.message}`); } } }; peer.send = function (chunk: any) { webRTCMessageQueue.push(chunk); if (webRTCPaused) { return; } sendMessageQueued(); }; } } } export { WebRTCTransport };