waveterm/frontend/app/store/wshrouter.ts

153 lines
4.8 KiB
TypeScript
Raw Normal View History

2024-09-16 20:59:39 +02:00
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { handleWaveEvent } from "@/app/store/wps";
import debug from "debug";
import * as util from "../../util/util";
const dlog = debug("wave:router");
const SysRouteName = "sys";
type RouteInfo = {
rpcId: string;
sourceRouteId: string;
destRouteId: string;
};
function makeWindowRouteId(windowId: string): string {
return `window:${windowId}`;
}
function makeFeBlockRouteId(feBlockId: string): string {
return `feblock:${feBlockId}`;
}
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();
2024-09-18 08:10:09 +02:00
if (upstreamClient == null) {
throw new Error("upstream client cannot be null");
}
2024-09-16 20:59:39 +02:00
this.upstreamClient = upstreamClient;
}
reannounceRoutes() {
for (const [routeId, client] of this.routeMap) {
const announceMsg: RpcMessage = {
command: "routeannounce",
data: routeId,
source: routeId,
};
this.upstreamClient.recvRpcMessage(announceMsg);
}
}
2024-09-16 20:59:39 +02:00
// returns true if the message was sent
2024-09-18 08:10:09 +02:00
_sendRoutedMessage(msg: RpcMessage, destRouteId: string) {
2024-09-16 20:59:39 +02:00
const client = this.routeMap.get(destRouteId);
if (client) {
client.recvRpcMessage(msg);
2024-09-18 08:10:09 +02:00
return;
2024-09-16 20:59:39 +02:00
}
2024-09-18 08:10:09 +02:00
// there should always an upstream client
2024-09-16 20:59:39 +02:00
if (!this.upstreamClient) {
2024-09-18 08:10:09 +02:00
throw new Error(`no upstream client for message: ${msg}`);
2024-09-16 20:59:39 +02:00
}
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
2024-09-18 08:10:09 +02:00
if (!util.isBlank(msg.reqid)) {
this._registerRouteInfo(msg.reqid, msg.source, msg.route);
2024-09-16 20:59:39 +02:00
}
2024-09-18 08:10:09 +02:00
this._sendRoutedMessage(msg, msg.route);
2024-09-16 20:59:39 +02:00
return;
}
if (!util.isBlank(msg.reqid)) {
const routeInfo = this.rpcMap.get(msg.reqid);
if (!routeInfo) {
// no route info, discard
2024-09-18 08:10:09 +02:00
dlog("no route info for reqid, discarding", msg);
2024-09-16 20:59:39 +02:00
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
2024-09-18 08:10:09 +02:00
dlog("no route info for resid, discarding", msg);
2024-09-16 20:59:39 +02:00
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, makeWindowRouteId, WshRouter };