mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
initwshrpc in electron (#391)
This commit is contained in:
parent
dae72e7009
commit
c7a60a80f8
@ -6,7 +6,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@ -177,7 +176,6 @@ func Execute() {
|
|||||||
}()
|
}()
|
||||||
err := rootCmd.Execute()
|
err := rootCmd.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[error] %v\n", err)
|
|
||||||
wshutil.DoShutdown("", 1, true)
|
wshutil.DoShutdown("", 1, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,14 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var webCmd = &cobra.Command{
|
var webCmd = &cobra.Command{
|
||||||
@ -26,15 +28,16 @@ var webOpenCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var webGetCmd = &cobra.Command{
|
var webGetCmd = &cobra.Command{
|
||||||
Use: "get [--inner] [--all] css-selector",
|
Use: "get [--inner] [--all] [--json] blockid css-selector",
|
||||||
Short: "get the html for a css selector",
|
Short: "get the html for a css selector",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(2),
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
RunE: webGetRun,
|
RunE: webGetRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
var webGetInner bool
|
var webGetInner bool
|
||||||
var webGetAll bool
|
var webGetAll bool
|
||||||
|
var webGetJson bool
|
||||||
var webOpenMagnified bool
|
var webOpenMagnified bool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -42,11 +45,59 @@ func init() {
|
|||||||
webCmd.AddCommand(webOpenCmd)
|
webCmd.AddCommand(webOpenCmd)
|
||||||
webGetCmd.Flags().BoolVarP(&webGetInner, "inner", "", false, "get inner html (instead of outer)")
|
webGetCmd.Flags().BoolVarP(&webGetInner, "inner", "", false, "get inner html (instead of outer)")
|
||||||
webGetCmd.Flags().BoolVarP(&webGetAll, "all", "", false, "get all matches (querySelectorAll)")
|
webGetCmd.Flags().BoolVarP(&webGetAll, "all", "", false, "get all matches (querySelectorAll)")
|
||||||
|
webGetCmd.Flags().BoolVarP(&webGetJson, "json", "", false, "output as json")
|
||||||
webCmd.AddCommand(webGetCmd)
|
webCmd.AddCommand(webGetCmd)
|
||||||
rootCmd.AddCommand(webCmd)
|
rootCmd.AddCommand(webCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func webGetRun(cmd *cobra.Command, args []string) error {
|
func webGetRun(cmd *cobra.Command, args []string) error {
|
||||||
|
oref := args[0]
|
||||||
|
if oref == "" {
|
||||||
|
return fmt.Errorf("blockid not specified")
|
||||||
|
}
|
||||||
|
err := validateEasyORef(oref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fullORef, err := resolveSimpleId(oref)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolving blockid: %w", err)
|
||||||
|
}
|
||||||
|
blockInfo, err := wshclient.BlockInfoCommand(RpcClient, fullORef.OID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting block info: %w", err)
|
||||||
|
}
|
||||||
|
if blockInfo.Meta.GetString(waveobj.MetaKey_View, "") != "web" {
|
||||||
|
return fmt.Errorf("block %s is not a web block", fullORef.OID)
|
||||||
|
}
|
||||||
|
data := wshrpc.CommandWebSelectorData{
|
||||||
|
WindowId: blockInfo.WindowId,
|
||||||
|
BlockId: fullORef.OID,
|
||||||
|
TabId: blockInfo.TabId,
|
||||||
|
Selector: args[1],
|
||||||
|
Opts: &wshrpc.WebSelectorOpts{
|
||||||
|
Inner: webGetInner,
|
||||||
|
All: webGetAll,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
output, err := wshclient.WebSelectorCommand(RpcClient, data, &wshrpc.RpcOpts{
|
||||||
|
Route: wshutil.ElectronRoute,
|
||||||
|
Timeout: 5000,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if webGetJson {
|
||||||
|
barr, err := json.MarshalIndent(output, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("json encoding: %w", err)
|
||||||
|
}
|
||||||
|
WriteStdout("%s\n", string(barr))
|
||||||
|
} else {
|
||||||
|
for _, item := range output {
|
||||||
|
WriteStdout("%s\n", item)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,7 @@
|
|||||||
|
|
||||||
import { BrowserWindow, ipcMain, webContents, WebContents } from "electron";
|
import { BrowserWindow, ipcMain, webContents, WebContents } from "electron";
|
||||||
|
|
||||||
export async function getWebContentsByBlockId(
|
export function getWebContentsByBlockId(win: BrowserWindow, tabId: string, blockId: string): Promise<WebContents> {
|
||||||
win: BrowserWindow,
|
|
||||||
tabId: string,
|
|
||||||
blockId: string
|
|
||||||
): Promise<WebContents> {
|
|
||||||
const prtn = new Promise<WebContents>((resolve, reject) => {
|
const prtn = new Promise<WebContents>((resolve, reject) => {
|
||||||
const randId = Math.floor(Math.random() * 1000000000).toString();
|
const randId = Math.floor(Math.random() * 1000000000).toString();
|
||||||
const respCh = `getWebContentsByBlockId-${randId}`;
|
const respCh = `getWebContentsByBlockId-${randId}`;
|
||||||
@ -46,18 +42,23 @@ export async function webGetSelector(wc: WebContents, selector: string, opts?: W
|
|||||||
if (!wc || !selector) {
|
if (!wc || !selector) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
const escapedSelector = escapeSelector(selector);
|
||||||
const escapedSelector = escapeSelector(selector);
|
const queryMethod = opts?.all ? "querySelectorAll" : "querySelector";
|
||||||
const queryMethod = opts?.all ? "querySelectorAll" : "querySelector";
|
const prop = opts?.inner ? "innerHTML" : "outerHTML";
|
||||||
const prop = opts?.inner ? "innerHTML" : "outerHTML";
|
const execExpr = `
|
||||||
const execExpr = `
|
(() => {
|
||||||
Array.from(document.${queryMethod}("${escapedSelector}") || []).map(el => el.${prop});
|
const toArr = x => (x instanceof NodeList) ? Array.from(x) : (x ? [x] : []);
|
||||||
`;
|
try {
|
||||||
|
const result = document.${queryMethod}("${escapedSelector}");
|
||||||
const results = await wc.executeJavaScript(execExpr);
|
const value = toArr(result).map(el => el.${prop});
|
||||||
return results;
|
return { value };
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.error("webGetSelector error", e);
|
return { error: error.message };
|
||||||
return null;
|
}
|
||||||
|
})()`;
|
||||||
|
const results = await wc.executeJavaScript(execExpr);
|
||||||
|
if (results.error) {
|
||||||
|
throw new Error(results.error);
|
||||||
}
|
}
|
||||||
|
return results.value;
|
||||||
}
|
}
|
||||||
|
31
emain/emain-wsh.ts
Normal file
31
emain/emain-wsh.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import { RpcResponseHelper, WshClient } from "@/app/store/wshclient";
|
||||||
|
import electron from "electron";
|
||||||
|
import { getWebContentsByBlockId, webGetSelector } from "emain/emain-web";
|
||||||
|
|
||||||
|
type WaveBrowserWindow = Electron.BrowserWindow & { waveWindowId: string; readyPromise: Promise<void> };
|
||||||
|
|
||||||
|
export class ElectronWshClientType extends WshClient {
|
||||||
|
constructor() {
|
||||||
|
super("electron");
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle_webselector(rh: RpcResponseHelper, data: CommandWebSelectorData): Promise<string[]> {
|
||||||
|
if (!data.tabid || !data.blockid || !data.windowid) {
|
||||||
|
throw new Error("tabid and blockid are required");
|
||||||
|
}
|
||||||
|
const windows = electron.BrowserWindow.getAllWindows();
|
||||||
|
const win = windows.find((w) => (w as WaveBrowserWindow).waveWindowId === data.windowid);
|
||||||
|
if (win == null) {
|
||||||
|
throw new Error(`no window found with id ${data.windowid}`);
|
||||||
|
}
|
||||||
|
const wc = await getWebContentsByBlockId(win, data.tabid, data.blockid);
|
||||||
|
if (wc == null) {
|
||||||
|
throw new Error(`no webcontents found with blockid ${data.blockid}`);
|
||||||
|
}
|
||||||
|
const rtn = await webGetSelector(wc, data.selector, data.opts);
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
// Copyright 2024, Command Line Inc.
|
// Copyright 2024, Command Line Inc.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import { initElectronWshrpc } from "@/app/store/wshrpcutil";
|
||||||
import * as electron from "electron";
|
import * as electron from "electron";
|
||||||
|
import { ElectronWshClientType } from "emain/emain-wsh";
|
||||||
import { FastAverageColor } from "fast-average-color";
|
import { FastAverageColor } from "fast-average-color";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import * as child_process from "node:child_process";
|
import * as child_process from "node:child_process";
|
||||||
@ -33,6 +35,7 @@ import {
|
|||||||
} from "./platform";
|
} from "./platform";
|
||||||
import { configureAutoUpdater, updater } from "./updater";
|
import { configureAutoUpdater, updater } from "./updater";
|
||||||
|
|
||||||
|
let ElectronWshClient = new ElectronWshClientType();
|
||||||
const electronApp = electron.app;
|
const electronApp = electron.app;
|
||||||
let WaveVersion = "unknown"; // set by WAVESRV-ESTART
|
let WaveVersion = "unknown"; // set by WAVESRV-ESTART
|
||||||
let WaveBuildTime = 0; // set by WAVESRV-ESTART
|
let WaveBuildTime = 0; // set by WAVESRV-ESTART
|
||||||
@ -857,6 +860,11 @@ async function appMain() {
|
|||||||
await relaunchBrowserWindows();
|
await relaunchBrowserWindows();
|
||||||
await configureAutoUpdater();
|
await configureAutoUpdater();
|
||||||
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
|
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
|
||||||
|
try {
|
||||||
|
initElectronWshrpc(ElectronWshClient, AuthKey);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error initializing wshrpc", e);
|
||||||
|
}
|
||||||
|
|
||||||
globalIsStarting = false;
|
globalIsStarting = false;
|
||||||
|
|
||||||
|
@ -3,10 +3,22 @@
|
|||||||
|
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
import { sprintf } from "sprintf-js";
|
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 dlog = debug("wave:ws");
|
||||||
|
|
||||||
const MaxWebSocketSendSize = 1024 * 1024; // 1MB
|
const WarnWebSocketSendSize = 1024 * 1024; // 1MB
|
||||||
|
const MaxWebSocketSendSize = 5 * 1024 * 1024; // 5MB
|
||||||
const reconnectHandlers: (() => void)[] = [];
|
const reconnectHandlers: (() => void)[] = [];
|
||||||
|
|
||||||
function addWSReconnectHandler(handler: () => void) {
|
function addWSReconnectHandler(handler: () => void) {
|
||||||
@ -23,7 +35,7 @@ function removeWSReconnectHandler(handler: () => void) {
|
|||||||
type WSEventCallback = (arg0: WSEventType) => void;
|
type WSEventCallback = (arg0: WSEventType) => void;
|
||||||
|
|
||||||
class WSControl {
|
class WSControl {
|
||||||
wsConn: any;
|
wsConn: WebSocket | ElectronWebSocketType;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
opening: boolean = false;
|
opening: boolean = false;
|
||||||
reconnectTimes: number = 0;
|
reconnectTimes: number = 0;
|
||||||
@ -35,12 +47,14 @@ class WSControl {
|
|||||||
wsLog: string[] = [];
|
wsLog: string[] = [];
|
||||||
baseHostPort: string;
|
baseHostPort: string;
|
||||||
lastReconnectTime: number = 0;
|
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.baseHostPort = baseHostPort;
|
||||||
this.messageCallback = messageCallback;
|
this.messageCallback = messageCallback;
|
||||||
this.windowId = windowId;
|
this.windowId = windowId;
|
||||||
this.open = false;
|
this.open = false;
|
||||||
|
this.authKey = authKey;
|
||||||
setInterval(this.sendPing.bind(this), 5000);
|
setInterval(this.sendPing.bind(this), 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +65,13 @@ class WSControl {
|
|||||||
this.lastReconnectTime = Date.now();
|
this.lastReconnectTime = Date.now();
|
||||||
dlog("try reconnect:", desc);
|
dlog("try reconnect:", desc);
|
||||||
this.opening = true;
|
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.onopen = this.onopen.bind(this);
|
||||||
this.wsConn.onmessage = this.onmessage.bind(this);
|
this.wsConn.onmessage = this.onmessage.bind(this);
|
||||||
this.wsConn.onclose = this.onclose.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));
|
console.log("ws message too large", byteSize, data.wscommand, msg.substring(0, 100));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (byteSize > WarnWebSocketSendSize) {
|
||||||
|
console.log("ws message large", byteSize, data.wscommand, msg.substring(0, 100));
|
||||||
|
}
|
||||||
this.wsConn.send(msg);
|
this.wsConn.send(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,16 +92,21 @@ class WshClient {
|
|||||||
// TODO implement a timeout (setTimeout + sendResponse)
|
// TODO implement a timeout (setTimeout + sendResponse)
|
||||||
const helper = new RpcResponseHelper(this, msg);
|
const helper = new RpcResponseHelper(this, msg);
|
||||||
const handlerName = `handle_${msg.command}`;
|
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 {
|
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) {
|
if (prtn instanceof Promise) {
|
||||||
await prtn;
|
result = await prtn;
|
||||||
|
} else {
|
||||||
|
result = prtn;
|
||||||
|
}
|
||||||
|
if (!helper.done) {
|
||||||
|
helper.sendResponse({ data: result });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!helper.done) {
|
if (!helper.done) {
|
||||||
|
@ -12,6 +12,11 @@ class RpcApiType {
|
|||||||
return client.wshRpcCall("authenticate", data, opts);
|
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]
|
// command "connconnect" [call]
|
||||||
ConnConnectCommand(client: WshClient, data: string, opts?: RpcOpts): Promise<void> {
|
ConnConnectCommand(client: WshClient, data: string, opts?: RpcOpts): Promise<void> {
|
||||||
return client.wshRpcCall("connconnect", data, opts);
|
return client.wshRpcCall("connconnect", data, opts);
|
||||||
@ -207,6 +212,11 @@ class RpcApiType {
|
|||||||
return client.wshRpcCall("test", data, opts);
|
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();
|
export const RpcApi = new RpcApiType();
|
||||||
|
@ -31,6 +31,9 @@ class WshRouter {
|
|||||||
constructor(upstreamClient: AbstractWshClient) {
|
constructor(upstreamClient: AbstractWshClient) {
|
||||||
this.routeMap = new Map();
|
this.routeMap = new Map();
|
||||||
this.rpcMap = new Map();
|
this.rpcMap = new Map();
|
||||||
|
if (upstreamClient == null) {
|
||||||
|
throw new Error("upstream client cannot be null");
|
||||||
|
}
|
||||||
this.upstreamClient = upstreamClient;
|
this.upstreamClient = upstreamClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,34 +49,17 @@ class WshRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the message was sent
|
// returns true if the message was sent
|
||||||
_sendRoutedMessage(msg: RpcMessage, destRouteId: string): boolean {
|
_sendRoutedMessage(msg: RpcMessage, destRouteId: string) {
|
||||||
const client = this.routeMap.get(destRouteId);
|
const client = this.routeMap.get(destRouteId);
|
||||||
if (client) {
|
if (client) {
|
||||||
client.recvRpcMessage(msg);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// send an error response
|
// there should always an upstream client
|
||||||
const nrMsg = { resid: msg.reqid, error: `no route for ${msg.route}` };
|
if (!this.upstreamClient) {
|
||||||
this._sendRoutedMessage(nrMsg, msg.source);
|
throw new Error(`no upstream client for message: ${msg}`);
|
||||||
|
}
|
||||||
|
this.upstreamClient?.recvRpcMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerRouteInfo(reqid: string, sourceRouteId: string, destRouteId: string) {
|
_registerRouteInfo(reqid: string, sourceRouteId: string, destRouteId: string) {
|
||||||
@ -102,18 +88,17 @@ class WshRouter {
|
|||||||
}
|
}
|
||||||
if (!util.isBlank(msg.command)) {
|
if (!util.isBlank(msg.command)) {
|
||||||
// send + register routeinfo
|
// send + register routeinfo
|
||||||
const ok = this._sendRoutedMessage(msg, msg.route);
|
if (!util.isBlank(msg.reqid)) {
|
||||||
if (!ok) {
|
this._registerRouteInfo(msg.reqid, msg.source, msg.route);
|
||||||
this._handleNoRoute(msg);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
this._registerRouteInfo(msg.reqid, msg.source, msg.route);
|
this._sendRoutedMessage(msg, msg.route);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!util.isBlank(msg.reqid)) {
|
if (!util.isBlank(msg.reqid)) {
|
||||||
const routeInfo = this.rpcMap.get(msg.reqid);
|
const routeInfo = this.rpcMap.get(msg.reqid);
|
||||||
if (!routeInfo) {
|
if (!routeInfo) {
|
||||||
// no route info, discard
|
// no route info, discard
|
||||||
|
dlog("no route info for reqid, discarding", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._sendRoutedMessage(msg, routeInfo.destRouteId);
|
this._sendRoutedMessage(msg, routeInfo.destRouteId);
|
||||||
@ -123,6 +108,7 @@ class WshRouter {
|
|||||||
const routeInfo = this.rpcMap.get(msg.resid);
|
const routeInfo = this.rpcMap.get(msg.resid);
|
||||||
if (!routeInfo) {
|
if (!routeInfo) {
|
||||||
// no route info, discard
|
// no route info, discard
|
||||||
|
dlog("no route info for resid, discarding", msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._sendRoutedMessage(msg, routeInfo.sourceRouteId);
|
this._sendRoutedMessage(msg, routeInfo.sourceRouteId);
|
||||||
|
@ -114,11 +114,24 @@ if (globalThis.window != null) {
|
|||||||
globalThis["consumeGenerator"] = consumeGenerator;
|
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 {
|
function initWshrpc(windowId: string): WSControl {
|
||||||
DefaultRouter = new WshRouter(new UpstreamWshRpcProxy());
|
DefaultRouter = new WshRouter(new UpstreamWshRpcProxy());
|
||||||
const handleFn = (event: WSEventType) => {
|
const handleFn = (event: WSEventType) => {
|
||||||
DefaultRouter.recvRpcMessage(event.data);
|
DefaultRouter.recvRpcMessage(event.data);
|
||||||
// handleIncomingRpcMessage(globalOpenRpcs, event);
|
|
||||||
};
|
};
|
||||||
globalWS = new WSControl(getWSServerEndpoint(), windowId, handleFn);
|
globalWS = new WSControl(getWSServerEndpoint(), windowId, handleFn);
|
||||||
globalWS.connectNow("connectWshrpc");
|
globalWS.connectNow("connectWshrpc");
|
||||||
@ -144,6 +157,7 @@ class UpstreamWshRpcProxy implements AbstractWshClient {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
DefaultRouter,
|
DefaultRouter,
|
||||||
|
initElectronWshrpc,
|
||||||
initWshrpc,
|
initWshrpc,
|
||||||
sendRawRpcMessage,
|
sendRawRpcMessage,
|
||||||
sendRpcCommand,
|
sendRpcCommand,
|
||||||
|
23
frontend/types/gotypes.d.ts
vendored
23
frontend/types/gotypes.d.ts
vendored
@ -25,6 +25,14 @@ declare global {
|
|||||||
meta?: MetaType;
|
meta?: MetaType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// wshrpc.BlockInfoData
|
||||||
|
type BlockInfoData = {
|
||||||
|
blockid: string;
|
||||||
|
tabid: string;
|
||||||
|
windowid: string;
|
||||||
|
meta: MetaType;
|
||||||
|
};
|
||||||
|
|
||||||
// webcmd.BlockInputWSCommand
|
// webcmd.BlockInputWSCommand
|
||||||
type BlockInputWSCommand = {
|
type BlockInputWSCommand = {
|
||||||
wscommand: "blockinput";
|
wscommand: "blockinput";
|
||||||
@ -147,6 +155,15 @@ declare global {
|
|||||||
meta: MetaType;
|
meta: MetaType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// wshrpc.CommandWebSelectorData
|
||||||
|
type CommandWebSelectorData = {
|
||||||
|
windowid: string;
|
||||||
|
blockid: string;
|
||||||
|
tabid: string;
|
||||||
|
selector: string;
|
||||||
|
opts?: WebSelectorOpts;
|
||||||
|
};
|
||||||
|
|
||||||
// wconfig.ConfigError
|
// wconfig.ConfigError
|
||||||
type ConfigError = {
|
type ConfigError = {
|
||||||
file: string;
|
file: string;
|
||||||
@ -641,6 +658,12 @@ declare global {
|
|||||||
updates?: WaveObjUpdate[];
|
updates?: WaveObjUpdate[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// wshrpc.WebSelectorOpts
|
||||||
|
type WebSelectorOpts = {
|
||||||
|
all?: boolean;
|
||||||
|
inner?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
// wconfig.WidgetConfigType
|
// wconfig.WidgetConfigType
|
||||||
type WidgetConfigType = {
|
type WidgetConfigType = {
|
||||||
"display:order"?: number;
|
"display:order"?: number;
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
"@types/throttle-debounce": "^5",
|
"@types/throttle-debounce": "^5",
|
||||||
"@types/tinycolor2": "^1",
|
"@types/tinycolor2": "^1",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
|
"@types/ws": "^8",
|
||||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||||
"@vitest/coverage-istanbul": "^2.1.1",
|
"@vitest/coverage-istanbul": "^2.1.1",
|
||||||
"electron": "^32.1.0",
|
"electron": "^32.1.0",
|
||||||
@ -127,7 +128,8 @@
|
|||||||
"throttle-debounce": "^5.0.2",
|
"throttle-debounce": "^5.0.2",
|
||||||
"tinycolor2": "^1.6.0",
|
"tinycolor2": "^1.6.0",
|
||||||
"use-device-pixel-ratio": "^1.1.2",
|
"use-device-pixel-ratio": "^1.1.2",
|
||||||
"winston": "^3.14.2"
|
"winston": "^3.14.2",
|
||||||
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.4.1"
|
"packageManager": "yarn@4.4.1"
|
||||||
}
|
}
|
||||||
|
@ -262,12 +262,18 @@ func HandleWsInternal(w http.ResponseWriter, r *http.Request) error {
|
|||||||
outputCh := make(chan any, 100)
|
outputCh := make(chan any, 100)
|
||||||
closeCh := make(chan any)
|
closeCh := make(chan any)
|
||||||
eventbus.RegisterWSChannel(wsConnId, windowId, outputCh)
|
eventbus.RegisterWSChannel(wsConnId, windowId, outputCh)
|
||||||
|
var routeId string
|
||||||
|
if windowId == wshutil.ElectronRoute {
|
||||||
|
routeId = wshutil.ElectronRoute
|
||||||
|
} else {
|
||||||
|
routeId = wshutil.MakeWindowRouteId(windowId)
|
||||||
|
}
|
||||||
defer eventbus.UnregisterWSChannel(wsConnId)
|
defer eventbus.UnregisterWSChannel(wsConnId)
|
||||||
// we create a wshproxy to handle rpc messages to/from the window
|
// we create a wshproxy to handle rpc messages to/from the window
|
||||||
wproxy := wshutil.MakeRpcProxy()
|
wproxy := wshutil.MakeRpcProxy()
|
||||||
wshutil.DefaultRouter.RegisterRoute(wshutil.MakeWindowRouteId(windowId), wproxy)
|
wshutil.DefaultRouter.RegisterRoute(routeId, wproxy)
|
||||||
defer func() {
|
defer func() {
|
||||||
wshutil.DefaultRouter.UnregisterRoute(wshutil.MakeWindowRouteId(windowId))
|
wshutil.DefaultRouter.UnregisterRoute(routeId)
|
||||||
close(wproxy.ToRemoteCh)
|
close(wproxy.ToRemoteCh)
|
||||||
}()
|
}()
|
||||||
// WshServerFactoryFn(rpcInputCh, rpcOutputCh, wshrpc.RpcContext{})
|
// WshServerFactoryFn(rpcInputCh, rpcOutputCh, wshrpc.RpcContext{})
|
||||||
|
@ -19,6 +19,12 @@ func AuthenticateCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) (
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// command "blockinfo", wshserver.BlockInfoCommand
|
||||||
|
func BlockInfoCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) (*wshrpc.BlockInfoData, error) {
|
||||||
|
resp, err := sendRpcRequestCallHelper[*wshrpc.BlockInfoData](w, "blockinfo", data, opts)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// command "connconnect", wshserver.ConnConnectCommand
|
// command "connconnect", wshserver.ConnConnectCommand
|
||||||
func ConnConnectCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
|
func ConnConnectCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
|
||||||
_, err := sendRpcRequestCallHelper[any](w, "connconnect", data, opts)
|
_, err := sendRpcRequestCallHelper[any](w, "connconnect", data, opts)
|
||||||
@ -248,4 +254,10 @@ func TestCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// command "webselector", wshserver.WebSelectorCommand
|
||||||
|
func WebSelectorCommand(w *wshutil.WshRpc, data wshrpc.CommandWebSelectorData, opts *wshrpc.RpcOpts) ([]string, error) {
|
||||||
|
resp, err := sendRpcRequestCallHelper[[]string](w, "webselector", data, opts)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ const (
|
|||||||
Command_FileAppend = "fileappend"
|
Command_FileAppend = "fileappend"
|
||||||
Command_FileAppendIJson = "fileappendijson"
|
Command_FileAppendIJson = "fileappendijson"
|
||||||
Command_ResolveIds = "resolveids"
|
Command_ResolveIds = "resolveids"
|
||||||
|
Command_BlockInfo = "blockinfo"
|
||||||
Command_CreateBlock = "createblock"
|
Command_CreateBlock = "createblock"
|
||||||
Command_DeleteBlock = "deleteblock"
|
Command_DeleteBlock = "deleteblock"
|
||||||
Command_FileWrite = "filewrite"
|
Command_FileWrite = "filewrite"
|
||||||
@ -65,6 +66,8 @@ const (
|
|||||||
Command_ConnConnect = "connconnect"
|
Command_ConnConnect = "connconnect"
|
||||||
Command_ConnDisconnect = "conndisconnect"
|
Command_ConnDisconnect = "conndisconnect"
|
||||||
Command_ConnList = "connlist"
|
Command_ConnList = "connlist"
|
||||||
|
|
||||||
|
Command_WebSelector = "webselector"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RespOrErrorUnion[T any] struct {
|
type RespOrErrorUnion[T any] struct {
|
||||||
@ -101,6 +104,7 @@ type WshRpcInterface interface {
|
|||||||
StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData]
|
StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData]
|
||||||
TestCommand(ctx context.Context, data string) error
|
TestCommand(ctx context.Context, data string) error
|
||||||
SetConfigCommand(ctx context.Context, data wconfig.MetaSettingsType) error
|
SetConfigCommand(ctx context.Context, data wconfig.MetaSettingsType) error
|
||||||
|
BlockInfoCommand(ctx context.Context, blockId string) (*BlockInfoData, error)
|
||||||
|
|
||||||
// connection functions
|
// connection functions
|
||||||
ConnStatusCommand(ctx context.Context) ([]ConnStatus, error)
|
ConnStatusCommand(ctx context.Context) ([]ConnStatus, error)
|
||||||
@ -120,6 +124,8 @@ type WshRpcInterface interface {
|
|||||||
RemoteWriteFileCommand(ctx context.Context, data CommandRemoteWriteFileData) error
|
RemoteWriteFileCommand(ctx context.Context, data CommandRemoteWriteFileData) error
|
||||||
RemoteFileJoinCommand(ctx context.Context, paths []string) (*FileInfo, error)
|
RemoteFileJoinCommand(ctx context.Context, paths []string) (*FileInfo, error)
|
||||||
RemoteStreamCpuDataCommand(ctx context.Context) chan RespOrErrorUnion[TimeSeriesData]
|
RemoteStreamCpuDataCommand(ctx context.Context) chan RespOrErrorUnion[TimeSeriesData]
|
||||||
|
|
||||||
|
WebSelectorCommand(ctx context.Context, data CommandWebSelectorData) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// for frontend
|
// for frontend
|
||||||
@ -348,3 +354,23 @@ type ConnStatus struct {
|
|||||||
ActiveConnNum int `json:"activeconnnum"`
|
ActiveConnNum int `json:"activeconnnum"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebSelectorOpts struct {
|
||||||
|
All bool `json:"all,omitempty"`
|
||||||
|
Inner bool `json:"inner,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandWebSelectorData struct {
|
||||||
|
WindowId string `json:"windowid"`
|
||||||
|
BlockId string `json:"blockid" wshcontext:"BlockId"`
|
||||||
|
TabId string `json:"tabid" wshcontext:"TabId"`
|
||||||
|
Selector string `json:"selector"`
|
||||||
|
Opts *WebSelectorOpts `json:"opts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockInfoData struct {
|
||||||
|
BlockId string `json:"blockid"`
|
||||||
|
TabId string `json:"tabid"`
|
||||||
|
WindowId string `json:"windowid"`
|
||||||
|
Meta waveobj.MetaMapType `json:"meta"`
|
||||||
|
}
|
||||||
|
@ -526,3 +526,24 @@ func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, connName strin
|
|||||||
func (ws *WshServer) ConnListCommand(ctx context.Context) ([]string, error) {
|
func (ws *WshServer) ConnListCommand(ctx context.Context) ([]string, error) {
|
||||||
return conncontroller.GetConnectionsList()
|
return conncontroller.GetConnectionsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WshServer) BlockInfoCommand(ctx context.Context, blockId string) (*wshrpc.BlockInfoData, error) {
|
||||||
|
blockData, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting block: %w", err)
|
||||||
|
}
|
||||||
|
tabId, err := wstore.DBFindTabForBlockId(ctx, blockId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error finding tab for block: %w", err)
|
||||||
|
}
|
||||||
|
windowId, err := wstore.DBFindWindowForTabId(ctx, tabId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error finding window for tab: %w", err)
|
||||||
|
}
|
||||||
|
return &wshrpc.BlockInfoData{
|
||||||
|
BlockId: blockId,
|
||||||
|
TabId: tabId,
|
||||||
|
WindowId: windowId,
|
||||||
|
Meta: blockData.Meta,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
const DefaultRoute = "wavesrv"
|
const DefaultRoute = "wavesrv"
|
||||||
const SysRoute = "sys" // this route doesn't exist, just a placeholder for system messages
|
const SysRoute = "sys" // this route doesn't exist, just a placeholder for system messages
|
||||||
|
const ElectronRoute = "electron"
|
||||||
|
|
||||||
// this works like a network switch
|
// this works like a network switch
|
||||||
|
|
||||||
|
13
yarn.lock
13
yarn.lock
@ -2594,6 +2594,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/ws@npm:^8":
|
||||||
|
version: 8.5.12
|
||||||
|
resolution: "@types/ws@npm:8.5.12"
|
||||||
|
dependencies:
|
||||||
|
"@types/node": "npm:*"
|
||||||
|
checksum: 10c0/3fd77c9e4e05c24ce42bfc7647f7506b08c40a40fe2aea236ef6d4e96fc7cb4006a81ed1b28ec9c457e177a74a72924f4768b7b4652680b42dfd52bc380e15f9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/yauzl@npm:^2.9.1":
|
"@types/yauzl@npm:^2.9.1":
|
||||||
version: 2.10.3
|
version: 2.10.3
|
||||||
resolution: "@types/yauzl@npm:2.10.3"
|
resolution: "@types/yauzl@npm:2.10.3"
|
||||||
@ -10210,6 +10219,7 @@ __metadata:
|
|||||||
"@types/throttle-debounce": "npm:^5"
|
"@types/throttle-debounce": "npm:^5"
|
||||||
"@types/tinycolor2": "npm:^1"
|
"@types/tinycolor2": "npm:^1"
|
||||||
"@types/uuid": "npm:^10.0.0"
|
"@types/uuid": "npm:^10.0.0"
|
||||||
|
"@types/ws": "npm:^8"
|
||||||
"@vitejs/plugin-react-swc": "npm:^3.7.0"
|
"@vitejs/plugin-react-swc": "npm:^3.7.0"
|
||||||
"@vitest/coverage-istanbul": "npm:^2.1.1"
|
"@vitest/coverage-istanbul": "npm:^2.1.1"
|
||||||
"@xterm/addon-fit": "npm:^0.10.0"
|
"@xterm/addon-fit": "npm:^0.10.0"
|
||||||
@ -10277,6 +10287,7 @@ __metadata:
|
|||||||
vite-tsconfig-paths: "npm:^5.0.1"
|
vite-tsconfig-paths: "npm:^5.0.1"
|
||||||
vitest: "npm:^2.1.1"
|
vitest: "npm:^2.1.1"
|
||||||
winston: "npm:^3.14.2"
|
winston: "npm:^3.14.2"
|
||||||
|
ws: "npm:^8.18.0"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
@ -11218,7 +11229,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ws@npm:^8.2.3":
|
"ws@npm:^8.18.0, ws@npm:^8.2.3":
|
||||||
version: 8.18.0
|
version: 8.18.0
|
||||||
resolution: "ws@npm:8.18.0"
|
resolution: "ws@npm:8.18.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
Loading…
Reference in New Issue
Block a user