UNPKG

@cliz/inlets

Version:
456 lines (455 loc) 19.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProtocolAdapterFactory = exports.NewProtocolAdapter = exports.LegacyProtocolAdapter = exports.parseBinaryMessage = exports.buildBinaryMessage = exports.MessageFlags = exports.MessageType = void 0; const type_1 = require("./type"); const utils_1 = require("../../utils"); const compression_1 = require("../../utils/compression"); const stream_manager_1 = require("./stream-manager"); const flow_controller_1 = require("./flow-controller"); var MessageType; (function (MessageType) { MessageType[MessageType["CONTROL"] = 0] = "CONTROL"; MessageType[MessageType["HTTP_REQUEST"] = 1] = "HTTP_REQUEST"; MessageType[MessageType["HTTP_RESPONSE"] = 2] = "HTTP_RESPONSE"; MessageType[MessageType["TCP_DATA"] = 3] = "TCP_DATA"; MessageType[MessageType["TCP_OPEN"] = 4] = "TCP_OPEN"; MessageType[MessageType["TCP_CLOSE"] = 5] = "TCP_CLOSE"; MessageType[MessageType["FLOW_CONTROL"] = 6] = "FLOW_CONTROL"; })(MessageType = exports.MessageType || (exports.MessageType = {})); var MessageFlags; (function (MessageFlags) { MessageFlags[MessageFlags["FIN"] = 1] = "FIN"; MessageFlags[MessageFlags["ACK"] = 2] = "ACK"; MessageFlags[MessageFlags["BACKPRESSURE"] = 4] = "BACKPRESSURE"; })(MessageFlags = exports.MessageFlags || (exports.MessageFlags = {})); function buildBinaryMessage(msg) { const streamIdBuffer = Buffer.from(msg.streamId, 'utf8'); const streamIdLength = streamIdBuffer.length; if (streamIdLength > 255) { throw new Error(`Stream ID too long: ${streamIdLength} bytes (max 255)`); } const headerSize = 1 + 1 + streamIdLength + 4 + 1 + 4; const totalSize = headerSize + msg.data.length; const buffer = Buffer.allocUnsafe(totalSize); let offset = 0; buffer.writeUInt8(msg.type, offset); offset += 1; buffer.writeUInt8(streamIdLength, offset); offset += 1; for (let i = 0; i < streamIdLength; i++) { buffer[offset + i] = streamIdBuffer[i]; } offset += streamIdLength; buffer.writeUInt32BE(msg.sequence, offset); offset += 4; buffer.writeUInt8(msg.flags, offset); offset += 1; buffer.writeUInt32BE(msg.data.length, offset); offset += 4; for (let i = 0; i < msg.data.length; i++) { buffer[offset + i] = msg.data[i]; } return buffer; } exports.buildBinaryMessage = buildBinaryMessage; function parseBinaryMessage(buffer) { if (buffer.length < 11) { throw new Error(`Message too short: ${buffer.length} bytes (min 11)`); } let offset = 0; const type = buffer.readUInt8(offset); offset += 1; const streamIdLength = buffer.readUInt8(offset); offset += 1; if (buffer.length < offset + streamIdLength) { throw new Error(`Message too short for stream ID: ${buffer.length} bytes`); } const streamId = buffer.slice(offset, offset + streamIdLength).toString('utf8'); offset += streamIdLength; const sequence = buffer.readUInt32BE(offset); offset += 4; const flags = buffer.readUInt8(offset); offset += 1; const dataLength = buffer.readUInt32BE(offset); offset += 4; if (buffer.length < offset + dataLength) { throw new Error(`Message too short for data: ${buffer.length} bytes, expected ${offset + dataLength}`); } const data = buffer.slice(offset, offset + dataLength); return { type, streamId, sequence, flags, data, }; } exports.parseBinaryMessage = parseBinaryMessage; class LegacyProtocolAdapter { constructor(socket, isClient = false) { this.socket = socket; this.isClient = isClient; this.tcpDataHandlers = new Set(); this.setupEventListeners(); } setupEventListeners() { if (this.isClient) { this.socket.on('request', async (data) => { if (this.httpRequestHandler) { const decoded = await utils_1.dataProcessor.client.onRequest(data.data); await this.httpRequestHandler(data.id, Buffer.from(decoded)); } }); } else { this.socket.on('response', async (response) => { if (this.httpResponseHandler) { const decoded = await utils_1.dataProcessor.server.onResponse(response.data); await this.httpResponseHandler(response.id, Buffer.from(decoded, 'base64')); } }); } } async sendHTTPRequest(id, data) { const base64Data = data.toString('base64'); const processed = await utils_1.dataProcessor.server.request(base64Data); this.socket.emit('request', { id, data: processed, }); } async sendHTTPResponse(id, data) { const base64Data = data.toString('base64'); const processed = await utils_1.dataProcessor.client.response(base64Data); this.socket.emit('response', { id, data: processed, }); } async sendTCPData(streamId, data) { throw new Error('TCP data transmission not supported in legacy protocol'); } onHTTPRequest(handler) { this.httpRequestHandler = handler; } onHTTPResponse(handler) { this.httpResponseHandler = handler; } onTCPData(handler) { this.tcpDataHandlers.add(handler); return () => this.tcpDataHandlers.delete(handler); } } exports.LegacyProtocolAdapter = LegacyProtocolAdapter; class NewProtocolAdapter { constructor(socket, capabilities, isClient = false) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; this.socket = socket; this.capabilities = capabilities; this.isClient = isClient; this.tcpDataHandlers = new Set(); this.sequenceCounter = new Map(); this.compressionAlgorithm = 'brotli'; this.chunkSize = 64 * 1024; if ((_b = (_a = capabilities.features) === null || _a === void 0 ? void 0 : _a.compression) === null || _b === void 0 ? void 0 : _b.preferred) { this.compressionAlgorithm = capabilities.features.compression.preferred; } else if ((_e = (_d = (_c = capabilities.features) === null || _c === void 0 ? void 0 : _c.compression) === null || _d === void 0 ? void 0 : _d.algorithms) === null || _e === void 0 ? void 0 : _e.length) { this.compressionAlgorithm = capabilities.features.compression.algorithms[0]; } if ((_g = (_f = capabilities.features) === null || _f === void 0 ? void 0 : _f.chunkSize) === null || _g === void 0 ? void 0 : _g.default) { this.chunkSize = capabilities.features.chunkSize.default; } if (capabilities.flags & type_1.CapabilityFlags.STREAMING) { this.streamManager = new stream_manager_1.StreamManager(this.chunkSize); } if (capabilities.flags & type_1.CapabilityFlags.FLOW_CONTROL) { const windowSize = ((_j = (_h = capabilities.features) === null || _h === void 0 ? void 0 : _h.flowControl) === null || _j === void 0 ? void 0 : _j.windowSize) || 1024 * 1024; this.flowController = new flow_controller_1.FlowController(windowSize, (streamId, pause) => { this.handleBackpressure(streamId, pause); }); } this.setupEventListeners(); } getNextSequence(streamId) { const current = this.sequenceCounter.get(streamId) || 0; const next = current + 1; this.sequenceCounter.set(streamId, next); return next; } handleBackpressure(streamId, pause) { if (this.streamManager) { const stream = this.streamManager.getStream(streamId); if (stream) { if (pause) { stream.pause(); } else { stream.resume(); } } } } async handleBinaryMessage(type, streamId, data, flags, sequence) { const isLast = !!(flags & MessageFlags.FIN); const useStreaming = this.capabilities.flags & type_1.CapabilityFlags.STREAMING; if (this.flowController) { if (!this.flowController.receive(streamId, data.length)) { await new Promise(resolve => setTimeout(resolve, 100)); } } const shouldStream = this.shouldUseStreaming(type); if (shouldStream && this.streamManager) { this.streamManager.addChunk(streamId, sequence, data, isLast); if (isLast) { return; } } else { let payload = data; if (this.shouldUseCompression(type)) { payload = await (0, compression_1.decompressBuffer)(payload, this.compressionAlgorithm); } if (type === MessageType.HTTP_REQUEST && this.isClient && this.httpRequestHandler) { await this.httpRequestHandler(streamId, payload); } else if (type === MessageType.HTTP_RESPONSE && !this.isClient && this.httpResponseHandler) { await this.httpResponseHandler(streamId, payload); } else if (type === MessageType.TCP_DATA && this.tcpDataHandlers.size > 0) { for (const handler of this.tcpDataHandlers) { await handler(streamId, payload); } } if (this.flowController) { this.flowController.releaseReceiveWindow(streamId, data.length); } } } setupEventListeners() { if (this.isClient) { this.socket.on('request', async (data) => { if (this.httpRequestHandler) { try { const messageBuffer = Buffer.from(data.data, 'base64'); const binaryMsg = parseBinaryMessage(messageBuffer); if (binaryMsg.type === MessageType.HTTP_REQUEST) { if (this.shouldUseStreaming(binaryMsg.type) && this.streamManager) { const stream = this.streamManager.getStream(binaryMsg.streamId); if (!stream) { this.streamManager.createStream(binaryMsg.streamId, async (completeData) => { let payload = completeData; if (this.shouldUseCompression(binaryMsg.type)) { payload = await (0, compression_1.decompressBuffer)(completeData, this.compressionAlgorithm); } if (this.httpRequestHandler) { await this.httpRequestHandler(binaryMsg.streamId, payload); } if (this.flowController) { this.flowController.releaseReceiveWindow(binaryMsg.streamId, completeData.length); } }, (error) => { console.error('[NewProtocolAdapter] Stream error:', error); }); } } await this.handleBinaryMessage(binaryMsg.type, binaryMsg.streamId, binaryMsg.data, binaryMsg.flags, binaryMsg.sequence); } } catch (error) { console.error('[NewProtocolAdapter] Failed to parse binary message:', error); } } }); } else { this.socket.on('response', async (response) => { if (this.httpResponseHandler) { try { const messageBuffer = Buffer.from(response.data, 'base64'); const binaryMsg = parseBinaryMessage(messageBuffer); if (binaryMsg.type === MessageType.HTTP_RESPONSE) { if (this.shouldUseStreaming(binaryMsg.type) && this.streamManager) { const stream = this.streamManager.getStream(binaryMsg.streamId); if (!stream) { this.streamManager.createStream(binaryMsg.streamId, async (completeData) => { let payload = completeData; if (this.shouldUseCompression(binaryMsg.type)) { payload = await (0, compression_1.decompressBuffer)(completeData, this.compressionAlgorithm); } if (this.httpResponseHandler) { await this.httpResponseHandler(binaryMsg.streamId, payload); } if (this.flowController) { this.flowController.releaseReceiveWindow(binaryMsg.streamId, completeData.length); } }, (error) => { console.error('[NewProtocolAdapter] Stream error:', error); }); } } await this.handleBinaryMessage(binaryMsg.type, binaryMsg.streamId, binaryMsg.data, binaryMsg.flags, binaryMsg.sequence); } } catch (error) { console.error('[NewProtocolAdapter] Failed to parse binary message:', error); } } }); } this.socket.on('tcp:data', async (data) => { if (this.tcpDataHandlers.size > 0) { try { const messageBuffer = Buffer.from(data.data, 'base64'); const binaryMsg = parseBinaryMessage(messageBuffer); if (binaryMsg.type === MessageType.TCP_DATA) { await this.handleBinaryMessage(binaryMsg.type, binaryMsg.streamId, binaryMsg.data, binaryMsg.flags, binaryMsg.sequence); } } catch (error) { console.error('[NewProtocolAdapter] Failed to parse TCP binary message:', error); } } }); } shouldUseStreaming(type) { if (!this.streamManager) { return false; } if (!(this.capabilities.flags & type_1.CapabilityFlags.STREAMING)) { return false; } if (type === MessageType.TCP_DATA) { return false; } return true; } shouldUseCompression(type) { if (!(this.capabilities.flags & type_1.CapabilityFlags.COMPRESSION)) { return false; } if (type === MessageType.TCP_DATA) { return false; } return true; } async buildAndSendMessage(type, streamId, data, flags = 0) { const canStream = this.shouldUseStreaming(type); const shouldChunk = canStream && data.length > this.chunkSize; if (shouldChunk && this.streamManager) { await this.sendStreaming(type, streamId, data); } else { await this.sendDirect(type, streamId, data, flags); } } async sendDirect(type, streamId, data, flags) { if (this.flowController) { this.flowController.initializeStream(streamId); } let processedData = data; if (this.shouldUseCompression(type)) { processedData = await (0, compression_1.compressBuffer)(processedData, this.compressionAlgorithm); } if (this.flowController) { while (!this.flowController.canSend(streamId, processedData.length)) { await new Promise(resolve => setTimeout(resolve, 50)); } this.flowController.send(streamId, processedData.length); } const sequence = this.getNextSequence(streamId); const message = buildBinaryMessage({ type, streamId, sequence, flags: flags | MessageFlags.FIN, data: processedData, }); await this.emitMessage(type, streamId, message); } async sendStreaming(type, streamId, data) { if (!this.streamManager) { return this.sendDirect(type, streamId, data, 0); } if (this.flowController) { this.flowController.initializeStream(streamId); } let processedData = data; if (this.shouldUseCompression(type)) { processedData = await (0, compression_1.compressBuffer)(processedData, this.compressionAlgorithm); } const chunks = this.streamManager.splitIntoChunks(processedData, this.chunkSize); for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; const isLast = i === chunks.length - 1; if (this.flowController) { while (!this.flowController.canSend(streamId, chunk.length)) { await new Promise(resolve => setTimeout(resolve, 50)); } this.flowController.send(streamId, chunk.length); } const sequence = i; const flags = isLast ? MessageFlags.FIN : 0; const message = buildBinaryMessage({ type, streamId, sequence, flags, data: chunk, }); await this.emitMessage(type, streamId, message); } } async emitMessage(type, streamId, message) { const base64Data = message.toString('base64'); if (type === MessageType.HTTP_REQUEST) { this.socket.emit('request', { id: streamId, data: base64Data, }); } else if (type === MessageType.HTTP_RESPONSE) { this.socket.emit('response', { id: streamId, data: base64Data, }); } else if (type === MessageType.TCP_DATA) { this.socket.emit('tcp:data', { streamId: streamId, data: base64Data, }); } } async sendHTTPRequest(id, data) { await this.buildAndSendMessage(MessageType.HTTP_REQUEST, id, data); } async sendHTTPResponse(id, data) { await this.buildAndSendMessage(MessageType.HTTP_RESPONSE, id, data); } async sendTCPData(streamId, data) { await this.buildAndSendMessage(MessageType.TCP_DATA, streamId, data, 0); } onHTTPRequest(handler) { this.httpRequestHandler = handler; } onHTTPResponse(handler) { this.httpResponseHandler = handler; } onTCPData(handler) { this.tcpDataHandlers.add(handler); return () => this.tcpDataHandlers.delete(handler); } } exports.NewProtocolAdapter = NewProtocolAdapter; class ProtocolAdapterFactory { static create(socket, capabilities, isClient = false) { if (capabilities) { return new NewProtocolAdapter(socket, capabilities, isClient); } else { return new LegacyProtocolAdapter(socket, isClient); } } } exports.ProtocolAdapterFactory = ProtocolAdapterFactory;