implement wscommand using type union interface, send resize events there

This commit is contained in:
sawka 2024-06-12 14:18:03 -07:00
parent 083e00227e
commit 8a71180f20
11 changed files with 159 additions and 45 deletions

View File

@ -157,4 +157,19 @@ function initWS() {
globalWS.connectNow("initWS");
}
export { WOS, atoms, getBackendHostPort, getORefSubject, globalStore, globalWS, initWS, useBlockAtom, useBlockCache };
function sendWSCommand(command: WSCommandType) {
globalWS.pushMessage(command);
}
export {
WOS,
atoms,
getBackendHostPort,
getORefSubject,
globalStore,
globalWS,
initWS,
sendWSCommand,
useBlockAtom,
useBlockCache,
};

View File

@ -283,21 +283,6 @@ function cleanWaveObjectCache() {
}
}
// Events.On("waveobj:update", (event: any) => {
// const data: WaveObjUpdate[] = event?.data;
// if (data == null) {
// return;
// }
// if (!Array.isArray(data)) {
// console.log("invalid waveobj:update, not an array", data);
// return;
// }
// if (data.length == 0) {
// return;
// }
// updateWaveObjects(data);
// });
// gets the value of a WaveObject from the cache.
// should provide getFn if it is available (e.g. inside of a jotai atom)
// otherwise it will use the globalStore.get function

View File

@ -1,7 +1,7 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { getBackendHostPort, getORefSubject, WOS } from "@/store/global";
import { WOS, getBackendHostPort, getORefSubject, sendWSCommand } from "@/store/global";
import * as services from "@/store/services";
import { base64ToArray } from "@/util/util";
import { FitAddon } from "@xterm/addon-fit";
@ -49,11 +49,12 @@ function handleResize(fitAddon: FitAddon, blockId: string, term: Terminal) {
const oldCols = term.cols;
fitAddon.fit();
if (oldRows !== term.rows || oldCols !== term.cols) {
const resizeCommand: BlockInputCommand = {
command: "controller:input",
const wsCommand: SetBlockTermSizeWSCommand = {
wscommand: "setblocktermsize",
blockid: blockId,
termsize: { rows: term.rows, cols: term.cols },
};
services.BlockService.SendCommand(blockId, resizeCommand);
sendWSCommand(wsCommand);
}
}
@ -81,8 +82,13 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
newTerm.loadAddon(newFitAddon);
newTerm.open(connectElemRef.current);
newFitAddon.fit();
services.BlockService.SendCommand(blockId, {
command: "controller:input",
// services.BlockService.SendCommand(blockId, {
// command: "controller:input",
// termsize: { rows: newTerm.rows, cols: newTerm.cols },
// });
sendWSCommand({
wscommand: "setblocktermsize",
blockid: blockId,
termsize: { rows: newTerm.rows, cols: newTerm.cols },
});
newTerm.onData((data) => {

View File

@ -16,7 +16,7 @@ declare global {
type BlockCommand = {
command: string;
} & ( BlockMessageCommand | BlockInputCommand | BlockSetViewCommand | BlockSetMetaCommand );
} & ( BlockInputCommand | BlockSetViewCommand | BlockSetMetaCommand );
// wstore.BlockDef
type BlockDef = {
@ -34,12 +34,6 @@ declare global {
termsize?: TermSize;
};
// blockcontroller.BlockMessageCommand
type BlockMessageCommand = {
command: "message";
message: string;
};
// blockcontroller.BlockSetMetaCommand
type BlockSetMetaCommand = {
command: "setmeta";
@ -117,6 +111,13 @@ declare global {
winsize?: WinSize;
};
// webcmd.SetBlockTermSizeWSCommand
type SetBlockTermSizeWSCommand = {
wscommand: "setblocktermsize";
blockid: string;
termsize: TermSize;
};
// wstore.Tab
type Tab = WaveObj & {
name: string;
@ -137,6 +138,10 @@ declare global {
activetabid: string;
};
type WSCommandType = {
wscommand: string;
} & ( SetBlockTermSizeWSCommand );
// eventbus.WSEventType
type WSEventType = {
eventtype: string;

View File

@ -22,7 +22,6 @@ const (
)
var CommandToTypeMap = map[string]reflect.Type{
BlockCommand_Message: reflect.TypeOf(BlockMessageCommand{}),
BlockCommand_Input: reflect.TypeOf(BlockInputCommand{}),
BlockCommand_SetView: reflect.TypeOf(BlockSetViewCommand{}),
BlockCommand_SetMeta: reflect.TypeOf(BlockSetMetaCommand{}),
@ -33,7 +32,6 @@ func CommandTypeUnionMeta() tsgenmeta.TypeUnionMeta {
BaseType: reflect.TypeOf((*BlockCommand)(nil)).Elem(),
TypeFieldName: "command",
Types: []reflect.Type{
reflect.TypeOf(BlockMessageCommand{}),
reflect.TypeOf(BlockInputCommand{}),
reflect.TypeOf(BlockSetViewCommand{}),
reflect.TypeOf(BlockSetMetaCommand{}),
@ -70,15 +68,6 @@ func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) {
return cmd.(BlockCommand), nil
}
type BlockMessageCommand struct {
Command string `json:"command" tstype:"\"message\""`
Message string `json:"message"`
}
func (mc *BlockMessageCommand) GetCommand() string {
return BlockCommand_Message
}
type BlockInputCommand struct {
Command string `json:"command" tstype:"\"controller:input\""`
InputData64 string `json:"inputdata64,omitempty"`

View File

@ -293,9 +293,6 @@ func ProcessStaticCommand(blockId string, cmdGen BlockCommand) error {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
switch cmd := cmdGen.(type) {
case *BlockMessageCommand:
log.Printf("MESSAGE: %s | %q\n", blockId, cmd.Message)
return nil
case *BlockSetViewCommand:
log.Printf("SETVIEW: %s | %q\n", blockId, cmd.View)
block, err := wstore.DBGet[*wstore.Block](ctx, blockId)

View File

@ -16,6 +16,8 @@ type BlockService struct{}
const DefaultTimeout = 2 * time.Second
var BlockServiceInstance = &BlockService{}
func (bs *BlockService) SendCommand_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
Desc: "send command to block",

View File

@ -16,11 +16,12 @@ import (
"github.com/wavetermdev/thenextwave/pkg/service/objectservice"
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/web/webcmd"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
var ServiceMap = map[string]any{
"block": &blockservice.BlockService{},
"block": blockservice.BlockServiceInstance,
"object": &objectservice.ObjectService{},
"file": &fileservice.FileService{},
"client": &clientservice.ClientService{},
@ -36,6 +37,7 @@ var methodMetaRType = reflect.TypeOf(tsgenmeta.MethodMeta{})
var waveObjUpdateRType = reflect.TypeOf(wstore.WaveObjUpdate{})
var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem()
var blockCommandRType = reflect.TypeOf((*blockcontroller.BlockCommand)(nil)).Elem()
var wsCommandRType = reflect.TypeOf((*webcmd.WSCommandType)(nil)).Elem()
type WebCallType struct {
Service string `json:"service"`
@ -91,7 +93,7 @@ func convertComplex(argType reflect.Type, jsonArg any) (any, error) {
}
func isSpecialWaveArgType(argType reflect.Type) bool {
return argType == waveObjRType || argType == waveObjSliceRType || argType == waveObjMapRType || argType == blockCommandRType
return argType == waveObjRType || argType == waveObjSliceRType || argType == waveObjMapRType || argType == blockCommandRType || argType == wsCommandRType
}
func convertBlockCommand(argType reflect.Type, jsonArg any) (any, error) {
@ -105,10 +107,23 @@ func convertBlockCommand(argType reflect.Type, jsonArg any) (any, error) {
return cmd, nil
}
func convertWSCommand(argType reflect.Type, jsonArg any) (any, error) {
if _, ok := jsonArg.(map[string]any); !ok {
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)
}
cmd, err := webcmd.ParseWSCommandMap(jsonArg.(map[string]any))
if err != nil {
return nil, fmt.Errorf("error parsing command map: %w", err)
}
return cmd, nil
}
func convertSpecial(argType reflect.Type, jsonArg any) (any, error) {
jsonType := reflect.TypeOf(jsonArg)
if argType == blockCommandRType {
return convertBlockCommand(argType, jsonArg)
} else if argType == wsCommandRType {
return convertWSCommand(argType, jsonArg)
} else if argType == waveObjRType {
if jsonType.Kind() != reflect.Map {
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType)

View File

@ -15,6 +15,7 @@ import (
"github.com/wavetermdev/thenextwave/pkg/service"
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/web/webcmd"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
@ -354,6 +355,7 @@ func GenerateServiceClass(serviceName string, serviceObj any, tsTypesMap map[ref
func GenerateWaveObjTypes(tsTypesMap map[reflect.Type]string) {
GenerateTSTypeUnion(blockcontroller.CommandTypeUnionMeta(), tsTypesMap)
GenerateTSTypeUnion(webcmd.WSCommandTypeUnionMeta(), tsTypesMap)
GenerateTSType(reflect.TypeOf(waveobj.ORef{}), tsTypesMap)
GenerateTSType(reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem(), tsTypesMap)
GenerateTSType(reflect.TypeOf(map[string]any{}), tsTypesMap)

60
pkg/web/webcmd/webcmd.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package webcmd
import (
"fmt"
"reflect"
"github.com/wavetermdev/thenextwave/pkg/shellexec"
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
)
const (
WSCommand_SetBlockTermSize = "setblocktermsize"
)
type WSCommandType interface {
GetWSCommand() string
}
func WSCommandTypeUnionMeta() tsgenmeta.TypeUnionMeta {
return tsgenmeta.TypeUnionMeta{
BaseType: reflect.TypeOf((*WSCommandType)(nil)).Elem(),
TypeFieldName: "wscommand",
Types: []reflect.Type{
reflect.TypeOf(SetBlockTermSizeWSCommand{}),
},
}
}
type SetBlockTermSizeWSCommand struct {
WSCommand string `json:"wscommand" tstype:"\"setblocktermsize\""`
BlockId string `json:"blockid"`
TermSize shellexec.TermSize `json:"termsize"`
}
func (cmd *SetBlockTermSizeWSCommand) GetWSCommand() string {
return cmd.WSCommand
}
func ParseWSCommandMap(cmdMap map[string]any) (WSCommandType, error) {
cmdType, ok := cmdMap["wscommand"].(string)
if !ok {
return nil, fmt.Errorf("no wscommand field in command map")
}
switch cmdType {
case WSCommand_SetBlockTermSize:
var cmd SetBlockTermSizeWSCommand
err := waveobj.DoMapStucture(&cmd, cmdMap)
if err != nil {
return nil, fmt.Errorf("error decoding SetBlockTermSizeWSCommand: %w", err)
}
return &cmd, nil
default:
return nil, fmt.Errorf("unknown wscommand type %q", cmdType)
}
}

View File

@ -15,7 +15,10 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/eventbus"
"github.com/wavetermdev/thenextwave/pkg/service/blockservice"
"github.com/wavetermdev/thenextwave/pkg/web/webcmd"
)
const wsReadWaitTimeout = 15 * time.Second
@ -70,7 +73,42 @@ func getStringFromMap(jmsg map[string]any, key string) string {
return ""
}
func processWSCommand(jmsg map[string]any, outputCh chan any) {
var rtnErr error
defer func() {
r := recover()
if r != nil {
rtnErr = fmt.Errorf("panic: %v", r)
log.Printf("panic in processMessage: %v\n", r)
debug.PrintStack()
}
if rtnErr == nil {
return
}
rtn := map[string]any{"type": "error", "error": rtnErr.Error()}
outputCh <- rtn
}()
wsCommand, err := webcmd.ParseWSCommandMap(jmsg)
if err != nil {
rtnErr = fmt.Errorf("cannot parse wscommand: %v", err)
return
}
switch cmd := wsCommand.(type) {
case *webcmd.SetBlockTermSizeWSCommand:
blockCmd := &blockcontroller.BlockInputCommand{
Command: blockcontroller.BlockCommand_Input,
TermSize: &cmd.TermSize,
}
blockservice.BlockServiceInstance.SendCommand(cmd.BlockId, blockCmd)
}
}
func processMessage(jmsg map[string]any, outputCh chan any) {
wsCommand := getStringFromMap(jmsg, "wscommand")
if wsCommand != "" {
processWSCommand(jmsg, outputCh)
return
}
msgType := getMessageType(jmsg)
if msgType != "rpc" {
return