new wshrpc mechanism (#112)

lots of changes. new wshrpc implementation. unify websocket, web,
blockcontroller, domain sockets, and terminal inputs to all use the new
rpc system.

lots of moving files around to deal with circular dependencies

use new wshrpc as a client in wsh cmd
This commit is contained in:
Mike Sawka
2024-07-17 15:24:43 -07:00
committed by GitHub
parent b178434c0a
commit 01b5d71709
49 changed files with 1810 additions and 1996 deletions
+2
View File
@@ -13,10 +13,12 @@ tasks:
generate:
cmds:
- go run cmd/generate/main-generate.go
- go run cmd/generatewshclient/main-generatewshclient.go
sources:
- "cmd/generate/*.go"
- "pkg/service/**/*.go"
- "pkg/wstore/*.go"
- "pkg/wshrpc/**/*.go"
electron:dev:
cmds:
+32
View File
@@ -12,6 +12,7 @@ import (
"github.com/wavetermdev/thenextwave/pkg/service"
"github.com/wavetermdev/thenextwave/pkg/tsgen"
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshserver"
)
func generateTypesFile(tsTypesMap map[reflect.Type]string) error {
@@ -27,6 +28,7 @@ func generateTypesFile(tsTypesMap map[reflect.Type]string) error {
fmt.Fprintf(os.Stderr, "Error generating service types: %v\n", err)
os.Exit(1)
}
err = tsgen.GenerateWshServerTypes(tsTypesMap)
fmt.Fprintf(fd, "// Copyright 2024, Command Line Inc.\n")
fmt.Fprintf(fd, "// SPDX-License-Identifier: Apache-2.0\n\n")
fmt.Fprintf(fd, "// generated by cmd/generate/main-generate.go\n\n")
@@ -71,6 +73,31 @@ func generateServicesFile(tsTypesMap map[reflect.Type]string) error {
return nil
}
func generateWshServerFile(tsTypeMap map[reflect.Type]string) error {
fd, err := os.Create("frontend/app/store/wshserver.ts")
if err != nil {
return err
}
defer fd.Close()
fmt.Fprintf(os.Stderr, "generating wshserver file to %s\n", fd.Name())
fmt.Fprintf(fd, "// Copyright 2024, Command Line Inc.\n")
fmt.Fprintf(fd, "// SPDX-License-Identifier: Apache-2.0\n\n")
fmt.Fprintf(fd, "// generated by cmd/generate/main-generate.go\n\n")
fmt.Fprintf(fd, "import * as WOS from \"./wos\";\n\n")
orderedKeys := utilfn.GetOrderedMapKeys(wshserver.WshServerCommandToDeclMap)
fmt.Fprintf(fd, "// WshServerCommandToDeclMap\n")
fmt.Fprintf(fd, "class WshServerType {\n")
for _, methodDecl := range orderedKeys {
methodDecl := wshserver.WshServerCommandToDeclMap[methodDecl]
methodStr := tsgen.GenerateWshServerMethod(methodDecl, tsTypeMap)
fmt.Fprint(fd, methodStr)
fmt.Fprintf(fd, "\n")
}
fmt.Fprintf(fd, "}\n\n")
fmt.Fprintf(fd, "export const WshServer = new WshServerType();\n")
return nil
}
func main() {
err := service.ValidateServiceMap()
if err != nil {
@@ -88,4 +115,9 @@ func main() {
fmt.Fprintf(os.Stderr, "Error generating services file: %v\n", err)
os.Exit(1)
}
err = generateWshServerFile(tsTypesMap)
if err != nil {
fmt.Fprintf(os.Stderr, "Error generating wshserver file: %v\n", err)
os.Exit(1)
}
}
@@ -0,0 +1,67 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package main
import (
"fmt"
"os"
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshserver"
"github.com/wavetermdev/thenextwave/pkg/wshutil"
)
func genMethod(fd *os.File, methodDecl *wshserver.WshServerMethodDecl) {
fmt.Fprintf(fd, "// command %q, wshserver.%s\n", methodDecl.Command, methodDecl.MethodName)
var dataType string
dataVarName := "nil"
if methodDecl.CommandDataType != nil {
dataType = ", data " + methodDecl.CommandDataType.String()
dataVarName = "data"
}
returnType := "error"
respName := "_"
tParamVal := "any"
if methodDecl.DefaultResponseDataType != nil {
returnType = "(" + methodDecl.DefaultResponseDataType.String() + ", error)"
respName = "resp"
tParamVal = methodDecl.DefaultResponseDataType.String()
}
fmt.Fprintf(fd, "func %s(w *wshutil.WshRpc%s, opts *wshrpc.WshRpcCommandOpts) %s {\n", methodDecl.MethodName, dataType, returnType)
if methodDecl.CommandType == wshutil.RpcType_Call {
fmt.Fprintf(fd, " %s, err := sendRpcRequestHelper[%s](w, %q, %s, opts)\n", respName, tParamVal, methodDecl.Command, dataVarName)
if methodDecl.DefaultResponseDataType != nil {
fmt.Fprintf(fd, " return resp, err\n")
} else {
fmt.Fprintf(fd, " return err\n")
}
} else {
panic("unsupported command type " + methodDecl.CommandType)
}
fmt.Fprintf(fd, "}\n\n")
}
func main() {
fd, err := os.Create("pkg/wshrpc/wshclient/wshclient.go")
if err != nil {
panic(err)
}
defer fd.Close()
fmt.Fprintf(os.Stderr, "generating wshclient file to %s\n", fd.Name())
fmt.Fprintf(fd, "// Copyright 2024, Command Line Inc.\n")
fmt.Fprintf(fd, "// SPDX-License-Identifier: Apache-2.0\n\n")
fmt.Fprintf(fd, "// generated by cmd/generatewshclient/main-generatewshclient.go\n\n")
fmt.Fprintf(fd, "package wshclient\n\n")
fmt.Fprintf(fd, "import (\n")
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/wshutil\"\n")
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/wshrpc\"\n")
fmt.Fprintf(fd, " \"github.com/wavetermdev/thenextwave/pkg/waveobj\"\n")
fmt.Fprintf(fd, ")\n\n")
for _, key := range utilfn.GetOrderedMapKeys(wshserver.WshServerCommandToDeclMap) {
methodDecl := wshserver.WshServerCommandToDeclMap[key]
genMethod(fd, methodDecl)
}
fmt.Fprintf(fd, "\n")
}
+9 -9
View File
@@ -7,7 +7,6 @@ import (
"context"
"fmt"
"log"
"net"
"os"
"os/signal"
"strconv"
@@ -17,11 +16,13 @@ import (
"syscall"
"time"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/filestore"
"github.com/wavetermdev/thenextwave/pkg/service"
"github.com/wavetermdev/thenextwave/pkg/wavebase"
"github.com/wavetermdev/thenextwave/pkg/wconfig"
"github.com/wavetermdev/thenextwave/pkg/web"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshserver"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
@@ -78,6 +79,8 @@ func configWatcher() {
func main() {
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.SetPrefix("[wavesrv] ")
blockcontroller.WshServerFactoryFn = wshserver.MakeWshServer
web.WshServerFactoryFn = wshserver.MakeWshServer
err := service.ValidateServiceMap()
if err != nil {
@@ -118,6 +121,7 @@ func main() {
return
}
installShutdownSignalHandlers()
go stdinReadWatch()
configWatcher()
go web.RunWebSocketServer()
@@ -126,14 +130,10 @@ func main() {
log.Printf("error creating web listener: %v\n", err)
return
}
var unixListener net.Listener
if runtime.GOOS != "windows" {
var err error
unixListener, err = web.MakeUnixListener()
if err != nil {
log.Printf("error creating unix listener: %v\n", err)
return
}
unixListener, err := web.MakeUnixListener()
if err != nil {
log.Printf("error creating unix listener: %v\n", err)
return
}
go func() {
pidStr := os.Getenv(ReadySignalPidVarName)
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package main
type WaveAppStyle struct {
BackgroundColor string `json:"backgroundColor,omitempty"`
Color string `json:"color,omitempty"`
Border string `json:"border,omitempty"`
FontSize string `json:"fontSize,omitempty"`
FontFamily string `json:"fontFamily,omitempty"`
FontWeight string `json:"fontWeight,omitempty"`
FontStyle string `json:"fontStyle,omitempty"`
TextDecoration string `json:"textDecoration,omitempty"`
}
type WaveAppMouseEvent struct {
TargetId string `json:"targetid"`
}
type WaveAppChangeEvent struct {
TargetId string `json:"targetid"`
Value string `json:"value"`
}
type WaveAppElement struct {
WaveId string `json:"waveid"`
Elem string `json:"elem"`
Props map[string]any `json:"props,omitempty"`
Handlers map[string]string `json:"handlers,omitempty"`
Children []*WaveAppElement `json:"children,omitempty"`
}
func (e *WaveAppElement) AddChild(child *WaveAppElement) {
e.Children = append(e.Children, child)
}
func (e *WaveAppElement) Style() *WaveAppStyle {
style, ok := e.Props["style"].(*WaveAppStyle)
if !ok {
style := &WaveAppStyle{}
e.Props["style"] = style
}
return style
}
func main() {
}
+3 -9
View File
@@ -10,7 +10,8 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/wavetermdev/thenextwave/pkg/wshutil"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshclient"
)
var getMetaCmd = &cobra.Command{
@@ -42,18 +43,11 @@ func getMetaRun(cmd *cobra.Command, args []string) {
fmt.Printf("error resolving oref: %v\r\n", err)
return
}
getMetaWshCmd := &wshutil.BlockGetMetaCommand{
Command: wshutil.BlockCommand_SetMeta,
ORef: fullORef,
}
resp, err := RpcClient.SendRpcRequest(getMetaWshCmd, 2000)
resp, err := wshclient.GetMetaCommand(RpcClient, wshrpc.CommandGetMetaData{ORef: *fullORef}, &wshrpc.WshRpcCommandOpts{Timeout: 2000})
if err != nil {
log.Printf("error getting metadata: %v\r\n", err)
return
}
if resp == nil {
resp = make(map[string]any)
}
if len(args) > 1 {
val, ok := resp[args[1]]
if !ok {
+25 -22
View File
@@ -18,6 +18,8 @@ import (
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshclient"
"github.com/wavetermdev/thenextwave/pkg/wshutil"
"golang.org/x/term"
)
@@ -45,11 +47,10 @@ func doShutdown(reason string, exitCode int) {
log.Printf("shutting down: %s\r\n", reason)
}
if usingHtmlMode {
cmd := &wshutil.BlockSetMetaCommand{
Command: wshutil.BlockCommand_SetMeta,
Meta: map[string]any{"term:mode": nil},
cmd := &wshrpc.CommandSetMetaData{
Meta: map[string]any{"term:mode": nil},
}
RpcClient.SendCommand(cmd)
RpcClient.SendCommand(wshrpc.Command_SetMeta, cmd)
time.Sleep(10 * time.Millisecond)
}
if origTermState != nil {
@@ -61,11 +62,13 @@ func doShutdown(reason string, exitCode int) {
// returns the wrapped stdin and a new rpc client (that wraps the stdin input and stdout output)
func setupRpcClient(handlerFn wshutil.CommandHandlerFnType) {
log.Printf("setup rpc client\r\n")
messageCh := make(chan wshutil.RpcMessage)
messageCh := make(chan []byte, 32)
outputCh := make(chan []byte, 32)
ptyBuf := wshutil.MakePtyBuffer(wshutil.WaveServerOSCPrefix, os.Stdin, messageCh)
rpcClient, outputCh := wshutil.MakeWshRpc(wshutil.WaveOSC, messageCh, handlerFn)
rpcClient := wshutil.MakeWshRpc(messageCh, outputCh, wshutil.RpcContext{}, handlerFn)
go func() {
for barr := range outputCh {
for msg := range outputCh {
barr := wshutil.EncodeWaveOSCBytes(wshutil.WaveOSC, msg)
os.Stdout.Write(barr)
}
}()
@@ -89,11 +92,10 @@ func setTermRawMode() {
func setTermHtmlMode() {
installShutdownSignalHandlers()
setTermRawMode()
cmd := &wshutil.BlockSetMetaCommand{
Command: wshutil.BlockCommand_SetMeta,
Meta: map[string]any{"term:mode": "html"},
cmd := &wshrpc.CommandSetMetaData{
Meta: map[string]any{"term:mode": "html"},
}
RpcClient.SendCommand(cmd)
RpcClient.SendCommand(wshrpc.Command_SetMeta, cmd)
usingHtmlMode = true
}
@@ -139,22 +141,23 @@ func isFullORef(orefStr string) bool {
return err == nil
}
func resolveSimpleId(id string) (string, error) {
func resolveSimpleId(id string) (*waveobj.ORef, error) {
if isFullORef(id) {
return id, nil
orefObj, err := waveobj.ParseORef(id)
if err != nil {
return nil, fmt.Errorf("error parsing full ORef: %v", err)
}
return &orefObj, nil
}
resolveCmd := &wshutil.ResolveIdsCommand{
Command: wshutil.Command_ResolveIds,
Ids: []string{id},
}
resp, err := RpcClient.SendRpcRequest(resolveCmd, 2000)
rtnData, err := wshclient.ResolveIdsCommand(RpcClient, wshrpc.CommandResolveIdsData{Ids: []string{id}}, &wshrpc.WshRpcCommandOpts{Timeout: 2000})
if err != nil {
return "", err
return nil, fmt.Errorf("error resolving ids: %v", err)
}
if resp[id] == nil {
return "", fmt.Errorf("id not found: %q", id)
oref, ok := rtnData.ResolvedIds[id]
if !ok {
return nil, fmt.Errorf("id not found: %q", id)
}
return resp[id].(string), nil
return &oref, nil
}
// Execute executes the root command.
+5 -6
View File
@@ -10,7 +10,7 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/wavetermdev/thenextwave/pkg/wshutil"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
)
var setMetaCmd = &cobra.Command{
@@ -80,12 +80,11 @@ func setMetaRun(cmd *cobra.Command, args []string) {
fmt.Printf("error resolving oref: %v\n", err)
return
}
setMetaWshCmd := &wshutil.BlockSetMetaCommand{
Command: wshutil.BlockCommand_SetMeta,
ORef: fullORef,
Meta: meta,
setMetaWshCmd := &wshrpc.CommandSetMetaData{
ORef: *fullORef,
Meta: meta,
}
_, err = RpcClient.SendRpcRequest(setMetaWshCmd, 2000)
_, err = RpcClient.SendRpcRequest(wshrpc.Command_SetMeta, setMetaWshCmd, 2000)
if err != nil {
fmt.Printf("error setting metadata: %v\n", err)
return
+3 -4
View File
@@ -10,7 +10,7 @@ import (
"path/filepath"
"github.com/spf13/cobra"
"github.com/wavetermdev/thenextwave/pkg/wshutil"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
@@ -44,8 +44,7 @@ func viewRun(cmd *cobra.Command, args []string) {
log.Printf("error getting file info: %v\n", err)
}
setTermRawMode()
viewWshCmd := &wshutil.CreateBlockCommand{
Command: wshutil.Command_CreateBlock,
viewWshCmd := &wshrpc.CommandCreateBlockData{
BlockDef: &wstore.BlockDef{
View: "preview",
Meta: map[string]interface{}{
@@ -53,7 +52,7 @@ func viewRun(cmd *cobra.Command, args []string) {
},
},
}
_, err = RpcClient.SendRpcRequest(viewWshCmd, 2000)
_, err = RpcClient.SendRpcRequest(wshrpc.Command_CreateBlock, viewWshCmd, 2000)
if err != nil {
log.Printf("error running view command: %v\r\n", err)
return
-1
View File
@@ -420,7 +420,6 @@ const BlockFrame = React.memo((props: BlockFrameProps) => {
});
function blockViewToIcon(view: string): string {
console.log("blockViewToIcon", view);
if (view == "term") {
return "terminal";
}
+8 -2
View File
@@ -5,6 +5,7 @@ import { LayoutTreeAction, LayoutTreeActionType, LayoutTreeInsertNodeAction, new
import { getLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom";
import { layoutTreeStateReducer } from "@/faraday/lib/layoutState";
import { handleIncomingRpcMessage } from "@/app/store/wshrpc";
import * as layoututil from "@/util/layoututil";
import { produce } from "immer";
import * as jotai from "jotai";
@@ -27,8 +28,8 @@ let globalClientId: string = null;
if (typeof window !== "undefined") {
// this if statement allows us to use the code in nodejs as well
const urlParams = new URLSearchParams(window.location.search);
globalWindowId = urlParams.get("windowid") || "74eba2d0-22fc-4221-82ad-d028dd496342";
globalClientId = urlParams.get("clientid") || "f4bc1713-a364-41b3-a5c4-b000ba10d622";
globalWindowId = urlParams.get("windowid");
globalClientId = urlParams.get("clientid");
}
const windowIdAtom = jotai.atom(null) as jotai.PrimitiveAtom<string>;
const clientIdAtom = jotai.atom(null) as jotai.PrimitiveAtom<string>;
@@ -223,6 +224,11 @@ function handleWSEventMessage(msg: WSEventType) {
}
return;
}
if (msg.eventtype == "rpc") {
const rpcMsg: RpcMessage = msg.data;
handleIncomingRpcMessage(rpcMsg);
return;
}
if (msg.eventtype == "layoutaction") {
const layoutAction: WSLayoutActionData = msg.data;
if (layoutAction.actiontype == LayoutTreeActionType.InsertNode) {
+5 -10
View File
@@ -13,14 +13,9 @@ class BlockServiceType {
SaveTerminalState(arg2: string, arg3: string, arg4: string, arg5: number): Promise<void> {
return WOS.callBackendService("block", "SaveTerminalState", Array.from(arguments))
}
// send command to block
SendCommand(cmd: string, arg3: BlockCommand): Promise<void> {
return WOS.callBackendService("block", "SendCommand", Array.from(arguments))
}
}
export const BlockService = new BlockServiceType()
export const BlockService = new BlockServiceType();
// clientservice.ClientService (client)
class ClientServiceType {
@@ -44,7 +39,7 @@ class ClientServiceType {
}
}
export const ClientService = new ClientServiceType()
export const ClientService = new ClientServiceType();
// fileservice.FileService (file)
class FileServiceType {
@@ -71,7 +66,7 @@ class FileServiceType {
}
}
export const FileService = new FileServiceType()
export const FileService = new FileServiceType();
// objectservice.ObjectService (object)
class ObjectServiceType {
@@ -126,7 +121,7 @@ class ObjectServiceType {
}
}
export const ObjectService = new ObjectServiceType()
export const ObjectService = new ObjectServiceType();
// windowservice.WindowService (window)
class WindowServiceType {
@@ -150,5 +145,5 @@ class WindowServiceType {
}
}
export const WindowService = new WindowServiceType()
export const WindowService = new WindowServiceType();
+47
View File
@@ -3,8 +3,10 @@
// WaveObjectStore
import { sendRpcCommand } from "@/app/store/wshrpc";
import * as jotai from "jotai";
import * as React from "react";
import { v4 as uuidv4 } from "uuid";
import { atoms, getBackendHostPort, globalStore } from "./global";
import * as services from "./services";
@@ -103,6 +105,50 @@ function callBackendService(service: string, method: string, args: any[], noUICo
return prtn;
}
function callWshServerRpc(
command: string,
data: any,
meta: WshServerCommandMeta,
opts: WshRpcCommandOpts
): Promise<any> {
let msg: RpcMessage = {
command: command,
data: data,
};
if (!opts?.noresponse) {
msg.reqid = uuidv4();
}
if (opts?.timeout) {
msg.timeout = opts.timeout;
}
if (meta.commandtype != "call") {
throw new Error("unimplemented wshserver commandtype " + meta.commandtype);
}
const rpcGen = sendRpcCommand(msg);
if (rpcGen == null) {
return null;
}
let resolveFn: (value: any) => void;
let rejectFn: (reason?: any) => void;
const prtn = new Promise((resolve, reject) => {
resolveFn = resolve;
rejectFn = reject;
});
const respMsg = rpcGen.next(true); // pass true to force termination of rpc after 1 response (not streaing)
respMsg.then((msg: IteratorResult<RpcMessage, void>) => {
if (msg.value == null) {
resolveFn(null);
}
let respMsg: RpcMessage = msg.value as RpcMessage;
if (respMsg.error != null) {
rejectFn(new Error(respMsg.error));
return;
}
resolveFn(respMsg.data);
});
return prtn;
}
const waveObjectValueCache = new Map<string, WaveObjectValue<any>>();
function clearWaveObjectCache() {
@@ -320,6 +366,7 @@ function setObjectValue<T extends WaveObj>(value: T, setFn?: jotai.Setter, pushT
export {
callBackendService,
callWshServerRpc,
cleanWaveObjectCache,
clearWaveObjectCache,
getObjectValue,
+3 -58
View File
@@ -3,19 +3,9 @@
import * as jotai from "jotai";
import { sprintf } from "sprintf-js";
import { v4 as uuidv4 } from "uuid";
const MaxWebSocketSendSize = 1024 * 1024; // 1MB
type RpcEntry = {
reqId: string;
startTs: number;
method: string;
resolve: (any) => void;
reject: (any) => void;
promise: Promise<any>;
};
type JotaiStore = {
get: <Value>(atom: jotai.Atom<Value>) => Value;
set: <Value>(atom: jotai.WritableAtom<Value, [Value], void>, value: Value) => void;
@@ -35,7 +25,6 @@ class WSControl {
authKey: string;
baseHostPort: string;
lastReconnectTime: number = 0;
rpcMap: Map<string, RpcEntry> = new Map(); // reqId -> RpcEntry
jotaiStore: JotaiStore;
constructor(
@@ -169,10 +158,6 @@ class WSControl {
this.reconnectTimes = 0;
return;
}
if (eventData.type == "rpcresp") {
this.handleRpcResp(eventData);
return;
}
if (this.messageCallback) {
try {
this.messageCallback(eventData);
@@ -189,60 +174,20 @@ class WSControl {
this.wsConn.send(JSON.stringify({ type: "ping", stime: Date.now() }));
}
handleRpcResp(data: any) {
let reqId = data.reqid;
let rpcEntry = this.rpcMap.get(reqId);
if (rpcEntry == null) {
console.log("rpcresp for unknown reqid", reqId);
return;
}
this.rpcMap.delete(reqId);
console.log("rpcresp", rpcEntry.method, Math.round(performance.now() - rpcEntry.startTs) + "ms");
if (data.error != null) {
rpcEntry.reject(data.error);
} else {
rpcEntry.resolve(data.data);
}
}
doRpc(method: string, params: any[]): Promise<any> {
if (!this.isOpen()) {
return Promise.reject("not connected");
}
let reqId = uuidv4();
let req = { type: "rpc", method: method, params: params, reqid: reqId };
let rpcEntry: RpcEntry = {
method: method,
startTs: performance.now(),
reqId: reqId,
resolve: null,
reject: null,
promise: null,
};
let rpcPromise = new Promise((resolve, reject) => {
rpcEntry.resolve = resolve;
rpcEntry.reject = reject;
});
rpcEntry.promise = rpcPromise;
this.rpcMap.set(reqId, rpcEntry);
this.wsConn.send(JSON.stringify(req));
return rpcPromise;
}
sendMessage(data: any) {
sendMessage(data: WSCommandType) {
if (!this.isOpen()) {
return;
}
let msg = JSON.stringify(data);
const byteSize = new Blob([msg]).size;
if (byteSize > MaxWebSocketSendSize) {
console.log("ws message too large", byteSize, data.type, msg.substring(0, 100));
console.log("ws message too large", byteSize, data.wscommand, msg.substring(0, 100));
return;
}
this.wsConn.send(msg);
}
pushMessage(data: any) {
pushMessage(data: WSCommandType) {
if (!this.isOpen()) {
this.msgQueue.push(data);
return;
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { globalWS } from "./global";
type RpcEntry = {
reqId: string;
startTs: number;
command: string;
msgFn: (msg: RpcMessage) => void;
};
let openRpcs = new Map<string, RpcEntry>();
async function* rpcResponseGenerator(
command: string,
reqid: string,
timeout: number
): AsyncGenerator<RpcMessage, void, boolean> {
const msgQueue: RpcMessage[] = [];
let signalFn: () => void;
let signalPromise = new Promise<void>((resolve) => (signalFn = resolve));
let timeoutId: NodeJS.Timeout = null;
if (timeout > 0) {
timeoutId = setTimeout(() => {
msgQueue.push({ resid: reqid, error: "EC-TIME: timeout waiting for response" });
signalFn();
}, timeout);
}
const msgFn = (msg: RpcMessage) => {
msgQueue.push(msg);
signalFn();
// reset signal promise
signalPromise = new Promise<void>((resolve) => (signalFn = resolve));
};
openRpcs.set(reqid, {
reqId: reqid,
startTs: Date.now(),
command: command,
msgFn: msgFn,
});
try {
while (true) {
while (msgQueue.length > 0) {
const msg = msgQueue.shift()!;
const shouldTerminate = yield msg;
if (shouldTerminate || !msg.cont) {
return;
}
}
await signalPromise;
}
} finally {
openRpcs.delete(reqid);
if (timeoutId != null) {
clearTimeout(timeoutId);
}
}
}
function sendRpcCommand(msg: RpcMessage): AsyncGenerator<RpcMessage, void, boolean> {
let wsMsg: WSRpcCommand = { wscommand: "rpc", message: msg };
globalWS.pushMessage(wsMsg);
if (msg.reqid == null) {
return null;
}
return rpcResponseGenerator(msg.command, msg.reqid, msg.timeout);
}
function handleIncomingRpcMessage(msg: RpcMessage) {
const isRequest = msg.command != null || msg.reqid != null;
if (isRequest) {
console.log("rpc request not supported", msg);
return;
}
if (msg.resid == null) {
console.log("rpc response missing resid", msg);
return;
}
const entry = openRpcs.get(msg.resid);
if (entry == null) {
console.log("rpc response generator not found", msg);
return;
}
entry.msgFn(msg);
}
export { handleIncomingRpcMessage, sendRpcCommand };
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
// generated by cmd/generate/main-generate.go
import * as WOS from "./wos";
// WshServerCommandToDeclMap
class WshServerType {
// command "controller:input" [call]
BlockInputCommand(data: CommandBlockInputData, opts?: WshRpcCommandOpts): Promise<void> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("controller:input", data, meta, opts);
}
// command "controller:restart" [call]
BlockRestartCommand(data: CommandBlockRestartData, opts?: WshRpcCommandOpts): Promise<void> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("controller:restart", data, meta, opts);
}
// command "createblock" [call]
CreateBlockCommand(data: CommandCreateBlockData, opts?: WshRpcCommandOpts): Promise<ORef> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("createblock", data, meta, opts);
}
// command "file:append" [call]
AppendFileCommand(data: CommandAppendFileData, opts?: WshRpcCommandOpts): Promise<void> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("file:append", data, meta, opts);
}
// command "file:appendijson" [call]
AppendIJsonCommand(data: CommandAppendIJsonData, opts?: WshRpcCommandOpts): Promise<void> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("file:appendijson", data, meta, opts);
}
// command "getmeta" [call]
GetMetaCommand(data: CommandGetMetaData, opts?: WshRpcCommandOpts): Promise<MetaType> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("getmeta", data, meta, opts);
}
// command "message" [call]
MessageCommand(data: CommandMessageData, opts?: WshRpcCommandOpts): Promise<void> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("message", data, meta, opts);
}
// command "resolveids" [call]
ResolveIdsCommand(data: CommandResolveIdsData, opts?: WshRpcCommandOpts): Promise<CommandResolveIdsRtnData> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("resolveids", data, meta, opts);
}
// command "setmeta" [call]
SetMetaCommand(data: CommandSetMetaData, opts?: WshRpcCommandOpts): Promise<void> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("setmeta", data, meta, opts);
}
// command "setview" [call]
BlockSetViewCommand(data: CommandBlockSetViewData, opts?: WshRpcCommandOpts): Promise<void> {
const meta: WshServerCommandMeta = {commandtype: "call"};
return WOS.callWshServerRpc("setview", data, meta, opts);
}
}
export const WshServer = new WshServerType();
+6 -36
View File
@@ -1,20 +1,10 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import {
WOS,
atoms,
getEventORefSubject,
globalStore,
sendWSCommand,
useBlockAtom,
useSettingsAtom,
} from "@/store/global";
import { WOS, atoms, getEventORefSubject, globalStore, useBlockAtom, useSettingsAtom } from "@/store/global";
import * as services from "@/store/services";
import * as keyutil from "@/util/keyutil";
import { FitAddon } from "@xterm/addon-fit";
import type { ITheme } from "@xterm/xterm";
import { Terminal } from "@xterm/xterm";
import clsx from "clsx";
import { produce } from "immer";
import * as jotai from "jotai";
@@ -23,6 +13,7 @@ import { IJsonView } from "./ijson";
import { TermStickers } from "./termsticker";
import { TermWrap } from "./termwrap";
import { WshServer } from "@/app/store/wshserver";
import "public/xterm.css";
import "./term.less";
@@ -54,23 +45,6 @@ function getThemeFromCSSVars(el: Element): ITheme {
return theme;
}
function handleResize(fitAddon: FitAddon, blockId: string, term: Terminal) {
if (term == null) {
return;
}
const oldRows = term.rows;
const oldCols = term.cols;
fitAddon.fit();
if (oldRows !== term.rows || oldCols !== term.cols) {
const wsCommand: SetBlockTermSizeWSCommand = {
wscommand: "setblocktermsize",
blockid: blockId,
termsize: { rows: term.rows, cols: term.cols },
};
sendWSCommand(wsCommand);
}
}
const keyMap = {
Enter: "\r",
Backspace: "\x7f",
@@ -177,14 +151,12 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
if (keyutil.checkKeyPressed(waveEvent, "Cmd:Escape")) {
event.preventDefault();
event.stopPropagation();
const metaCmd: BlockSetMetaCommand = { command: "setmeta", meta: { "term:mode": "html" } };
services.BlockService.SendCommand(this.blockId, metaCmd);
WshServer.SetMetaCommand({ oref: WOS.makeORef("block", blockId), meta: { "term:mode": null } });
return false;
}
if (shellProcStatusRef.current != "running" && keyutil.checkKeyPressed(waveEvent, "Enter")) {
// restart
const restartCmd: BlockRestartCommand = { command: "controller:restart", blockid: blockId };
services.BlockService.SendCommand(blockId, restartCmd);
WshServer.BlockRestartCommand({ blockid: blockId });
return false;
}
}
@@ -224,8 +196,7 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
const waveEvent = keyutil.adaptFromReactOrNativeKeyEvent(event);
if (keyutil.checkKeyPressed(waveEvent, "Cmd:Escape")) {
// reset term:mode
const metaCmd: BlockSetMetaCommand = { command: "setmeta", meta: { "term:mode": null } };
services.BlockService.SendCommand(blockId, metaCmd);
WshServer.SetMetaCommand({ oref: WOS.makeORef("block", blockId), meta: { "term:mode": null } });
return false;
}
const asciiVal = keyboardEventToASCII(event);
@@ -233,8 +204,7 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
return false;
}
const b64data = btoa(asciiVal);
const inputCmd: BlockInputCommand = { command: "controller:input", inputdata64: b64data, blockid: blockId };
services.BlockService.SendCommand(blockId, inputCmd);
WshServer.BlockInputCommand({ blockid: blockId, inputdata64: b64data });
return true;
};
+2 -7
View File
@@ -1,8 +1,8 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { WshServer } from "@/app/store/wshserver";
import { createBlock, getBackendHostPort } from "@/store/global";
import * as services from "@/store/services";
import clsx from "clsx";
import * as jotai from "jotai";
import * as React from "react";
@@ -98,12 +98,7 @@ function TermSticker({ sticker, config }: { sticker: StickerType; config: Sticke
console.log("clickHandler", sticker.clickcmd, sticker.clickblockdef);
if (sticker.clickcmd) {
const b64data = btoa(sticker.clickcmd);
const inputCmd: BlockInputCommand = {
command: "controller:input",
inputdata64: b64data,
blockid: config.blockId,
};
services.BlockService.SendCommand(config.blockId, inputCmd);
WshServer.BlockInputCommand({ blockid: config.blockId, inputdata64: b64data });
}
if (sticker.clickblockdef) {
createBlock(sticker.clickblockdef);
+2 -11
View File
@@ -1,6 +1,7 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { WshServer } from "@/app/store/wshserver";
import { PLATFORM, fetchWaveFile, getFileSubject, sendWSCommand } from "@/store/global";
import * as services from "@/store/services";
import { base64ToArray } from "@/util/util";
@@ -75,17 +76,7 @@ export class TermWrap {
handleTermData(data: string) {
const b64data = btoa(data);
if (b64data.length < 512) {
const wsCmd: BlockInputWSCommand = { wscommand: "blockinput", blockid: this.blockId, inputdata64: b64data };
sendWSCommand(wsCmd);
} else {
const inputCmd: BlockInputCommand = {
command: "controller:input",
blockid: this.blockId,
inputdata64: b64data,
};
services.BlockService.SendCommand(this.blockId, inputCmd);
}
WshServer.BlockInputCommand({ blockid: this.blockId, inputdata64: b64data });
}
addFocusListener(focusFn: () => void) {
+98 -18
View File
@@ -107,6 +107,73 @@ declare global {
meta: MetaType;
};
// wshrpc.CommandAppendFileData
type CommandAppendFileData = {
zoneid: string;
filename: string;
data64: string;
};
// wshrpc.CommandAppendIJsonData
type CommandAppendIJsonData = {
zoneid: string;
filename: string;
data: MetaType;
};
// wshrpc.CommandBlockInputData
type CommandBlockInputData = {
blockid: string;
inputdata64?: string;
signame?: string;
termsize?: TermSize;
};
// wshrpc.CommandBlockRestartData
type CommandBlockRestartData = {
blockid: string;
};
// wshrpc.CommandBlockSetViewData
type CommandBlockSetViewData = {
blockid: string;
view: string;
};
// wshrpc.CommandCreateBlockData
type CommandCreateBlockData = {
tabid: string;
blockdef: BlockDef;
rtopts: RuntimeOpts;
};
// wshrpc.CommandGetMetaData
type CommandGetMetaData = {
oref: ORef;
};
// wshrpc.CommandMessageData
type CommandMessageData = {
oref: ORef;
message: string;
};
// wshrpc.CommandResolveIdsData
type CommandResolveIdsData = {
ids: string[];
};
// wshrpc.CommandResolveIdsRtnData
type CommandResolveIdsRtnData = {
resolvedids: {[key: string]: ORef};
};
// wshrpc.CommandSetMetaData
type CommandSetMetaData = {
oref: ORef;
meta: MetaType;
};
// wshutil.CreateBlockCommand
type CreateBlockCommand = {
command: "createblock";
@@ -115,18 +182,6 @@ declare global {
rtopts?: RuntimeOpts;
};
// wconfig.DateTimeConfigType
type DateTimeConfigType = {
locale: string;
format: DateTimeFormatConfigType;
};
// wconfig.DateTimeFormatConfigType
type DateTimeFormatConfigType = {
dateStyle: number;
timeStyle: number;
};
// wstore.FileDef
type FileDef = {
filetype?: string;
@@ -185,10 +240,7 @@ declare global {
};
// waveobj.ORef
type ORef = {
otype: string;
oid: string;
};
type ORef = string;
// wstore.Point
type Point = {
@@ -202,6 +254,18 @@ declare global {
ids: string[];
};
// wshutil.RpcMessage
type RpcMessage = {
command?: string;
reqid?: string;
resid?: string;
timeout?: number;
cont?: boolean;
error?: string;
datatype?: string;
data?: any;
};
// wstore.RuntimeOpts
type RuntimeOpts = {
termsize?: TermSize;
@@ -218,7 +282,6 @@ declare global {
// wconfig.SettingsConfigType
type SettingsConfigType = {
mimetypes: {[key: string]: MimeTypeConfigType};
datetime: DateTimeConfigType;
term: TerminalConfigType;
widgets: WidgetsConfigType[];
blockheader: BlockHeaderOpts;
@@ -273,7 +336,7 @@ declare global {
type WSCommandType = {
wscommand: string;
} & ( SetBlockTermSizeWSCommand | BlockInputWSCommand );
} & ( SetBlockTermSizeWSCommand | BlockInputWSCommand | WSRpcCommand );
// eventbus.WSEventType
type WSEventType = {
@@ -297,6 +360,12 @@ declare global {
blockid: string;
};
// webcmd.WSRpcCommand
type WSRpcCommand = {
wscommand: "rpc";
message: RpcMessage;
};
// wconfig.WatcherUpdate
type WatcherUpdate = {
file: string;
@@ -380,6 +449,17 @@ declare global {
meta: MetaType;
};
// wshrpc.WshRpcCommandOpts
type WshRpcCommandOpts = {
timeout: number;
noresponse: boolean;
};
// wshrpc.WshServerCommandMeta
type WshServerCommandMeta = {
commandtype: string;
};
}
export {}

Some files were not shown because too many files have changed in this diff Show More