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 }