Skip to content

fix(client): automatically reconnect to the voice plugin after websocket close - #161

Open
ElektroHeld wants to merge 1 commit into
yaca-systems:mainfrom
ElektroHeld:fix/websocket-auto-reconnect
Open

fix(client): automatically reconnect to the voice plugin after websocket close#161
ElektroHeld wants to merge 1 commit into
yaca-systems:mainfrom
ElektroHeld:fix/websocket-auto-reconnect

Conversation

@ElektroHeld

Copy link
Copy Markdown

Problem

Some players occasionally get stuck after the websocket connection between the FiveM client resource and the local YaCA voice plugin drops (plugin crash, TS/Mumble connection loss, etc.). The only way for them to recover was to fully restart the game and rejoin the server.

Root cause

In apps/yaca-client/src/yaca/main.ts, the client:yaca:init handler only ever calls websocket.start() once, inside an if (!this.websocket.initialized) guard. initialized is set to true on the first successful connect and never reset. The close event handler only logs the disconnect and sets the plugin state to NOT_CONNECTED — it never triggers a new start() call.

Since websocket.initialized stays true forever, that guard can never run again, so nothing re-establishes the connection. Any subsequent client:yaca:init from the server falls through to initRequest(), which calls websocket.send() — a no-op once readyState !== 1. The rangeInterval also keeps firing every 250ms sending doomed no-op writes. The server-side reconnect path (playerReconnect() via server:yaca:wsReady) already exists but is unreachable, since it depends on the client's open event firing again, which never happens.

Fix

Add a scheduleReconnect() method that:

  • Retries websocket.start() with exponential backoff (1s, 2s, 4s, ... capped at 30s), up to 10 attempts.
  • Is triggered from the close handler.
  • Resets the attempt counter (and cancels any pending retry) once the open event fires again.
  • Notifies the player via the existing connect_error locale string if all retries are exhausted.

This reuses the existing (previously unreachable) server-side playerReconnect() / wsReady flow rather than duplicating the init logic — once websocket.start() succeeds again, the existing open handler already does the right thing (re-sends INIT on first connect, or emits server:yaca:wsReady for the server to re-sync the player).

Testing

  • pnpm run typecheck — passes
  • pnpm biome check — passes
  • pnpm run build — passes (client + server build successfully)
  • Not yet tested against a live TS/Mumble plugin disconnect in-game — I don't have a way to reliably reproduce the underlying plugin crash in this environment. Happy to test further or adjust the backoff/attempt-limit values based on maintainer feedback.

Notes

I don't have push access to this repo, so I opened this from a fork. Let me know if you'd like a different approach (e.g. unlimited retries, different backoff shape, or resetting websocket.initialized instead of adding a dedicated reconnect path).

@deepsource-io

deepsource-io Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

DeepSource Code Review

We reviewed changes in 543aa72...0bf0608 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Secrets Jul 21, 2026 10:31p.m. Review ↗
JavaScript Jul 21, 2026 10:31p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

…ket close

Previously, when the NUI websocket connection to the local voice plugin
was lost (plugin crash, TS/Mumble connection drop, etc.), the client only
logged the disconnect and set the plugin state to NOT_CONNECTED. Nothing
ever called websocket.start() again, so the player stayed stuck in a
disconnected state until the whole game client was restarted.

Add scheduleReconnect(), which retries websocket.start() with exponential
backoff (capped at 30s, up to 10 attempts) whenever the websocket closes,
and resets the attempt counter once it reopens. This reuses the existing
server-side playerReconnect()/wsReady flow that was already in place but
unreachable without a fresh open event.
@ElektroHeld
ElektroHeld force-pushed the fix/websocket-auto-reconnect branch from b296c33 to 0bf0608 Compare July 21, 2026 22:31
@ElektroHeld
ElektroHeld marked this pull request as ready for review July 21, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant