checkpoint on domain sockets + update background colors + transparency (#160)

This commit is contained in:
Mike Sawka
2024-07-26 13:30:11 -07:00
committed by GitHub
parent f1837d6fae
commit 9df9c99fbd
31 changed files with 1000 additions and 436 deletions
+65 -30
View File
@@ -9,24 +9,29 @@ import (
"github.com/wavetermdev/thenextwave/pkg/wshutil"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/waveai"
)
// command "controller:input", wshserver.BlockInputCommand
func BlockInputCommand(w *wshutil.WshRpc, data wshrpc.CommandBlockInputData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "controller:input", data, opts)
// command "authenticate", wshserver.AuthenticateCommand
func AuthenticateCommand(w *wshutil.WshRpc, data string, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "authenticate", data, opts)
return err
}
// command "controller:restart", wshserver.BlockRestartCommand
func BlockRestartCommand(w *wshutil.WshRpc, data wshrpc.CommandBlockRestartData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "controller:restart", data, opts)
// command "controllerinput", wshserver.ControllerInputCommand
func ControllerInputCommand(w *wshutil.WshRpc, data wshrpc.CommandBlockInputData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "controllerinput", data, opts)
return err
}
// command "controllerrestart", wshserver.ControllerRestartCommand
func ControllerRestartCommand(w *wshutil.WshRpc, data wshrpc.CommandBlockRestartData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "controllerrestart", data, opts)
return err
}
// command "createblock", wshserver.CreateBlockCommand
func CreateBlockCommand(w *wshutil.WshRpc, data wshrpc.CommandCreateBlockData, opts *wshrpc.WshRpcCommandOpts) (*waveobj.ORef, error) {
resp, err := sendRpcRequestCallHelper[*waveobj.ORef](w, "createblock", data, opts)
func CreateBlockCommand(w *wshutil.WshRpc, data wshrpc.CommandCreateBlockData, opts *wshrpc.WshRpcCommandOpts) (waveobj.ORef, error) {
resp, err := sendRpcRequestCallHelper[waveobj.ORef](w, "createblock", data, opts)
return resp, err
}
@@ -36,27 +41,57 @@ func DeleteBlockCommand(w *wshutil.WshRpc, data wshrpc.CommandDeleteBlockData, o
return err
}
// command "file:append", wshserver.AppendFileCommand
func AppendFileCommand(w *wshutil.WshRpc, data wshrpc.CommandFileData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "file:append", data, opts)
// command "eventpublish", wshserver.EventPublishCommand
func EventPublishCommand(w *wshutil.WshRpc, data wshrpc.WaveEvent, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "eventpublish", data, opts)
return err
}
// command "file:appendijson", wshserver.AppendIJsonCommand
func AppendIJsonCommand(w *wshutil.WshRpc, data wshrpc.CommandAppendIJsonData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "file:appendijson", data, opts)
// command "eventrecv", wshserver.EventRecvCommand
func EventRecvCommand(w *wshutil.WshRpc, data wshrpc.WaveEvent, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "eventrecv", data, opts)
return err
}
// command "file:read", wshserver.ReadFile
func ReadFile(w *wshutil.WshRpc, data wshrpc.CommandFileData, opts *wshrpc.WshRpcCommandOpts) (string, error) {
resp, err := sendRpcRequestCallHelper[string](w, "file:read", data, opts)
// command "eventsub", wshserver.EventSubCommand
func EventSubCommand(w *wshutil.WshRpc, data wshrpc.SubscriptionRequest, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "eventsub", data, opts)
return err
}
// command "eventunsub", wshserver.EventUnsubCommand
func EventUnsubCommand(w *wshutil.WshRpc, data wshrpc.SubscriptionRequest, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "eventunsub", data, opts)
return err
}
// command "eventunsuball", wshserver.EventUnsubAllCommand
func EventUnsubAllCommand(w *wshutil.WshRpc, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "eventunsuball", nil, opts)
return err
}
// command "fileappend", wshserver.FileAppendCommand
func FileAppendCommand(w *wshutil.WshRpc, data wshrpc.CommandFileData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "fileappend", data, opts)
return err
}
// command "fileappendijson", wshserver.FileAppendIJsonCommand
func FileAppendIJsonCommand(w *wshutil.WshRpc, data wshrpc.CommandAppendIJsonData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "fileappendijson", data, opts)
return err
}
// command "fileread", wshserver.FileReadCommand
func FileReadCommand(w *wshutil.WshRpc, data wshrpc.CommandFileData, opts *wshrpc.WshRpcCommandOpts) (string, error) {
resp, err := sendRpcRequestCallHelper[string](w, "fileread", data, opts)
return resp, err
}
// command "file:write", wshserver.WriteFile
func WriteFile(w *wshutil.WshRpc, data wshrpc.CommandFileData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "file:write", data, opts)
// command "filewrite", wshserver.FileWriteCommand
func FileWriteCommand(w *wshutil.WshRpc, data wshrpc.CommandFileData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "filewrite", data, opts)
return err
}
@@ -84,20 +119,20 @@ func SetMetaCommand(w *wshutil.WshRpc, data wshrpc.CommandSetMetaData, opts *wsh
return err
}
// command "setview", wshserver.BlockSetViewCommand
func BlockSetViewCommand(w *wshutil.WshRpc, data wshrpc.CommandBlockSetViewData, opts *wshrpc.WshRpcCommandOpts) error {
// command "setview", wshserver.SetViewCommand
func SetViewCommand(w *wshutil.WshRpc, data wshrpc.CommandBlockSetViewData, opts *wshrpc.WshRpcCommandOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "setview", data, opts)
return err
}
// command "stream:waveai", wshserver.RespStreamWaveAi
func RespStreamWaveAi(w *wshutil.WshRpc, data waveai.OpenAiStreamRequest, opts *wshrpc.WshRpcCommandOpts) chan wshrpc.RespOrErrorUnion[waveai.OpenAIPacketType] {
return sendRpcRequestResponseStreamHelper[waveai.OpenAIPacketType](w, "stream:waveai", data, opts)
}
// command "streamtest", wshserver.RespStreamTest
func RespStreamTest(w *wshutil.WshRpc, opts *wshrpc.WshRpcCommandOpts) chan wshrpc.RespOrErrorUnion[int] {
// command "streamtest", wshserver.StreamTestCommand
func StreamTestCommand(w *wshutil.WshRpc, opts *wshrpc.WshRpcCommandOpts) chan wshrpc.RespOrErrorUnion[int] {
return sendRpcRequestResponseStreamHelper[int](w, "streamtest", nil, opts)
}
// command "streamwaveai", wshserver.StreamWaveAiCommand
func StreamWaveAiCommand(w *wshutil.WshRpc, data wshrpc.OpenAiStreamRequest, opts *wshrpc.WshRpcCommandOpts) chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] {
return sendRpcRequestResponseStreamHelper[wshrpc.OpenAIPacketType](w, "streamwaveai", data, opts)
}
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wshrpc
import (
"context"
"fmt"
"log"
"reflect"
"strings"
)
type WshRpcMethodDecl struct {
Command string
CommandType string
MethodName string
CommandDataType reflect.Type
DefaultResponseDataType reflect.Type
}
var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem()
var wshRpcInterfaceRType = reflect.TypeOf((*WshRpcInterface)(nil)).Elem()
func getWshCommandType(method reflect.Method) string {
if method.Type.NumOut() == 1 {
outType := method.Type.Out(0)
if outType.Kind() == reflect.Chan {
return RpcType_ResponseStream
}
}
return RpcType_Call
}
func getWshMethodResponseType(commandType string, method reflect.Method) reflect.Type {
switch commandType {
case RpcType_ResponseStream:
if method.Type.NumOut() != 1 {
panic(fmt.Sprintf("method %q has invalid number of return values for response stream", method.Name))
}
outType := method.Type.Out(0)
if outType.Kind() != reflect.Chan {
panic(fmt.Sprintf("method %q has invalid return type %s for response stream", method.Name, outType))
}
elemType := outType.Elem()
if !strings.HasPrefix(elemType.Name(), "RespOrErrorUnion") {
panic(fmt.Sprintf("method %q has invalid return element type %s for response stream (should be RespOrErrorUnion)", method.Name, elemType))
}
respField, found := elemType.FieldByName("Response")
if !found {
panic(fmt.Sprintf("method %q has invalid return element type %s for response stream (missing Response field)", method.Name, elemType))
}
return respField.Type
case RpcType_Call:
if method.Type.NumOut() > 1 {
return method.Type.Out(0)
}
return nil
default:
panic(fmt.Sprintf("unsupported command type %q", commandType))
}
}
func generateWshCommandDecl(method reflect.Method) *WshRpcMethodDecl {
if method.Type.NumIn() == 0 || method.Type.In(0) != contextRType {
panic(fmt.Sprintf("method %q does not have context as first argument", method.Name))
}
cmdStr := method.Name
decl := &WshRpcMethodDecl{}
// remove Command suffix
if !strings.HasSuffix(cmdStr, "Command") {
panic(fmt.Sprintf("method %q does not have Command suffix", cmdStr))
}
cmdStr = cmdStr[:len(cmdStr)-len("Command")]
decl.Command = strings.ToLower(cmdStr)
decl.CommandType = getWshCommandType(method)
decl.MethodName = method.Name
var cdataType reflect.Type
if method.Type.NumIn() > 1 {
cdataType = method.Type.In(1)
}
decl.CommandDataType = cdataType
decl.DefaultResponseDataType = getWshMethodResponseType(decl.CommandType, method)
return decl
}
func MakeMethodMapForImpl(impl any, declMap map[string]*WshRpcMethodDecl) map[string]reflect.Method {
rtype := reflect.TypeOf(impl)
rtnMap := make(map[string]reflect.Method)
for midx := 0; midx < rtype.NumMethod(); midx++ {
method := rtype.Method(midx)
if !strings.HasSuffix(method.Name, "Command") {
continue
}
commandName := strings.ToLower(method.Name[:len(method.Name)-len("Command")])
decl := declMap[commandName]
if decl == nil {
log.Printf("WARNING: method %q does not match a command method", method.Name)
continue
}
rtnMap[commandName] = method
}
return rtnMap
}
func GenerateWshCommandDeclMap() map[string]*WshRpcMethodDecl {
rtype := wshRpcInterfaceRType
rtnMap := make(map[string]*WshRpcMethodDecl)
for midx := 0; midx < rtype.NumMethod(); midx++ {
method := rtype.Method(midx)
decl := generateWshCommandDecl(method)
rtnMap[decl.Command] = decl
}
return rtnMap
}
+111 -22
View File
@@ -5,45 +5,77 @@
package wshrpc
import (
"context"
"reflect"
"github.com/wavetermdev/thenextwave/pkg/ijson"
"github.com/wavetermdev/thenextwave/pkg/shellexec"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wshutil"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
const (
Command_Message = "message"
Command_SetView = "setview"
Command_SetMeta = "setmeta"
Command_GetMeta = "getmeta"
Command_BlockInput = "controller:input"
Command_Restart = "controller:restart"
Command_AppendFile = "file:append"
Command_AppendIJson = "file:appendijson"
Command_ResolveIds = "resolveids"
Command_CreateBlock = "createblock"
Command_DeleteBlock = "deleteblock"
Command_WriteFile = "file:write"
Command_ReadFile = "file:read"
Command_StreamWaveAi = "stream:waveai"
RpcType_Call = "call" // single response (regular rpc)
RpcType_ResponseStream = "responsestream" // stream of responses (streaming rpc)
RpcType_StreamingRequest = "streamingrequest" // streaming request
RpcType_Complex = "complex" // streaming request/response
)
const (
Command_Authenticate = "authenticate"
Command_Message = "message"
Command_GetMeta = "getmeta"
Command_SetMeta = "setmeta"
Command_SetView = "setview"
Command_ControllerInput = "controllerinput"
Command_ControllerRestart = "controllerrestart"
Command_FileAppend = "fileappend"
Command_FileAppendIJson = "fileappendijson"
Command_ResolveIds = "resolveids"
Command_CreateBlock = "createblock"
Command_DeleteBlock = "deleteblock"
Command_FileWrite = "filewrite"
Command_FileRead = "fileread"
Command_EventPublish = "eventpublish"
Command_EventRecv = "eventrecv"
Command_EventSub = "eventsub"
Command_EventUnsub = "eventunsub"
Command_EventUnsubAll = "eventunsuball"
Command_StreamTest = "streamtest"
Command_StreamWaveAi = "streamwaveai"
)
type MetaDataType = map[string]any
var DataTypeMap = map[string]reflect.Type{
"meta": reflect.TypeOf(MetaDataType{}),
"resolveidsrtn": reflect.TypeOf(CommandResolveIdsRtnData{}),
"oref": reflect.TypeOf(waveobj.ORef{}),
}
type RespOrErrorUnion[T any] struct {
Response T
Error error
}
type WshRpcInterface interface {
AuthenticateCommand(ctx context.Context, data string) error
MessageCommand(ctx context.Context, data CommandMessageData) error
GetMetaCommand(ctx context.Context, data CommandGetMetaData) (MetaDataType, error)
SetMetaCommand(ctx context.Context, data CommandSetMetaData) error
SetViewCommand(ctx context.Context, data CommandBlockSetViewData) error
ControllerInputCommand(ctx context.Context, data CommandBlockInputData) error
ControllerRestartCommand(ctx context.Context, data CommandBlockRestartData) error
FileAppendCommand(ctx context.Context, data CommandFileData) error
FileAppendIJsonCommand(ctx context.Context, data CommandAppendIJsonData) error
ResolveIdsCommand(ctx context.Context, data CommandResolveIdsData) (CommandResolveIdsRtnData, error)
CreateBlockCommand(ctx context.Context, data CommandCreateBlockData) (waveobj.ORef, error)
DeleteBlockCommand(ctx context.Context, data CommandDeleteBlockData) error
FileWriteCommand(ctx context.Context, data CommandFileData) error
FileReadCommand(ctx context.Context, data CommandFileData) (string, error)
EventPublishCommand(ctx context.Context, data WaveEvent) error
EventRecvCommand(ctx context.Context, data WaveEvent) error
EventSubCommand(ctx context.Context, data SubscriptionRequest) error
EventUnsubCommand(ctx context.Context, data SubscriptionRequest) error
EventUnsubAllCommand(ctx context.Context) error
StreamTestCommand(ctx context.Context) chan RespOrErrorUnion[int]
StreamWaveAiCommand(ctx context.Context, request OpenAiStreamRequest) chan RespOrErrorUnion[OpenAIPacketType]
}
// for frontend
type WshServerCommandMeta struct {
CommandType string `json:"commandtype"`
@@ -54,7 +86,13 @@ type WshRpcCommandOpts struct {
NoResponse bool `json:"noresponse"`
}
func HackRpcContextIntoData(dataPtr any, rpcContext wshutil.RpcContext) {
type RpcContext struct {
BlockId string `json:"blockid,omitempty"`
TabId string `json:"tabid,omitempty"`
WindowId string `json:"windowid,omitempty"`
}
func HackRpcContextIntoData(dataPtr any, rpcContext RpcContext) {
dataVal := reflect.ValueOf(dataPtr).Elem()
dataType := dataVal.Type()
for i := 0; i < dataVal.NumField(); i++ {
@@ -141,3 +179,54 @@ type CommandAppendIJsonData struct {
type CommandDeleteBlockData struct {
BlockId string `json:"blockid" wshcontext:"BlockId"`
}
type WaveEvent struct {
Event string `json:"event"`
Scopes []string `json:"scopes,omitempty"`
Sender string `json:"sender,omitempty"`
Data any `json:"data,omitempty"`
}
type SubscriptionRequest struct {
Event string `json:"event"`
Scopes []string `json:"scopes,omitempty"`
AllScopes bool `json:"allscopes,omitempty"`
}
type OpenAiStreamRequest struct {
ClientId string `json:"clientid,omitempty"`
Opts *OpenAIOptsType `json:"opts"`
Prompt []OpenAIPromptMessageType `json:"prompt"`
}
type OpenAIPromptMessageType struct {
Role string `json:"role"`
Content string `json:"content"`
Name string `json:"name,omitempty"`
}
type OpenAIOptsType struct {
Model string `json:"model"`
APIToken string `json:"apitoken"`
BaseURL string `json:"baseurl,omitempty"`
MaxTokens int `json:"maxtokens,omitempty"`
MaxChoices int `json:"maxchoices,omitempty"`
Timeout int `json:"timeout,omitempty"`
}
type OpenAIPacketType struct {
Type string `json:"type"`
Model string `json:"model,omitempty"`
Created int64 `json:"created,omitempty"`
FinishReason string `json:"finish_reason,omitempty"`
Usage *OpenAIUsageType `json:"usage,omitempty"`
Index int `json:"index,omitempty"`
Text string `json:"text,omitempty"`
Error string `json:"error,omitempty"`
}
type OpenAIUsageType struct {
PromptTokens int `json:"prompt_tokens,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"`
TotalTokens int `json:"total_tokens,omitempty"`
}
+67 -44
View File
@@ -11,7 +11,6 @@ import (
"fmt"
"io/fs"
"log"
"reflect"
"strings"
"time"
@@ -20,45 +19,26 @@ import (
"github.com/wavetermdev/thenextwave/pkg/filestore"
"github.com/wavetermdev/thenextwave/pkg/waveai"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wps"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wshutil"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
var RespStreamTest_MethodDecl = &WshServerMethodDecl{
Command: "streamtest",
CommandType: wshutil.RpcType_ResponseStream,
MethodName: "RespStreamTest",
Method: reflect.ValueOf(WshServerImpl.RespStreamTest),
CommandDataType: nil,
DefaultResponseDataType: reflect.TypeOf((int)(0)),
}
var RespStreamWaveAi_MethodDecl = &WshServerMethodDecl{
Command: wshrpc.Command_StreamWaveAi,
CommandType: wshutil.RpcType_ResponseStream,
MethodName: "RespStreamWaveAi",
Method: reflect.ValueOf(WshServerImpl.RespStreamWaveAi),
CommandDataType: reflect.TypeOf(waveai.OpenAiStreamRequest{}),
DefaultResponseDataType: reflect.TypeOf(waveai.OpenAIPacketType{}),
}
var WshServerCommandToDeclMap = map[string]*WshServerMethodDecl{
wshrpc.Command_Message: GetWshServerMethod(wshrpc.Command_Message, wshutil.RpcType_Call, "MessageCommand", WshServerImpl.MessageCommand),
wshrpc.Command_SetView: GetWshServerMethod(wshrpc.Command_SetView, wshutil.RpcType_Call, "BlockSetViewCommand", WshServerImpl.BlockSetViewCommand),
wshrpc.Command_SetMeta: GetWshServerMethod(wshrpc.Command_SetMeta, wshutil.RpcType_Call, "SetMetaCommand", WshServerImpl.SetMetaCommand),
wshrpc.Command_GetMeta: GetWshServerMethod(wshrpc.Command_GetMeta, wshutil.RpcType_Call, "GetMetaCommand", WshServerImpl.GetMetaCommand),
wshrpc.Command_ResolveIds: GetWshServerMethod(wshrpc.Command_ResolveIds, wshutil.RpcType_Call, "ResolveIdsCommand", WshServerImpl.ResolveIdsCommand),
wshrpc.Command_CreateBlock: GetWshServerMethod(wshrpc.Command_CreateBlock, wshutil.RpcType_Call, "CreateBlockCommand", WshServerImpl.CreateBlockCommand),
wshrpc.Command_Restart: GetWshServerMethod(wshrpc.Command_Restart, wshutil.RpcType_Call, "BlockRestartCommand", WshServerImpl.BlockRestartCommand),
wshrpc.Command_BlockInput: GetWshServerMethod(wshrpc.Command_BlockInput, wshutil.RpcType_Call, "BlockInputCommand", WshServerImpl.BlockInputCommand),
wshrpc.Command_AppendFile: GetWshServerMethod(wshrpc.Command_AppendFile, wshutil.RpcType_Call, "AppendFileCommand", WshServerImpl.AppendFileCommand),
wshrpc.Command_AppendIJson: GetWshServerMethod(wshrpc.Command_AppendIJson, wshutil.RpcType_Call, "AppendIJsonCommand", WshServerImpl.AppendIJsonCommand),
wshrpc.Command_DeleteBlock: GetWshServerMethod(wshrpc.Command_DeleteBlock, wshutil.RpcType_Call, "DeleteBlockCommand", WshServerImpl.DeleteBlockCommand),
wshrpc.Command_WriteFile: GetWshServerMethod(wshrpc.Command_WriteFile, wshutil.RpcType_Call, "WriteFile", WshServerImpl.WriteFile),
wshrpc.Command_ReadFile: GetWshServerMethod(wshrpc.Command_ReadFile, wshutil.RpcType_Call, "ReadFile", WshServerImpl.ReadFile),
wshrpc.Command_StreamWaveAi: RespStreamWaveAi_MethodDecl,
"streamtest": RespStreamTest_MethodDecl,
func (ws *WshServer) AuthenticateCommand(ctx context.Context, data string) error {
w := wshutil.GetWshRpcFromContext(ctx)
if w == nil {
return fmt.Errorf("no wshrpc in context")
}
newCtx, err := wshutil.ValidateAndExtractRpcContextFromToken(data)
if err != nil {
return fmt.Errorf("error validating token: %w", err)
}
if newCtx == nil {
return fmt.Errorf("no context found in jwt token")
}
w.SetRpcContext(*newCtx)
return nil
}
// for testing
@@ -68,7 +48,7 @@ func (ws *WshServer) MessageCommand(ctx context.Context, data wshrpc.CommandMess
}
// for testing
func (ws *WshServer) RespStreamTest(ctx context.Context) chan wshrpc.RespOrErrorUnion[int] {
func (ws *WshServer) StreamTestCommand(ctx context.Context) chan wshrpc.RespOrErrorUnion[int] {
rtn := make(chan wshrpc.RespOrErrorUnion[int])
go func() {
for i := 1; i <= 5; i++ {
@@ -80,7 +60,7 @@ func (ws *WshServer) RespStreamTest(ctx context.Context) chan wshrpc.RespOrError
return rtn
}
func (ws *WshServer) RespStreamWaveAi(ctx context.Context, request waveai.OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[waveai.OpenAIPacketType] {
func (ws *WshServer) StreamWaveAiCommand(ctx context.Context, request wshrpc.OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] {
if request.Opts.BaseURL == "" && request.Opts.APIToken == "" {
return waveai.RunCloudCompletionStream(ctx, request)
}
@@ -224,7 +204,7 @@ func (ws *WshServer) CreateBlockCommand(ctx context.Context, data wshrpc.Command
return &waveobj.ORef{OType: wstore.OType_Block, OID: blockData.OID}, nil
}
func (ws *WshServer) BlockSetViewCommand(ctx context.Context, data wshrpc.CommandBlockSetViewData) error {
func (ws *WshServer) SetViewCommand(ctx context.Context, data wshrpc.CommandBlockSetViewData) error {
log.Printf("SETVIEW: %s | %q\n", data.BlockId, data.View)
ctx = wstore.ContextWithUpdates(ctx)
block, err := wstore.DBGet[*wstore.Block](ctx, data.BlockId)
@@ -241,7 +221,7 @@ func (ws *WshServer) BlockSetViewCommand(ctx context.Context, data wshrpc.Comman
return nil
}
func (ws *WshServer) BlockRestartCommand(ctx context.Context, data wshrpc.CommandBlockRestartData) error {
func (ws *WshServer) ControllerRestartCommand(ctx context.Context, data wshrpc.CommandBlockRestartData) error {
bc := blockcontroller.GetBlockController(data.BlockId)
if bc == nil {
return fmt.Errorf("block controller not found for block %q", data.BlockId)
@@ -249,7 +229,7 @@ func (ws *WshServer) BlockRestartCommand(ctx context.Context, data wshrpc.Comman
return bc.RestartController()
}
func (ws *WshServer) BlockInputCommand(ctx context.Context, data wshrpc.CommandBlockInputData) error {
func (ws *WshServer) ControllerInputCommand(ctx context.Context, data wshrpc.CommandBlockInputData) error {
bc := blockcontroller.GetBlockController(data.BlockId)
if bc == nil {
return fmt.Errorf("block controller not found for block %q", data.BlockId)
@@ -269,7 +249,7 @@ func (ws *WshServer) BlockInputCommand(ctx context.Context, data wshrpc.CommandB
return bc.SendInput(inputUnion)
}
func (ws *WshServer) WriteFile(ctx context.Context, data wshrpc.CommandFileData) error {
func (ws *WshServer) FileWriteCommand(ctx context.Context, data wshrpc.CommandFileData) error {
dataBuf, err := base64.StdEncoding.DecodeString(data.Data64)
if err != nil {
return fmt.Errorf("error decoding data64: %w", err)
@@ -290,7 +270,7 @@ func (ws *WshServer) WriteFile(ctx context.Context, data wshrpc.CommandFileData)
return nil
}
func (ws *WshServer) ReadFile(ctx context.Context, data wshrpc.CommandFileData) (string, error) {
func (ws *WshServer) FileReadCommand(ctx context.Context, data wshrpc.CommandFileData) (string, error) {
_, dataBuf, err := filestore.WFS.ReadFile(ctx, data.ZoneId, data.FileName)
if err != nil {
return "", fmt.Errorf("error reading blockfile: %w", err)
@@ -298,7 +278,7 @@ func (ws *WshServer) ReadFile(ctx context.Context, data wshrpc.CommandFileData)
return base64.StdEncoding.EncodeToString(dataBuf), nil
}
func (ws *WshServer) AppendFileCommand(ctx context.Context, data wshrpc.CommandFileData) error {
func (ws *WshServer) FileAppendCommand(ctx context.Context, data wshrpc.CommandFileData) error {
dataBuf, err := base64.StdEncoding.DecodeString(data.Data64)
if err != nil {
return fmt.Errorf("error decoding data64: %w", err)
@@ -320,7 +300,7 @@ func (ws *WshServer) AppendFileCommand(ctx context.Context, data wshrpc.CommandF
return nil
}
func (ws *WshServer) AppendIJsonCommand(ctx context.Context, data wshrpc.CommandAppendIJsonData) error {
func (ws *WshServer) FileAppendIJsonCommand(ctx context.Context, data wshrpc.CommandAppendIJsonData) error {
tryCreate := true
if data.FileName == blockcontroller.BlockFile_Html && tryCreate {
err := filestore.WFS.MakeFile(ctx, data.ZoneId, data.FileName, nil, filestore.FileOptsType{MaxSize: blockcontroller.DefaultHtmlMaxFileSize, IJson: true})
@@ -378,3 +358,46 @@ func (ws *WshServer) DeleteBlockCommand(ctx context.Context, data wshrpc.Command
sendWStoreUpdatesToEventBus(updates)
return nil
}
func (ws *WshServer) EventRecvCommand(ctx context.Context, data wshrpc.WaveEvent) error {
return nil
}
func (ws *WshServer) EventPublishCommand(ctx context.Context, data wshrpc.WaveEvent) error {
wrpc := wshutil.GetWshRpcFromContext(ctx)
if wrpc == nil {
return fmt.Errorf("no wshrpc in context")
}
if data.Sender == "" {
data.Sender = wrpc.ClientId()
}
wps.Broker.Publish(data)
return nil
}
func (ws *WshServer) EventSubCommand(ctx context.Context, data wshrpc.SubscriptionRequest) error {
wrpc := wshutil.GetWshRpcFromContext(ctx)
if wrpc == nil {
return fmt.Errorf("no wshrpc in context")
}
wps.Broker.Subscribe(wrpc, data)
return nil
}
func (ws *WshServer) EventUnsubCommand(ctx context.Context, data wshrpc.SubscriptionRequest) error {
wrpc := wshutil.GetWshRpcFromContext(ctx)
if wrpc == nil {
return fmt.Errorf("no wshrpc in context")
}
wps.Broker.Unsubscribe(wrpc, data)
return nil
}
func (ws *WshServer) EventUnsubAllCommand(ctx context.Context) error {
wrpc := wshutil.GetWshRpcFromContext(ctx)
if wrpc == nil {
return fmt.Errorf("no wshrpc in context")
}
wps.Broker.UnsubscribeAll(wrpc)
return nil
}
+23 -86
View File
@@ -10,9 +10,7 @@ import (
"net"
"os"
"reflect"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
"github.com/wavetermdev/thenextwave/pkg/wavebase"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
@@ -41,6 +39,7 @@ type WshServerMethodDecl struct {
var WshServerImpl = WshServer{}
var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem()
var wshCommandDeclMap = wshrpc.GenerateWshCommandDeclMap()
func GetWshServerMethod(command string, commandType string, methodName string, methodFunc any) *WshServerMethodDecl {
methodVal := reflect.ValueOf(methodFunc)
@@ -55,12 +54,16 @@ func GetWshServerMethod(command string, commandType string, methodName string, m
if methodType.NumOut() > 1 {
defResponseType = methodType.Out(0)
}
var cdataType reflect.Type
if methodType.NumIn() > 1 {
cdataType = methodType.In(1)
}
rtn := &WshServerMethodDecl{
Command: command,
CommandType: commandType,
MethodName: methodName,
Method: methodVal,
CommandDataType: methodType.In(1),
CommandDataType: cdataType,
DefaultResponseDataType: defResponseType,
}
return rtn
@@ -89,7 +92,7 @@ func decodeRtnVals(rtnVals []reflect.Value) (any, error) {
func mainWshServerHandler(handler *wshutil.RpcResponseHandler) bool {
command := handler.GetCommand()
methodDecl := WshServerCommandToDeclMap[command]
methodDecl := wshCommandDeclMap[command]
if methodDecl == nil {
handler.SendResponseError(fmt.Errorf("command %q not found", command))
return true
@@ -106,8 +109,18 @@ func mainWshServerHandler(handler *wshutil.RpcResponseHandler) bool {
wshrpc.HackRpcContextIntoData(commandData, handler.GetRpcContext())
callParams = append(callParams, reflect.ValueOf(commandData).Elem())
}
if methodDecl.CommandType == wshutil.RpcType_Call {
rtnVals := methodDecl.Method.Call(callParams)
implVal := reflect.ValueOf(&WshServerImpl)
implMethod := implVal.MethodByName(methodDecl.MethodName)
if !implMethod.IsValid() {
if !handler.NeedsResponse() {
// we also send an out of band message here since this is likely unexpected and will require debugging
handler.SendMessage(fmt.Sprintf("command %q method %q not found", handler.GetCommand(), methodDecl.MethodName))
}
handler.SendResponseError(fmt.Errorf("method %q not found", methodDecl.MethodName))
return true
}
if methodDecl.CommandType == wshrpc.RpcType_Call {
rtnVals := implMethod.Call(callParams)
rtnData, rtnErr := decodeRtnVals(rtnVals)
if rtnErr != nil {
handler.SendResponseError(rtnErr)
@@ -115,8 +128,8 @@ func mainWshServerHandler(handler *wshutil.RpcResponseHandler) bool {
}
handler.SendResponse(rtnData, true)
return true
} else if methodDecl.CommandType == wshutil.RpcType_ResponseStream {
rtnVals := methodDecl.Method.Call(callParams)
} else if methodDecl.CommandType == wshrpc.RpcType_ResponseStream {
rtnVals := implMethod.Call(callParams)
rtnChVal := rtnVals[0]
if rtnChVal.IsNil() {
handler.SendResponse(nil, true)
@@ -163,7 +176,7 @@ func runWshRpcWithStream(conn net.Conn) {
outputCh := make(chan []byte, DefaultOutputChSize)
go wshutil.AdaptMsgChToStream(outputCh, conn)
go wshutil.AdaptStreamToMsgCh(conn, inputCh)
wshutil.MakeWshRpc(inputCh, outputCh, wshutil.RpcContext{}, mainWshServerHandler)
wshutil.MakeWshRpc(inputCh, outputCh, wshrpc.RpcContext{}, mainWshServerHandler)
}
func RunWshRpcOverListener(listener net.Listener) {
@@ -179,82 +192,6 @@ func RunWshRpcOverListener(listener net.Listener) {
}()
}
func MakeClientJWTToken(rpcCtx wshutil.RpcContext, sockName string) (string, error) {
claims := jwt.MapClaims{}
claims["iat"] = time.Now().Unix()
claims["iss"] = "waveterm"
claims["sock"] = sockName
claims["exp"] = time.Now().Add(time.Hour * 24 * 365).Unix()
if rpcCtx.BlockId != "" {
claims["blockid"] = rpcCtx.BlockId
}
if rpcCtx.TabId != "" {
claims["tabid"] = rpcCtx.TabId
}
if rpcCtx.WindowId != "" {
claims["windowid"] = rpcCtx.WindowId
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString([]byte(wavebase.JwtSecret))
if err != nil {
return "", fmt.Errorf("error signing token: %w", err)
}
return tokenStr, nil
}
func ValidateAndExtractRpcContextFromToken(tokenStr string) (wshutil.RpcContext, error) {
parser := jwt.NewParser(jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Name}))
token, err := parser.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
return []byte(wavebase.JwtSecret), nil
})
if err != nil {
return wshutil.RpcContext{}, fmt.Errorf("error parsing token: %w", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return wshutil.RpcContext{}, fmt.Errorf("error getting claims from token")
}
// validate "exp" claim
if exp, ok := claims["exp"].(float64); ok {
if int64(exp) < time.Now().Unix() {
return wshutil.RpcContext{}, fmt.Errorf("token has expired")
}
} else {
return wshutil.RpcContext{}, fmt.Errorf("exp claim is missing or invalid")
}
// validate "iss" claim
if iss, ok := claims["iss"].(string); ok {
if iss != "waveterm" {
return wshutil.RpcContext{}, fmt.Errorf("unexpected issuer: %s", iss)
}
} else {
return wshutil.RpcContext{}, fmt.Errorf("iss claim is missing or invalid")
}
rpcCtx := wshutil.RpcContext{}
rpcCtx.BlockId = claims["blockid"].(string)
rpcCtx.TabId = claims["tabid"].(string)
rpcCtx.WindowId = claims["windowid"].(string)
return rpcCtx, nil
}
func ExtractUnverifiedSocketName(tokenStr string) (string, error) {
// this happens on the client who does not have access to the secret key
// we want to read the claims without validating the signature
token, _, err := new(jwt.Parser).ParseUnverified(tokenStr, jwt.MapClaims{})
if err != nil {
return "", fmt.Errorf("error parsing token: %w", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return "", fmt.Errorf("error getting claims from token")
}
sockName, ok := claims["sock"].(string)
if !ok {
return "", fmt.Errorf("sock claim is missing or invalid")
}
return sockName, nil
}
func RunDomainSocketWshServer() error {
sockName := wavebase.GetDomainSocketName()
listener, err := MakeUnixListener(sockName)
@@ -266,6 +203,6 @@ func RunDomainSocketWshServer() error {
return nil
}
func MakeWshServer(inputCh chan []byte, outputCh chan []byte, initialCtx wshutil.RpcContext) {
func MakeWshServer(inputCh chan []byte, outputCh chan []byte, initialCtx wshrpc.RpcContext) {
wshutil.MakeWshRpc(inputCh, outputCh, initialCtx, mainWshServerHandler)
}