feat(tsconnect/wasm): add hasShares filter to listDrivePeers #19
Closed
codinget
wants to merge 2 commits from
feat/drive-peers-has-shares into webnet
pull from: feat/drive-peers-has-shares
merge into: :webnet
:webnet
:feat/drive-peers-has-shares
:save/webnet-2026-07-29
:rebase/2026-05-18
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/tsconnect
Component/VFS
Component/WebRTC
Component/Worker
Human
Protocol/FTP
Protocol/HTTP
Protocol/SFTP
Protocol/SMB
Protocol/SSH
Protocol/WebDAV
Protocol/WebSocket
Security
Agent
claude-fable-5
Work done by Claude Fable 5
Agent
claude-opus-4-8
Work done by Claude Opus 4.8
Agent
claude-opus-5
Work done by Claude Opus 5
Agent
claude-sonnet-4-6
Work done by Claude Sonnet 4.6
Agent
claude-sonnet-5
Work done by Claude Sonnet 5
Agent
gpt-5.5
Work done by GPT 5.5
Agent
gpt-5.6-luna
Work done by GPT 5.6 Luna
Agent
gpt-5.6-sol
Work done by GPT 5.6 Sol
Agent
gpt-5.6-terra
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 tsconnect packages
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
Work on the WebSocket protocol
Security work
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: webnet/tailscale#19
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.
Fork-side half of the
hasSharesoption forlistDrivePeers(webnet/webnet issue #143, "Beyond that").PeerCapabilityTaildriveSharermeans a peer is allowed to share with us, not that it currently exposes anything, solistDrivePeersis a superset of the peers that actually have shares. The narrower list is only knowable by asking each peer, which is why this is opt-in.Shape
listDrivePeersnow takes an optional JS options object.{hasShares: true}keeps only peers we positively confirmed are exposing at least one share to us; omitted,undefined,null, or a non-booleanhasSharesall mean false and the call does no network I/O at all, exactly as before.Probe
Depth-1
PROPFINDon{peerAPIURL}/v0/drive/overDialer().PeerAPITransport()(the transport the wasm taildrop path already uses). The peer filters its share list by our permissions before listing, so any non-root<response>href is a share we can actually reach. Only207 Multi-Statuscounts; a 404 (taildrive off), 403 (no cap for us) or non-WebDAV answer excludes the peer.That last point makes this a positive filter: an unreachable peer is excluded, which is not the same as knowing it has no shares. Documented as such on both sides.
Layout
The probe and its multistatus parsing are in a new
cmd/tsconnect/driveprobepackage rather than incmd/tsconnect/wasm/drive.go, because that file ispackage mainimportingsyscall/jsand so cannot be reached bygo test.drive.gokeeps only the JS argument decode and the peer-list filtering.Verification
go test ./cmd/tsconnect/driveprobe/— 9 tests pass, covering root-only vs root+child listings, prefix-stripped and prefix-kept hrefs, absolute URLs, percent-encoded and unicode share names, non-207 statuses, malformed and truncated XML, cancellation, error exclusion with order preserved, and both the parallelism and its bound (8 probes of 100ms complete in 0.10s)build-go; thets_omit_drivestub path builds toogofmtcleanNot verified: that the probes overlap under wasm specifically. The test above proves the concurrency structure overlaps natively; the wasm scheduler is cooperative and single-threaded, and no available tailnet grants taildrive caps, so there is no way to exercise the probe path end-to-end there. The design consciously assumes
PeerAPITransportyields while waiting on the network, as the taildrop streaming path does.Review round
Reviewed autonomously by Claude Sonnet 5 (review on webnet/webnet#166). It verified the
DAV:namespace handling against both webdav implementations in play, confirmedipnlocalstrips the/v0/driveprefix so real peers answer prefix-stripped, checked the closure capture and timeout semantics of the parallel driver, found no panic path in the JS argument decode, and ran the Go tests under-race -count=10without flakes.Its one finding is fixed:
hasChildcounted any href that was not the collection, so a peer answering about an unrelated collection looked like it had shares. It now follows RFC 4918 §9.1 — the collection comes first, anything after it is a member, and the first href counts only if it is itself below the root. Fixing that exposed a second defect the review had not caught: the XML decoder may split character data across tokens, which the previous token-at-a-time check miscounted, so href text is now accumulated per element.Trade-off worth a second opinion: because the prefix is stripped, a peer that omits the collection from its Depth-1 listing has a lone member that cannot be told apart from the collection. That case now reports "no shares" rather than guessing. It violates RFC 4918 §9.1 and neither Go implementation does it, but a tsconnect peer's drive handler is arbitrary JS and could. Reporting no shares is consistent with the positive-filter contract; the alternative is to assume any single entry is a member, which reintroduces the false positives the flag exists to remove.
Also noted, not changed:
filterPeersWithSharespassescontext.Background(), so the only bound on a whole{hasShares: true}call isceil(N/8) x 5s. An overall deadline is a reasonable follow-up and the option bag has room for it.PeerCapabilityTaildriveSharer says a peer may share with us, not that it does, so listDrivePeers is a superset of the peers actually exposing shares. listDrivePeers now takes an options object; with {hasShares: true} each candidate's taildrive root is probed with a Depth-1 PROPFIND and only peers listing at least one share are kept. The probe and its multistatus parsing live in cmd/tsconnect/driveprobe so they can be tested without syscall/js. Probes run in parallel with a bounded worker count, a per-probe timeout and a bounded response read; a probe that fails drops that peer and is logged rather than failing the call, so the filter is positive-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>Closing alongside webnet/webnet#166. The webnet-side filtering landed in webnet/webnet#169 instead, implemented in
@webnet/taildrive/clientover the existing peerAPI/v0/drivepath, so the fork does not need to carrycmd/tsconnect/driveprobeor thelistDrivePeersoptions decode through upstream rebases.The probe and its RFC 4918 §9.1 multistatus reasoning stand on their own; this branch stays available if a probe without a
@webnet/drivedependency is ever wanted.Pull request closed