diff --git a/client.go b/client.go index 96f65c3..ccbf598 100644 --- a/client.go +++ b/client.go @@ -83,6 +83,29 @@ func DialConn(conn Conn, ocb *OCB) (*Client, error) { return c, nil } +// DialConnManual creates a mosh client without an internal sendLoop. +// The caller must call Tick() periodically (e.g., every 8-16ms) to flush +// outgoing datagrams. Use this in WASM where Go timers are unreliable. +func DialConnManual(conn Conn, ocb *OCB) (*Client, error) { + c := &Client{ + conn: conn, + transport: NewTransport(ocb, false), + ocb: ocb, + outputC: make(chan struct{}, 1), + done: make(chan struct{}), + sentActionCounts: make(map[uint64]int), + } + + c.wg.Add(1) + go c.recvLoop() + + // Send initial keepalive to associate with the server. + c.transport.ForceNextSend() + c.tick() + + return c, nil +} + // Send sends keystrokes to the server. func (c *Client) Send(keys []byte) { c.actionsMu.Lock() @@ -147,6 +170,13 @@ func (c *Client) Transport() *Transport { return c.transport } +// Tick flushes pending actions as mosh datagrams. Call this periodically +// from an external timer (e.g., JS setInterval in WASM) when the internal +// sendLoop is not used. +func (c *Client) Tick() { + c.tick() +} + func (c *Client) tick() { // Batch: compute diff from accumulated actions. c.actionsMu.Lock() diff --git a/cmd/mosh-wasm/main.go b/cmd/mosh-wasm/main.go index 0bb0bb7..d2c5792 100644 --- a/cmd/mosh-wasm/main.go +++ b/cmd/mosh-wasm/main.go @@ -72,7 +72,8 @@ func newSession(url, key string) (*session, error) { return nil, err } - client, err := mosh.DialConn(conn, ocb) + // Use DialConnManual — no internal sendLoop. JS drives Tick() via setInterval. + client, err := mosh.DialConnManual(conn, ocb) if err != nil { conn.Close() return nil, err @@ -90,6 +91,8 @@ func (s *session) jsObject() js.Value { } data := args[0].String() s.client.Send([]byte(data)) + // Tick immediately so keystrokes go out without waiting for the interval. + s.client.Tick() return nil })) @@ -100,6 +103,13 @@ func (s *session) jsObject() js.Value { cols := args[0].Int() rows := args[1].Int() s.client.Resize(uint16(cols), uint16(rows)) + s.client.Tick() + return nil + })) + + // tick() — flush pending datagrams. Called by JS setInterval. + obj.Set("tick", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + s.client.Tick() return nil }))