feat(tsconnect): expose service advertisement to JS #9
No Reviewers
Labels
Clear labels
Agentic
Component/CI
Component/Funnel
Component/React
Component/State
Component/Taildrive
Component/Taildrop
Component/Tailscale
Component/Tailshare
Component/Transport
Component/VFS
Component/WebRTC
Component/Worker
Human
Protocol/FTP
Protocol/HTTP
Protocol/SFTP
Protocol/SMB
Protocol/SSH
Protocol/WebDAV
Security
Agent
claude-fable-5
1
Work done by Claude Fable 5
Agent
claude-opus-4-8
2
Work done by Claude Opus 4.8
Agent
claude-opus-5
Work done by Claude Opus 5
Agent
claude-sonnet-4-6
3
Work done by Claude Sonnet 4.6
Agent
claude-sonnet-5
Work done by Claude Sonnet 5
Agent
gpt-5.5
7
Work done by GPT 5.5
Agent
gpt-5.6-luna
6
Work done by GPT 5.6 Luna
Agent
gpt-5.6-sol
4
Work done by GPT 5.6 Sol
Agent
gpt-5.6-terra
5
Work done by GPT 5.6 Terra
Opened by an agent
Work on the CI tooling
Work on the Tailscale Funnel or certificate system
Work on a React binding
Work on a state store (eg Redux)
Work on the taildrive system
Work on the taildrop system
Work on the Tailscale fork
Work on the Tailshare app
Work on the transport system
Work on the VFS system
Work on the WebRTC system
Work on the worker system
Opened by a human
Kind
Bug
Bug work
Kind
Enhancement
Enhancement work
Kind
Feature
Feature work
Kind
Maintenance
Maintenance work
Priority
P0
1
Critical work that must be done right now
Priority
P1
2
Urgent work
Priority
P2
3
Medium priority work
Priority
P3
4
Low priority work
Priority
P4
5
Lowest priority work, wishlist-tier
Work on the FTP protocol
Work on the HTTP protocol
Work on the SFTP protocol
Work on the SMB protocol
Work on the SSH protocol
Work on the WebDAV protocol
Security work
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: webnet/tailscale#9
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
SetExplicitServices([]tailcfg.Service)toLocalBackend— a browser-safe alternative to the OS portlist scanner that bypasses theShouldUploadServicesgate when services are declared explicitly.setServices(services)method on the WASM IPN object, accepting{proto, port, description?}entries.servicesfield on every node in the netmap JSON (self and peers), populated fromHostinfo.Serviceswith internal peerapi entries stripped (those are already inpeerAPIURL).SetExplicitServicescallsAuto.RestartMap()(new exported method oncontrolclient.Auto) after updating hostinfo, so the control server sends back a fresh streaming netmap andnotifyNetMapactually fires with the new services — a plain hostinfo sync only triggers a "lite" map update whose response body is discarded.Design notes
The
ShouldUploadServiceshook is normally set by the portlist extension via OS-level port scanning, which can't run in a browser. Rather than registering a hook (which panics if set twice), the fix adds anexplicitServicesfield toLocalBackend.hostInfoWithServicesLockedonly clearshi.Serviceswhen that slice is empty, leaving all non-WASM callers unaffected.userServicesFromViewreturns a non-nil empty slice (notnil) so the JSON-encodedservicesfield is always[]rather thannullwhen a node has none — the JS/TS side declaresservicesas a non-optional array.Test plan
GOOS=js GOARCH=wasm go build ./cmd/tsconnect/wasm/— passesgo build tailscale.com/ipn/ipnlocal— passespackages/tsconnect/src/ipn.test.ts), which spins up real nodes against a headscale control server and verifiessetServices(),self.servicesinnotifyNetMap, andpeer.servicesvisibility on a second node — run 5x in a row with zero flakesAdd SetExplicitServices on LocalBackend so the browser WASM node can declare TCP/UDP services that get uploaded to the control server and distributed to all peers in the netmap — without the OS port-scanner (portlist extension) that cannot run in a browser. The ShouldUploadServices gate in hostInfoWithServicesLocked is bypassed when services were set explicitly, leaving all other callers unaffected. On the JS side, a new setServices(services) method accepts an array of {proto, port, description?} objects. The netmap JSON now includes a services field on every node (self and peers), populated from Hostinfo.Services with internal peerapi entries stripped (they are already reflected in peerAPIURL). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>dd9c9f6844to7bfc64c379