initwshrpc in electron (#391)

This commit is contained in:
Mike Sawka
2024-09-17 23:10:09 -07:00
committed by GitHub
parent dae72e7009
commit c7a60a80f8
18 changed files with 296 additions and 67 deletions
+27 -4
View File
@@ -3,10 +3,22 @@
import debug from "debug";
import { sprintf } from "sprintf-js";
import type { WebSocket as ElectronWebSocketType } from "ws";
let ElectronWebSocket: typeof ElectronWebSocketType;
const AuthKeyHeader = "X-AuthKey";
if (typeof window === "undefined") {
try {
const WebSocket = require("ws") as typeof ElectronWebSocketType;
ElectronWebSocket = WebSocket;
} catch (e) {}
}
const dlog = debug("wave:ws");
const MaxWebSocketSendSize = 1024 * 1024; // 1MB
const WarnWebSocketSendSize = 1024 * 1024; // 1MB
const MaxWebSocketSendSize = 5 * 1024 * 1024; // 5MB
const reconnectHandlers: (() => void)[] = [];
function addWSReconnectHandler(handler: () => void) {
@@ -23,7 +35,7 @@ function removeWSReconnectHandler(handler: () => void) {
type WSEventCallback = (arg0: WSEventType) => void;
class WSControl {
wsConn: any;
wsConn: WebSocket | ElectronWebSocketType;
open: boolean;
opening: boolean = false;
reconnectTimes: number = 0;
@@ -35,12 +47,14 @@ class WSControl {
wsLog: string[] = [];
baseHostPort: string;
lastReconnectTime: number = 0;
authKey: string = null; // used only by electron
constructor(baseHostPort: string, windowId: string, messageCallback: WSEventCallback) {
constructor(baseHostPort: string, windowId: string, messageCallback: WSEventCallback, authKey?: string) {
this.baseHostPort = baseHostPort;
this.messageCallback = messageCallback;
this.windowId = windowId;
this.open = false;
this.authKey = authKey;
setInterval(this.sendPing.bind(this), 5000);
}
@@ -51,7 +65,13 @@ class WSControl {
this.lastReconnectTime = Date.now();
dlog("try reconnect:", desc);
this.opening = true;
this.wsConn = new WebSocket(this.baseHostPort + "/ws?windowid=" + this.windowId);
if (ElectronWebSocket) {
this.wsConn = new ElectronWebSocket(this.baseHostPort + "/ws?windowid=" + this.windowId, {
headers: { [AuthKeyHeader]: this.authKey },
});
} else {
this.wsConn = new WebSocket(this.baseHostPort + "/ws?windowid=" + this.windowId);
}
this.wsConn.onopen = this.onopen.bind(this);
this.wsConn.onmessage = this.onmessage.bind(this);
this.wsConn.onclose = this.onclose.bind(this);
@@ -172,6 +192,9 @@ class WSControl {
console.log("ws message too large", byteSize, data.wscommand, msg.substring(0, 100));
return;
}
if (byteSize > WarnWebSocketSendSize) {
console.log("ws message large", byteSize, data.wscommand, msg.substring(0, 100));
}
this.wsConn.send(msg);
}
+13 -8
View File
@@ -92,16 +92,21 @@ class WshClient {
// TODO implement a timeout (setTimeout + sendResponse)
const helper = new RpcResponseHelper(this, msg);
const handlerName = `handle_${msg.command}`;
let prtn: any = null;
if (handlerName in this) {
prtn = this[handlerName](helper, msg.data);
return;
} else {
prtn = this.handle_default(helper, msg);
}
try {
let result: any = null;
let prtn: any = null;
if (handlerName in this) {
prtn = this[handlerName](helper, msg.data);
} else {
prtn = this.handle_default(helper, msg);
}
if (prtn instanceof Promise) {
await prtn;
result = await prtn;
} else {
result = prtn;
}
if (!helper.done) {
helper.sendResponse({ data: result });
}
} catch (e) {
if (!helper.done) {
+10
View File
@@ -12,6 +12,11 @@ class RpcApiType {
return client.wshRpcCall("authenticate", data, opts);
}
// command "blockinfo" [call]
BlockInfoCommand(client: WshClient, data: string, opts?: RpcOpts): Promise<BlockInfoData> {
return client.wshRpcCall("blockinfo", data, opts);
}
// command "connconnect" [call]
ConnConnectCommand(client: WshClient, data: string, opts?: RpcOpts): Promise<void> {
return client.wshRpcCall("connconnect", data, opts);
@@ -207,6 +212,11 @@ class RpcApiType {
return client.wshRpcCall("test", data, opts);
}
// command "webselector" [call]
WebSelectorCommand(client: WshClient, data: CommandWebSelectorData, opts?: RpcOpts): Promise<string[]> {
return client.wshRpcCall("webselector", data, opts);
}
}
export const RpcApi = new RpcApiType();
+14 -28
View File
@@ -31,6 +31,9 @@ class WshRouter {
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;
}
@@ -46,34 +49,17 @@ class WshRouter {
}
// returns true if the message was sent
_sendRoutedMessage(msg: RpcMessage, destRouteId: string): boolean {
_sendRoutedMessage(msg: RpcMessage, destRouteId: string) {
const client = this.routeMap.get(destRouteId);
if (client) {
client.recvRpcMessage(msg);
return true;
}
if (!this.upstreamClient) {
// there should always be an upstream client
return false;
}
this.upstreamClient?.recvRpcMessage(msg);
return true;
}
_handleNoRoute(msg: RpcMessage) {
dlog("no route for message", msg);
if (util.isBlank(msg.reqid)) {
// send a message instead
if (msg.command == "message") {
return;
}
const nrMsg = { command: "message", route: msg.source, data: { message: `no route for ${msg.route}` } };
this._sendRoutedMessage(nrMsg, SysRouteName);
return;
}
// send an error response
const nrMsg = { resid: msg.reqid, error: `no route for ${msg.route}` };
this._sendRoutedMessage(nrMsg, msg.source);
// 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) {
@@ -102,18 +88,17 @@ class WshRouter {
}
if (!util.isBlank(msg.command)) {
// send + register routeinfo
const ok = this._sendRoutedMessage(msg, msg.route);
if (!ok) {
this._handleNoRoute(msg);
return;
if (!util.isBlank(msg.reqid)) {
this._registerRouteInfo(msg.reqid, msg.source, msg.route);
}
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);
@@ -123,6 +108,7 @@ class WshRouter {
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);
+15 -1
View File
@@ -114,11 +114,24 @@ if (globalThis.window != null) {
globalThis["consumeGenerator"] = consumeGenerator;
}
function initElectronWshrpc(electronClient: WshClient, authKey: string) {
DefaultRouter = new WshRouter(new UpstreamWshRpcProxy());
const handleFn = (event: WSEventType) => {
DefaultRouter.recvRpcMessage(event.data);
};
globalWS = new WSControl(getWSServerEndpoint(), "electron", handleFn, authKey);
globalWS.connectNow("connectWshrpc");
DefaultRouter.registerRoute(electronClient.routeId, electronClient);
addWSReconnectHandler(() => {
DefaultRouter.reannounceRoutes();
});
addWSReconnectHandler(wpsReconnectHandler);
}
function initWshrpc(windowId: string): WSControl {
DefaultRouter = new WshRouter(new UpstreamWshRpcProxy());
const handleFn = (event: WSEventType) => {
DefaultRouter.recvRpcMessage(event.data);
// handleIncomingRpcMessage(globalOpenRpcs, event);
};
globalWS = new WSControl(getWSServerEndpoint(), windowId, handleFn);
globalWS.connectNow("connectWshrpc");
@@ -144,6 +157,7 @@ class UpstreamWshRpcProxy implements AbstractWshClient {
export {
DefaultRouter,
initElectronWshrpc,
initWshrpc,
sendRawRpcMessage,
sendRpcCommand,
+23
View File
@@ -25,6 +25,14 @@ declare global {
meta?: MetaType;
};
// wshrpc.BlockInfoData
type BlockInfoData = {
blockid: string;
tabid: string;
windowid: string;
meta: MetaType;
};
// webcmd.BlockInputWSCommand
type BlockInputWSCommand = {
wscommand: "blockinput";
@@ -147,6 +155,15 @@ declare global {
meta: MetaType;
};
// wshrpc.CommandWebSelectorData
type CommandWebSelectorData = {
windowid: string;
blockid: string;
tabid: string;
selector: string;
opts?: WebSelectorOpts;
};
// wconfig.ConfigError
type ConfigError = {
file: string;
@@ -641,6 +658,12 @@ declare global {
updates?: WaveObjUpdate[];
};
// wshrpc.WebSelectorOpts
type WebSelectorOpts = {
all?: boolean;
inner?: boolean;
};
// wconfig.WidgetConfigType
type WidgetConfigType = {
"display:order"?: number;