Commit Graph
6 Commits
Author SHA1 Message Date
codingetandClaude b9555a463b feat(taildrop): fix DirectFileMode, void callbacks, and empty WaitingFiles
- Add SetStagedFileOps to Extension: sets fileOps without enabling
  DirectFileMode, so WASM clients use staged retrieval (WaitingFiles,
  OpenFile, DeleteFile) instead of direct-write mode.
- Add directFileOps bool field: SetFileOps (Android SAF) sets it true;
  SetStagedFileOps (WASM JS) leaves it false. onChangeProfile now uses
  `fops != nil && e.directFileOps` to determine DirectFileMode.
- Add jsCallVoid to jsFileOps: void ops (openWriter, write, closeWriter,
  remove) now use cb(err?: string) instead of cb(null, err: string).
- Fix waitingFiles() returning JSON null when no files are waiting:
  normalise nil slice to empty slice before marshalling.
- Update wireTaildropFileOps to call SetStagedFileOps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 20:10:01 +00:00
codingetandClaude b2547cc664 feat(tsconnect): expose exit node selection to JS
Add exit node support to the wasm JS bridge:

- Include `exitNodeOption` and `stableNodeID` on each peer in the
  notifyNetMap payload so callers can identify which peers are exit
  nodes and reference them by stable ID.
- Call `notifyExitNode(stableNodeID)` whenever prefs change, so
  callers can track which exit node (if any) is currently active.
- Expose `setExitNode(stableNodeID)` — sets ExitNodeID via EditPrefs.
- Expose `setExitNodeEnabled(enabled)` — toggles the last-used exit
  node on/off via SetUseExitNodeEnabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 20:10:01 +00:00
codinget 358f47bc79 feat(tsconnect): add TCP listening to ipn.listen
Extend ipn.listen to also accept "tcp"/"tcp4"/"tcp6" and return a
TCPListener bound to a netstack gonet.TCPListener. The listener
exposes accept/close/addr like a Go net.Listener and additionally
implements Symbol.asyncIterator so JS callers can write:

  for await (const conn of listener) { ... }

The async iterator returns done when the listener is closed (via
errors.Is(net.ErrClosed)) and rejects on any other accept error.
Symbol-keyed properties are set via Reflect.set since syscall/js
only exposes string-keyed Set.
2026-07-28 20:10:01 +00:00
codinget 58095f829c feat(tsconnect): expose dialTLS to JS
Add ipn.dialTLS(addr, opts?) which dials a TCP connection through
the Tailscale dialer and performs a TLS handshake on top, returning
a JS Conn just like ipn.dial.

WASM has no system root pool, so verification defaults to the
baked-in LetsEncrypt ISRG roots already linked via net/bakedroots.
That covers any tailnet HTTPS endpoint provisioned via
`tailscale cert`. Callers can override with opts.caCerts (PEM) or
bypass entirely with opts.insecureSkipVerify, and override SNI with
opts.serverName.

Marginal binary cost is ~10 KiB on top of the existing ~31.6 MiB
wasm: crypto/tls and the x509 verification path are already pulled
in by control/controlclient and net/tlsdial.
2026-07-28 20:10:01 +00:00
codinget d42da2fbd7 feat(tsconnect): expose dial, listen and listenICMP to JS
Wire up the userspace networking primitives to the JS bridge so
browser callers can initiate outbound and receive inbound traffic
over the Tailscale network:

- ipn.dial(network, addr) wraps a tsdial UserDial into a JS Conn
  with read/write/close/localAddr/remoteAddr.
- ipn.listen(network, addr) wraps a netstack ListenPacket into a
  JS PacketConn with readFrom/writeTo/close/localAddr.
- ipn.listenICMP("icmp4"|"icmp6"|"icmp") creates a raw ICMP
  endpoint on the underlying gVisor stack and wraps it as a
  PacketConn for sending/receiving ping traffic.

To support listenICMP, netstack.Impl gains a Stack() accessor that
returns the underlying *stack.Stack so jsIPN can call NewEndpoint
with icmp.ProtocolNumber4/6.

Binary I/O uses js.CopyBytesToGo / js.CopyBytesToJS to move bytes
across the syscall/js boundary without base64 round-trips.
2026-07-28 20:10:01 +00:00
codingetandClaude c695e579fa fix(tsconnect): link fork features into the wasm build
Upstream's featuretags work turned the wasm build into an allow-list scoped
to its SSH-in-browser client, which strips most of what this fork's JS bridge
exposes. Two problems, both silent at build time:

- cmd/tsconnect/wasm never imported feature/condregister, so extensions only
  registered if the wasm happened to import them directly (taildrop did, ACME
  did not). Without it getCert/listenTLS/setFunnel fail with "cert support not
  compiled in this build".
- The Keep allow-list omitted acme, serve, taildrop, drive, tailnetlock,
  bakedroots and the exit node features. bakedroots matters especially: a
  browser has no system roots, so net/tlsdial's LetsEncrypt fallback is the
  only verification path there.

Invert the polarity to an explicit Omit list, matching how this build behaved
before featuretags existed. Only feature/ace is omitted, because it does not
compile for GOOS=js. Trimming the bundle is worth doing later with
measurements; an allow-list turns each mistake into a runtime failure rather
than a build error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 20:09:08 +00:00