diff --git a/ws_js.go b/ws_js.go index 026b75fc..44e8152b 100644 --- a/ws_js.go +++ b/ws_js.go @@ -101,8 +101,9 @@ func (c *Conn) init() { }) c.releaseOnError = c.ws.OnError(func(v js.Value) { + // A close event always follows. Closing here would wait for it + // inside this callback and deadlock. c.setCloseErr(errors.New(v.Get("message").String())) - c.closeWithInternal() }) c.releaseOnMessage = c.ws.OnMessage(func(e wsjs.MessageEvent) { @@ -259,6 +260,9 @@ func (c *Conn) exportedClose(code StatusCode, reason string) error { }) c.setCloseErr(ce) + if code != StatusNormalClosure && (code < 3000 || code > 4999) { + code = StatusNormalClosure // Browsers throw on any other code. + } err := c.ws.Close(int(code), reason) if err != nil { return err diff --git a/ws_js_test.go b/ws_js_test.go index 1fa242f4..68ec6d68 100644 --- a/ws_js_test.go +++ b/ws_js_test.go @@ -52,3 +52,31 @@ func TestWasmDialTimeout(t *testing.T) { t.Fatal("wasm context dial timeout is not working", time.Since(beforeDial)) } } + +func TestWasmCloseStatus(t *testing.T) { + t.Parallel() + + // The library closes with these itself, but browsers only accept + // 1000 and 3000-4999. + for _, code := range []websocket.StatusCode{ + websocket.StatusGoingAway, + websocket.StatusPolicyViolation, + websocket.StatusMessageTooBig, + websocket.StatusInternalError, + } { + t.Run(code.String(), func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + c, _, err := websocket.Dial(ctx, os.Getenv("WS_ECHO_SERVER_URL"), &websocket.DialOptions{ + Subprotocols: []string{"echo"}, + }) + assert.Success(t, err) + + err = c.Close(code, "") + assert.Success(t, err) + }) + } +}