feat: add WebSocket support for JavaScript/WebAssembly

Implement WebSocket dialer and stream for JS/WASM environment, enabling io.ReadWriteCloser interface for WebSocket connections with proper buffering and synchronization.

lemon-mint committed Oct 30, 2025 at 16:16 UTC 725a0d8dcaea7fa5f5acac213294226e533ca2e1
2 files changed +89
cmd/webclient/sdk_js.go new
+22
@@ -0,0 +1,22 @@
1 +package main
2 +
3 +import (
4 + "context"
5 + "io"
6 +
7 + "github.com/gosuda/portal/cmd/webclient/wsjs"
8 +)
9 +
10 +// WebSocketDialerJS creates a WebSocket dialer function for JavaScript/WebAssembly environment
11 +func WebSocketDialerJS() func(context.Context, string) (io.ReadWriteCloser, error) {
12 + return func(ctx context.Context, url string) (io.ReadWriteCloser, error) {
13 + // Use the wsjs package to create a WebSocket connection
14 + conn, err := wsjs.Dial(url)
15 + if err != nil {
16 + return nil, err
17 + }
18 +
19 + // Wrap the WebSocket connection with WsStream for io.ReadWriteCloser interface
20 + return wsjs.NewWsStream(conn), nil
21 + }
22 +}
cmd/webclient/wsjs/wsstream_js.go new
+67
@@ -0,0 +1,67 @@
1 +package wsjs
2 +
3 +import (
4 + "sync"
5 +)
6 +
7 +// WsStream provides an io.Reader and io.Writer interface for WebSocket connections
8 +type WsStream struct {
9 + conn *Conn
10 + currentBuffer []byte
11 + readMu sync.Mutex
12 + writeMu sync.Mutex
13 +}
14 +
15 +// NewWsStream creates a new WsStream from a WebSocket connection
16 +func NewWsStream(conn *Conn) *WsStream {
17 + return &WsStream{
18 + conn: conn,
19 + }
20 +}
21 +
22 +// Read implements io.Reader interface
23 +func (ws *WsStream) Read(p []byte) (n int, err error) {
24 + ws.readMu.Lock()
25 + defer ws.readMu.Unlock()
26 +
27 + // If we have remaining data from previous message, use it first
28 + if len(ws.currentBuffer) > 0 {
29 + n = copy(p, ws.currentBuffer)
30 + ws.currentBuffer = ws.currentBuffer[n:]
31 + return n, nil
32 + }
33 +
34 + // Get next message from WebSocket
35 + msg, err := ws.conn.NextMessage()
36 + if err != nil {
37 + return 0, err
38 + }
39 +
40 + // Copy message data to buffer
41 + n = copy(p, msg)
42 +
43 + // Store any remaining data for next read
44 + if n < len(msg) {
45 + ws.currentBuffer = msg[n:]
46 + }
47 +
48 + return n, nil
49 +}
50 +
51 +// Write implements io.Writer interface
52 +func (ws *WsStream) Write(p []byte) (n int, err error) {
53 + ws.writeMu.Lock()
54 + defer ws.writeMu.Unlock()
55 +
56 + err = ws.conn.Send(p)
57 + if err != nil {
58 + return 0, err
59 + }
60 +
61 + return len(p), nil
62 +}
63 +
64 +// Close closes the WebSocket connection
65 +func (ws *WsStream) Close() error {
66 + return ws.conn.Close()
67 +}