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 (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
@ -177,7 +176,6 @@ func Execute() {
|
||||
}()
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
log.Printf("[error] %v\n", err)
|
||||
wshutil.DoShutdown("", 1, true)
|
||||
return
|
||||
}
|
||||
|
@ -4,12 +4,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
||||
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
||||
)
|
||||
|
||||
var webCmd = &cobra.Command{
|
||||
@ -26,15 +28,16 @@ var webOpenCmd = &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",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Args: cobra.ExactArgs(2),
|
||||
Hidden: true,
|
||||
RunE: webGetRun,
|
||||
}
|
||||
|
||||
var webGetInner bool
|
||||
var webGetAll bool
|
||||
var webGetJson bool
|
||||
var webOpenMagnified bool
|
||||
|
||||
func init() {
|
||||
@ -42,11 +45,59 @@ func init() {
|
||||
webCmd.AddCommand(webOpenCmd)
|
||||
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(&webGetJson, "json", "", false, "output as json")
|
||||
webCmd.AddCommand(webGetCmd)
|
||||
rootCmd.AddCommand(webCmd)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,7 @@
|
||||
|
||||
import { BrowserWindow, ipcMain, webContents, WebContents } from "electron";
|
||||
|
||||
export async function getWebContentsByBlockId(
|
||||
win: BrowserWindow,
|
||||
tabId: string,
|
||||
blockId: string
|
||||
): Promise<WebContents> {
|
||||
export function getWebContentsByBlockId(win: BrowserWindow, tabId: string, blockId: string): Promise<WebContents> {
|
||||
const prtn = new Promise<WebContents>((resolve, reject) => {
|
||||
const randId = Math.floor(Math.random() * 1000000000).toString();
|
||||
const respCh = `getWebContentsByBlockId-${randId}`;
|
||||
@ -46,18 +42,23 @@ export async function webGetSelector(wc: WebContents, selector: string, opts?: W
|
||||
if (!wc || !selector) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const escapedSelector = escapeSelector(selector);
|
||||
const queryMethod = opts?.all ? "querySelectorAll" : "querySelector";
|
||||
const prop = opts?.inner ? "innerHTML" : "outerHTML";
|
||||
const execExpr = `
|
||||
Array.from(document.${queryMethod}("${escapedSelector}") || []).map(el => el.${prop});
|
||||
`;
|
||||
|
||||
const results = await wc.executeJavaScript(execExpr);
|
||||
return results;
|
||||
} catch (e) {
|
||||
console.error("webGetSelector error", e);
|
||||
return null;
|
||||
const escapedSelector = escapeSelector(selector);
|
||||
const queryMethod = opts?.all ? "querySelectorAll" : "querySelector";
|
||||
const prop = opts?.inner ? "innerHTML" : "outerHTML";
|
||||
const execExpr = `
|
||||
(() => {
|
||||
const toArr = x => (x instanceof NodeList) ? Array.from(x) : (x ? [x] : []);
|
||||
try {
|
||||
const result = document.${queryMethod}("${escapedSelector}");
|
||||
const value = toArr(result).map(el => el.${prop});
|
||||
return { value };
|
||||
} catch (error) {
|
||||
return { error: error.message };
|
||||
}
|
||||
})()`;
|
||||
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.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { initElectronWshrpc } from "@/app/store/wshrpcutil";
|
||||
import * as electron from "electron";
|
||||
import { ElectronWshClientType } from "emain/emain-wsh";
|
||||
import { FastAverageColor } from "fast-average-color";
|
||||
import fs from "fs";
|
||||
import * as child_process from "node:child_process";
|
||||
@ -33,6 +35,7 @@ import {
|
||||
} from "./platform";
|
||||
import { configureAutoUpdater, updater } from "./updater";
|
||||
|
||||
let ElectronWshClient = new ElectronWshClientType();
|
||||
const electronApp = electron.app;
|
||||
let WaveVersion = "unknown"; // set by WAVESRV-ESTART
|
||||
let WaveBuildTime = 0; // set by WAVESRV-ESTART
|
||||
@ -857,6 +860,11 @@ async function appMain() {
|
||||
await relaunchBrowserWindows();
|
||||
await configureAutoUpdater();
|
||||
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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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
frontend/types/gotypes.d.ts
vendored
23
frontend/types/gotypes.d.ts
vendored
@ -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;
|
||||
|
@ -51,6 +51,7 @@
|
||||
"@types/throttle-debounce": "^5",
|
||||
"@types/tinycolor2": "^1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/ws": "^8",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"@vitest/coverage-istanbul": "^2.1.1",
|
||||
"electron": "^32.1.0",
|
||||
@ -127,7 +128,8 @@
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"use-device-pixel-ratio": "^1.1.2",
|
||||
"winston": "^3.14.2"
|
||||
"winston": "^3.14.2",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"packageManager": "yarn@4.4.1"
|
||||
}
|
||||
|
@ -262,12 +262,18 @@ func HandleWsInternal(w http.ResponseWriter, r *http.Request) error {
|
||||
outputCh := make(chan any, 100)
|
||||
closeCh := make(chan any)
|
||||
eventbus.RegisterWSChannel(wsConnId, windowId, outputCh)
|
||||
var routeId string
|
||||
if windowId == wshutil.ElectronRoute {
|
||||
routeId = wshutil.ElectronRoute
|
||||
} else {
|
||||
routeId = wshutil.MakeWindowRouteId(windowId)
|
||||
}
|
||||
defer eventbus.UnregisterWSChannel(wsConnId)
|
||||
// we create a wshproxy to handle rpc messages to/from the window
|
||||
wproxy := wshutil.MakeRpcProxy()
|
||||
wshutil.DefaultRouter.RegisterRoute(wshutil.MakeWindowRouteId(windowId), wproxy)
|
||||
wshutil.DefaultRouter.RegisterRoute(routeId, wproxy)
|
||||
defer func() {
|
||||
wshutil.DefaultRouter.UnregisterRoute(wshutil.MakeWindowRouteId(windowId))
|
||||
wshutil.DefaultRouter.UnregisterRoute(routeId)
|
||||
close(wproxy.ToRemoteCh)
|
||||
}()
|
||||
// WshServerFactoryFn(rpcInputCh, rpcOutputCh, wshrpc.RpcContext{})
|
||||
|
@ -19,6 +19,12 @@ func AuthenticateCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) (
|
||||
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
|
||||
func ConnConnectCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
|
||||
_, err := sendRpcRequestCallHelper[any](w, "connconnect", data, opts)
|
||||
@ -248,4 +254,10 @@ func TestCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
|
||||
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_FileAppendIJson = "fileappendijson"
|
||||
Command_ResolveIds = "resolveids"
|
||||
Command_BlockInfo = "blockinfo"
|
||||
Command_CreateBlock = "createblock"
|
||||
Command_DeleteBlock = "deleteblock"
|
||||
Command_FileWrite = "filewrite"
|
||||
@ -65,6 +66,8 @@ const (
|
||||
Command_ConnConnect = "connconnect"
|
||||
Command_ConnDisconnect = "conndisconnect"
|
||||
Command_ConnList = "connlist"
|
||||
|
||||
Command_WebSelector = "webselector"
|
||||
)
|
||||
|
||||
type RespOrErrorUnion[T any] struct {
|
||||
@ -101,6 +104,7 @@ type WshRpcInterface interface {
|
||||
StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData]
|
||||
TestCommand(ctx context.Context, data string) error
|
||||
SetConfigCommand(ctx context.Context, data wconfig.MetaSettingsType) error
|
||||
BlockInfoCommand(ctx context.Context, blockId string) (*BlockInfoData, error)
|
||||
|
||||
// connection functions
|
||||
ConnStatusCommand(ctx context.Context) ([]ConnStatus, error)
|
||||
@ -120,6 +124,8 @@ type WshRpcInterface interface {
|
||||
RemoteWriteFileCommand(ctx context.Context, data CommandRemoteWriteFileData) error
|
||||
RemoteFileJoinCommand(ctx context.Context, paths []string) (*FileInfo, error)
|
||||
RemoteStreamCpuDataCommand(ctx context.Context) chan RespOrErrorUnion[TimeSeriesData]
|
||||
|
||||
WebSelectorCommand(ctx context.Context, data CommandWebSelectorData) ([]string, error)
|
||||
}
|
||||
|
||||
// for frontend
|
||||
@ -348,3 +354,23 @@ type ConnStatus struct {
|
||||
ActiveConnNum int `json:"activeconnnum"`
|
||||
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) {
|
||||
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 SysRoute = "sys" // this route doesn't exist, just a placeholder for system messages
|
||||
const ElectronRoute = "electron"
|
||||
|
||||
// this works like a network switch
|
||||
|
||||
|
13
yarn.lock
13
yarn.lock
@ -2594,6 +2594,15 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 2.10.3
|
||||
resolution: "@types/yauzl@npm:2.10.3"
|
||||
@ -10210,6 +10219,7 @@ __metadata:
|
||||
"@types/throttle-debounce": "npm:^5"
|
||||
"@types/tinycolor2": "npm:^1"
|
||||
"@types/uuid": "npm:^10.0.0"
|
||||
"@types/ws": "npm:^8"
|
||||
"@vitejs/plugin-react-swc": "npm:^3.7.0"
|
||||
"@vitest/coverage-istanbul": "npm:^2.1.1"
|
||||
"@xterm/addon-fit": "npm:^0.10.0"
|
||||
@ -10277,6 +10287,7 @@ __metadata:
|
||||
vite-tsconfig-paths: "npm:^5.0.1"
|
||||
vitest: "npm:^2.1.1"
|
||||
winston: "npm:^3.14.2"
|
||||
ws: "npm:^8.18.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@ -11218,7 +11229,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.2.3":
|
||||
"ws@npm:^8.18.0, ws@npm:^8.2.3":
|
||||
version: 8.18.0
|
||||
resolution: "ws@npm:8.18.0"
|
||||
peerDependencies:
|
||||
|
Loading…
Reference in New Issue
Block a user