fix: disable Jupyter binary WebSocket protocol for Bun compatibility#324
fix: disable Jupyter binary WebSocket protocol for Bun compatibility#324dinohamzic merged 4 commits intomainfrom
Conversation
📝 WalkthroughWalkthroughAdds a JSON-only WebSocket negotiation filter: introduces a JUPYTER_BINARY_PROTOCOL constant and a createJsonWebSocketFactory() that removes the Jupyter binary protocol from the Sec-WebSocket-Protocol list. The factory is injected into ServerConnection.makeSettings() via the WebSocket option so created sockets negotiate JSON-only protocols. Tests were updated to accept partial server settings and a new test asserts a WebSocket factory is passed and is a function. Sequence Diagram(s)sequenceDiagram
participant Client
participant ServerConnection
participant WebSocketFactory
participant WebSocket
participant JupyterKernel
Client->>ServerConnection: makeSettings(baseUrl, wsUrl, WebSocket?)
ServerConnection->>WebSocketFactory: call factory(url, protocols)
Note right of WebSocketFactory: createJsonWebSocketFactory strips JUPYTER_BINARY_PROTOCOL
WebSocketFactory->>WebSocket: new WebSocket(url, filteredProtocols)
Client->>WebSocket: open connection
WebSocket->>JupyterKernel: negotiate protocols (JSON-only)
JupyterKernel->>WebSocket: handshake complete
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Codecov Report❌ Patch coverage is
❌ Your patch status has failed because the patch coverage (30.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #324 +/- ##
==========================================
- Coverage 83.49% 83.42% -0.08%
==========================================
Files 122 122
Lines 7345 7355 +10
Branches 2035 2040 +5
==========================================
+ Hits 6133 6136 +3
- Misses 1212 1219 +7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/runtime-core/src/kernel-client.test.ts`:
- Around line 119-125: The test only checks that a WebSocket function was passed
to mockMakeSettings but not that the binary protocol was removed; update the
test inside the it('passes JsonOnlyWebSocket to server settings', ...) block to
assert the filtering behavior by obtaining callArg =
mockMakeSettings.mock.calls[0][0], then either compare callArg.WebSocket to the
exported JsonOnlyWebSocket wrapper (JsonOnlyWebSocket) or instantiate the
provided constructor (new callArg.WebSocket(...)) and verify it does not accept
binary frames (e.g., sending an ArrayBuffer throws or is serialized to a string
/ binaryType is not set), so the test asserts the binary protocol is actually
removed rather than only checking for a function.
- Line 122: The test reads a potentially stale invocation via
mockMakeSettings.mock.calls[0][0]; update the test to either reset
mockMakeSettings in a beforeEach (call mockMakeSettings.mockClear() or
mockMakeSettings.mockReset()) or read the most recent invocation (use
mockMakeSettings.mock.lastCall or mockMakeSettings.mock.calls.at(-1)[0]) when
assigning callArg so the assertion always inspects the latest call to
mockMakeSettings.
In `@packages/runtime-core/src/kernel-client.ts`:
- Around line 30-34: The constructor in createJsonWebSocketFactory returns
single-string protocols unchanged, so if protocols === JUPYTER_BINARY_PROTOCOL
the binary protocol still gets negotiated; update the constructor in the class
returned by createJsonWebSocketFactory to normalize protocols whether it's an
array or a string (e.g., if protocols is a string and equals
JUPYTER_BINARY_PROTOCOL treat it as undefined/removed, otherwise keep it), then
pass the filtered/normalized value to super(url, filtered) — reference the
createJsonWebSocketFactory function, the inner class constructor, the protocols
parameter, and JUPYTER_BINARY_PROTOCOL to locate and fix the logic.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
packages/runtime-core/src/kernel-client.test.tspackages/runtime-core/src/kernel-client.ts
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/runtime-core/src/kernel-client.test.ts (1)
119-125:⚠️ Potential issue | 🟡 MinorTest verifies presence, not filtering behavior.
This confirms a WebSocket factory is passed but doesn't verify that
v1.kernel.websocket.jupyter.orggets filtered out. The core fix remains unguarded.Consider instantiating the factory and asserting the protocol is excluded:
it('WebSocket factory filters out binary protocol', async () => { await client.connect('http://localhost:8888') const callArg = mockMakeSettings.mock.calls[mockMakeSettings.mock.calls.length - 1][0] const WebSocketFactory = callArg.WebSocket as typeof WebSocket // Verify binary protocol is filtered from array const ws = new WebSocketFactory('ws://test', ['v1.kernel.websocket.jupyter.org', 'other']) // Would need to capture what super() received - alternatively test createJsonWebSocketFactory directly })Or export and unit test
createJsonWebSocketFactorydirectly for cleaner assertions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/runtime-core/src/kernel-client.test.ts` around lines 119 - 125, The test currently only checks that a WebSocket factory is passed (mockMakeSettings call) but does not assert that the unwanted protocol 'v1.kernel.websocket.jupyter.org' is filtered; update the test to either (a) instantiate the passed WebSocket factory from the last mockMakeSettings call (inspect callArg.WebSocket) and wrap/spy the underlying WebSocket constructor to capture the protocols array so you can assert the binary protocol is omitted, or (b) export and unit-test the createJsonWebSocketFactory function directly and assert that when given protocols including 'v1.kernel.websocket.jupyter.org' it returns/constructs with that protocol removed; reference mockMakeSettings, callArg.WebSocket and createJsonWebSocketFactory in the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/runtime-core/src/kernel-client.test.ts`:
- Around line 32-35: The mock function mockMakeSettings currently accepts a
WebSocket parameter but omits it from the returned settings object; update the
mockMakeSettings implementation so its return value includes the passed
WebSocket (e.g., return { baseUrl: config.baseUrl, wsUrl: config.wsUrl,
WebSocket: config.WebSocket }) to mirror the real factory shape and ensure tests
preserve the WebSocket factory behavior.
---
Duplicate comments:
In `@packages/runtime-core/src/kernel-client.test.ts`:
- Around line 119-125: The test currently only checks that a WebSocket factory
is passed (mockMakeSettings call) but does not assert that the unwanted protocol
'v1.kernel.websocket.jupyter.org' is filtered; update the test to either (a)
instantiate the passed WebSocket factory from the last mockMakeSettings call
(inspect callArg.WebSocket) and wrap/spy the underlying WebSocket constructor to
capture the protocols array so you can assert the binary protocol is omitted, or
(b) export and unit-test the createJsonWebSocketFactory function directly and
assert that when given protocols including 'v1.kernel.websocket.jupyter.org' it
returns/constructs with that protocol removed; reference mockMakeSettings,
callArg.WebSocket and createJsonWebSocketFactory in the fix.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
packages/runtime-core/src/kernel-client.test.tspackages/runtime-core/src/kernel-client.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/runtime-core/src/kernel-client.test.ts (1)
120-125:⚠️ Potential issue | 🟠 MajorTest the protocol filtering, not just function presence.
Line 123-Line 125 confirms only that
WebSocketis a function. It still does not provev1.kernel.websocket.jupyter.orgis removed, so the Bun fix can regress silently.♻️ Proposed test update
- it('passes a WebSocket factory to server settings', async () => { + it('filters out Jupyter binary protocol from negotiated protocols', async () => { + const wsCtor = vi.fn() + vi.stubGlobal('WebSocket', wsCtor) + await client.connect('http://localhost:8888') - const callArg = mockMakeSettings.mock.calls[mockMakeSettings.mock.calls.length - 1][0] - expect(callArg).toHaveProperty('WebSocket') - expect(typeof callArg.WebSocket).toBe('function') + const callArg = mockMakeSettings.mock.lastCall?.[0] + expect(callArg?.WebSocket).toEqual(expect.any(Function)) + + const protocols = ['v1.kernel.websocket.jupyter.org', 'json'] + new (callArg!.WebSocket as typeof globalThis.WebSocket)('ws://localhost:8888/', protocols) + + expect(wsCtor).toHaveBeenCalled() + const [, negotiatedProtocols] = wsCtor.mock.calls[0] + expect(negotiatedProtocols).toEqual( + expect.not.arrayContaining(['v1.kernel.websocket.jupyter.org']) + ) + expect(negotiatedProtocols).toEqual(expect.arrayContaining(['json'])) + + vi.unstubAllGlobals() })As per coding guidelines, "Create comprehensive tests for all new features using Vitest" and "Test edge cases, error handling, and special characters in test files".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/runtime-core/src/kernel-client.test.ts` around lines 120 - 125, The test only checks that settings.WebSocket is a function but not that it filters out the banned protocol; update the test to call the factory function returned in mockMakeSettings (grabbed via mockMakeSettings.mock.calls[...] -> callArg.WebSocket) with a protocols array that includes 'v1.kernel.websocket.jupyter.org' and verify the underlying WebSocket constructor/spy receives a protocols array that does NOT contain 'v1.kernel.websocket.jupyter.org' (and still includes other protocols), ensuring protocol filtering behavior is asserted rather than just function presence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/runtime-core/src/kernel-client.test.ts`:
- Around line 120-125: The test only checks that settings.WebSocket is a
function but not that it filters out the banned protocol; update the test to
call the factory function returned in mockMakeSettings (grabbed via
mockMakeSettings.mock.calls[...] -> callArg.WebSocket) with a protocols array
that includes 'v1.kernel.websocket.jupyter.org' and verify the underlying
WebSocket constructor/spy receives a protocols array that does NOT contain
'v1.kernel.websocket.jupyter.org' (and still includes other protocols), ensuring
protocol filtering behavior is asserted rather than just function presence.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
packages/runtime-core/src/kernel-client.test.ts
The PyPI build (
bun build --compile) crashes withRangeError: Out of bounds accesswhen@jupyterlab/servicesdeserializes binary WebSocket messages using thev1.kernel.websocket.jupyter.orgprotocol. This fix excludes the binary protocol from WebSocket negotiation via the documentedServerConnection.makeSettings({ WebSocket })API, forcing JSON-based messaging which works correctly on both Node.js and Bun runtimes.The trade-off is marginally higher serialization overhead for kernel messages, which is negligible for a CLI tool where Python execution time dominates.
Summary by CodeRabbit
Bug Fixes
Tests