1 Commits

Author SHA1 Message Date
Claude
e652fdc130 Fix duplicate answer processing race condition (#6)
Add polling guard to prevent concurrent pollInternal() execution.
The setInterval callback doesn't await the async pollInternal(),
which could cause multiple polls to process the same answer before
lastPollTimestamp is updated, resulting in "Called in wrong state:
stable" errors from setRemoteDescription().
2025-12-14 11:59:43 +00:00
3 changed files with 14 additions and 3 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.18.2", "version": "0.18.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.18.2", "version": "0.18.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/ed25519": "^3.0.0" "@noble/ed25519": "^3.0.0"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.18.2", "version": "0.18.1",
"description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing", "description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -259,6 +259,7 @@ export class Rondevu extends EventEmitter {
private filling = false private filling = false
private pollingInterval: ReturnType<typeof setInterval> | null = null private pollingInterval: ReturnType<typeof setInterval> | null = null
private lastPollTimestamp = 0 private lastPollTimestamp = 0
private isPolling = false // Guard against concurrent poll execution
private constructor( private constructor(
apiUrl: string, apiUrl: string,
@@ -634,6 +635,13 @@ export class Rondevu extends EventEmitter {
private async pollInternal(): Promise<void> { private async pollInternal(): Promise<void> {
if (!this.filling) return if (!this.filling) return
// Prevent concurrent poll execution to avoid duplicate answer processing
if (this.isPolling) {
this.debug('Poll already in progress, skipping')
return
}
this.isPolling = true
try { try {
const result = await this.api.poll(this.lastPollTimestamp) const result = await this.api.poll(this.lastPollTimestamp)
@@ -674,6 +682,8 @@ export class Rondevu extends EventEmitter {
} }
} catch (err) { } catch (err) {
console.error('[Rondevu] Polling error:', err) console.error('[Rondevu] Polling error:', err)
} finally {
this.isPolling = false
} }
} }
@@ -710,6 +720,7 @@ export class Rondevu extends EventEmitter {
stopFilling(): void { stopFilling(): void {
this.debug('Stopping offer filling and polling') this.debug('Stopping offer filling and polling')
this.filling = false this.filling = false
this.isPolling = false // Reset polling guard
// Stop polling // Stop polling
if (this.pollingInterval) { if (this.pollingInterval) {