HTML5: upstream the extension-websocket subprotocol fix #15
Labels
No labels
area:defold
area:docs
area:infra
area:protocol
area:testing
kind:bug
kind:chore
phase:breadth
phase:core
phase:hardening
phase:slice
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
yuris/spacetimedb-defold#15
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
HTML5 is blocked on a bug in extension-websocket 4.2.3, diagnosed and patched in #8 but not yet fixed at the source. Until it is, every consumer who wants a browser build has to carry a fork, which is a bad thing to ask of a library's users.
Upstream: https://github.com/defold/extension-websocket/issues/67 (filed 2026-08-21).
The report deliberately gives only the symptom and the two handshakes, not the cause, so a maintainer has room to reach the ordering independently. The analysis below, the patch, and the patched-build handshake are held back for that thread. The ordering is still present on
master, so this is not a fixed-but-unreleased case.The bug
websocket/src/websocket.cppopens the browser socket before recording the subprotocol:CreateConnection()callsCreateConnectionEmscripten(conn)LuaConnect()then assignsconn->m_Protocol = strdup(protocol)connismemsetto zero, soemscripten_websocket_newreads a nullprotocolspointer and the glue takes its no-subprotocol branch.Sec-WebSocket-Protocolnever leaves the browser. Native builds are unaffected — wslay writes the handshake later, from the update loop.Any server that requires a subprotocol is unreachable from HTML5. SpacetimeDB answers
400 no valid protocol selected.The fix is six lines, moving socket creation to the end of
LuaConnect:patches/extension-websocket-html5-subprotocol.patch. Verified against a live 2.8.2 server with awasm-webbundle in headless Chromium — handshake goes fromsubprotocol=<ABSENT>/ 400 tosubprotocol=v1.json.spacetimedb/ 101, and the full smoke client passes.Custom headers are not part of this
Re-tested 2026-08-21 in a scratch project against a raw TCP listener, so the handshake is read off the wire rather than inferred. Native sends both
Sec-WebSocket-Protocoland the custom header. Unpatched HTML5 sends neither. Patched HTML5 sends the subprotocol but still no custom header, because emscripten's WebSocket API cannot set arbitrary request headers at all.So the two failures have different causes and only one is upstream's to fix. The
?token=workaround stays permanently, and the upstream report covers the subprotocol only.Work
Open the PR with the patch and the handshake evidence, linked to #67— overtaken by events: upstream fixed it themselves from the report alone, so no PR from us was needed.Publish an interim fork (tagged, so consumers can pin it) and document the dependency URL in the README's Install section— never done, and no longer needed. See "Worth deciding" below: the harness vendored and patched the extension itself, so CI never needed a fork.Point— moot. It now builds against the plain 4.2.4 dependency, unpatched, and passes.dev/tools/html5-conformance.shat the fork by defaultWorth deciding
Whether the interim fork lives under this account or the README simply tells people to fork it themselves. Hosting it is friendlier but becomes a thing to maintain and re-tag against upstream releases.
Worth reconsidering now that
dev/tools/html5-conformance.shvendors and patches the extension by itself: no fork is strictly needed for CI, only for downstream consumers of this SDK.Scoped down to one item. Two of the four checkboxes were solved by a better mechanism, and the open question answers itself.
The bug is still there upstream, and nobody has reported it
Checked
defold/extension-websocketmaster (websocket.cpp, last touched 2026-06-01,c40a1d4): identical to 4.2.3, at the same line numbers.4.2.3 is still the latest release, and its only change was an unrelated use-after-free fix (upstream #66). A search of every issue and PR in that repo for "subprotocol" returns 0 results — so this is unreported, not merely unfixed. The diagnosis in the description holds unchanged.
Two checkboxes are already obsolete
Point the harness at the fork by default, so HTML5 conformance runs without manual setup— the harness does not need a fork.dev/tools/html5-conformance.sh:44-61unzips whatever extension archive bob resolved, appliespatches/extension-websocket-html5-subprotocol.patchto it, and builds a scratch project carrying it as a local extension with the remote dependency stripped. It already runs with zero manual setup, and it re-applies the patch on every run, so it fails loudly the moment upstream moves the code.Publish an interim fork, tagged so consumers can pin it— see below."Worth deciding": neither. No fork.
The framing assumed a consumer's only options are pin a hosted fork or fork it themselves. There is a third, which is what the harness proves out on every run: vendor the extension into your own project and patch it there. Defold cannot patch a dependency in place, but a local extension directory is not a dependency.
README now documents that directly instead of telling people to fork:
…plus the trap that matters: drop the remote
extension-websocketURL fromgame.projectafterwards, or the build carries twowebsocketextensions.Hosting a fork would mean an account-level repo to re-tag against every upstream release, a second URL in the Install section, and a consumer's build silently pinned to my mirror rather than Defold's. Four lines of unzip-and-patch beats all of that, and it is the same four lines CI already runs.
What is actually left
defold/extension-websocketwith the patch and the handshake evidenceThat is the whole issue now. It needs a GitHub account pushing a branch, so it is a human action — the patch, the commit message, and the before/after handshake evidence are all in
patches/extension-websocket-html5-subprotocol.patch, ready to paste.patches/and the harness's vendoring step once upstream ships a release containing the fixNot blocking #16
#16 listed this issue as a blocker. It is not, and never was — the harness patches the extension itself, so HTML5 conformance runs green today against an unpatched upstream. The HTML5 CI step has landed; details there.
Upstream has a pull request: defold/extension-websocket#68, opened by a Defold maintainer against #67.
It reaches the ordering independently of anything we said — the report deliberately carried the two handshakes and not the cause. The fix separates connection allocation from platform-specific startup, so the Emscripten WebSocket constructor and the native worker never observe a half-initialised connection, and starts the connection only once its protocol, headers, timeout, callback and registration are set. Verified upstream on
wasm-web,wasm_pthread-weband both macOS targets, withSec-WebSocket-Protocolconfirmed present in Chromium and native.Nothing here is waiting on us. This issue closes when that merges and a release carries it, at which point
patches/extension-websocket-html5-subprotocol.patchand the harness step that applies it can both go, along with the "remove the remote dependency" instruction in the install docs.Worth restating what it does not fix, since it would be easy to expect: custom headers. Emscripten's WebSocket API cannot set arbitrary request headers at all, so a patched build sends the subprotocol and still no
Authorization. Passing the token as?token=stays permanent on HTML5 rather than being a stopgap.Resolved upstream. extension-websocket 4.2.4 shipped on 2026-08-22 with the
fix, so this SDK carries no patch and no fork.
The release notes name the issue directly: "The
protocoloption is now honoredon HTML5, ensuring the
Sec-WebSocket-Protocolheader is included in theWebSocket handshake (#67)."
How the work items landed
the analysis, the patch and the patched-build handshake stayed held back as the
original plan intended.
bottom of this issue. Hosting one would have bought a few weeks of convenience
and left a thing to re-tag against every upstream release. The harness vendored
and patched the extension itself in the meantime, so CI never needed a fork,
and consumers who could not wait had the patch in-tree.
dev/tools/html5-conformance.shno longer vendors or patches anything. Itbuilds against the plain dependency.
Verified against a real browser, not assumed
The HTML5 tier passes unpatched, and the handshake assertion in
dev/tools/html5-conformance.mjsis what proves the upstream fix rather than aworkaround:
That is the exact transition this issue was opened about, from
subprotocol=<ABSENT>and a 400 to a named subprotocol and a 101.What did not change
Custom headers are still unreachable on HTML5. Emscripten's WebSocket API cannot
set arbitrary request headers at all, so that was never upstream's to fix and the
?token=query parameter stays permanently.stdb.connectpicks itautomatically on that target, so a consumer does nothing.
What a consumer does now
Two plain dependency URLs, on every target including HTML5. The three manual
steps a browser build used to require, unpacking the fetched extension, applying
the patch, and removing the remote dependency, are gone. Documented in the
README's Install section and in the wiki's Installation page.
Closed by #112.