fix(tsconnect/wasm): Node.js compatibility — safesocket unique name, listen addr normalisation #13

Merged
codinget merged 3 commits from wasm-node-fixes into webnet 2026-06-15 00:17:31 +02:00
Owner

Summary

Two Go-side bugs found while making @webnet/tsconnect work in Node.js (see webnet/webnet#33).

fix(safesocket/js): unique memconn name per IPN instance

safesocket_js.go hardcoded memName = "Tailscale-IPN" for the in-memory IPC address. When a second newIPN() call was made in the same WASM process (e.g. two test nodes sharing a WASM instance), the second safesocket.Listen("") would fail with addr unavailable and log.Fatal the entire WASM process.

Fixed with an atomic counter: each listener gets a distinct name (Tailscale-IPN-1, Tailscale-IPN-2, …). The connect() path is unchanged — in the tsconnect build all LocalAPI calls go through the in-process httptest handler and connect() is never called.

fix(tsconnect/wasm): normalise :port listen addr to 0.0.0.0:port

netstack.ListenTCP requires a full host:port address. Callers passing the standard Go net.Listen form ":0" (any-interface ephemeral port) received ParseAddrPort(":0"): no IP. The listen() handler in wasm_js.go now prepends "0.0.0.0" when the address starts with ":", matching the behaviour of net.Listen.

Test plan

  • Covered by the integration test suite in webnet/webnet#33: the two-node dial/listen test spins up two IPN instances in the same WASM process and verifies that one can accept a connection from the other.

🤖 Generated with Claude Code

## Summary Two Go-side bugs found while making `@webnet/tsconnect` work in Node.js (see webnet/webnet#33). ### fix(safesocket/js): unique memconn name per IPN instance `safesocket_js.go` hardcoded `memName = "Tailscale-IPN"` for the in-memory IPC address. When a second `newIPN()` call was made in the same WASM process (e.g. two test nodes sharing a WASM instance), the second `safesocket.Listen("")` would fail with `addr unavailable` and `log.Fatal` the entire WASM process. Fixed with an atomic counter: each listener gets a distinct name (`Tailscale-IPN-1`, `Tailscale-IPN-2`, …). The `connect()` path is unchanged — in the tsconnect build all LocalAPI calls go through the in-process `httptest` handler and `connect()` is never called. ### fix(tsconnect/wasm): normalise `:port` listen addr to `0.0.0.0:port` `netstack.ListenTCP` requires a full `host:port` address. Callers passing the standard Go `net.Listen` form `":0"` (any-interface ephemeral port) received `ParseAddrPort(":0"): no IP`. The `listen()` handler in `wasm_js.go` now prepends `"0.0.0.0"` when the address starts with `":"`, matching the behaviour of `net.Listen`. ## Test plan - [x] Covered by the integration test suite in webnet/webnet#33: the two-node dial/listen test spins up two IPN instances in the same WASM process and verifies that one can accept a connection from the other. 🤖 Generated with [Claude Code](https://claude.ai/code)
codinget added 2 commits 2026-06-13 22:35:44 +02:00
Each call to newIPN() starts an independent Go backend that calls
safesocket.Listen() to serve the ipnserver IPC channel. Because
memName was a global constant, the second instance would fail with
"addr unavailable" and log.Fatal the whole WASM process.

Use an atomic counter to give each listener a distinct name
(Tailscale-IPN-1, Tailscale-IPN-2, …). The connect() path is
unchanged: in the wasm/tsconnect build all LocalAPI calls go through
the in-process httptest handler, so connect() is never called.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
netstack.ListenTCP requires a full host:port address; callers passing
the standard net.Listen form (":0" for any-interface ephemeral port)
would get ParseAddrPort error. Prepend "0.0.0.0" when the address
starts with ":" so the API matches Go's net.Listen behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
codinget force-pushed wasm-node-fixes from cef1031b8e to 4618ee1496 2026-06-13 22:35:44 +02:00 Compare
codinget added 1 commit 2026-06-14 23:57:03 +02:00
lb and ln are only initialised during run(); calling shutdown() before
run() panics on nil. Guard both fields before dereferencing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
codinget marked the pull request as ready for review 2026-06-15 00:17:16 +02:00
codinget merged commit e7270026f7 into webnet 2026-06-15 00:17:31 +02:00
codinget deleted branch wasm-node-fixes 2026-06-15 00:17:31 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: webnet/tailscale#13