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:
@@ -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