github.com/soypat/vectytemplater@v0.0.0-20220501050640-d40b24e35168/_templates/websocket-cli/app/client.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"syscall/js"
     8  	"time"
     9  
    10  	"vecty-templater-project/app/store"
    11  	"vecty-templater-project/app/store/actions"
    12  	"vecty-templater-project/model"
    13  
    14  	"vecty-templater-project/app/views"
    15  
    16  	"github.com/hexops/vecty"
    17  	"nhooyr.io/websocket"
    18  	"nhooyr.io/websocket/wsjson"
    19  )
    20  
    21  func main() {
    22  	// OnAction must be registered before any storage manipulation.
    23  	actions.Register(store.OnAction)
    24  
    25  	attachItemsStorage()
    26  
    27  	body := &views.Body{
    28  		Ctx:  store.Ctx,
    29  		Info: "Welcome!",
    30  	}
    31  	store.Listeners.Add(body, func(interface{}) {
    32  		body.Ctx = store.Ctx
    33  		body.Info = store.ServerReply
    34  		vecty.Rerender(body)
    35  	})
    36  	vecty.RenderBody(body)
    37  }
    38  
    39  // attachItemsStorage provides persistent local storage saved on edits so
    40  // no data is lost due to bad connection or refreshed page.
    41  func attachItemsStorage() {
    42  	const key = "vecty_items"
    43  	defer initWSConn()
    44  	store.Listeners.Add(nil, func(action interface{}) {
    45  		if _, ok := action.(*actions.NewItem); !ok {
    46  			// Only save state upon adding an item
    47  			return
    48  		}
    49  		store.ServerReply = "" // reset server reply
    50  		// After item addition save items locally.
    51  		b, err := json.Marshal(&store.Items)
    52  		if err != nil {
    53  			panic(err)
    54  		}
    55  		js.Global().Get("localStorage").Set(key, string(b))
    56  		var reply model.ServerReply
    57  		ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    58  		defer cancel()
    59  		err = wsjson.Read(ctx, wsConn, &reply)
    60  		if err != nil {
    61  			fmt.Println("reading server reply from websocket:", err)
    62  			go initWSConn()
    63  			return
    64  		}
    65  		// store items remotely too.
    66  		ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
    67  		defer cancel()
    68  		err = wsjson.Write(ctx, wsConn, &store.Items)
    69  		if err != nil {
    70  			fmt.Println("writing items to websocket:", err)
    71  			go initWSConn()
    72  			return
    73  		}
    74  		store.ServerReply = reply.Info
    75  		actions.Dispatch(&actions.Refresh{})
    76  	})
    77  
    78  	if data := js.Global().Get("localStorage").Get(key); !data.IsUndefined() {
    79  		// Old session data found, initialize store data.
    80  		err := json.Unmarshal([]byte(data.String()), &store.Items)
    81  		if err != nil {
    82  			panic(err)
    83  		}
    84  	}
    85  }
    86  
    87  var wsConn *websocket.Conn
    88  
    89  func initWSConn() {
    90  	if wsConn != nil {
    91  		wsConn.Close(websocket.StatusAbnormalClosure, "client wanted to reinitialize")
    92  	}
    93  	ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
    94  	defer cancel()
    95  
    96  	c, _, err := websocket.Dial(ctx, "ws://localhost"+model.HTTPServerAddr+"/ws", &websocket.DialOptions{
    97  		Subprotocols: []string{model.WSSubprotocol},
    98  	})
    99  	if err != nil {
   100  		fmt.Println("websocket initialization failed:", err.Error())
   101  	}
   102  	wsConn = c
   103  }