Merge pull request #4889 from rommapp/fix/netplay-websocket-transport-4294

fix(netplay): connect over websocket only
This commit is contained in:
Georges-Antoine Assi
2026-09-28 14:31:03 -04:00
committed by GitHub
3 changed files with 49 additions and 11 deletions
@@ -52,6 +52,7 @@ import {
loadEmulatorJSState,
invalidateEmulatorJSRomCacheIfRenamed,
installEJSDefaultOptionsTrap,
patchNetplaySocketIo,
createQuickLoadButton,
createSaveQuitButton,
createExitEmulationButton,
@@ -839,17 +840,7 @@ window.EJS_onGameStart = async () => {
};
}
// Wrap the bundled global `io` so netplay uses mounted socket path.
if (window.io && !window.io.__rommNetplayPatched) {
const originalIo = window.io;
const patchedIo = ((url: string, opts?: Record<string, unknown>) =>
originalIo(url, {
...opts,
path: "/netplay/socket.io",
})) as NonNullable<Window["io"]>;
patchedIo.__rommNetplayPatched = true;
window.io = patchedIo;
}
patchNetplaySocketIo();
void (async () => {
const ready = await waitForGameManager();
@@ -14,6 +14,7 @@ import {
bootEmulatorJSSave,
installEJSDefaultOptionsTrap,
loadEmulatorJSSave,
patchNetplaySocketIo,
pollSaveFiles,
resolveScreenshot,
saveSave,
@@ -104,6 +105,36 @@ function saveSettings(settings: Record<string, unknown>) {
);
}
describe("patchNetplaySocketIo", () => {
afterEach(() => {
delete window.io;
});
it("connects netplay over websocket on the mounted path", () => {
const io = vi.fn();
window.io = io;
patchNetplaySocketIo();
window.io("https://romm.example", { transports: ["polling", "websocket"] });
expect(io).toHaveBeenCalledWith("https://romm.example", {
path: "/netplay/socket.io",
transports: ["websocket"],
});
});
it("wraps the global only once", () => {
const io = vi.fn();
window.io = io;
patchNetplaySocketIo();
patchNetplaySocketIo();
window.io("https://romm.example");
expect(io).toHaveBeenCalledTimes(1);
});
});
describe("installEJSDefaultOptionsTrap", () => {
beforeEach(() => {
localStorage.clear();
@@ -592,6 +592,22 @@ export function installEJSDefaultOptionsTrap() {
});
}
/** Points EmulatorJS netplay at RomM's mounted Socket.IO server. */
export function patchNetplaySocketIo() {
if (!window.io || window.io.__rommNetplayPatched) return;
const originalIo = window.io;
const patchedIo = ((url: string, opts?: Record<string, unknown>) =>
originalIo(url, {
...opts,
path: "/netplay/socket.io",
// Gunicorn runs several workers without sticky sessions, so a polling
// request can land on a worker that never saw the handshake.
transports: ["websocket"],
})) as NonNullable<Window["io"]>;
patchedIo.__rommNetplayPatched = true;
window.io = patchedIo;
}
const IOS_FULLSCREEN_NAV_SELECTOR =
".v-app-bar, .v-bottom-navigation, .v-navigation-drawer";
const IOS_FULLSCREEN_STYLE = `