From 22a4006afea03e1b34bd14843b1752d5195f20dc Mon Sep 17 00:00:00 2001
From: Anmol Sethi <hi@nhooyr.io>
Date: Sat, 15 Feb 2020 22:31:40 -0500
Subject: [PATCH] Remove dependency on golang.org/x/xerrors

See https://github.com/nhooyr/websocket/issues/182
---
 accept.go                    | 28 ++++++++++++++--------------
 accept_js.go                 |  5 ++---
 accept_test.go               |  5 ++---
 autobahn_test.go             | 12 +++++-------
 close.go                     |  9 ++++-----
 close_notjs.go               | 20 ++++++++++----------
 conn_notjs.go                | 18 +++++++++---------
 conn_test.go                 |  5 ++---
 dial.go                      | 32 ++++++++++++++++----------------
 example_echo_test.go         |  8 ++++----
 frame.go                     |  5 ++---
 go.mod                       |  3 +--
 internal/errd/wrap.go        | 32 ++------------------------------
 internal/test/wstest/echo.go |  7 +++----
 internal/test/wstest/pipe.go |  7 +++----
 internal/xsync/go.go         |  4 ++--
 netconn.go                   |  5 ++---
 read.go                      | 34 +++++++++++++++++-----------------
 write.go                     | 21 +++++++++++----------
 ws_js.go                     | 36 ++++++++++++++++++------------------
 wsjson/wsjson.go             |  9 ++++-----
 wspb/wspb.go                 |  8 ++++----
 22 files changed, 137 insertions(+), 176 deletions(-)

diff --git a/accept.go b/accept.go
index 75d6d64..fda8cdc 100644
--- a/accept.go
+++ b/accept.go
@@ -6,14 +6,14 @@ import (
 	"bytes"
 	"crypto/sha1"
 	"encoding/base64"
+	"errors"
+	"fmt"
 	"io"
 	"net/http"
 	"net/textproto"
 	"net/url"
 	"strings"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket/internal/errd"
 )
 
@@ -85,7 +85,7 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (_ *Con
 
 	hj, ok := w.(http.Hijacker)
 	if !ok {
-		err = xerrors.New("http.ResponseWriter does not implement http.Hijacker")
+		err = errors.New("http.ResponseWriter does not implement http.Hijacker")
 		http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
 		return nil, err
 	}
@@ -110,7 +110,7 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (_ *Con
 
 	netConn, brw, err := hj.Hijack()
 	if err != nil {
-		err = xerrors.Errorf("failed to hijack connection: %w", err)
+		err = fmt.Errorf("failed to hijack connection: %w", err)
 		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 		return nil, err
 	}
@@ -133,32 +133,32 @@ func accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (_ *Con
 
 func verifyClientRequest(w http.ResponseWriter, r *http.Request) (errCode int, _ error) {
 	if !r.ProtoAtLeast(1, 1) {
-		return http.StatusUpgradeRequired, xerrors.Errorf("WebSocket protocol violation: handshake request must be at least HTTP/1.1: %q", r.Proto)
+		return http.StatusUpgradeRequired, fmt.Errorf("WebSocket protocol violation: handshake request must be at least HTTP/1.1: %q", r.Proto)
 	}
 
 	if !headerContainsToken(r.Header, "Connection", "Upgrade") {
 		w.Header().Set("Connection", "Upgrade")
 		w.Header().Set("Upgrade", "websocket")
-		return http.StatusUpgradeRequired, xerrors.Errorf("WebSocket protocol violation: Connection header %q does not contain Upgrade", r.Header.Get("Connection"))
+		return http.StatusUpgradeRequired, fmt.Errorf("WebSocket protocol violation: Connection header %q does not contain Upgrade", r.Header.Get("Connection"))
 	}
 
 	if !headerContainsToken(r.Header, "Upgrade", "websocket") {
 		w.Header().Set("Connection", "Upgrade")
 		w.Header().Set("Upgrade", "websocket")
-		return http.StatusUpgradeRequired, xerrors.Errorf("WebSocket protocol violation: Upgrade header %q does not contain websocket", r.Header.Get("Upgrade"))
+		return http.StatusUpgradeRequired, fmt.Errorf("WebSocket protocol violation: Upgrade header %q does not contain websocket", r.Header.Get("Upgrade"))
 	}
 
 	if r.Method != "GET" {
-		return http.StatusMethodNotAllowed, xerrors.Errorf("WebSocket protocol violation: handshake request method is not GET but %q", r.Method)
+		return http.StatusMethodNotAllowed, fmt.Errorf("WebSocket protocol violation: handshake request method is not GET but %q", r.Method)
 	}
 
 	if r.Header.Get("Sec-WebSocket-Version") != "13" {
 		w.Header().Set("Sec-WebSocket-Version", "13")
-		return http.StatusBadRequest, xerrors.Errorf("unsupported WebSocket protocol version (only 13 is supported): %q", r.Header.Get("Sec-WebSocket-Version"))
+		return http.StatusBadRequest, fmt.Errorf("unsupported WebSocket protocol version (only 13 is supported): %q", r.Header.Get("Sec-WebSocket-Version"))
 	}
 
 	if r.Header.Get("Sec-WebSocket-Key") == "" {
-		return http.StatusBadRequest, xerrors.New("WebSocket protocol violation: missing Sec-WebSocket-Key")
+		return http.StatusBadRequest, errors.New("WebSocket protocol violation: missing Sec-WebSocket-Key")
 	}
 
 	return 0, nil
@@ -169,10 +169,10 @@ func authenticateOrigin(r *http.Request) error {
 	if origin != "" {
 		u, err := url.Parse(origin)
 		if err != nil {
-			return xerrors.Errorf("failed to parse Origin header %q: %w", origin, err)
+			return fmt.Errorf("failed to parse Origin header %q: %w", origin, err)
 		}
 		if !strings.EqualFold(u.Host, r.Host) {
-			return xerrors.Errorf("request Origin %q is not authorized for Host %q", origin, r.Host)
+			return fmt.Errorf("request Origin %q is not authorized for Host %q", origin, r.Host)
 		}
 	}
 	return nil
@@ -223,7 +223,7 @@ func acceptDeflate(w http.ResponseWriter, ext websocketExtension, mode Compressi
 			continue
 		}
 
-		err := xerrors.Errorf("unsupported permessage-deflate parameter: %q", p)
+		err := fmt.Errorf("unsupported permessage-deflate parameter: %q", p)
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return nil, err
 	}
@@ -253,7 +253,7 @@ func acceptWebkitDeflate(w http.ResponseWriter, ext websocketExtension, mode Com
 		//
 		// Either way, we're only implementing this for webkit which never sends the max_window_bits
 		// parameter so we don't need to worry about it.
-		err := xerrors.Errorf("unsupported x-webkit-deflate-frame parameter: %q", p)
+		err := fmt.Errorf("unsupported x-webkit-deflate-frame parameter: %q", p)
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return nil, err
 	}
diff --git a/accept_js.go b/accept_js.go
index 5db12d7..724b35b 100644
--- a/accept_js.go
+++ b/accept_js.go
@@ -1,9 +1,8 @@
 package websocket
 
 import (
+	"errors"
 	"net/http"
-
-	"golang.org/x/xerrors"
 )
 
 // AcceptOptions represents Accept's options.
@@ -16,5 +15,5 @@ type AcceptOptions struct {
 
 // Accept is stubbed out for Wasm.
 func Accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (*Conn, error) {
-	return nil, xerrors.New("unimplemented")
+	return nil, errors.New("unimplemented")
 }
diff --git a/accept_test.go b/accept_test.go
index 53338e1..523d468 100644
--- a/accept_test.go
+++ b/accept_test.go
@@ -4,14 +4,13 @@ package websocket
 
 import (
 	"bufio"
+	"errors"
 	"net"
 	"net/http"
 	"net/http/httptest"
 	"strings"
 	"testing"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket/internal/test/assert"
 )
 
@@ -80,7 +79,7 @@ func TestAccept(t *testing.T) {
 		w := mockHijacker{
 			ResponseWriter: httptest.NewRecorder(),
 			hijack: func() (conn net.Conn, writer *bufio.ReadWriter, err error) {
-				return nil, nil, xerrors.New("haha")
+				return nil, nil, errors.New("haha")
 			},
 		}
 
diff --git a/autobahn_test.go b/autobahn_test.go
index fb24a06..5047353 100644
--- a/autobahn_test.go
+++ b/autobahn_test.go
@@ -15,8 +15,6 @@ import (
 	"testing"
 	"time"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket"
 	"nhooyr.io/websocket/internal/errd"
 	"nhooyr.io/websocket/internal/test/assert"
@@ -108,7 +106,7 @@ func wstestClientServer(ctx context.Context) (url string, closeFn func(), err er
 		"exclude-cases": excludedAutobahnCases,
 	})
 	if err != nil {
-		return "", nil, xerrors.Errorf("failed to write spec: %w", err)
+		return "", nil, fmt.Errorf("failed to write spec: %w", err)
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute*15)
@@ -126,7 +124,7 @@ func wstestClientServer(ctx context.Context) (url string, closeFn func(), err er
 	wstest := exec.CommandContext(ctx, "wstest", args...)
 	err = wstest.Start()
 	if err != nil {
-		return "", nil, xerrors.Errorf("failed to start wstest: %w", err)
+		return "", nil, fmt.Errorf("failed to start wstest: %w", err)
 	}
 
 	return url, func() {
@@ -209,7 +207,7 @@ func unusedListenAddr() (_ string, err error) {
 func tempJSONFile(v interface{}) (string, error) {
 	f, err := ioutil.TempFile("", "temp.json")
 	if err != nil {
-		return "", xerrors.Errorf("temp file: %w", err)
+		return "", fmt.Errorf("temp file: %w", err)
 	}
 	defer f.Close()
 
@@ -217,12 +215,12 @@ func tempJSONFile(v interface{}) (string, error) {
 	e.SetIndent("", "\t")
 	err = e.Encode(v)
 	if err != nil {
-		return "", xerrors.Errorf("json encode: %w", err)
+		return "", fmt.Errorf("json encode: %w", err)
 	}
 
 	err = f.Close()
 	if err != nil {
-		return "", xerrors.Errorf("close temp file: %w", err)
+		return "", fmt.Errorf("close temp file: %w", err)
 	}
 
 	return f.Name(), nil
diff --git a/close.go b/close.go
index 2007323..7cbc19e 100644
--- a/close.go
+++ b/close.go
@@ -1,9 +1,8 @@
 package websocket
 
 import (
+	"errors"
 	"fmt"
-
-	"golang.org/x/xerrors"
 )
 
 // StatusCode represents a WebSocket status code.
@@ -53,7 +52,7 @@ const (
 
 // CloseError is returned when the connection is closed with a status and reason.
 //
-// Use Go 1.13's xerrors.As to check for this error.
+// Use Go 1.13's errors.As to check for this error.
 // Also see the CloseStatus helper.
 type CloseError struct {
 	Code   StatusCode
@@ -64,13 +63,13 @@ func (ce CloseError) Error() string {
 	return fmt.Sprintf("status = %v and reason = %q", ce.Code, ce.Reason)
 }
 
-// CloseStatus is a convenience wrapper around Go 1.13's xerrors.As to grab
+// CloseStatus is a convenience wrapper around Go 1.13's errors.As to grab
 // the status code from a CloseError.
 //
 // -1 will be returned if the passed error is nil or not a CloseError.
 func CloseStatus(err error) StatusCode {
 	var ce CloseError
-	if xerrors.As(err, &ce) {
+	if errors.As(err, &ce) {
 		return ce.Code
 	}
 	return -1
diff --git a/close_notjs.go b/close_notjs.go
index 3367ea0..c25b088 100644
--- a/close_notjs.go
+++ b/close_notjs.go
@@ -5,11 +5,11 @@ package websocket
 import (
 	"context"
 	"encoding/binary"
+	"errors"
+	"fmt"
 	"log"
 	"time"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket/internal/errd"
 )
 
@@ -46,7 +46,7 @@ func (c *Conn) closeHandshake(code StatusCode, reason string) (err error) {
 	return nil
 }
 
-var errAlreadyWroteClose = xerrors.New("already wrote close")
+var errAlreadyWroteClose = errors.New("already wrote close")
 
 func (c *Conn) writeClose(code StatusCode, reason string) error {
 	c.closeMu.Lock()
@@ -62,7 +62,7 @@ func (c *Conn) writeClose(code StatusCode, reason string) error {
 		Reason: reason,
 	}
 
-	c.setCloseErr(xerrors.Errorf("sent close frame: %w", ce))
+	c.setCloseErr(fmt.Errorf("sent close frame: %w", ce))
 
 	var p []byte
 	var err error
@@ -119,7 +119,7 @@ func parseClosePayload(p []byte) (CloseError, error) {
 	}
 
 	if len(p) < 2 {
-		return CloseError{}, xerrors.Errorf("close payload %q too small, cannot even contain the 2 byte status code", p)
+		return CloseError{}, fmt.Errorf("close payload %q too small, cannot even contain the 2 byte status code", p)
 	}
 
 	ce := CloseError{
@@ -128,7 +128,7 @@ func parseClosePayload(p []byte) (CloseError, error) {
 	}
 
 	if !validWireCloseCode(ce.Code) {
-		return CloseError{}, xerrors.Errorf("invalid status code %v", ce.Code)
+		return CloseError{}, fmt.Errorf("invalid status code %v", ce.Code)
 	}
 
 	return ce, nil
@@ -155,7 +155,7 @@ func validWireCloseCode(code StatusCode) bool {
 func (ce CloseError) bytes() ([]byte, error) {
 	p, err := ce.bytesErr()
 	if err != nil {
-		err = xerrors.Errorf("failed to marshal close frame: %w", err)
+		err = fmt.Errorf("failed to marshal close frame: %w", err)
 		ce = CloseError{
 			Code: StatusInternalError,
 		}
@@ -168,11 +168,11 @@ const maxCloseReason = maxControlPayload - 2
 
 func (ce CloseError) bytesErr() ([]byte, error) {
 	if len(ce.Reason) > maxCloseReason {
-		return nil, xerrors.Errorf("reason string max is %v but got %q with length %v", maxCloseReason, ce.Reason, len(ce.Reason))
+		return nil, fmt.Errorf("reason string max is %v but got %q with length %v", maxCloseReason, ce.Reason, len(ce.Reason))
 	}
 
 	if !validWireCloseCode(ce.Code) {
-		return nil, xerrors.Errorf("status code %v cannot be set", ce.Code)
+		return nil, fmt.Errorf("status code %v cannot be set", ce.Code)
 	}
 
 	buf := make([]byte, 2+len(ce.Reason))
@@ -189,7 +189,7 @@ func (c *Conn) setCloseErr(err error) {
 
 func (c *Conn) setCloseErrLocked(err error) {
 	if c.closeErr == nil {
-		c.closeErr = xerrors.Errorf("WebSocket closed: %w", err)
+		c.closeErr = fmt.Errorf("WebSocket closed: %w", err)
 	}
 }
 
diff --git a/conn_notjs.go b/conn_notjs.go
index 8598ded..7ee60fb 100644
--- a/conn_notjs.go
+++ b/conn_notjs.go
@@ -5,13 +5,13 @@ package websocket
 import (
 	"bufio"
 	"context"
+	"errors"
+	"fmt"
 	"io"
 	"runtime"
 	"strconv"
 	"sync"
 	"sync/atomic"
-
-	"golang.org/x/xerrors"
 )
 
 // Conn represents a WebSocket connection.
@@ -108,7 +108,7 @@ func newConn(cfg connConfig) *Conn {
 	}
 
 	runtime.SetFinalizer(c, func(c *Conn) {
-		c.close(xerrors.New("connection garbage collected"))
+		c.close(errors.New("connection garbage collected"))
 	})
 
 	go c.timeoutLoop()
@@ -165,10 +165,10 @@ func (c *Conn) timeoutLoop() {
 		case readCtx = <-c.readTimeout:
 
 		case <-readCtx.Done():
-			c.setCloseErr(xerrors.Errorf("read timed out: %w", readCtx.Err()))
-			go c.writeError(StatusPolicyViolation, xerrors.New("timed out"))
+			c.setCloseErr(fmt.Errorf("read timed out: %w", readCtx.Err()))
+			go c.writeError(StatusPolicyViolation, errors.New("timed out"))
 		case <-writeCtx.Done():
-			c.close(xerrors.Errorf("write timed out: %w", writeCtx.Err()))
+			c.close(fmt.Errorf("write timed out: %w", writeCtx.Err()))
 			return
 		}
 	}
@@ -190,7 +190,7 @@ func (c *Conn) Ping(ctx context.Context) error {
 
 	err := c.ping(ctx, strconv.Itoa(int(p)))
 	if err != nil {
-		return xerrors.Errorf("failed to ping: %w", err)
+		return fmt.Errorf("failed to ping: %w", err)
 	}
 	return nil
 }
@@ -217,7 +217,7 @@ func (c *Conn) ping(ctx context.Context, p string) error {
 	case <-c.closed:
 		return c.closeErr
 	case <-ctx.Done():
-		err := xerrors.Errorf("failed to wait for pong: %w", ctx.Err())
+		err := fmt.Errorf("failed to wait for pong: %w", ctx.Err())
 		c.close(err)
 		return err
 	case <-pong:
@@ -242,7 +242,7 @@ func (m *mu) Lock(ctx context.Context) error {
 	case <-m.c.closed:
 		return m.c.closeErr
 	case <-ctx.Done():
-		err := xerrors.Errorf("failed to acquire lock: %w", ctx.Err())
+		err := fmt.Errorf("failed to acquire lock: %w", ctx.Err())
 		m.c.close(err)
 		return err
 	case m.ch <- struct{}{}:
diff --git a/conn_test.go b/conn_test.go
index 7755048..c68a25f 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -19,7 +19,6 @@ import (
 
 	"github.com/golang/protobuf/ptypes"
 	"github.com/golang/protobuf/ptypes/duration"
-	"golang.org/x/xerrors"
 
 	"nhooyr.io/websocket"
 	"nhooyr.io/websocket/internal/test/assert"
@@ -289,10 +288,10 @@ func TestWasm(t *testing.T) {
 
 func assertCloseStatus(exp websocket.StatusCode, err error) error {
 	if websocket.CloseStatus(err) == -1 {
-		return xerrors.Errorf("expected websocket.CloseError: %T %v", err, err)
+		return fmt.Errorf("expected websocket.CloseError: %T %v", err, err)
 	}
 	if websocket.CloseStatus(err) != exp {
-		return xerrors.Errorf("expected close status %v but got ", exp, err)
+		return fmt.Errorf("expected close status %v but got %v", exp, err)
 	}
 	return nil
 }
diff --git a/dial.go b/dial.go
index 09546ac..6e37a58 100644
--- a/dial.go
+++ b/dial.go
@@ -8,6 +8,8 @@ import (
 	"context"
 	"crypto/rand"
 	"encoding/base64"
+	"errors"
+	"fmt"
 	"io"
 	"io/ioutil"
 	"net/http"
@@ -15,8 +17,6 @@ import (
 	"strings"
 	"sync"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket/internal/errd"
 )
 
@@ -78,7 +78,7 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) (
 
 	secWebSocketKey, err := secWebSocketKey(rand)
 	if err != nil {
-		return nil, nil, xerrors.Errorf("failed to generate Sec-WebSocket-Key: %w", err)
+		return nil, nil, fmt.Errorf("failed to generate Sec-WebSocket-Key: %w", err)
 	}
 
 	resp, err := handshakeRequest(ctx, urls, opts, secWebSocketKey)
@@ -104,7 +104,7 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) (
 
 	rwc, ok := respBody.(io.ReadWriteCloser)
 	if !ok {
-		return nil, resp, xerrors.Errorf("response body is not a io.ReadWriteCloser: %T", respBody)
+		return nil, resp, fmt.Errorf("response body is not a io.ReadWriteCloser: %T", respBody)
 	}
 
 	return newConn(connConfig{
@@ -120,12 +120,12 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) (
 
 func handshakeRequest(ctx context.Context, urls string, opts *DialOptions, secWebSocketKey string) (*http.Response, error) {
 	if opts.HTTPClient.Timeout > 0 {
-		return nil, xerrors.New("use context for cancellation instead of http.Client.Timeout; see https://github.com/nhooyr/websocket/issues/67")
+		return nil, errors.New("use context for cancellation instead of http.Client.Timeout; see https://github.com/nhooyr/websocket/issues/67")
 	}
 
 	u, err := url.Parse(urls)
 	if err != nil {
-		return nil, xerrors.Errorf("failed to parse url: %w", err)
+		return nil, fmt.Errorf("failed to parse url: %w", err)
 	}
 
 	switch u.Scheme {
@@ -134,7 +134,7 @@ func handshakeRequest(ctx context.Context, urls string, opts *DialOptions, secWe
 	case "wss":
 		u.Scheme = "https"
 	default:
-		return nil, xerrors.Errorf("unexpected url scheme: %q", u.Scheme)
+		return nil, fmt.Errorf("unexpected url scheme: %q", u.Scheme)
 	}
 
 	req, _ := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
@@ -153,7 +153,7 @@ func handshakeRequest(ctx context.Context, urls string, opts *DialOptions, secWe
 
 	resp, err := opts.HTTPClient.Do(req)
 	if err != nil {
-		return nil, xerrors.Errorf("failed to send handshake request: %w", err)
+		return nil, fmt.Errorf("failed to send handshake request: %w", err)
 	}
 	return resp, nil
 }
@@ -165,26 +165,26 @@ func secWebSocketKey(rr io.Reader) (string, error) {
 	b := make([]byte, 16)
 	_, err := io.ReadFull(rr, b)
 	if err != nil {
-		return "", xerrors.Errorf("failed to read random data from rand.Reader: %w", err)
+		return "", fmt.Errorf("failed to read random data from rand.Reader: %w", err)
 	}
 	return base64.StdEncoding.EncodeToString(b), nil
 }
 
 func verifyServerResponse(opts *DialOptions, secWebSocketKey string, resp *http.Response) (*compressionOptions, error) {
 	if resp.StatusCode != http.StatusSwitchingProtocols {
-		return nil, xerrors.Errorf("expected handshake response status code %v but got %v", http.StatusSwitchingProtocols, resp.StatusCode)
+		return nil, fmt.Errorf("expected handshake response status code %v but got %v", http.StatusSwitchingProtocols, resp.StatusCode)
 	}
 
 	if !headerContainsToken(resp.Header, "Connection", "Upgrade") {
-		return nil, xerrors.Errorf("WebSocket protocol violation: Connection header %q does not contain Upgrade", resp.Header.Get("Connection"))
+		return nil, fmt.Errorf("WebSocket protocol violation: Connection header %q does not contain Upgrade", resp.Header.Get("Connection"))
 	}
 
 	if !headerContainsToken(resp.Header, "Upgrade", "WebSocket") {
-		return nil, xerrors.Errorf("WebSocket protocol violation: Upgrade header %q does not contain websocket", resp.Header.Get("Upgrade"))
+		return nil, fmt.Errorf("WebSocket protocol violation: Upgrade header %q does not contain websocket", resp.Header.Get("Upgrade"))
 	}
 
 	if resp.Header.Get("Sec-WebSocket-Accept") != secWebSocketAccept(secWebSocketKey) {
-		return nil, xerrors.Errorf("WebSocket protocol violation: invalid Sec-WebSocket-Accept %q, key %q",
+		return nil, fmt.Errorf("WebSocket protocol violation: invalid Sec-WebSocket-Accept %q, key %q",
 			resp.Header.Get("Sec-WebSocket-Accept"),
 			secWebSocketKey,
 		)
@@ -210,7 +210,7 @@ func verifySubprotocol(subprotos []string, resp *http.Response) error {
 		}
 	}
 
-	return xerrors.Errorf("WebSocket protocol violation: unexpected Sec-WebSocket-Protocol from server: %q", proto)
+	return fmt.Errorf("WebSocket protocol violation: unexpected Sec-WebSocket-Protocol from server: %q", proto)
 }
 
 func verifyServerExtensions(h http.Header) (*compressionOptions, error) {
@@ -221,7 +221,7 @@ func verifyServerExtensions(h http.Header) (*compressionOptions, error) {
 
 	ext := exts[0]
 	if ext.name != "permessage-deflate" || len(exts) > 1 {
-		return nil, xerrors.Errorf("WebSocket protcol violation: unsupported extensions from server: %+v", exts[1:])
+		return nil, fmt.Errorf("WebSocket protcol violation: unsupported extensions from server: %+v", exts[1:])
 	}
 
 	copts := &compressionOptions{}
@@ -232,7 +232,7 @@ func verifyServerExtensions(h http.Header) (*compressionOptions, error) {
 		case "server_no_context_takeover":
 			copts.serverNoContextTakeover = true
 		default:
-			return nil, xerrors.Errorf("unsupported permessage-deflate parameter: %q", p)
+			return nil, fmt.Errorf("unsupported permessage-deflate parameter: %q", p)
 		}
 	}
 
diff --git a/example_echo_test.go b/example_echo_test.go
index 1daec8a..cd195d2 100644
--- a/example_echo_test.go
+++ b/example_echo_test.go
@@ -4,6 +4,7 @@ package websocket_test
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"log"
@@ -12,7 +13,6 @@ import (
 	"time"
 
 	"golang.org/x/time/rate"
-	"golang.org/x/xerrors"
 
 	"nhooyr.io/websocket"
 	"nhooyr.io/websocket/wsjson"
@@ -78,7 +78,7 @@ func echoServer(w http.ResponseWriter, r *http.Request) error {
 
 	if c.Subprotocol() != "echo" {
 		c.Close(websocket.StatusPolicyViolation, "client must speak the echo subprotocol")
-		return xerrors.New("client does not speak echo sub protocol")
+		return errors.New("client does not speak echo sub protocol")
 	}
 
 	l := rate.NewLimiter(rate.Every(time.Millisecond*100), 10)
@@ -88,7 +88,7 @@ func echoServer(w http.ResponseWriter, r *http.Request) error {
 			return nil
 		}
 		if err != nil {
-			return xerrors.Errorf("failed to echo with %v: %w", r.RemoteAddr, err)
+			return fmt.Errorf("failed to echo with %v: %w", r.RemoteAddr, err)
 		}
 	}
 }
@@ -117,7 +117,7 @@ func echo(ctx context.Context, c *websocket.Conn, l *rate.Limiter) error {
 
 	_, err = io.Copy(w, r)
 	if err != nil {
-		return xerrors.Errorf("failed to io.Copy: %w", err)
+		return fmt.Errorf("failed to io.Copy: %w", err)
 	}
 
 	err = w.Close()
diff --git a/frame.go b/frame.go
index 4acaecf..2a036f9 100644
--- a/frame.go
+++ b/frame.go
@@ -3,12 +3,11 @@ package websocket
 import (
 	"bufio"
 	"encoding/binary"
+	"fmt"
 	"io"
 	"math"
 	"math/bits"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket/internal/errd"
 )
 
@@ -87,7 +86,7 @@ func readFrameHeader(r *bufio.Reader, readBuf []byte) (h header, err error) {
 	}
 
 	if h.payloadLength < 0 {
-		return header{}, xerrors.Errorf("received negative payload length: %v", h.payloadLength)
+		return header{}, fmt.Errorf("received negative payload length: %v", h.payloadLength)
 	}
 
 	if h.masked {
diff --git a/go.mod b/go.mod
index a10c7b1..801d6be 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module nhooyr.io/websocket
 
-go 1.12
+go 1.13
 
 require (
 	github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
@@ -11,5 +11,4 @@ require (
 	github.com/gorilla/websocket v1.4.1
 	github.com/klauspost/compress v1.10.0
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0
-	golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
 )
diff --git a/internal/errd/wrap.go b/internal/errd/wrap.go
index ed0b775..6e77913 100644
--- a/internal/errd/wrap.go
+++ b/internal/errd/wrap.go
@@ -2,41 +2,13 @@ package errd
 
 import (
 	"fmt"
-
-	"golang.org/x/xerrors"
 )
 
-type wrapError struct {
-	msg   string
-	err   error
-	frame xerrors.Frame
-}
-
-func (e *wrapError) Error() string {
-	return fmt.Sprint(e)
-}
-
-func (e *wrapError) Format(s fmt.State, v rune) { xerrors.FormatError(e, s, v) }
-
-func (e *wrapError) FormatError(p xerrors.Printer) (next error) {
-	p.Print(e.msg)
-	e.frame.Format(p)
-	return e.err
-}
-
-func (e *wrapError) Unwrap() error {
-	return e.err
-}
-
-// Wrap wraps err with xerrors.Errorf if err is non nil.
+// Wrap wraps err with fmt.Errorf if err is non nil.
 // Intended for use with defer and a named error return.
 // Inspired by https://github.com/golang/go/issues/32676.
 func Wrap(err *error, f string, v ...interface{}) {
 	if *err != nil {
-		*err = &wrapError{
-			msg:   fmt.Sprintf(f, v...),
-			err:   *err,
-			frame: xerrors.Caller(1),
-		}
+		*err = fmt.Errorf(f+": %w", append(v, *err)...)
 	}
 }
diff --git a/internal/test/wstest/echo.go b/internal/test/wstest/echo.go
index 714767f..ab7bc25 100644
--- a/internal/test/wstest/echo.go
+++ b/internal/test/wstest/echo.go
@@ -3,11 +3,10 @@ package wstest
 import (
 	"bytes"
 	"context"
+	"fmt"
 	"io"
 	"time"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket"
 	"nhooyr.io/websocket/internal/test/cmp"
 	"nhooyr.io/websocket/internal/test/xrand"
@@ -73,11 +72,11 @@ func Echo(ctx context.Context, c *websocket.Conn, max int) error {
 	}
 
 	if expType != actType {
-		return xerrors.Errorf("unexpected message typ (%v): %v", expType, actType)
+		return fmt.Errorf("unexpected message typ (%v): %v", expType, actType)
 	}
 
 	if !bytes.Equal(msg, act) {
-		return xerrors.Errorf("unexpected msg read: %v", cmp.Diff(msg, act))
+		return fmt.Errorf("unexpected msg read: %v", cmp.Diff(msg, act))
 	}
 
 	return nil
diff --git a/internal/test/wstest/pipe.go b/internal/test/wstest/pipe.go
index 81705a8..0a2899e 100644
--- a/internal/test/wstest/pipe.go
+++ b/internal/test/wstest/pipe.go
@@ -5,12 +5,11 @@ package wstest
 import (
 	"bufio"
 	"context"
+	"fmt"
 	"net"
 	"net/http"
 	"net/http/httptest"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket"
 	"nhooyr.io/websocket/internal/errd"
 	"nhooyr.io/websocket/internal/test/xrand"
@@ -39,11 +38,11 @@ func Pipe(dialOpts *websocket.DialOptions, acceptOpts *websocket.AcceptOptions)
 
 	clientConn, _, err := websocket.Dial(context.Background(), "ws://example.com", dialOpts)
 	if err != nil {
-		return nil, nil, xerrors.Errorf("failed to dial with fake transport: %w", err)
+		return nil, nil, fmt.Errorf("failed to dial with fake transport: %w", err)
 	}
 
 	if serverConn == nil {
-		return nil, nil, xerrors.Errorf("failed to get server conn from fake transport: %w", acceptErr)
+		return nil, nil, fmt.Errorf("failed to get server conn from fake transport: %w", acceptErr)
 	}
 
 	if xrand.Bool() {
diff --git a/internal/xsync/go.go b/internal/xsync/go.go
index 712739a..7a61f27 100644
--- a/internal/xsync/go.go
+++ b/internal/xsync/go.go
@@ -1,7 +1,7 @@
 package xsync
 
 import (
-	"golang.org/x/xerrors"
+	"fmt"
 )
 
 // Go allows running a function in another goroutine
@@ -13,7 +13,7 @@ func Go(fn func() error) <-chan error {
 			r := recover()
 			if r != nil {
 				select {
-				case errs <- xerrors.Errorf("panic in go fn: %v", r):
+				case errs <- fmt.Errorf("panic in go fn: %v", r):
 				default:
 				}
 			}
diff --git a/netconn.go b/netconn.go
index a2d8f4f..64aadf0 100644
--- a/netconn.go
+++ b/netconn.go
@@ -2,13 +2,12 @@ package websocket
 
 import (
 	"context"
+	"fmt"
 	"io"
 	"math"
 	"net"
 	"sync"
 	"time"
-
-	"golang.org/x/xerrors"
 )
 
 // NetConn converts a *websocket.Conn into a net.Conn.
@@ -108,7 +107,7 @@ func (c *netConn) Read(p []byte) (int, error) {
 			return 0, err
 		}
 		if typ != c.msgType {
-			err := xerrors.Errorf("unexpected frame type read (expected %v): %v", c.msgType, typ)
+			err := fmt.Errorf("unexpected frame type read (expected %v): %v", c.msgType, typ)
 			c.c.Close(StatusUnsupportedData, err.Error())
 			return 0, err
 		}
diff --git a/read.go b/read.go
index bbad30d..a1efeca 100644
--- a/read.go
+++ b/read.go
@@ -5,13 +5,13 @@ package websocket
 import (
 	"bufio"
 	"context"
+	"errors"
+	"fmt"
 	"io"
 	"io/ioutil"
 	"strings"
 	"time"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket/internal/errd"
 	"nhooyr.io/websocket/internal/xsync"
 )
@@ -144,13 +144,13 @@ func (c *Conn) readLoop(ctx context.Context) (header, error) {
 		}
 
 		if h.rsv1 && c.readRSV1Illegal(h) || h.rsv2 || h.rsv3 {
-			err := xerrors.Errorf("received header with unexpected rsv bits set: %v:%v:%v", h.rsv1, h.rsv2, h.rsv3)
+			err := fmt.Errorf("received header with unexpected rsv bits set: %v:%v:%v", h.rsv1, h.rsv2, h.rsv3)
 			c.writeError(StatusProtocolError, err)
 			return header{}, err
 		}
 
 		if !c.client && !h.masked {
-			return header{}, xerrors.New("received unmasked frame from client")
+			return header{}, errors.New("received unmasked frame from client")
 		}
 
 		switch h.opcode {
@@ -161,12 +161,12 @@ func (c *Conn) readLoop(ctx context.Context) (header, error) {
 				if h.opcode == opClose && CloseStatus(err) != -1 {
 					return header{}, err
 				}
-				return header{}, xerrors.Errorf("failed to handle control frame %v: %w", h.opcode, err)
+				return header{}, fmt.Errorf("failed to handle control frame %v: %w", h.opcode, err)
 			}
 		case opContinuation, opText, opBinary:
 			return h, nil
 		default:
-			err := xerrors.Errorf("received unknown opcode %v", h.opcode)
+			err := fmt.Errorf("received unknown opcode %v", h.opcode)
 			c.writeError(StatusProtocolError, err)
 			return header{}, err
 		}
@@ -217,7 +217,7 @@ func (c *Conn) readFramePayload(ctx context.Context, p []byte) (int, error) {
 		case <-ctx.Done():
 			return n, ctx.Err()
 		default:
-			err = xerrors.Errorf("failed to read frame payload: %w", err)
+			err = fmt.Errorf("failed to read frame payload: %w", err)
 			c.close(err)
 			return n, err
 		}
@@ -234,13 +234,13 @@ func (c *Conn) readFramePayload(ctx context.Context, p []byte) (int, error) {
 
 func (c *Conn) handleControl(ctx context.Context, h header) (err error) {
 	if h.payloadLength < 0 || h.payloadLength > maxControlPayload {
-		err := xerrors.Errorf("received control frame payload with invalid length: %d", h.payloadLength)
+		err := fmt.Errorf("received control frame payload with invalid length: %d", h.payloadLength)
 		c.writeError(StatusProtocolError, err)
 		return err
 	}
 
 	if !h.fin {
-		err := xerrors.New("received fragmented control frame")
+		err := errors.New("received fragmented control frame")
 		c.writeError(StatusProtocolError, err)
 		return err
 	}
@@ -277,12 +277,12 @@ func (c *Conn) handleControl(ctx context.Context, h header) (err error) {
 
 	ce, err := parseClosePayload(b)
 	if err != nil {
-		err = xerrors.Errorf("received invalid close payload: %w", err)
+		err = fmt.Errorf("received invalid close payload: %w", err)
 		c.writeError(StatusProtocolError, err)
 		return err
 	}
 
-	err = xerrors.Errorf("received close frame: %w", ce)
+	err = fmt.Errorf("received close frame: %w", ce)
 	c.setCloseErr(err)
 	c.writeClose(ce.Code, ce.Reason)
 	c.close(err)
@@ -299,7 +299,7 @@ func (c *Conn) reader(ctx context.Context) (_ MessageType, _ io.Reader, err erro
 	defer c.readMu.Unlock()
 
 	if !c.msgReader.fin {
-		return 0, nil, xerrors.New("previous message not read to completion")
+		return 0, nil, errors.New("previous message not read to completion")
 	}
 
 	h, err := c.readLoop(ctx)
@@ -308,7 +308,7 @@ func (c *Conn) reader(ctx context.Context) (_ MessageType, _ io.Reader, err erro
 	}
 
 	if h.opcode == opContinuation {
-		err := xerrors.New("received continuation frame without text or binary frame")
+		err := errors.New("received continuation frame without text or binary frame")
 		c.writeError(StatusProtocolError, err)
 		return 0, nil, err
 	}
@@ -357,10 +357,10 @@ func (mr *msgReader) setFrame(h header) {
 
 func (mr *msgReader) Read(p []byte) (n int, err error) {
 	defer func() {
-		if xerrors.Is(err, io.ErrUnexpectedEOF) && mr.fin && mr.flate {
+		if errors.Is(err, io.ErrUnexpectedEOF) && mr.fin && mr.flate {
 			err = io.EOF
 		}
-		if xerrors.Is(err, io.EOF) {
+		if errors.Is(err, io.EOF) {
 			err = io.EOF
 			mr.putFlateReader()
 			return
@@ -397,7 +397,7 @@ func (mr *msgReader) read(p []byte) (int, error) {
 				return 0, err
 			}
 			if h.opcode != opContinuation {
-				err := xerrors.New("received new data message without finishing the previous message")
+				err := errors.New("received new data message without finishing the previous message")
 				mr.c.writeError(StatusProtocolError, err)
 				return 0, err
 			}
@@ -448,7 +448,7 @@ func (lr *limitReader) reset(r io.Reader) {
 
 func (lr *limitReader) Read(p []byte) (int, error) {
 	if lr.n <= 0 {
-		err := xerrors.Errorf("read limited at %v bytes", lr.limit.Load())
+		err := fmt.Errorf("read limited at %v bytes", lr.limit.Load())
 		lr.c.writeError(StatusMessageTooBig, err)
 		return 0, err
 	}
diff --git a/write.go b/write.go
index b560b44..81b9141 100644
--- a/write.go
+++ b/write.go
@@ -7,12 +7,13 @@ import (
 	"context"
 	"crypto/rand"
 	"encoding/binary"
+	"errors"
+	"fmt"
 	"io"
 	"sync"
 	"time"
 
 	"github.com/klauspost/compress/flate"
-	"golang.org/x/xerrors"
 
 	"nhooyr.io/websocket/internal/errd"
 )
@@ -27,7 +28,7 @@ import (
 func (c *Conn) Writer(ctx context.Context, typ MessageType) (io.WriteCloser, error) {
 	w, err := c.writer(ctx, typ)
 	if err != nil {
-		return nil, xerrors.Errorf("failed to get writer: %w", err)
+		return nil, fmt.Errorf("failed to get writer: %w", err)
 	}
 	return w, nil
 }
@@ -41,7 +42,7 @@ func (c *Conn) Writer(ctx context.Context, typ MessageType) (io.WriteCloser, err
 func (c *Conn) Write(ctx context.Context, typ MessageType, p []byte) error {
 	_, err := c.write(ctx, typ, p)
 	if err != nil {
-		return xerrors.Errorf("failed to write msg: %w", err)
+		return fmt.Errorf("failed to write msg: %w", err)
 	}
 	return nil
 }
@@ -53,14 +54,14 @@ type msgWriter struct {
 
 func (mw *msgWriter) Write(p []byte) (int, error) {
 	if mw.closed {
-		return 0, xerrors.New("cannot use closed writer")
+		return 0, errors.New("cannot use closed writer")
 	}
 	return mw.mw.Write(p)
 }
 
 func (mw *msgWriter) Close() error {
 	if mw.closed {
-		return xerrors.New("cannot use closed writer")
+		return errors.New("cannot use closed writer")
 	}
 	mw.closed = true
 	return mw.mw.Close()
@@ -182,7 +183,7 @@ func (mw *msgWriterState) Write(p []byte) (_ int, err error) {
 func (mw *msgWriterState) write(p []byte) (int, error) {
 	n, err := mw.c.writeFrame(mw.ctx, false, mw.flate, mw.opcode, p)
 	if err != nil {
-		return n, xerrors.Errorf("failed to write data frame: %w", err)
+		return n, fmt.Errorf("failed to write data frame: %w", err)
 	}
 	mw.opcode = opContinuation
 	return n, nil
@@ -197,7 +198,7 @@ func (mw *msgWriterState) Close() (err error) {
 
 	_, err = mw.c.writeFrame(mw.ctx, true, mw.flate, mw.opcode, nil)
 	if err != nil {
-		return xerrors.Errorf("failed to write fin frame: %w", err)
+		return fmt.Errorf("failed to write fin frame: %w", err)
 	}
 
 	if mw.flate && !mw.flateContextTakeover() {
@@ -218,7 +219,7 @@ func (c *Conn) writeControl(ctx context.Context, opcode opcode, p []byte) error
 
 	_, err := c.writeFrame(ctx, true, false, opcode, p)
 	if err != nil {
-		return xerrors.Errorf("failed to write control frame %v: %w", opcode, err)
+		return fmt.Errorf("failed to write control frame %v: %w", opcode, err)
 	}
 	return nil
 }
@@ -245,7 +246,7 @@ func (c *Conn) writeFrame(ctx context.Context, fin bool, flate bool, opcode opco
 		c.writeHeader.masked = true
 		_, err = io.ReadFull(rand.Reader, c.writeHeaderBuf[:4])
 		if err != nil {
-			return 0, xerrors.Errorf("failed to generate masking key: %w", err)
+			return 0, fmt.Errorf("failed to generate masking key: %w", err)
 		}
 		c.writeHeader.maskKey = binary.LittleEndian.Uint32(c.writeHeaderBuf[:])
 	}
@@ -268,7 +269,7 @@ func (c *Conn) writeFrame(ctx context.Context, fin bool, flate bool, opcode opco
 	if c.writeHeader.fin {
 		err = c.bw.Flush()
 		if err != nil {
-			return n, xerrors.Errorf("failed to flush: %w", err)
+			return n, fmt.Errorf("failed to flush: %w", err)
 		}
 	}
 
diff --git a/ws_js.go b/ws_js.go
index ecf3d78..2b560ce 100644
--- a/ws_js.go
+++ b/ws_js.go
@@ -3,6 +3,8 @@ package websocket // import "nhooyr.io/websocket"
 import (
 	"bytes"
 	"context"
+	"errors"
+	"fmt"
 	"io"
 	"net/http"
 	"reflect"
@@ -10,8 +12,6 @@ import (
 	"sync"
 	"syscall/js"
 
-	"golang.org/x/xerrors"
-
 	"nhooyr.io/websocket/internal/bpool"
 	"nhooyr.io/websocket/internal/wsjs"
 	"nhooyr.io/websocket/internal/xsync"
@@ -45,7 +45,7 @@ func (c *Conn) close(err error, wasClean bool) {
 		runtime.SetFinalizer(c, nil)
 
 		if !wasClean {
-			err = xerrors.Errorf("unclean connection close: %w", err)
+			err = fmt.Errorf("unclean connection close: %w", err)
 		}
 		c.setCloseErr(err)
 		c.closeWasClean = wasClean
@@ -87,7 +87,7 @@ func (c *Conn) init() {
 	})
 
 	runtime.SetFinalizer(c, func(c *Conn) {
-		c.setCloseErr(xerrors.New("connection garbage collected"))
+		c.setCloseErr(errors.New("connection garbage collected"))
 		c.closeWithInternal()
 	})
 }
@@ -100,15 +100,15 @@ func (c *Conn) closeWithInternal() {
 // The maximum time spent waiting is bounded by the context.
 func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error) {
 	if c.isReadClosed.Load() == 1 {
-		return 0, nil, xerrors.New("WebSocket connection read closed")
+		return 0, nil, errors.New("WebSocket connection read closed")
 	}
 
 	typ, p, err := c.read(ctx)
 	if err != nil {
-		return 0, nil, xerrors.Errorf("failed to read: %w", err)
+		return 0, nil, fmt.Errorf("failed to read: %w", err)
 	}
 	if int64(len(p)) > c.msgReadLimit.Load() {
-		err := xerrors.Errorf("read limited at %v bytes", c.msgReadLimit.Load())
+		err := fmt.Errorf("read limited at %v bytes", c.msgReadLimit.Load())
 		c.Close(StatusMessageTooBig, err.Error())
 		return 0, nil, err
 	}
@@ -166,7 +166,7 @@ func (c *Conn) Write(ctx context.Context, typ MessageType, p []byte) error {
 		// to match the Go API. It can only error if the message type
 		// is unexpected or the passed bytes contain invalid UTF-8 for
 		// MessageText.
-		err := xerrors.Errorf("failed to write: %w", err)
+		err := fmt.Errorf("failed to write: %w", err)
 		c.setCloseErr(err)
 		c.closeWithInternal()
 		return err
@@ -184,7 +184,7 @@ func (c *Conn) write(ctx context.Context, typ MessageType, p []byte) error {
 	case MessageText:
 		return c.ws.SendText(string(p))
 	default:
-		return xerrors.Errorf("unexpected message type: %v", typ)
+		return fmt.Errorf("unexpected message type: %v", typ)
 	}
 }
 
@@ -195,7 +195,7 @@ func (c *Conn) write(ctx context.Context, typ MessageType, p []byte) error {
 func (c *Conn) Close(code StatusCode, reason string) error {
 	err := c.exportedClose(code, reason)
 	if err != nil {
-		return xerrors.Errorf("failed to close WebSocket: %w", err)
+		return fmt.Errorf("failed to close WebSocket: %w", err)
 	}
 	return nil
 }
@@ -204,13 +204,13 @@ func (c *Conn) exportedClose(code StatusCode, reason string) error {
 	c.closingMu.Lock()
 	defer c.closingMu.Unlock()
 
-	ce := xerrors.Errorf("sent close: %w", CloseError{
+	ce := fmt.Errorf("sent close: %w", CloseError{
 		Code:   code,
 		Reason: reason,
 	})
 
 	if c.isClosed() {
-		return xerrors.Errorf("tried to close with %q but connection already closed: %w", ce, c.closeErr)
+		return fmt.Errorf("tried to close with %q but connection already closed: %w", ce, c.closeErr)
 	}
 
 	c.setCloseErr(ce)
@@ -245,7 +245,7 @@ type DialOptions struct {
 func Dial(ctx context.Context, url string, opts *DialOptions) (*Conn, *http.Response, error) {
 	c, resp, err := dial(ctx, url, opts)
 	if err != nil {
-		return nil, nil, xerrors.Errorf("failed to WebSocket dial %q: %w", url, err)
+		return nil, nil, fmt.Errorf("failed to WebSocket dial %q: %w", url, err)
 	}
 	return c, resp, nil
 }
@@ -318,25 +318,25 @@ type writer struct {
 
 func (w writer) Write(p []byte) (int, error) {
 	if w.closed {
-		return 0, xerrors.New("cannot write to closed writer")
+		return 0, errors.New("cannot write to closed writer")
 	}
 	n, err := w.b.Write(p)
 	if err != nil {
-		return n, xerrors.Errorf("failed to write message: %w", err)
+		return n, fmt.Errorf("failed to write message: %w", err)
 	}
 	return n, nil
 }
 
 func (w writer) Close() error {
 	if w.closed {
-		return xerrors.New("cannot close closed writer")
+		return errors.New("cannot close closed writer")
 	}
 	w.closed = true
 	defer bpool.Put(w.b)
 
 	err := w.c.Write(w.ctx, w.typ, w.b.Bytes())
 	if err != nil {
-		return xerrors.Errorf("failed to close writer: %w", err)
+		return fmt.Errorf("failed to close writer: %w", err)
 	}
 	return nil
 }
@@ -361,7 +361,7 @@ func (c *Conn) SetReadLimit(n int64) {
 
 func (c *Conn) setCloseErr(err error) {
 	c.closeErrOnce.Do(func() {
-		c.closeErr = xerrors.Errorf("WebSocket closed: %w", err)
+		c.closeErr = fmt.Errorf("WebSocket closed: %w", err)
 	})
 }
 
diff --git a/wsjson/wsjson.go b/wsjson/wsjson.go
index e6f06a2..99996a6 100644
--- a/wsjson/wsjson.go
+++ b/wsjson/wsjson.go
@@ -4,8 +4,7 @@ package wsjson // import "nhooyr.io/websocket/wsjson"
 import (
 	"context"
 	"encoding/json"
-
-	"golang.org/x/xerrors"
+	"fmt"
 
 	"nhooyr.io/websocket"
 	"nhooyr.io/websocket/internal/bpool"
@@ -28,7 +27,7 @@ func read(ctx context.Context, c *websocket.Conn, v interface{}) (err error) {
 
 	if typ != websocket.MessageText {
 		c.Close(websocket.StatusUnsupportedData, "expected text message")
-		return xerrors.Errorf("expected text message for JSON but got: %v", typ)
+		return fmt.Errorf("expected text message for JSON but got: %v", typ)
 	}
 
 	b := bpool.Get()
@@ -42,7 +41,7 @@ func read(ctx context.Context, c *websocket.Conn, v interface{}) (err error) {
 	err = json.Unmarshal(b.Bytes(), v)
 	if err != nil {
 		c.Close(websocket.StatusInvalidFramePayloadData, "failed to unmarshal JSON")
-		return xerrors.Errorf("failed to unmarshal JSON: %w", err)
+		return fmt.Errorf("failed to unmarshal JSON: %w", err)
 	}
 
 	return nil
@@ -66,7 +65,7 @@ func write(ctx context.Context, c *websocket.Conn, v interface{}) (err error) {
 	// a copy of the byte slice but Encoder does as it directly writes to w.
 	err = json.NewEncoder(w).Encode(v)
 	if err != nil {
-		return xerrors.Errorf("failed to marshal JSON: %w", err)
+		return fmt.Errorf("failed to marshal JSON: %w", err)
 	}
 
 	return w.Close()
diff --git a/wspb/wspb.go b/wspb/wspb.go
index 06ac336..e43042d 100644
--- a/wspb/wspb.go
+++ b/wspb/wspb.go
@@ -4,9 +4,9 @@ package wspb // import "nhooyr.io/websocket/wspb"
 import (
 	"bytes"
 	"context"
+	"fmt"
 
 	"github.com/golang/protobuf/proto"
-	"golang.org/x/xerrors"
 
 	"nhooyr.io/websocket"
 	"nhooyr.io/websocket/internal/bpool"
@@ -29,7 +29,7 @@ func read(ctx context.Context, c *websocket.Conn, v proto.Message) (err error) {
 
 	if typ != websocket.MessageBinary {
 		c.Close(websocket.StatusUnsupportedData, "expected binary message")
-		return xerrors.Errorf("expected binary message for protobuf but got: %v", typ)
+		return fmt.Errorf("expected binary message for protobuf but got: %v", typ)
 	}
 
 	b := bpool.Get()
@@ -43,7 +43,7 @@ func read(ctx context.Context, c *websocket.Conn, v proto.Message) (err error) {
 	err = proto.Unmarshal(b.Bytes(), v)
 	if err != nil {
 		c.Close(websocket.StatusInvalidFramePayloadData, "failed to unmarshal protobuf")
-		return xerrors.Errorf("failed to unmarshal protobuf: %w", err)
+		return fmt.Errorf("failed to unmarshal protobuf: %w", err)
 	}
 
 	return nil
@@ -66,7 +66,7 @@ func write(ctx context.Context, c *websocket.Conn, v proto.Message) (err error)
 
 	err = pb.Marshal(v)
 	if err != nil {
-		return xerrors.Errorf("failed to marshal protobuf: %w", err)
+		return fmt.Errorf("failed to marshal protobuf: %w", err)
 	}
 
 	return c.Write(ctx, websocket.MessageBinary, pb.Bytes())
-- 
GitLab