@litert/televoke
Version:
A simple RPC service framework.
197 lines • 7.69 kB
JavaScript
"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.createLegacyHttpClient = createLegacyHttpClient;
const $Http = require("http");
const $Https = require("https");
const Shared = require("../../shared");
const v1 = require("../../shared/Encodings/v1");
const node_events_1 = require("node:events");
const HTTP_HEADER_CONTENT_LENGTH = 'content-length';
const HTTP_HEADER_TV_VER = 'x-tv-ver';
const DEFAULT_MAX_CONNECTIONS = 100;
const DEFAULT_TIMEOUT = 30000;
const disabledStreamManager = Shared.createDisabledStreamManagerFactory()(null);
class TvLegacyHttpClient extends node_events_1.EventEmitter {
constructor(_request, _opts, _agent, _retryOnConnReset = true, _apiNameWrapper) {
var _a;
super();
this._request = _request;
this._opts = _opts;
this._agent = _agent;
this._retryOnConnReset = _retryOnConnReset;
this._apiNameWrapper = _apiNameWrapper;
this.writable = true;
this.ended = false;
this.finished = false;
this.transporter = null;
(_a = this._opts).headers ?? (_a.headers = {});
}
get timeout() {
return this._opts.timeout ?? DEFAULT_TIMEOUT;
}
get streams() {
return disabledStreamManager;
}
setHeaders(newHeaders, append = true) {
if (append) {
Object.assign(this._opts.headers, newHeaders);
}
else {
this._opts.headers = newHeaders;
}
}
sendBinaryChunk() {
return Promise.reject(new Shared.errors.cmd_not_impl());
}
sendMessage() {
return Promise.reject(new Shared.errors.cmd_not_impl());
}
ping() {
return Promise.reject(new Shared.errors.cmd_not_impl());
}
async invoke(api, ...args) {
try {
return await this._invoke(api, ...args);
}
catch (e) {
if (this._retryOnConnReset && e?.code === 'ECONNRESET') {
return this._invoke(api, ...args);
}
else {
throw e;
}
}
}
_invoke(api, ...args) {
const CST = Date.now();
const content = JSON.stringify({
ttl: this._opts.timeout ?? DEFAULT_TIMEOUT,
rid: 0,
cst: CST,
args,
api: this._apiNameWrapper?.(api) ?? api
});
return new Promise((resolve, reject) => {
const length = Buffer.byteLength(content);
if (length > v1.MAX_PACKET_SIZE) {
reject(new Shared.errors.invalid_packet({ reason: 'packet_too_large' }));
return;
}
const req = this._request({
...this._opts,
agent: this._agent,
method: 'POST',
headers: {
...this._opts.headers,
[HTTP_HEADER_CONTENT_LENGTH]: Buffer.byteLength(content),
[HTTP_HEADER_TV_VER]: 1
},
}, (resp) => {
if (resp.statusCode !== 200) {
resp.socket.destroy();
reject(new Shared.errors.invalid_response({
reason: 'invalid_status_code',
statusCode: resp.statusCode
}));
return;
}
if (!resp.headers[HTTP_HEADER_CONTENT_LENGTH]) {
resp.socket.destroy();
reject(new Shared.errors.invalid_response({ reason: 'missing_content_length' }));
return;
}
const length = parseInt(resp.headers[HTTP_HEADER_CONTENT_LENGTH]);
if (!Number.isSafeInteger(length) || length > v1.MAX_PACKET_SIZE) { // Maximum request packet is 64MB
resp.socket.destroy();
reject(new Shared.errors.invalid_packet({ reason: 'packet_too_large' }));
return;
}
const buf = Buffer.allocUnsafe(length);
let offset = 0;
resp.on('data', (chunk) => {
const index = offset;
offset += chunk.byteLength;
if (offset > length) {
resp.removeAllListeners('end');
resp.removeAllListeners('data');
resp.destroy();
reject(new Shared.errors.invalid_packet({ reason: 'packet_length_mismatched' }));
return;
}
chunk.copy(buf, index);
}).on('end', () => {
let data;
try {
data = JSON.parse(buf);
}
catch (e) {
reject(new Shared.errors.invalid_response({
reason: 'invalid_json',
raw: buf
}, e));
return;
}
switch (data.code) {
case v1.EResponseCode.OK:
resolve(data.body);
break;
case v1.EResponseCode.FAILURE:
reject(new Shared.TvErrorResponse(data.body));
break;
case v1.EResponseCode.API_NOT_FOUND:
reject(new Shared.errors.api_not_found({ api }));
break;
case v1.EResponseCode.MALFORMED_ARGUMENTS:
case v1.EResponseCode.SYSTEM_ERROR:
default:
reject(new Shared.errors.server_internal_error({
api, ...data
}));
break;
}
});
});
req.once('timeout', () => {
reject(new Shared.errors.timeout({
metadata: { api, time: Date.now(), details: null }
}));
});
req.once('error', reject);
req.end(content);
});
}
connect() {
return Promise.resolve();
}
close() {
this._agent.destroy();
return Promise.resolve();
}
}
function createLegacyHttpClient(opts) {
return new TvLegacyHttpClient(opts.https ? $Https.request : $Http.request, opts, opts.https ? new $Https.Agent({
'maxSockets': opts.maxConnections ?? DEFAULT_MAX_CONNECTIONS,
'keepAlive': opts.keepAlive ?? true,
'keepAliveMsecs': opts.keepAliveTimeout ?? DEFAULT_TIMEOUT
}) : new $Http.Agent({
'maxSockets': opts.maxConnections ?? DEFAULT_MAX_CONNECTIONS,
'keepAlive': opts.keepAlive ?? true,
'keepAliveMsecs': opts.keepAliveTimeout ?? DEFAULT_TIMEOUT
}), opts.retryOnConnReset, opts.apiNameWrapper);
}
//# sourceMappingURL=LegacyHttp.Client.Native.js.map