UNPKG

@litert/televoke

Version:
205 lines 8.42 kB
"use strict"; /** * Copyright 2025 Angus.Fenying <fenying@litert.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createLegacyHttpGateway = createLegacyHttpGateway; exports.createCustomLegacyHttpGateway = createCustomLegacyHttpGateway; const Shared = require("../../shared"); const node_events_1 = require("node:events"); const LegacyHttp_Transporter_1 = require("./LegacyHttp.Transporter"); const v1 = require("../../shared/Encodings/v1"); const encoder = new v1.TvEncoderV1(); const INVALID_REQUEST_RESPONSE = Buffer.from(encoder.encodeApiErrorResponse('null', Shared.Encodings.v1.EResponseCode.MALFORMED_ARGUMENTS, '"INVALID REQUEST"', 0)); function refuseBadRequest(resp) { try { resp.writeHead(400, { 'content-length': INVALID_REQUEST_RESPONSE.byteLength, }); const socket = resp.socket; resp.end(INVALID_REQUEST_RESPONSE); socket.destroy(); } catch { // do nothing. } } class LegacyHttpGateway extends node_events_1.EventEmitter { constructor(registerListener, _server) { super(); this._server = _server; this._onRequest = (req, resp) => { if (req.method !== 'POST' || !req.headers['content-length']) { refuseBadRequest(resp); this.emit('error', new Shared.errors.invalid_packet({ reason: 'invalid_request', data: { 'method': req.method, 'headers': req.headers, 'url': req.url, }, })); return; } const length = parseInt(req.headers['content-length']); if (!Number.isSafeInteger(length) || length > v1.MAX_PACKET_SIZE) { // Maximum request packet is 64MB refuseBadRequest(resp); this.emit('error', new Shared.errors.invalid_packet({ reason: 'invalid_packet_length', data: { 'length': length, 'max': v1.MAX_PACKET_SIZE, }, })); return; } const buf = Buffer.allocUnsafe(length); let offset = 0; const recvAt = Date.now(); resp.on('error', (e) => this.emit('error', e)); req.on('error', (e) => this.emit('error', e)) .on('data', (chunk) => { const index = offset; offset += chunk.byteLength; if (offset > length) { refuseBadRequest(resp); this.emit('error', new Shared.errors.invalid_packet({ reason: 'length_exceeded', recv: offset, expected: length, })); return; } chunk.copy(buf, index); }) .on('end', () => { let input; if (offset !== length) { refuseBadRequest(resp); return; } try { input = JSON.parse(buf); } catch (e) { refuseBadRequest(resp); this.emit('error', new Shared.errors.invalid_packet({ reason: 'invalid_json', }, e)); return; } if (typeof input?.api !== 'string') { refuseBadRequest(resp); this.emit('error', new Shared.errors.invalid_packet({ reason: 'malformed_json', })); return; } this._server.processLegacyApi((result) => { if (!resp.writable) { return; } if (result instanceof Shared.TelevokeError) { if (result instanceof Shared.errors.app_error) { this._sendResponse(resp, encoder.encodeApiErrorResponse(input.rid, v1.EResponseCode.FAILURE, result.message, recvAt)); } else if (result instanceof Shared.errors.api_not_found) { this._sendResponse(resp, encoder.encodeApiErrorResponse(input.rid, v1.EResponseCode.API_NOT_FOUND, 'null', recvAt)); } else if (result instanceof Shared.ProtocolError) { this._sendResponse(resp, encoder.encodeApiErrorResponse(input.rid, v1.EResponseCode.SYSTEM_ERROR, JSON.stringify({ name: result.name, message: result.message, data: result.data, }), recvAt)); } else { this._sendResponse(resp, encoder.encodeApiErrorResponse(input.rid, v1.EResponseCode.SYSTEM_ERROR, 'null', recvAt)); } return; } let data; try { data = encoder.encodeApiOkResponse(input.rid, result ?? null, recvAt); } catch (e) { data = encoder.encodeApiErrorResponse(input.rid, v1.EResponseCode.SYSTEM_ERROR, 'null', recvAt); this.emit('error', new Shared.errors.unprocessable_error({ api: input.api }, e)); } this._sendResponse(resp, data); }, input.api, input.args, new LegacyHttp_Transporter_1.LegacyHttpTransporter(req)); }); }; if (this._server.router.encoding !== 'json') { throw new TypeError('Legacy HTTP gateway only supports JSON encoding'); } this._listener = registerListener({ onErrorCallback: (e) => this.emit('error', e), onRequestCallback: this._onRequest, }); } _sendResponse(resp, data) { try { resp.setHeader('content-length', Buffer.byteLength(data)); resp.end(data); } catch (e) { this.emit('error', e); } } get running() { return this._listener.running; } async start() { if (this.running) { return; } await this._listener.start?.(); } async stop() { if (!this.running) { return Promise.resolve(); } await this._listener.stop?.(); } } /** * Create a legacy HTTP gateway, binding to a built-in simple HTTP server. * * > When using built-in HTTP server, the api will ignore headers, path and query string in the URL. * * @param listener The built-in HTTP listener to bind to. * @param server The server to process the requests. */ function createLegacyHttpGateway(listener, server) { return new LegacyHttpGateway((o) => { listener.on('error', o.onErrorCallback); listener.setLegacyApiProcessor(o.onRequestCallback); return listener; }, server); } /** * Create a legacy HTTP gateway, binding to a custom HTTP server. * * > When using a custom HTTP server, it's able to preprocess the request before passing to the server, like * > authentication, rate limiting, etc. * * @param registerListener The function to register the listener to the custom HTTP server. * @param server The server to process the requests. */ function createCustomLegacyHttpGateway(registerListener, server) { return new LegacyHttpGateway(registerListener, server); } //# sourceMappingURL=LegacyHttp.Server.js.map