@litert/televoke
Version:
A simple RPC service framework.
194 lines • 6.92 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.createJsonApiClient = createJsonApiClient;
const Shared = require("../shared");
const cC = require("./Client.Constants");
const node_events_1 = require("node:events");
const Channel_impl_1 = require("../shared/Channel.impl");
const v2 = require("../shared/Encodings/v2");
const Utils_1 = require("../shared/Utils");
let channelIdCounter = Date.now();
class TvJsonApiClient extends node_events_1.EventEmitter {
constructor(_connector, timeout, _streamManagerFactory) {
super();
this._connector = _connector;
this.timeout = timeout;
this._streamManagerFactory = _streamManagerFactory;
this._channel = null;
this._connecting = null;
this._onChannelClose = () => {
this._channel?.off('close', this._onChannelClose)
.off('push_message', this._onMessage);
this._channel = null;
this.emit('close');
};
this._onMessage = (msg) => {
this.emit('push_message', msg);
};
this._onError = (e) => {
this.emit('error', e);
};
}
get streams() {
const ret = this._channel?.streams;
if (!ret) {
throw new Shared.errors.channel_inactive();
}
return ret;
}
get transporter() {
return this._channel?.transporter ?? null;
}
get ended() {
return false !== this._channel?.ended;
}
get finished() {
return false !== this._channel?.finished;
}
get writable() {
return false !== this._channel?.writable;
}
async _getChannel() {
if (this._channel?.transporter?.writable) {
// debugLog('televoke', 'Reusing existing channel.');
return this._channel;
}
if (this._connecting) {
// debugLog('televoke', 'Waiting for previous connection to be established.');
return this._connecting;
}
try {
// debugLog('televoke', 'Attempting to connect...');
return this._channel = await (this._connecting = this._connect());
}
catch (e) {
// debugLog('televoke', 'Attempts to connect failed.');
this._channel = null;
this._connecting = null;
throw e;
}
finally {
// debugLog('televoke', 'Attempts to connect finished.');
this._connecting = null;
}
}
async _connect() {
this._channel = new TvClientChannel(await this._connector.connect(), this._streamManagerFactory, this.timeout)
.on('close', this._onChannelClose)
.on('error', this._onError)
.on('push_message', this._onMessage);
return this._channel;
}
async invoke(name, ...args) {
try {
const resp = await (await this._getChannel()).apiCall(name, JSON.stringify(args), cC.EArgsEncodings.JSON);
return (0, Utils_1.buffer2json)(resp.body);
}
catch (e) {
if (e instanceof Shared.errors.app_error) {
let info;
try {
info = JSON.parse(e.message);
}
catch {
throw e;
}
throw new Shared.TvErrorResponse(info);
}
throw e;
}
}
async callApi(name, args, opts) {
try {
const resp = await (await this._getChannel()).apiCall(name, JSON.stringify(args), cC.EArgsEncodings.JSON, opts?.binChunks);
return {
result: (0, Utils_1.buffer2json)(resp.body),
binChunks: resp.binChunks,
};
}
catch (e) {
if (e instanceof Shared.errors.app_error) {
let info;
try {
info = JSON.parse(e.message);
}
catch {
throw e;
}
throw new Shared.TvErrorResponse(info);
}
throw e;
}
}
close() {
this._channel?.close();
// this._onChannelClose();
}
async ping(msg) {
return (await this._getChannel()).ping(msg);
}
async connect() {
await this._getChannel();
}
async sendBinaryChunk(streamId, index, chunk) {
return (await this._getChannel()).sendBinaryChunk(streamId, index, chunk);
}
}
class TvClientChannel extends Channel_impl_1.AbstractTvChannelV2 {
constructor(transporter, streamManagerFactory, timeout = 60000) {
super(channelIdCounter++, transporter, timeout, streamManagerFactory);
}
apiCall(name, argsBody, argsEncoding, extBinaryChunks) {
if (!this.writable) {
return Promise.reject(new Shared.errors.channel_inactive());
}
const seq = this._seqCounter++;
this.transporter.write(Channel_impl_1.encoder.encode({
'typ': v2.EPacketType.REQUEST,
'cmd': v2.ECommand.API_CALL,
'seq': seq,
'ct': {
'name': name,
'body': argsBody,
'bodyEnc': argsEncoding,
'binChunks': extBinaryChunks,
}
}));
return new Promise((resolve, reject) => {
this._setTimeout(v2.ECommand.API_CALL, seq, (resp) => {
if (resp.ct instanceof Shared.TelevokeError) {
reject(resp.ct);
return;
}
resolve(resp.ct);
});
});
}
}
/**
* Create a JSON-RPC client instance, over televoke2 protocol.
*
* @param connector The connector instance.
* @param streamManagerFactory The stream manager factory function. [default: createSharedStreamManagerFactory()]
* @param timeout The default timeout value of commands, in milliseconds. [default: 60000]
* @returns The client instance.
*/
function createJsonApiClient(connector, streamManagerFactory = Shared.createSharedStreamManagerFactory(), timeout = 60000) {
return new TvJsonApiClient(connector, timeout, streamManagerFactory);
}
//# sourceMappingURL=JsonApiClient.impl.js.map