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"); 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. // gets the value of a WaveObject from the cache.
// should provide getFn if it is available (e.g. inside of a jotai atom) // should provide getFn if it is available (e.g. inside of a jotai atom)
// otherwise it will use the globalStore.get function // otherwise it will use the globalStore.get function

View File

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

View File

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

View File

@ -22,7 +22,6 @@ const (
) )
var CommandToTypeMap = map[string]reflect.Type{ var CommandToTypeMap = map[string]reflect.Type{
BlockCommand_Message: reflect.TypeOf(BlockMessageCommand{}),
BlockCommand_Input: reflect.TypeOf(BlockInputCommand{}), BlockCommand_Input: reflect.TypeOf(BlockInputCommand{}),
BlockCommand_SetView: reflect.TypeOf(BlockSetViewCommand{}), BlockCommand_SetView: reflect.TypeOf(BlockSetViewCommand{}),
BlockCommand_SetMeta: reflect.TypeOf(BlockSetMetaCommand{}), BlockCommand_SetMeta: reflect.TypeOf(BlockSetMetaCommand{}),
@ -33,7 +32,6 @@ func CommandTypeUnionMeta() tsgenmeta.TypeUnionMeta {
BaseType: reflect.TypeOf((*BlockCommand)(nil)).Elem(), BaseType: reflect.TypeOf((*BlockCommand)(nil)).Elem(),
TypeFieldName: "command", TypeFieldName: "command",
Types: []reflect.Type{ Types: []reflect.Type{
reflect.TypeOf(BlockMessageCommand{}),
reflect.TypeOf(BlockInputCommand{}), reflect.TypeOf(BlockInputCommand{}),
reflect.TypeOf(BlockSetViewCommand{}), reflect.TypeOf(BlockSetViewCommand{}),
reflect.TypeOf(BlockSetMetaCommand{}), reflect.TypeOf(BlockSetMetaCommand{}),
@ -70,15 +68,6 @@ func ParseCmdMap(cmdMap map[string]any) (BlockCommand, error) {
return cmd.(BlockCommand), nil 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 { type BlockInputCommand struct {
Command string `json:"command" tstype:"\"controller:input\""` Command string `json:"command" tstype:"\"controller:input\""`
InputData64 string `json:"inputdata64,omitempty"` 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) ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn() defer cancelFn()
switch cmd := cmdGen.(type) { switch cmd := cmdGen.(type) {
case *BlockMessageCommand:
log.Printf("MESSAGE: %s | %q\n", blockId, cmd.Message)
return nil
case *BlockSetViewCommand: case *BlockSetViewCommand:
log.Printf("SETVIEW: %s | %q\n", blockId, cmd.View) log.Printf("SETVIEW: %s | %q\n", blockId, cmd.View)
block, err := wstore.DBGet[*wstore.Block](ctx, blockId) block, err := wstore.DBGet[*wstore.Block](ctx, blockId)

View File

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

View File

@ -16,11 +16,12 @@ import (
"github.com/wavetermdev/thenextwave/pkg/service/objectservice" "github.com/wavetermdev/thenextwave/pkg/service/objectservice"
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
"github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/web/webcmd"
"github.com/wavetermdev/thenextwave/pkg/wstore" "github.com/wavetermdev/thenextwave/pkg/wstore"
) )
var ServiceMap = map[string]any{ var ServiceMap = map[string]any{
"block": &blockservice.BlockService{}, "block": blockservice.BlockServiceInstance,
"object": &objectservice.ObjectService{}, "object": &objectservice.ObjectService{},
"file": &fileservice.FileService{}, "file": &fileservice.FileService{},
"client": &clientservice.ClientService{}, "client": &clientservice.ClientService{},
@ -36,6 +37,7 @@ var methodMetaRType = reflect.TypeOf(tsgenmeta.MethodMeta{})
var waveObjUpdateRType = reflect.TypeOf(wstore.WaveObjUpdate{}) var waveObjUpdateRType = reflect.TypeOf(wstore.WaveObjUpdate{})
var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem() var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem()
var blockCommandRType = reflect.TypeOf((*blockcontroller.BlockCommand)(nil)).Elem() var blockCommandRType = reflect.TypeOf((*blockcontroller.BlockCommand)(nil)).Elem()
var wsCommandRType = reflect.TypeOf((*webcmd.WSCommandType)(nil)).Elem()
type WebCallType struct { type WebCallType struct {
Service string `json:"service"` Service string `json:"service"`
@ -91,7 +93,7 @@ func convertComplex(argType reflect.Type, jsonArg any) (any, error) {
} }
func isSpecialWaveArgType(argType reflect.Type) bool { 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) { 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 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) { func convertSpecial(argType reflect.Type, jsonArg any) (any, error) {
jsonType := reflect.TypeOf(jsonArg) jsonType := reflect.TypeOf(jsonArg)
if argType == blockCommandRType { if argType == blockCommandRType {
return convertBlockCommand(argType, jsonArg) return convertBlockCommand(argType, jsonArg)
} else if argType == wsCommandRType {
return convertWSCommand(argType, jsonArg)
} else if argType == waveObjRType { } else if argType == waveObjRType {
if jsonType.Kind() != reflect.Map { if jsonType.Kind() != reflect.Map {
return nil, fmt.Errorf("cannot convert %T to %s", jsonArg, argType) 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/service"
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
"github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/web/webcmd"
"github.com/wavetermdev/thenextwave/pkg/wstore" "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) { func GenerateWaveObjTypes(tsTypesMap map[reflect.Type]string) {
GenerateTSTypeUnion(blockcontroller.CommandTypeUnionMeta(), tsTypesMap) GenerateTSTypeUnion(blockcontroller.CommandTypeUnionMeta(), tsTypesMap)
GenerateTSTypeUnion(webcmd.WSCommandTypeUnionMeta(), tsTypesMap)
GenerateTSType(reflect.TypeOf(waveobj.ORef{}), tsTypesMap) GenerateTSType(reflect.TypeOf(waveobj.ORef{}), tsTypesMap)
GenerateTSType(reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem(), tsTypesMap) GenerateTSType(reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem(), tsTypesMap)
GenerateTSType(reflect.TypeOf(map[string]any{}), 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/google/uuid"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/eventbus" "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 const wsReadWaitTimeout = 15 * time.Second
@ -70,7 +73,42 @@ func getStringFromMap(jmsg map[string]any, key string) string {
return "" 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) { func processMessage(jmsg map[string]any, outputCh chan any) {
wsCommand := getStringFromMap(jmsg, "wscommand")
if wsCommand != "" {
processWSCommand(jmsg, outputCh)
return
}
msgType := getMessageType(jmsg) msgType := getMessageType(jmsg)
if msgType != "rpc" { if msgType != "rpc" {
return return