diff --git a/src/RealtimeClient.ts b/src/RealtimeClient.ts index 56d87ec..7dde68c 100755 --- a/src/RealtimeClient.ts +++ b/src/RealtimeClient.ts @@ -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, @@ -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') diff --git a/test/RealtimeClient.test.ts b/test/RealtimeClient.test.ts index a8a4e6d..fc19b4a 100644 --- a/test/RealtimeClient.test.ts +++ b/test/RealtimeClient.test.ts @@ -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', { @@ -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() + }) +})