waveterm/frontend/app/store/wshrouter.ts
2024-10-17 14:34:02 -07:00

153 lines
4.8 KiB
TypeScript

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { handleWaveEvent } from "@/app/store/wps";
import * as util from "@/util/util";
import debug from "debug";
const dlog = debug("wave:router");
const SysRouteName = "sys";
type RouteInfo = {
rpcId: string;
sourceRouteId: string;
destRouteId: string;
};
function makeFeBlockRouteId(feBlockId: string): string {
return `feblock:${feBlockId}`;
}
function makeTabRouteId(tabId: string): string {
return `tab:${tabId}`;
}
class WshRouter {
routeMap: Map<string, AbstractWshClient>; // routeid -> client
upstreamClient: AbstractWshClient;
rpcMap: Map<string, RouteInfo>; // rpcid -> routeinfo
constructor(upstreamClient: AbstractWshClient) {
this.routeMap = new Map();
this.rpcMap = new Map();
if (upstreamClient == null) {
throw new Error("upstream client cannot be null");
}
this.upstreamClient = upstreamClient;
}
reannounceRoutes() {
for (const [routeId, client] of this.routeMap) {
const announceMsg: RpcMessage = {
command: "routeannounce",
data: routeId,
source: routeId,
};
this.upstreamClient.recvRpcMessage(announceMsg);
}
}
// returns true if the message was sent
_sendRoutedMessage(msg: RpcMessage, destRouteId: string) {
const client = this.routeMap.get(destRouteId);
if (client) {
client.recvRpcMessage(msg);
return;
}
// there should always an upstream client
if (!this.upstreamClient) {
throw new Error(`no upstream client for message: ${msg}`);
}
this.upstreamClient?.recvRpcMessage(msg);
}
_registerRouteInfo(reqid: string, sourceRouteId: string, destRouteId: string) {
dlog("registering route info", reqid, sourceRouteId, destRouteId);
if (util.isBlank(reqid)) {
return;
}
const routeInfo: RouteInfo = {
rpcId: reqid,
sourceRouteId: sourceRouteId,
destRouteId: destRouteId,
};
this.rpcMap.set(reqid, routeInfo);
}
recvRpcMessage(msg: RpcMessage) {
dlog("router received message", msg);
// we are a terminal node by definition, so we don't need to process with announce/unannounce messages
if (msg.command == "routeannounce" || msg.command == "routeunannounce") {
return;
}
// handle events
if (msg.command == "eventrecv") {
handleWaveEvent(msg.data);
return;
}
if (!util.isBlank(msg.command)) {
// send + register routeinfo
if (!util.isBlank(msg.reqid)) {
this._registerRouteInfo(msg.reqid, msg.source, msg.route);
}
this._sendRoutedMessage(msg, msg.route);
return;
}
if (!util.isBlank(msg.reqid)) {
const routeInfo = this.rpcMap.get(msg.reqid);
if (!routeInfo) {
// no route info, discard
dlog("no route info for reqid, discarding", msg);
return;
}
this._sendRoutedMessage(msg, routeInfo.destRouteId);
return;
}
if (!util.isBlank(msg.resid)) {
const routeInfo = this.rpcMap.get(msg.resid);
if (!routeInfo) {
// no route info, discard
dlog("no route info for resid, discarding", msg);
return;
}
this._sendRoutedMessage(msg, routeInfo.sourceRouteId);
if (!msg.cont) {
dlog("deleting route info", msg.resid);
this.rpcMap.delete(msg.resid);
}
return;
}
dlog("bad rpc message recevied by router, no command, reqid, or resid (discarding)", msg);
}
registerRoute(routeId: string, client: AbstractWshClient) {
if (routeId == SysRouteName) {
throw new Error(`Cannot register route with reserved name (${routeId})`);
}
dlog("registering route: ", routeId);
// announce
const announceMsg: RpcMessage = {
command: "routeannounce",
data: routeId,
source: routeId,
};
this.upstreamClient.recvRpcMessage(announceMsg);
this.routeMap.set(routeId, client);
}
unregisterRoute(routeId: string) {
dlog("unregister route: ", routeId);
// unannounce
const unannounceMsg: RpcMessage = {
command: "routeunannounce",
data: routeId,
source: routeId,
};
this.upstreamClient?.recvRpcMessage(unannounceMsg);
this.routeMap.delete(routeId);
}
}
export { makeFeBlockRouteId, makeTabRouteId, WshRouter };