Skip to content

Commit bc1defe

Browse files
committed
Add WebSocket API
Closes #121
1 parent e09e295 commit bc1defe

21 files changed

+205
-27
lines changed

Diff for: accept.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: accept_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: ci/wasm.sh

+2-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,5 @@ cd "$(git rev-parse --show-toplevel)"
66

77
GOOS=js GOARCH=wasm go vet ./...
88
go install golang.org/x/lint/golint
9-
# Get passing later.
10-
#GOOS=js GOARCH=wasm golint -set_exit_status ./...
11-
GOOS=js GOARCH=wasm go test ./internal/wsjs
9+
GOOS=js GOARCH=wasm golint -set_exit_status ./...
10+
GOOS=js GOARCH=wasm go test -race ./internal/wsjs

Diff for: dial.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: dial_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: doc.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
// Package websocket is a minimal and idiomatic implementation of the WebSocket protocol.
24
//
35
// https://tools.ietf.org/html/rfc6455

Diff for: example_echo_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket_test
24

35
import (

Diff for: example_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket_test
24

35
import (

Diff for: export_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: header.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: header_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: internal/wsjs/wsjs.go

+28-23
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package wsjs
66

77
import (
8-
"context"
98
"syscall/js"
109
)
1110

@@ -26,17 +25,17 @@ func handleJSError(err *error, onErr func()) {
2625
}
2726
}
2827

29-
func New(ctx context.Context, url string, protocols []string) (c *WebSocket, err error) {
28+
func New(url string, protocols []string) (c WebSocket, err error) {
3029
defer handleJSError(&err, func() {
31-
c = nil
30+
c = WebSocket{}
3231
})
3332

3433
jsProtocols := make([]interface{}, len(protocols))
3534
for i, p := range protocols {
3635
jsProtocols[i] = p
3736
}
3837

39-
c = &WebSocket{
38+
c = WebSocket{
4039
v: js.Global().Get("WebSocket").New(url, jsProtocols),
4140
}
4241

@@ -57,19 +56,25 @@ type WebSocket struct {
5756
v js.Value
5857
}
5958

60-
func (c *WebSocket) setBinaryType(typ string) {
59+
func (c WebSocket) setBinaryType(typ string) {
6160
c.v.Set("binaryType", string(typ))
6261
}
6362

64-
func (c *WebSocket) BufferedAmount() uint32 {
63+
func (c WebSocket) BufferedAmount() uint32 {
6564
return uint32(c.v.Get("bufferedAmount").Int())
6665
}
6766

68-
func (c *WebSocket) addEventListener(eventType string, fn func(e js.Value)) {
69-
c.v.Call("addEventListener", eventType, js.FuncOf(func(this js.Value, args []js.Value) interface{} {
67+
func (c WebSocket) addEventListener(eventType string, fn func(e js.Value)) func() {
68+
f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
7069
fn(args[0])
7170
return nil
72-
}))
71+
})
72+
c.v.Call("addEventListener", eventType, f)
73+
74+
return func() {
75+
c.v.Call("removeEventListener", eventType, f)
76+
f.Release()
77+
}
7378
}
7479

7580
type CloseEvent struct {
@@ -78,8 +83,8 @@ type CloseEvent struct {
7883
WasClean bool
7984
}
8085

81-
func (c *WebSocket) OnClose(fn func(CloseEvent)) {
82-
c.addEventListener("close", func(e js.Value) {
86+
func (c WebSocket) OnClose(fn func(CloseEvent)) (remove func()) {
87+
return c.addEventListener("close", func(e js.Value) {
8388
ce := CloseEvent{
8489
Code: uint16(e.Get("code").Int()),
8590
Reason: e.Get("reason").String(),
@@ -89,23 +94,23 @@ func (c *WebSocket) OnClose(fn func(CloseEvent)) {
8994
})
9095
}
9196

92-
func (c *WebSocket) OnError(fn func(e js.Value)) {
93-
c.addEventListener("error", fn)
97+
func (c WebSocket) OnError(fn func(e js.Value)) (remove func()) {
98+
return c.addEventListener("error", fn)
9499
}
95100

96101
type MessageEvent struct {
97-
Data []byte
102+
Data interface{}
98103
// There are more types to the interface but we don't use them.
99104
// See https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
100105
}
101106

102-
func (c *WebSocket) OnMessage(fn func(m MessageEvent)) {
103-
c.addEventListener("message", func(e js.Value) {
104-
var data []byte
107+
func (c WebSocket) OnMessage(fn func(m MessageEvent)) (remove func()) {
108+
return c.addEventListener("message", func(e js.Value) {
109+
var data interface{}
105110

106111
arrayBuffer := e.Get("data")
107112
if arrayBuffer.Type() == js.TypeString {
108-
data = []byte(arrayBuffer.String())
113+
data = arrayBuffer.String()
109114
} else {
110115
data = extractArrayBuffer(arrayBuffer)
111116
}
@@ -119,23 +124,23 @@ func (c *WebSocket) OnMessage(fn func(m MessageEvent)) {
119124
})
120125
}
121126

122-
func (c *WebSocket) OnOpen(fn func(e js.Value)) {
123-
c.addEventListener("open", fn)
127+
func (c WebSocket) OnOpen(fn func(e js.Value)) (remove func()) {
128+
return c.addEventListener("open", fn)
124129
}
125130

126-
func (c *WebSocket) Close(code int, reason string) (err error) {
131+
func (c WebSocket) Close(code int, reason string) (err error) {
127132
defer handleJSError(&err, nil)
128133
c.v.Call("close", code, reason)
129134
return err
130135
}
131136

132-
func (c *WebSocket) SendText(v string) (err error) {
137+
func (c WebSocket) SendText(v string) (err error) {
133138
defer handleJSError(&err, nil)
134139
c.v.Call("send", v)
135140
return err
136141
}
137142

138-
func (c *WebSocket) SendBytes(v []byte) (err error) {
143+
func (c WebSocket) SendBytes(v []byte) (err error) {
139144
defer handleJSError(&err, nil)
140145
c.v.Call("send", uint8Array(v))
141146
return err

Diff for: netconn.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: opcode.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
// opcode represents a WebSocket Opcode.

Diff for: websocket.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: websocket_autobahn_python_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This file contains the old autobahn test suite tests that use the
2-
// python binary. The approach is very clunky and slow so new tests
2+
// python binary. The approach is clunky and slow so new tests
33
// have been written in pure Go in websocket_test.go.
4+
// These have been kept for correctness purposes and are occasionally ran.
45
// +build autobahn-python
56

67
package websocket_test

Diff for: websocket_bench_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket_test
24

35
import (

Diff for: websocket_js.go

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package websocket
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"golang.org/x/xerrors"
7+
"net/http"
8+
"nhooyr.io/websocket/internal/wsjs"
9+
"reflect"
10+
"syscall/js"
11+
)
12+
13+
type Conn struct {
14+
ws wsjs.WebSocket
15+
16+
closed chan struct{}
17+
closeErr CloseError
18+
19+
releases []func()
20+
21+
read chan wsjs.MessageEvent
22+
}
23+
24+
func (c *Conn) init() {
25+
c.closed = make(chan struct{})
26+
c.read = make(chan wsjs.MessageEvent, 1)
27+
28+
c.appendRelease(c.ws.OnClose(func(e wsjs.CloseEvent) {
29+
c.closeErr = makeCloseError(e)
30+
c.release()
31+
close(c.closed)
32+
}))
33+
34+
c.appendRelease(c.ws.OnMessage(func(e wsjs.MessageEvent) {
35+
c.read <- e
36+
}))
37+
}
38+
39+
func (c *Conn) appendRelease(r func()) {
40+
c.releases = append(c.releases, r)
41+
}
42+
43+
func (c *Conn) release() {
44+
for _, r := range c.releases {
45+
r()
46+
}
47+
}
48+
49+
func (c *Conn) Read(ctx context.Context) (_ MessageType, _ []byte, err error) {
50+
defer errdWrap(&err, "failed to read")
51+
52+
var me wsjs.MessageEvent
53+
select {
54+
case <-ctx.Done():
55+
return 0, nil, ctx.Err()
56+
case me = <-c.read:
57+
58+
}
59+
switch p := me.Data.(type) {
60+
case string:
61+
return MessageText, []byte(p), nil
62+
case []byte:
63+
return MessageBinary, p, nil
64+
default:
65+
panic("websocket: unexpected data type from wsjs OnMessage: " + reflect.TypeOf(me.Data).String())
66+
}
67+
}
68+
69+
func (c *Conn) Write(ctx context.Context, typ MessageType, p []byte) error {
70+
switch typ {
71+
case MessageBinary:
72+
c.ws.SendBytes(p)
73+
case MessageText:
74+
c.ws.SendText(string(p))
75+
}
76+
return nil
77+
}
78+
79+
func (c *Conn) Close(code StatusCode, reason string) error {
80+
err := c.ws.Close(int(code), reason)
81+
if err != nil {
82+
return xerrors.Errorf("failed to close websocket: %w", err)
83+
}
84+
return nil
85+
}
86+
87+
func (c *Conn) Subprotocol() string {
88+
return c.ws.Protocol
89+
}
90+
91+
// DialOptions represents the options available to pass to Dial.
92+
type DialOptions struct {
93+
// Subprotocols lists the subprotocols to negotiate with the server.
94+
Subprotocols []string
95+
}
96+
97+
func Dial(ctx context.Context, url string, opts *DialOptions) (_ *Conn, _ *http.Response, err error) {
98+
defer errdWrap(&err, "failed to dial")
99+
100+
ws, err := wsjs.New(url, opts.Subprotocols)
101+
if err != nil {
102+
return nil, nil, err
103+
}
104+
105+
c := &Conn{
106+
ws: ws,
107+
}
108+
c.init()
109+
110+
opench := make(chan struct{})
111+
releaseOpen := ws.OnOpen(func(e js.Value) {
112+
close(opench)
113+
})
114+
defer releaseOpen()
115+
116+
select {
117+
case <-ctx.Done():
118+
return nil, nil, ctx.Err()
119+
case <-opench:
120+
case <-c.closed:
121+
return c, nil, c.closeErr
122+
}
123+
124+
// Have to return a non nil response as the normal API does that.
125+
return c, &http.Response{}, nil
126+
}
127+
128+
func makeCloseError(e wsjs.CloseEvent) CloseError {
129+
return CloseError{
130+
Code: StatusCode(e.Code),
131+
Reason: e.Reason,
132+
}
133+
}
134+
135+
func errdWrap(errp *error, f string, args ...interface{}) {
136+
if *errp != nil {
137+
*errp = fmt.Errorf(f+": %w", append(args, *errp))
138+
}
139+
}

Diff for: websocket_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket_test
24

35
import (

Diff for: xor.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

Diff for: xor_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !js
2+
13
package websocket
24

35
import (

0 commit comments

Comments
 (0)