diff --git a/package.json b/package.json index b8f33d6..48e2737 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,6 @@ "dependencies": { "@types/ws": "^8.2.2", "event-iterator": "^2.0.0", - "iso-url": "^1.1.2", "it-stream-types": "^2.0.1", "uint8arrays": "^4.0.2", "ws": "^8.4.0" @@ -208,5 +207,12 @@ "ws": false, "http": false, "https": false + }, + "react-native": { + "./dist/src/web-socket.js": "./dist/src/web-socket.browser.js", + "./dist/src/server.js": false, + "ws": false, + "http": false, + "https": false } } diff --git a/src/client.ts b/src/client.ts index c4fbce3..e58d575 100644 --- a/src/client.ts +++ b/src/client.ts @@ -11,11 +11,12 @@ export interface WebSocketOptions extends SinkOptions { } export function connect (addr: string, opts?: WebSocketOptions): DuplexWebSocket { - const location = typeof window === 'undefined' ? '' : window.location + const location = typeof window === 'undefined' ? undefined : window.location opts = opts ?? {} - const url = wsurl(addr, location.toString()) - const socket = new WebSocket(url, opts.websocket) + const url = wsurl(addr, location) + // it's necessary to stringify the URL object otherwise react-native crashes + const socket = new WebSocket(url.toString(), opts.websocket) return duplex(socket, opts) } diff --git a/src/ws-url.ts b/src/ws-url.ts index 94672fc..5131979 100644 --- a/src/ws-url.ts +++ b/src/ws-url.ts @@ -1,6 +1,25 @@ -import { relative } from 'iso-url' +const map = { 'http:': 'ws:', 'https:': 'wss:' } +const defaultProtocol = 'ws:' -const map = { http: 'ws', https: 'wss' } -const def = 'ws' +export default (url: string, location?: Partial): URL => { + if (url.startsWith('//')) { + url = `${location?.protocol ?? defaultProtocol}${url}` + } -export default (url: string, location: string | Partial): string => relative(url, location, map, def) + if (url.startsWith('/') && location != null) { + const proto = location.protocol ?? defaultProtocol + const host = location.host + const port = location.port != null && host?.endsWith(`:${location.port}`) !== true ? `:${location.port}` : '' + url = `${proto}//${host}${port}${url}` + } + + const wsUrl = new URL(url) + + for (const [httpProto, wsProto] of Object.entries(map)) { + if (wsUrl.protocol === httpProto) { + wsUrl.protocol = wsProto + } + } + + return wsUrl +} diff --git a/test/error.spec.ts b/test/error.spec.ts index 558b0f7..bd2d705 100644 --- a/test/error.spec.ts +++ b/test/error.spec.ts @@ -3,6 +3,7 @@ import drain from 'it-drain' import { pipe } from 'it-pipe' import defer from 'p-defer' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { isNode } from 'wherearewe' import * as WS from '../src/index.js' import WebSocket from '../src/web-socket.js' @@ -50,18 +51,36 @@ describe('error', () => { expect(sinkError.message).to.equal(sourceError.message) }) - it('test connection error awaiting connected', async () => { - await expect( - WS.duplex(new WebSocket(`ws://localhost:34897/${Math.random()}`)).connected() - ).to.eventually.be.rejected.with.property('message').that.matches(/ECONNREFUSED/g) - }) + // ws under node throws AggregateErrors + if (isNode) { + it('test connection error awaiting connected', async () => { + await expect( + WS.duplex(new WebSocket(`ws://localhost:34897/${Math.random()}`)).connected() + ).to.eventually.be.rejected.with.nested.property('errors[0].message').that.matches(/ECONNREFUSED/g) + }) - it('test connection error in stream', async function () { - await expect( - pipe( - WS.duplex(new WebSocket(`ws://localhost:34897/${Math.random()}`)).source, - drain - ) - ).to.eventually.be.rejected.with.property('message').that.matches(/ECONNREFUSED/g) - }) + it('test connection error in stream', async function () { + await expect( + pipe( + WS.duplex(new WebSocket(`ws://localhost:34897/${Math.random()}`)).source, + drain + ) + ).to.eventually.be.rejected.with.nested.property('errors[0].message').that.matches(/ECONNREFUSED/g) + }) + } else { + it('test connection error awaiting connected', async () => { + await expect( + WS.duplex(new WebSocket(`ws://localhost:34897/${Math.random()}`)).connected() + ).to.eventually.be.rejected.with.property('message').that.matches(/ECONNREFUSED/g) + }) + + it('test connection error in stream', async function () { + await expect( + pipe( + WS.duplex(new WebSocket(`ws://localhost:34897/${Math.random()}`)).source, + drain + ) + ).to.eventually.be.rejected.with.property('message').that.matches(/ECONNREFUSED/g) + }) + } }) diff --git a/test/ws-url.spec.ts b/test/ws-url.spec.ts index 61b3f2b..761b060 100644 --- a/test/ws-url.spec.ts +++ b/test/ws-url.spec.ts @@ -4,35 +4,40 @@ import wsurl from '../src/ws-url.js' describe('ws-url', () => { it('map from a relative url to one for this domain', () => { const location = { - protocol: 'http', + protocol: 'http:', host: 'foo.com', pathname: '/whatever', search: '?okay=true' } - expect(wsurl('//bar.com', location)).to.equal('ws://bar.com/') - expect(wsurl('/this', location)).to.equal('ws://foo.com/this') + expect(wsurl('//bar.com', location).toString()).to.equal('ws://bar.com/') + expect(wsurl('/this', location).toString()).to.equal('ws://foo.com/this') }) it('same path works on dev and deployed', () => { expect(wsurl('/', { - protocol: 'http', + protocol: 'http:', host: 'localhost:8000' - })).to.equal('ws://localhost:8000/') + }).toString()).to.equal('ws://localhost:8000/') expect(wsurl('/', { - protocol: 'http', + protocol: 'http:', host: 'server.com:8000' - })).to.equal('ws://server.com:8000/') + }).toString()).to.equal('ws://server.com:8000/') }) it('universal url still works', () => { expect(wsurl('ws://what.com/okay', { - protocol: 'http', + protocol: 'http:', host: 'localhost:8000' - })).to.equal('ws://what.com/okay') + }).toString()).to.equal('ws://what.com/okay') expect(wsurl('wss://localhost/', { - protocol: 'https', + protocol: 'https:', host: 'localhost:8000' - })).to.equal('wss://localhost/') + }).toString()).to.equal('wss://localhost/') + expect(wsurl('wss://localhost/', { + protocol: 'https:', + host: 'localhost:8000', + port: '8000' + }).toString()).to.equal('wss://localhost/') }) })