Skip to content

feat: add graceful fallback for isows dependency #508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions src/RealtimeClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { WebSocket } from 'isows'
// Conditional import of WebSocket from isows with fallback
let WebSocket: any
try {
WebSocket = require('isows').WebSocket
} catch (e) {
// isows not available, will use fallback WebSocket
WebSocket = null
}

import {
CHANNEL_EVENTS,
Expand Down Expand Up @@ -215,7 +222,27 @@ export default class RealtimeClient {
return
}
if (!this.transport) {
this.transport = WebSocket
// Try to use isows WebSocket first, then fallback to environment WebSocket
if (WebSocket) {
this.transport = WebSocket
} else {
// Fallback for when isows is not available
if (typeof window !== 'undefined' && window.WebSocket) {
this.transport = window.WebSocket as any
} else if (typeof global !== 'undefined' && (global as any).WebSocket) {
this.transport = (global as any).WebSocket
} else {
// Try to require ws for Node.js environments
try {
const ws = require('ws')
this.transport = ws
} catch (wsError) {
console.warn(
'No WebSocket implementation available. Please provide a custom transport.'
)
}
}
}
}
if (!this.transport) {
throw new Error('No transport provided')
Expand Down
64 changes: 64 additions & 0 deletions test/RealtimeClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import RealtimeClient, { HeartbeatStatus } from '../src/RealtimeClient'
import jwt from 'jsonwebtoken'
import { CHANNEL_STATES, DEFAULT_VERSION } from '../src/lib/constants'
import path from 'path'
import fs from 'fs'

function generateJWT(exp: string): string {
return jwt.sign({}, 'your-256-bit-secret', {
Expand Down Expand Up @@ -1017,3 +1018,66 @@ describe('worker', () => {
assert.ok(ref === client.workerRef)
})
})

describe('isows compatibility', () => {
let isowsPath: string
let backupPath: string

beforeEach(() => {
isowsPath = path.join(__dirname, '..', 'node_modules', 'isows')
backupPath = path.join(__dirname, '..', 'node_modules', 'isows_backup')
})

afterEach(() => {
// Restore isows if it was backed up
try {
if (fs.existsSync(backupPath)) {
fs.renameSync(backupPath, isowsPath)
}
} catch {}
})

test('should work without isows dependency', () => {
// Temporarily remove isows to simulate server environment
try {
if (fs.existsSync(isowsPath)) {
fs.renameSync(isowsPath, backupPath)
}
} catch {}

// Verify isows is not available
let isowsAvailable = true
try {
require('isows')
} catch {
isowsAvailable = false
}
expect(isowsAvailable).toBe(false)

// RealtimeClient should work gracefully without isows
// Using dynamic import to force re-evaluation of the conditional import
expect(() => {
const client = new RealtimeClient(url, { transport: MockWebSocket })
client.connect()
expect(client.transport).toBeDefined()
}).not.toThrow()
})

test('should prefer isows when available', () => {
// Ensure isows is available
let isowsAvailable = true
try {
require('isows')
} catch {
isowsAvailable = false
}
expect(isowsAvailable).toBe(true)

// RealtimeClient should use isows when available
const client = new RealtimeClient(url)
client.connect()

expect(client.transport).toBeDefined()
expect(client.transport).not.toBeNull()
})
})