mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
checkpoint on domain sockets + update background colors + transparency (#160)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user