Give every workspace a README describing what it does, its entry points,
and a short usage example, and link them from the root package table.
Frame Tailshare in the root README as the application the libraries exist
to be composed into, with the other apps as development and testing
surfaces. Its own README records what works today and what the open
issues intend for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add @webnet/smb2, an SMB2/3 client that runs over the existing transport
abstraction and exposes a remote Windows/Samba share as an AsyncVFS, the
same way @webnet/drive exposes WebDAV. Targets default Windows 10/11
(including 24H2 mandatory signing) and supported Samba, negotiating
dialects {2.0.2, 2.1, 3.1.1}.
Runs unchanged in browser and Node with no Node APIs and no new
dependencies: MD4/MD5/HMAC-MD5 are hand-written (NTLM only), AES-CMAC is
built on Web Crypto AES-CBC, SP800-108 KDF on HMAC-SHA256, and SHA-512
preauth and HMAC-SHA256 signing use Web Crypto. NTLMv2 over NTLMSSP/SPNEGO
with MIC; SMB signing implemented, SMB3 encryption deferred.
Includes crypto/auth vector tests, protocol codec round-trips, an in-repo
mock SMB2 server for end-to-end coverage over the loopback transport, and
an env-gated integration test. Wired into test-app as window.smb2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014AcGBx7EjVaByiHcBcsSfP
- example-app: replace local useClient duplicate with @webnet/react, add
useLocalStorage to persist SharedWorker preference across page reloads
- example-app: replace local fmtSize duplicate with @webnet/utils/fmtSize,
use download() util in WaitingFileDebug instead of inline logic
- test-app: add @webnet/utils dependency and expose it on window alongside
other globals for console testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Splits the VFS abstraction (AsyncVFS, Stat, VFSError, VFSErrorCode) and
its implementations (MemoryVFS, NodeVFS, FsaVFS) into a new standalone
@webnet/vfs package, following the same pattern as the @webnet/transport
split from @webnet/http.
packages/drive now depends on @webnet/vfs and imports directly from it.
packages/test-app likewise imports MemoryVFS and FsaVFS from @webnet/vfs.
The drive package.json export map drops the ./vfs/* sub-exports.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add packages/http with package.json, tsconfig.json, and empty index.ts
- Add @webnet/http as a dependency of test-app
- Expose http namespace on window for console testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Import @webnet/tsconnect/cacert.pem as a URL and expose cacertURL plus a
fetch-and-cache loadCACerts() helper on window. The example snippet in
index.html now shows how to pass the bundle into IPN.dialTLS for hosts that
the baked-in LE roots don't cover.
Vite's default server.fs.deny blocks *.{crt,pem} to guard TLS private keys;
the shipped bundle is a public trust store, so vite.config.ts overrides the
deny list to drop that pattern while keeping the .env guards. (The 403 error
reports this as an allow-list failure, which is misleading — both paths go
through the same handler in isFileLoadingAllowed.)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The raw objects returned from the wasm bridge are untyped bags of
js.FuncOf handlers. Calling them with the wrong types, on a closed
handle, or before the IPN is running, panics the Go runtime and
takes down the entire wasm instance for the lifetime of the page —
one bad call and the user has to reload.
Add src/ipn.ts with IPN / Conn / PacketConn / TCPListener classes
that wrap the raw handles and:
- Throw NotRunningError if an IPN method is called before run().
- Throw ClosedError if read/write/accept is called on a closed
Conn/PacketConn/TCPListener, without dispatching into Go.
- Type-check string/Uint8Array arguments in JS before they reach
js.CopyBytesToGo, which would otherwise crash.
- Range-check network literals for dial/listen/listenICMP.
- Expose addr / localAddr / remoteAddr / state / running / closed
as getters rather than methods. Addresses are cached in the
constructor so reading them never dispatches into Go, eliminating
the "call .localAddr() on a closed conn" crash.
- Parse the netmap JSON inside notifyNetMap before handing it off,
and make all run() callbacks optional (state is also a getter).
initIPN now returns a factory that produces IPN class instances
directly; index.ts re-exports the classes and hides the raw types,
so the public surface is the validated one.
The test-app inline example is updated to use getters (conn.remoteAddr,
ln.addr, ipn.state) instead of method calls.
Type the new TCP listening surface from the tailscale submodule:
- listen() is now overloaded: tcp/tcp4/tcp6 returns a TCPListener,
udp/udp4/udp6 returns a PacketConn. The network parameter is a
literal union instead of a bare string so callers get completion
and typo protection.
- TCPListener extends AsyncIterable<Conn>, so consumers can write
`for await (const conn of listener)`.
- dial() drops "udp" from its accepted networks. Connected UDP
through a stream-shaped Conn was confusing; UDP belongs in
listen(), and ICMP in listenICMP(). The Go side still accepts
whatever string for callers who really want to bypass the type.
Bumps the TS lib/target to ES2018 for AsyncIterable, bumps the
tailscale submodule, and updates the test-app inline example to
demonstrate `for await` over a TCP listener.
Type the new ipn.dialTLS bridge from the tailscale submodule. The
default verification path uses the baked-in LetsEncrypt roots that
ship with the existing tsconnect wasm — sufficient for any tailnet
HTTPS endpoint provisioned via `tailscale cert`. Callers can supply
PEM roots, override SNI, or skip verification via TLSDialOptions.
Bumps the tailscale submodule and updates the test-app inline
example to demonstrate dialTLS.
Adds a minimal @webnet/test-app package that boots a Vite dev server,
imports initIPN from @webnet/tsconnect and the bundled main.wasm as a
URL asset, and hangs both on window so you can drive the full IPN
lifecycle from devtools:
const newIPN = await initIPN(wasmURL)
const ipn = newIPN({ authKey, hostname })
ipn.run({ ... })
ipn.login()
const conn = await ipn.dial("tcp", "peer:22")
const pc = await ipn.listenICMP("icmp4")
The index.html doubles as usage documentation for the wrapper API.