mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
200 lines
5.6 KiB
Go
200 lines
5.6 KiB
Go
|
// Copyright 2024, Command Line Inc.
|
||
|
// SPDX-License-Identifier: Apache-2.0
|
||
|
|
||
|
package vdomclient
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"os"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/google/uuid"
|
||
|
"github.com/wavetermdev/waveterm/pkg/vdom"
|
||
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||
|
"github.com/wavetermdev/waveterm/pkg/wps"
|
||
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||
|
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
||
|
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
||
|
)
|
||
|
|
||
|
type Client struct {
|
||
|
Root *vdom.RootElem
|
||
|
RootElem *vdom.VDomElem
|
||
|
RpcClient *wshutil.WshRpc
|
||
|
RpcContext *wshrpc.RpcContext
|
||
|
ServerImpl *VDomServerImpl
|
||
|
IsDone bool
|
||
|
RouteId string
|
||
|
DoneReason string
|
||
|
DoneOnce *sync.Once
|
||
|
DoneCh chan struct{}
|
||
|
Opts vdom.VDomBackendOpts
|
||
|
GlobalEventHandler func(client *Client, event vdom.VDomEvent)
|
||
|
}
|
||
|
|
||
|
type VDomServerImpl struct {
|
||
|
Client *Client
|
||
|
BlockId string
|
||
|
}
|
||
|
|
||
|
func (*VDomServerImpl) WshServerImpl() {}
|
||
|
|
||
|
func (impl *VDomServerImpl) VDomRenderCommand(ctx context.Context, feUpdate vdom.VDomFrontendUpdate) (*vdom.VDomBackendUpdate, error) {
|
||
|
if feUpdate.Dispose {
|
||
|
log.Printf("got dispose from frontend\n")
|
||
|
impl.Client.doShutdown("got dispose from frontend")
|
||
|
return nil, nil
|
||
|
}
|
||
|
if impl.Client.IsDone {
|
||
|
return nil, nil
|
||
|
}
|
||
|
// set atoms
|
||
|
for _, ss := range feUpdate.StateSync {
|
||
|
impl.Client.Root.SetAtomVal(ss.Atom, ss.Value, false)
|
||
|
}
|
||
|
// run events
|
||
|
for _, event := range feUpdate.Events {
|
||
|
if event.WaveId == "" {
|
||
|
if impl.Client.GlobalEventHandler != nil {
|
||
|
impl.Client.GlobalEventHandler(impl.Client, event)
|
||
|
}
|
||
|
} else {
|
||
|
impl.Client.Root.Event(event.WaveId, event.PropName, event.EventData)
|
||
|
}
|
||
|
}
|
||
|
if feUpdate.Initialize || feUpdate.Resync {
|
||
|
return impl.Client.fullRender()
|
||
|
}
|
||
|
return impl.Client.incrementalRender()
|
||
|
}
|
||
|
|
||
|
func (c *Client) doShutdown(reason string) {
|
||
|
c.DoneOnce.Do(func() {
|
||
|
c.DoneReason = reason
|
||
|
c.IsDone = true
|
||
|
close(c.DoneCh)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (c *Client) SetGlobalEventHandler(handler func(client *Client, event vdom.VDomEvent)) {
|
||
|
c.GlobalEventHandler = handler
|
||
|
}
|
||
|
|
||
|
func MakeClient(opts *vdom.VDomBackendOpts) (*Client, error) {
|
||
|
client := &Client{
|
||
|
Root: vdom.MakeRoot(),
|
||
|
DoneCh: make(chan struct{}),
|
||
|
DoneOnce: &sync.Once{},
|
||
|
}
|
||
|
if opts != nil {
|
||
|
client.Opts = *opts
|
||
|
}
|
||
|
jwtToken := os.Getenv(wshutil.WaveJwtTokenVarName)
|
||
|
if jwtToken == "" {
|
||
|
return nil, fmt.Errorf("no %s env var set", wshutil.WaveJwtTokenVarName)
|
||
|
}
|
||
|
rpcCtx, err := wshutil.ExtractUnverifiedRpcContext(jwtToken)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error extracting rpc context from %s: %v", wshutil.WaveJwtTokenVarName, err)
|
||
|
}
|
||
|
client.RpcContext = rpcCtx
|
||
|
if client.RpcContext == nil || client.RpcContext.BlockId == "" {
|
||
|
return nil, fmt.Errorf("no block id in rpc context")
|
||
|
}
|
||
|
client.ServerImpl = &VDomServerImpl{BlockId: client.RpcContext.BlockId, Client: client}
|
||
|
sockName, err := wshutil.ExtractUnverifiedSocketName(jwtToken)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error extracting socket name from %s: %v", wshutil.WaveJwtTokenVarName, err)
|
||
|
}
|
||
|
rpcClient, err := wshutil.SetupDomainSocketRpcClient(sockName, client.ServerImpl)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error setting up domain socket rpc client: %v", err)
|
||
|
}
|
||
|
client.RpcClient = rpcClient
|
||
|
authRtn, err := wshclient.AuthenticateCommand(client.RpcClient, jwtToken, &wshrpc.RpcOpts{NoResponse: true})
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error authenticating rpc connection: %v", err)
|
||
|
}
|
||
|
client.RouteId = authRtn.RouteId
|
||
|
return client, nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) SetRootElem(elem *vdom.VDomElem) {
|
||
|
c.RootElem = elem
|
||
|
}
|
||
|
|
||
|
func (c *Client) CreateVDomContext() error {
|
||
|
err := wshclient.VDomCreateContextCommand(c.RpcClient, vdom.VDomCreateContext{}, &wshrpc.RpcOpts{Route: wshutil.MakeFeBlockRouteId(c.RpcContext.BlockId)})
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
wshclient.EventSubCommand(c.RpcClient, wps.SubscriptionRequest{Event: "blockclose", Scopes: []string{
|
||
|
waveobj.MakeORef("block", c.RpcContext.BlockId).String(),
|
||
|
}}, nil)
|
||
|
c.RpcClient.EventListener.On("blockclose", func(event *wps.WaveEvent) {
|
||
|
c.doShutdown("got blockclose event")
|
||
|
})
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) SendAsyncInitiation() {
|
||
|
wshclient.VDomAsyncInitiationCommand(c.RpcClient, vdom.MakeAsyncInitiationRequest(c.RpcContext.BlockId), &wshrpc.RpcOpts{Route: wshutil.MakeFeBlockRouteId(c.RpcContext.BlockId)})
|
||
|
}
|
||
|
|
||
|
func (c *Client) SetAtomVals(m map[string]any) {
|
||
|
for k, v := range m {
|
||
|
c.Root.SetAtomVal(k, v, true)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Client) SetAtomVal(name string, val any) {
|
||
|
c.Root.SetAtomVal(name, val, true)
|
||
|
}
|
||
|
|
||
|
func (c *Client) GetAtomVal(name string) any {
|
||
|
return c.Root.GetAtomVal(name)
|
||
|
}
|
||
|
|
||
|
func makeNullVDom() *vdom.VDomElem {
|
||
|
return &vdom.VDomElem{WaveId: uuid.New().String(), Tag: vdom.WaveNullTag}
|
||
|
}
|
||
|
|
||
|
func (c *Client) fullRender() (*vdom.VDomBackendUpdate, error) {
|
||
|
c.Root.RunWork()
|
||
|
c.Root.Render(c.RootElem)
|
||
|
renderedVDom := c.Root.MakeVDom()
|
||
|
if renderedVDom == nil {
|
||
|
renderedVDom = makeNullVDom()
|
||
|
}
|
||
|
return &vdom.VDomBackendUpdate{
|
||
|
Type: "backendupdate",
|
||
|
Ts: time.Now().UnixMilli(),
|
||
|
BlockId: c.RpcContext.BlockId,
|
||
|
Opts: &c.Opts,
|
||
|
RenderUpdates: []vdom.VDomRenderUpdate{
|
||
|
{UpdateType: "root", VDom: *renderedVDom},
|
||
|
},
|
||
|
StateSync: c.Root.GetStateSync(true),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) incrementalRender() (*vdom.VDomBackendUpdate, error) {
|
||
|
c.Root.RunWork()
|
||
|
renderedVDom := c.Root.MakeVDom()
|
||
|
if renderedVDom == nil {
|
||
|
renderedVDom = makeNullVDom()
|
||
|
}
|
||
|
return &vdom.VDomBackendUpdate{
|
||
|
Type: "backendupdate",
|
||
|
Ts: time.Now().UnixMilli(),
|
||
|
BlockId: c.RpcContext.BlockId,
|
||
|
RenderUpdates: []vdom.VDomRenderUpdate{
|
||
|
{UpdateType: "root", VDom: *renderedVDom},
|
||
|
},
|
||
|
StateSync: c.Root.GetStateSync(false),
|
||
|
}, nil
|
||
|
}
|