Skip to content

Commit 2fc5162

Browse files
authored
fix: prevent multiple web workers from starting (#490)
1 parent 93fb5cc commit 2fc5162

File tree

5 files changed

+95
-75
lines changed

5 files changed

+95
-75
lines changed

src/RealtimeClient.ts

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -562,36 +562,47 @@ export default class RealtimeClient {
562562
this.flushSendBuffer()
563563
this.reconnectTimer.reset()
564564
if (!this.worker) {
565-
this.heartbeatTimer && clearInterval(this.heartbeatTimer)
566-
this.heartbeatTimer = setInterval(
567-
() => this.sendHeartbeat(),
568-
this.heartbeatIntervalMs
569-
)
565+
this._startHeartbeat()
570566
} else {
571-
if (this.workerUrl) {
572-
this.log('worker', `starting worker for from ${this.workerUrl}`)
573-
} else {
574-
this.log('worker', `starting default worker`)
575-
}
576-
const objectUrl = this._workerObjectUrl(this.workerUrl!)
577-
this.workerRef = new Worker(objectUrl)
578-
this.workerRef.onerror = (error) => {
579-
this.log('worker', 'worker error', (error as ErrorEvent).message)
580-
this.workerRef!.terminate()
581-
}
582-
this.workerRef.onmessage = (event) => {
583-
if (event.data.event === 'keepAlive') {
584-
this.sendHeartbeat()
585-
}
567+
if (!this.workerRef) {
568+
this._startWorkerHeartbeat()
586569
}
587-
this.workerRef.postMessage({
588-
event: 'start',
589-
interval: this.heartbeatIntervalMs,
590-
})
591570
}
571+
592572
this.stateChangeCallbacks.open.forEach((callback) => callback())
593573
}
574+
/** @internal */
575+
private _startHeartbeat() {
576+
this.heartbeatTimer && clearInterval(this.heartbeatTimer)
577+
this.heartbeatTimer = setInterval(
578+
() => this.sendHeartbeat(),
579+
this.heartbeatIntervalMs
580+
)
581+
}
594582

583+
/** @internal */
584+
private _startWorkerHeartbeat() {
585+
if (this.workerUrl) {
586+
this.log('worker', `starting worker for from ${this.workerUrl}`)
587+
} else {
588+
this.log('worker', `starting default worker`)
589+
}
590+
const objectUrl = this._workerObjectUrl(this.workerUrl!)
591+
this.workerRef = new Worker(objectUrl)
592+
this.workerRef.onerror = (error) => {
593+
this.log('worker', 'worker error', (error as ErrorEvent).message)
594+
this.workerRef!.terminate()
595+
}
596+
this.workerRef.onmessage = (event) => {
597+
if (event.data.event === 'keepAlive') {
598+
this.sendHeartbeat()
599+
}
600+
}
601+
this.workerRef.postMessage({
602+
event: 'start',
603+
interval: this.heartbeatIntervalMs,
604+
})
605+
}
595606
/** @internal */
596607
private _onConnClose(event: any) {
597608
this.log('transport', 'close', event)

test/channel.test.ts renamed to test/RealtimeChannel.test.ts

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
import assert from 'assert'
22
import sinon from 'sinon'
33
import crypto from 'crypto'
4-
import {
5-
describe,
6-
beforeEach,
7-
afterEach,
8-
test,
9-
beforeAll,
10-
afterAll,
11-
vi,
12-
it,
13-
} from 'vitest'
4+
import { describe, beforeEach, afterEach, test, vi } from 'vitest'
145

156
import RealtimeClient from '../src/RealtimeClient'
167
import RealtimeChannel from '../src/RealtimeChannel'
178
import { Response } from '@supabase/node-fetch'
18-
import Worker from 'web-worker'
199
import { Server, WebSocket } from 'mock-socket'
2010
import { CHANNEL_STATES } from '../src/lib/constants'
2111
import Push from '../src/lib/push'
@@ -1416,37 +1406,3 @@ describe('trigger', () => {
14161406
assert.equal(client.accessTokenValue, '123')
14171407
})
14181408
})
1419-
1420-
describe('worker', () => {
1421-
let client: RealtimeClient
1422-
let mockServer: Server
1423-
1424-
beforeAll(() => {
1425-
window.Worker = Worker
1426-
projectRef = randomProjectRef()
1427-
url = `wss://${projectRef}/socket`
1428-
mockServer = new Server(url)
1429-
})
1430-
1431-
afterAll(() => {
1432-
// @ts-ignore - Deliberately removing Worker to clean up test environment
1433-
window.Worker = undefined
1434-
mockServer.close()
1435-
})
1436-
1437-
beforeEach(() => {
1438-
client = new RealtimeClient('ws://localhost:8080/socket', {
1439-
worker: true,
1440-
workerUrl: 'https://realtime.supabase.com/worker.js',
1441-
heartbeatIntervalMs: 10,
1442-
})
1443-
})
1444-
1445-
test('sets worker flag', () => {
1446-
assert.ok(client.worker)
1447-
})
1448-
1449-
test('sets worker URL', () => {
1450-
assert.equal(client.workerUrl, 'https://realtime.supabase.com/worker.js')
1451-
})
1452-
})

test/client.test.ts renamed to test/RealtimeClient.test.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
import assert from 'assert'
2-
import { describe, beforeEach, afterEach, test, vi, expect } from 'vitest'
2+
import {
3+
afterAll,
4+
afterEach,
5+
beforeAll,
6+
beforeEach,
7+
describe,
8+
expect,
9+
test,
10+
vi,
11+
} from 'vitest'
312
import { Server, WebSocket as MockWebSocket } from 'mock-socket'
413
import WebSocket from 'ws'
514
import sinon from 'sinon'
615
import crypto from 'crypto'
7-
8-
import RealtimeClient, {
9-
HeartbeatStatus,
10-
RealtimeMessage,
11-
} from '../src/RealtimeClient'
16+
import RealtimeClient, { HeartbeatStatus } from '../src/RealtimeClient'
1217
import jwt from 'jsonwebtoken'
1318
import { CHANNEL_STATES } from '../src/lib/constants'
19+
import path from 'path'
1420

1521
function generateJWT(exp: string): string {
1622
return jwt.sign({}, 'your-256-bit-secret', {
1723
algorithm: 'HS256',
1824
expiresIn: exp || '1h',
1925
})
2026
}
27+
import Worker from 'web-worker'
2128

2229
let socket: RealtimeClient
2330
let randomProjectRef = () => crypto.randomUUID()
@@ -853,3 +860,44 @@ describe('log operations', () => {
853860
)
854861
})
855862
})
863+
864+
describe('worker', () => {
865+
let mockServer: Server
866+
let client: RealtimeClient
867+
const workerPath = path.join(__dirname, 'test_worker.js')
868+
beforeAll(() => {
869+
window.Worker = Worker
870+
projectRef = randomProjectRef()
871+
url = `wss://${projectRef}/socket`
872+
mockServer = new Server(url)
873+
})
874+
875+
afterAll(() => {
876+
// @ts-ignore - Deliberately removing Worker to clean up test environment
877+
window.Worker = undefined
878+
mockServer.close()
879+
})
880+
881+
beforeEach(() => {
882+
client = new RealtimeClient('ws://localhost:8080/socket', {
883+
worker: true,
884+
workerUrl: workerPath,
885+
heartbeatIntervalMs: 10,
886+
})
887+
})
888+
test('sets worker flag', () => {
889+
assert.ok(client.worker)
890+
})
891+
892+
test('sets worker URL', () => {
893+
assert.equal(client.workerUrl, workerPath)
894+
})
895+
896+
test('ensures single worker ref is started even with multiple connect calls', () => {
897+
client._onConnOpen()
898+
let ref = client.workerRef
899+
900+
client._onConnOpen()
901+
assert.ok(ref === client.workerRef)
902+
})
903+
})
File renamed without changes.

test/test_worker.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
addEventListener('message', (e) => {
2+
if (e.data.event === 'start') {
3+
setInterval(() => postMessage({ event: 'keepAlive' }), e.data.interval)
4+
}
5+
})

0 commit comments

Comments
 (0)