Skip to content

Commit b314ae1

Browse files
authored
feat: replace isows with native WebSocket factory (#509)
* feat: replace isows with native WebSocket factory * fix: update dynamicRequire code * feat: add tests * fix: run npm i * fix: move ws back to deps * fix: add extra tests for coverage
1 parent ece8663 commit b314ae1

File tree

8 files changed

+586
-60
lines changed

8 files changed

+586
-60
lines changed

package-lock.json

Lines changed: 8 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@
4040
"dependencies": {
4141
"@supabase/node-fetch": "^2.6.13",
4242
"@types/phoenix": "^1.6.6",
43-
"isows": "^1.0.7",
44-
"@types/ws": "^8.18.1",
45-
"ws": "^8.18.2"
43+
"ws": "^8.18.2",
44+
"@types/ws": "^8.18.1"
4645
},
4746
"devDependencies": {
4847
"@arethetypeswrong/cli": "^0.16.4",

pnpm-lock.yaml

Lines changed: 0 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RealtimeClient.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { WebSocket } from 'isows'
1+
import WebSocketFactory, { WebSocketLike } from './lib/websocket-factory'
22

33
import {
44
CHANNEL_EVENTS,
@@ -69,8 +69,6 @@ export interface WebSocketLikeConstructor {
6969
): WebSocketLike
7070
}
7171

72-
export type WebSocketLike = WebSocket
73-
7472
export interface WebSocketLikeError {
7573
error: any
7674
message: string
@@ -197,17 +195,19 @@ export default class RealtimeClient {
197195

198196
this._setConnectionState('connecting')
199197
this._setAuthSafely('connect')
200-
198+
201199
// Establish WebSocket connection
202200
if (!this.transport) {
203-
this.transport = WebSocket
204-
}
205-
if (!this.transport) {
206-
this._setConnectionState('disconnected')
207-
throw new Error('No transport provided')
201+
try {
202+
this.conn = WebSocketFactory.createWebSocket(this.endpointURL())
203+
} catch (error) {
204+
this._setConnectionState('disconnected')
205+
throw new Error(`WebSocket not available: ${(error as Error).message}`)
206+
}
207+
} else {
208+
// Use custom transport if provided
209+
this.conn = new this.transport!(this.endpointURL()) as WebSocketLike
208210
}
209-
210-
this.conn = new this.transport!(this.endpointURL()) as WebSocketLike
211211
this._setupConnectionHandlers()
212212
}
213213

@@ -234,7 +234,7 @@ export default class RealtimeClient {
234234
}
235235

236236
this._setConnectionState('disconnecting', true)
237-
237+
238238
if (this.conn) {
239239
// Setup fallback timer to prevent hanging in disconnecting state
240240
const fallbackTimer = setTimeout(() => {
@@ -413,11 +413,11 @@ export default class RealtimeClient {
413413
'heartbeat timeout. Attempting to re-establish connection'
414414
)
415415
this.heartbeatCallback('timeout')
416-
416+
417417
// Force reconnection after heartbeat timeout
418418
this._wasManualDisconnect = false
419419
this.conn?.close(WS_CLOSE_NORMAL, 'heartbeat timeout')
420-
420+
421421
setTimeout(() => {
422422
if (!this.isConnected()) {
423423
this.reconnectTimer?.scheduleTimeout()
@@ -435,7 +435,7 @@ export default class RealtimeClient {
435435
ref: this.pendingHeartbeatRef,
436436
})
437437
this.heartbeatCallback('sent')
438-
438+
439439
this._setAuthSafely('heartbeat')
440440
}
441441

@@ -462,10 +462,16 @@ export default class RealtimeClient {
462462
if (customFetch) {
463463
_fetch = customFetch
464464
} else if (typeof fetch === 'undefined') {
465+
// Node.js environment without native fetch
465466
_fetch = (...args) =>
466-
import('@supabase/node-fetch' as any).then(({ default: fetch }) =>
467-
fetch(...args)
468-
)
467+
import('@supabase/node-fetch' as any)
468+
.then(({ default: fetch }) => fetch(...args))
469+
.catch((error) => {
470+
throw new Error(
471+
`Failed to load @supabase/node-fetch: ${error.message}. ` +
472+
`This is required for HTTP requests in Node.js environments without native fetch.`
473+
)
474+
})
469475
} else {
470476
_fetch = fetch
471477
}
@@ -548,8 +554,6 @@ export default class RealtimeClient {
548554
})
549555
}
550556

551-
552-
553557
/**
554558
* Clear specific timer
555559
* @internal
@@ -579,7 +583,11 @@ export default class RealtimeClient {
579583
private _setupConnectionHandlers(): void {
580584
if (!this.conn) return
581585

582-
this.conn.binaryType = 'arraybuffer'
586+
// Set binary type if supported (browsers and most WebSocket implementations)
587+
if ('binaryType' in this.conn) {
588+
;(this.conn as any).binaryType = 'arraybuffer'
589+
}
590+
583591
this.conn.onopen = () => this._onConnOpen()
584592
this.conn.onerror = (error: Event) => this._onConnError(error)
585593
this.conn.onmessage = (event: any) => this._onConnMessage(event)
@@ -798,7 +806,6 @@ export default class RealtimeClient {
798806
}
799807
}
800808

801-
802809
/**
803810
* Setup reconnection timer with proper configuration
804811
* @internal
@@ -814,8 +821,6 @@ export default class RealtimeClient {
814821
}, this.reconnectAfterMs)
815822
}
816823

817-
818-
819824
/**
820825
* Initialize client options with defaults
821826
* @internal

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import RealtimePresence, {
2222
RealtimePresenceLeavePayload,
2323
REALTIME_PRESENCE_LISTEN_EVENTS,
2424
} from './RealtimePresence'
25+
import WebSocketFactory, { WebSocketLike } from './lib/websocket-factory'
2526

2627
export {
2728
RealtimePresence,
@@ -45,4 +46,6 @@ export {
4546
REALTIME_PRESENCE_LISTEN_EVENTS,
4647
REALTIME_SUBSCRIBE_STATES,
4748
REALTIME_CHANNEL_STATES,
49+
WebSocketFactory,
50+
WebSocketLike,
4851
}

0 commit comments

Comments
 (0)