feat(tsconnect): expose service advertisement to JS #9
Reference in New Issue
Block a user
Delete Branch "feat/tsconnect-service-advertisement"
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?
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