Skip to content
This repository was archived by the owner on Aug 31, 2026. It is now read-only.

fix(events): use the WebSocket global and drop the ws dependency - #370

Closed
georgeglarson wants to merge 1 commit into
OpenHands:mainfrom
georgeglarson:refactor-drop-ws
Closed

fix(events): use the WebSocket global and drop the ws dependency#370
georgeglarson wants to merge 1 commit into
OpenHands:mainfrom
georgeglarson:refactor-drop-ws

Conversation

@georgeglarson

@georgeglarson georgeglarson commented Aug 29, 2026

Copy link
Copy Markdown
Contributor
  • A human has tested these changes.

HUMAN:

Human verified, screenshot below.

Why

Both WebSocket clients picked their implementation like this:

if (typeof window !== 'undefined' && window.WebSocket) {
  WebSocketImpl = window.WebSocket;
} else {
  const ws = require('ws');   // ReferenceError in ESM
}

window is undefined in Node on every version, so Node always took the second branch, and require does not exist in an ESM module. The fallback never worked in the environment it was written for. It only appeared to work under the CommonJS Jest runner, which is why the existing tests had to fake the failure with jest.doMock('ws', ...).

Meanwhile WebSocket has been a global in Node since 22.4, and the CI floor is above that. So the fallback is dead code guarding a case that cannot happen.

Summary

  • Both clients read globalThis.WebSocket. Browsers and Node both provide it.
  • ws comes out of dependencies, so it stops shipping to consumers.
  • package-import.test.ts deletes the global instead of mocking a module, which is the condition a consumer would actually hit rather than a simulation of it. Two tests added for the present-global path.

The load-time invariant is unchanged: reading a missing global yields undefined rather than throwing, and "no WebSocket implementation" still surfaces through onError at start().

Issue Number

No issue filed. Follows the Node 24 discussion in #proj-agent-canvas on 2026-08-29.

How to Test

npm ci && npm run lint && npm run build && npm run format:check && npm test

18 suites, 311 tests, 0 lint errors on Node 24.20.

The suite runs under CommonJS Jest, which is the reason the old bug hid, so it cannot prove the ESM path on its own. npm pack the branch, install the tarball into a project with "type": "module", and import the barrel:

import { RemoteWorkspace, WebSocketCallbackClient } from "@openhands/typescript-client";
new RemoteWorkspace({ host: "https://agent.example.com", workingDir: "/workspace", apiKey: "k" });
const c = new WebSocketCallbackClient({ host: "http://example.com", conversationId: "c1", callback: () => {}, onError: (e) => console.log("onError:", e.message) });
c.start(); c.stop();

On main that file throws before the first line runs. On this branch the barrel imports, the workspace constructs, and the client opens through globalThis.WebSocket.

Video/Screenshots

image

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

This subsumes #362. That PR changes the same window check to globalThis, which is the first half of this one. Close #362 in favour of this, or land it first and this rebases to just the deletion and the dependency drop. Whichever you prefer.

Independent of #368 and #369. The global exists on 22.12 as well as 24, so this does not depend on the floor moving.

Both clients chose their implementation with a `window` check falling back to
`require('ws')`. `window` is undefined in Node on every version, so Node always
took the fallback, and `require` does not exist in an ESM module. The branch
never worked in the environment it was written for. It only looked like it did
under the CommonJS Jest runner, which is why the tests had to fake the failure
with `jest.doMock('ws', ...)`.

WebSocket has been a global in Node since 22.4 and the CI floor is above that,
so both clients now read `globalThis.WebSocket` and `ws` leaves dependencies.

The load-time invariant is unchanged: reading a missing global yields undefined
rather than throwing, and "no WebSocket implementation" still reaches the caller
through onError at start().

package-import.test.ts now deletes the global instead of mocking a module,
which is the condition a consumer actually hits, and gains two cases covering
the present-global path.

Verified against the packed tarball from a real ESM consumer on Node 24.20:
the barrel imports, RemoteWorkspace constructs, and the client opens through
globalThis.WebSocket.
@github-actions github-actions Bot added the type: fix A bug fix label Aug 29, 2026
@georgeglarson
georgeglarson marked this pull request as ready for review August 29, 2026 20:13
@all-hands-bot

Copy link
Copy Markdown
Contributor

🚦 CI is currently failing on this PR's latest commit.

Please fix the failing checks before OpenHands reviews it - this is re-checked automatically once you push a new commit. (A maintainer can also request @all-hands-bot as a reviewer to have it reviewed regardless of CI status.)

This is an automated check - no AI was used to generate this comment.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

type: fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants