diff --git a/README.md b/README.md
index e967cd8af6f956b84e2cf07deddd6fd1e084b542..2d71ce0b23e9175202a1e0a989fd8ff1290960c2 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # websocket
 
-[![godoc](https://godoc.org/nhooyr.io/websocket?status.svg)](https://godoc.org/nhooyr.io/websocket)
+[![godoc](https://godoc.org/nhooyr.io/websocket?status.svg)](https://pkg.go.dev/nhooyr.io/websocket)
 
 websocket is a minimal and idiomatic WebSocket library for Go.
 
@@ -16,8 +16,8 @@ go get nhooyr.io/websocket
 - First class [context.Context](https://blog.golang.org/context) support
 - Fully passes the WebSocket [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
 - Thorough unit tests with [90% coverage](https://coveralls.io/github/nhooyr/websocket)
-- [Minimal dependencies](https://godoc.org/nhooyr.io/websocket?imports)
-- JSON and protobuf helpers in the [wsjson](https://godoc.org/nhooyr.io/websocket/wsjson) and [wspb](https://godoc.org/nhooyr.io/websocket/wspb) subpackages
+- [Minimal dependencies](https://pkg.go.dev/nhooyr.io/websocket?tab=imports)
+- JSON and protobuf helpers in the [wsjson](https://pkg.go.dev/nhooyr.io/websocket/wsjson?tab=doc) and [wspb](https://pkg.go.dev/nhooyr.io/websocket/wspb?tab=doc) subpackages
 - Zero alloc reads and writes
 - Concurrent writes
 - [Close handshake](https://godoc.org/nhooyr.io/websocket#Conn.Close)
@@ -98,7 +98,7 @@ Advantages of nhooyr.io/websocket:
 - [net.Conn](https://godoc.org/nhooyr.io/websocket#NetConn) wrapper
 - Zero alloc reads and writes ([gorilla/websocket#535](https://github.com/gorilla/websocket/issues/535))
 - Full [context.Context](https://blog.golang.org/context) support
-- Dial uses [net/http.Client](https://golang.org/pkg/net/http/#Client)
+- Dials use [net/http.Client](https://golang.org/pkg/net/http/#Client)
   - Will enable easy HTTP/2 support in the future
   - Gorilla writes directly to a net.Conn and so duplicates features of net/http.Client.
 - Concurrent writes
diff --git a/accept_js.go b/accept_js.go
index 724b35b5bc3e4712a3a8d4299c587ba2d47e7fef..daad4b79fec613b311121eb6cc4f7beb014d9cd3 100644
--- a/accept_js.go
+++ b/accept_js.go
@@ -9,6 +9,7 @@ import (
 type AcceptOptions struct {
 	Subprotocols         []string
 	InsecureSkipVerify   bool
+	OriginPatterns       []string
 	CompressionMode      CompressionMode
 	CompressionThreshold int
 }
diff --git a/chat-example/chat_test.go b/chat-example/chat_test.go
index 491499ccee6d8669f695da478736d05ca04fa2af..2cbc995eb54abe08ed815ae4c3f03f071eb4a2e7 100644
--- a/chat-example/chat_test.go
+++ b/chat-example/chat_test.go
@@ -61,7 +61,7 @@ func Test_chatServer(t *testing.T) {
 
 		const nmessages = 128
 		const maxMessageSize = 128
-		const nclients = 10
+		const nclients = 16
 
 		url, closeFn := setupTest(t)
 		defer closeFn()
@@ -191,8 +191,7 @@ type client struct {
 }
 
 func newClient(ctx context.Context, url string) (*client, error) {
-	wsURL := strings.Replace(url, "http://", "ws://", 1)
-	c, _, err := websocket.Dial(ctx, wsURL+"/subscribe", nil)
+	c, _, err := websocket.Dial(ctx, url+"/subscribe", nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/close_notjs.go b/close_notjs.go
index 2537299560bd2f7560a112d8c86a67b639aecf61..4f1cebcbb9ec6a096aa542ae014f828aff78f4b5 100644
--- a/close_notjs.go
+++ b/close_notjs.go
@@ -34,14 +34,15 @@ func (c *Conn) Close(code StatusCode, reason string) error {
 func (c *Conn) closeHandshake(code StatusCode, reason string) (err error) {
 	defer errd.Wrap(&err, "failed to close WebSocket")
 
-	err = c.writeClose(code, reason)
-	if err != nil && CloseStatus(err) == -1 && err != errAlreadyWroteClose {
-		return err
+	writeErr := c.writeClose(code, reason)
+	closeHandshakeErr := c.waitCloseHandshake()
+
+	if writeErr != nil {
+		return writeErr
 	}
 
-	err = c.waitCloseHandshake()
-	if CloseStatus(err) == -1 {
-		return err
+	if CloseStatus(closeHandshakeErr) == -1 {
+		return closeHandshakeErr
 	}
 	return nil
 }
@@ -50,10 +51,10 @@ var errAlreadyWroteClose = errors.New("already wrote close")
 
 func (c *Conn) writeClose(code StatusCode, reason string) error {
 	c.closeMu.Lock()
-	closing := c.wroteClose
+	wroteClose := c.wroteClose
 	c.wroteClose = true
 	c.closeMu.Unlock()
-	if closing {
+	if wroteClose {
 		return errAlreadyWroteClose
 	}
 
diff --git a/conn_test.go b/conn_test.go
index af4fa4c0baa71c886c72aabd071595b30c62bc1e..7514540dfdb8bd66e84ee0eac33edbde68eaf627 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -298,7 +298,7 @@ func TestWasm(t *testing.T) {
 	defer cancel()
 
 	cmd := exec.CommandContext(ctx, "go", "test", "-exec=wasmbrowsertest", "./...")
-	cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm", fmt.Sprintf("WS_ECHO_SERVER_URL=%v", wstest.URL(s)))
+	cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm", fmt.Sprintf("WS_ECHO_SERVER_URL=%v", s.URL))
 
 	b, err := cmd.CombinedOutput()
 	if err != nil {
diff --git a/dial.go b/dial.go
index 50a0ecce3f1aeedde43715337e2b67f85f50eb85..9ab680ebcf402431fb3a2de0e415ecd1a8d92ba4 100644
--- a/dial.go
+++ b/dial.go
@@ -58,6 +58,8 @@ type DialOptions struct {
 // This function requires at least Go 1.12 as it uses a new feature
 // in net/http to perform WebSocket handshakes.
 // See docs on the HTTPClient option and https://github.com/golang/go/issues/26937#issuecomment-415855861
+//
+// URLs with http/https schemes will work and translated into ws/wss.
 func Dial(ctx context.Context, u string, opts *DialOptions) (*Conn, *http.Response, error) {
 	return dial(ctx, u, opts, nil)
 }
@@ -145,6 +147,7 @@ func handshakeRequest(ctx context.Context, urls string, opts *DialOptions, copts
 		u.Scheme = "http"
 	case "wss":
 		u.Scheme = "https"
+	case "http", "https":
 	default:
 		return nil, fmt.Errorf("unexpected url scheme: %q", u.Scheme)
 	}
diff --git a/example_test.go b/example_test.go
index 462de3761044d4741a77bc2bd62ed45896a6e2b9..39de0b8026888c556bc5f8e5e6af106478daf612 100644
--- a/example_test.go
+++ b/example_test.go
@@ -1,5 +1,3 @@
-// +build !js
-
 package websocket_test
 
 import (
@@ -187,3 +185,8 @@ func ExampleGrace() {
 	s.Shutdown(ctx)
 	g.Shutdown(ctx)
 }
+
+// This example demonstrates full stack chat with an automated test.
+func Example_fullStackChat() {
+	// https://github.com/nhooyr/websocket/tree/master/chat-example
+}
diff --git a/internal/test/wstest/url.go b/internal/test/wstest/url.go
deleted file mode 100644
index a11c61b46d88a2b5633d1621fbee8b93ef9fe732..0000000000000000000000000000000000000000
--- a/internal/test/wstest/url.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package wstest
-
-import (
-	"net/http/httptest"
-	"strings"
-)
-
-// URL returns the ws url for s.
-func URL(s *httptest.Server) string {
-	return strings.Replace(s.URL, "http", "ws", 1)
-}
diff --git a/ws_js.go b/ws_js.go
index a8c8b77187d956b0eb27376fb1926a7973124f20..69019e61600635fd829e52a0193df2540d2b5a95 100644
--- a/ws_js.go
+++ b/ws_js.go
@@ -9,6 +9,7 @@ import (
 	"net/http"
 	"reflect"
 	"runtime"
+	"strings"
 	"sync"
 	"syscall/js"
 
@@ -257,6 +258,9 @@ func dial(ctx context.Context, url string, opts *DialOptions) (*Conn, *http.Resp
 		opts = &DialOptions{}
 	}
 
+	url = strings.Replace(url, "http://", "ws://", 1)
+	url = strings.Replace(url, "https://", "wss://", 1)
+
 	ws, err := wsjs.New(url, opts.Subprotocols)
 	if err != nil {
 		return nil, nil, err