Skip to content

Commit

Permalink
fix: replace ws require with import (#267)
Browse files Browse the repository at this point in the history
* fix: replace ws require with import

* fix: introduce an intermediary class when importing ws

* fix: ditch @types/websocket dependency (#269)

---------

Co-authored-by: Marshall Polaris <[email protected]>
  • Loading branch information
w3b6x9 and mqp authored Dec 19, 2023
1 parent 57d9b65 commit 3c5c3db
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 46 deletions.
12 changes: 2 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"dependencies": {
"@supabase/node-fetch": "^2.6.14",
"@types/phoenix": "^1.5.4",
"@types/websocket": "^1.0.3",
"ws": "^8.14.2"
},
"devDependencies": {
Expand Down
80 changes: 63 additions & 17 deletions src/RealtimeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ interface WebSocketLikeConstructor {
): WebSocketLike
}

type WebSocketLike = WebSocket | WSWebSocket
type WebSocketLike = WebSocket | WSWebSocket | WSWebSocketDummy

interface WebSocketLikeError {
error: any
Expand All @@ -61,18 +61,14 @@ interface WebSocketLikeError {

const NATIVE_WEBSOCKET_AVAILABLE = typeof WebSocket !== 'undefined'

const WebSocketVariant: WebSocketLikeConstructor = NATIVE_WEBSOCKET_AVAILABLE
? WebSocket
: require('ws')

export default class RealtimeClient {
accessToken: string | null = null
channels: RealtimeChannel[] = []
endPoint: string = ''
headers?: { [key: string]: string } = DEFAULT_HEADERS
params?: { [key: string]: string } = {}
timeout: number = DEFAULT_TIMEOUT
transport: WebSocketLikeConstructor = WebSocketVariant
transport: WebSocketLikeConstructor | null
heartbeatIntervalMs: number = 30000
heartbeatTimer: ReturnType<typeof setInterval> | undefined = undefined
pendingHeartbeatRef: string | null = null
Expand Down Expand Up @@ -115,11 +111,15 @@ export default class RealtimeClient {
constructor(endPoint: string, options?: RealtimeClientOptions) {
this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`

if (options?.transport) {
this.transport = options.transport
} else {
this.transport = null
}
if (options?.params) this.params = options.params
if (options?.headers) this.headers = { ...this.headers, ...options.headers }
if (options?.timeout) this.timeout = options.timeout
if (options?.logger) this.logger = options.logger
if (options?.transport) this.transport = options.transport
if (options?.heartbeatIntervalMs)
this.heartbeatIntervalMs = options.heartbeatIntervalMs

Expand Down Expand Up @@ -155,22 +155,31 @@ export default class RealtimeClient {
return
}

if (NATIVE_WEBSOCKET_AVAILABLE) {
this.conn = new this.transport(this._endPointURL())
} else {
if (this.transport) {
this.conn = new this.transport(this._endPointURL(), undefined, {
headers: this.headers,
})
return
}

if (this.conn) {
this.conn.binaryType = 'arraybuffer'
this.conn.onopen = () => this._onConnOpen()
this.conn.onerror = (error: WebSocketLikeError) =>
this._onConnError(error as WebSocketLikeError)
this.conn.onmessage = (event: any) => this._onConnMessage(event)
this.conn.onclose = (event: any) => this._onConnClose(event)
if (NATIVE_WEBSOCKET_AVAILABLE) {
this.conn = new WebSocket(this._endPointURL())
this.setupConnection()
return
}

this.conn = new WSWebSocketDummy(this._endPointURL(), undefined, {
close: () => {
this.conn = null
},
})

import('ws').then(({ default: WS }) => {
this.conn = new WS(this._endPointURL(), undefined, {
headers: this.headers,
})
this.setupConnection()
})
}

/**
Expand Down Expand Up @@ -368,6 +377,22 @@ export default class RealtimeClient {
)
}

/**
* Sets up connection handlers.
*
* @internal
*/
private setupConnection(): void {
if (this.conn) {
this.conn.binaryType = 'arraybuffer'
this.conn.onopen = () => this._onConnOpen()
this.conn.onerror = (error: WebSocketLikeError) =>
this._onConnError(error as WebSocketLikeError)
this.conn.onmessage = (event: any) => this._onConnMessage(event)
this.conn.onclose = (event: any) => this._onConnClose(event)
}
}

/**
* Returns the URL of the websocket.
*
Expand Down Expand Up @@ -489,3 +514,24 @@ export default class RealtimeClient {
this.setAuth(this.accessToken)
}
}

class WSWebSocketDummy {
binaryType: string = 'arraybuffer'
close: Function
onclose: Function = () => {}
onerror: Function = () => {}
onmessage: Function = () => {}
onopen: Function = () => {}
readyState: number = SOCKET_STATES.connecting
send: Function = () => {}
url: string | URL | null = null

constructor(
address: string,
_protocols: undefined,
options: { close: Function }
) {
this.url = address
this.close = options.close
}
}
34 changes: 16 additions & 18 deletions test/socket_test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import assert from 'assert'
import { Server as WebSocketServer, WebSocket } from 'mock-socket'
import sinon from 'sinon'
import { WebSocket as W3CWebSocket } from 'ws'
import { WebSocket as WSWebSocket } from 'ws'
import { RealtimeClient } from '../dist/main'
import { CONNECTION_STATE } from '../dist/main/lib/constants'

let socket

Expand Down Expand Up @@ -33,7 +32,7 @@ describe('constructor', () => {
error: [],
message: [],
})
assert.equal(socket.transport, W3CWebSocket)
assert.equal(socket.transport, null)
assert.equal(socket.timeout, 10000)
assert.equal(socket.heartbeatIntervalMs, 30000)
assert.equal(typeof socket.logger, 'function')
Expand Down Expand Up @@ -82,7 +81,7 @@ describe('constructor', () => {

it('defaults to Websocket transport if available', () => {
socket = new RealtimeClient('wss://example.com/socket')
assert.equal(socket.transport, W3CWebSocket)
assert.equal(socket.transport, null)
})
})
})
Expand Down Expand Up @@ -145,7 +144,6 @@ describe('connect with WebSocket', () => {
socket.connect()

let conn = socket.conn
assert.ok(conn instanceof W3CWebSocket)
assert.equal(conn.url, socket._endPointURL())
})

Expand Down Expand Up @@ -227,15 +225,15 @@ describe('connectionState', () => {
assert.equal(socket.connectionState(), 'closed')
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('returns closed if readyState unrecognized', () => {
socket.connect()

socket.conn.readyState = 5678
assert.equal(socket.connectionState(), 'closed')
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('returns connecting', () => {
socket.connect()

Expand All @@ -244,7 +242,7 @@ describe('connectionState', () => {
assert.ok(!socket.isConnected(), 'is not connected')
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('returns open', () => {
socket.connect()

Expand All @@ -253,7 +251,7 @@ describe('connectionState', () => {
assert.ok(socket.isConnected(), 'is connected')
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('returns closing', () => {
socket.connect()

Expand All @@ -262,7 +260,7 @@ describe('connectionState', () => {
assert.ok(!socket.isConnected(), 'is not connected')
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('returns closed', () => {
socket.connect()

Expand Down Expand Up @@ -417,7 +415,7 @@ describe('push', () => {
socket.disconnect()
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('sends data to connection when connected', () => {
socket.connect()
socket.conn.readyState = 1 // open
Expand All @@ -429,7 +427,7 @@ describe('push', () => {
assert.ok(spy.calledWith(json))
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('buffers data when not connected', () => {
socket.connect()
socket.conn.readyState = 0 // connecting
Expand Down Expand Up @@ -539,7 +537,7 @@ describe('sendHeartbeat', () => {
socket.disconnect()
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip("closes socket when heartbeat is not ack'd within heartbeat window", () => {
let closed = false
socket.conn.readyState = 1 // open
Expand All @@ -551,7 +549,7 @@ describe('sendHeartbeat', () => {
assert.equal(closed, true)
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('pushes heartbeat data when connected', () => {
socket.conn.readyState = 1 // open

Expand All @@ -563,7 +561,7 @@ describe('sendHeartbeat', () => {
assert.ok(spy.calledWith(data))
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('no ops when not connected', () => {
socket.conn.readyState = 0 // connecting

Expand Down Expand Up @@ -594,7 +592,7 @@ describe('flushSendBuffer', () => {
socket.disconnect()
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('calls callbacks in buffer when connected', () => {
socket.conn.readyState = 1 // open
const spy1 = sinon.spy()
Expand All @@ -610,7 +608,7 @@ describe('flushSendBuffer', () => {
assert.equal(spy3.callCount, 0)
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket
it.skip('empties sendBuffer', () => {
socket.conn.readyState = 1 // open
socket.sendBuffer.push(() => { })
Expand Down Expand Up @@ -646,7 +644,7 @@ describe('_onConnOpen', () => {
socket.disconnect()
})

// TODO: fix for W3CWebSocket
// TODO: fix for WSWebSocket

it.skip('flushes the send buffer', () => {
socket.conn.readyState = 1 // open
Expand Down

0 comments on commit 3c5c3db

Please sign in to comment.