Commit Graph
25 Commits
Author SHA1 Message Date
codingetandClaude db193130fb feat(sftp): package scaffold, SSH codecs, keys, and SFTP wire helpers
New @webnet/sftp package skeleton: big-endian SSH cursor (string/nameList/
mpint), SSH message constants, openssh-key-v1 Ed25519 key parse/serialize
with PKCS#8 seed wrapping and generateHostKey(), host-key/signature blob
codecs with Ed25519/RSA verification and SHA256 fingerprints, SFTP v3
packet/ATTRS codecs with Stat mapping and packet reassembly, SSH_FX status
to VFSError mapping, unix ls longname formatter, and lexical path helpers.
95 unit tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3bV2ANs8WMHxefD5pF9LN
2026-07-06 18:20:52 +00:00
codingetandClaude 37f2fb04a0 feat: integrate @webnet/react and @webnet/utils into example-app and test-app
Checks / lint (pull_request) Successful in 2m50s
Checks / format (pull_request) Successful in 3m22s
Checks / typetest (pull_request) Successful in 10m10s
Checks / typecheck (pull_request) Successful in 10m43s
Node Tests / node-tests (pull_request) Successful in 10m50s
Checks / build (pull_request) Successful in 11m27s
Browser Tests / browser-tests (pull_request) Successful in 12m2s
- 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>
2026-06-28 16:34:42 +00:00
codingetandClaude 5dc0478970 feat: add @webnet/react and @webnet/utils packages
Checks / lint (pull_request) Successful in 2m5s
Checks / format (pull_request) Successful in 2m4s
Checks / typecheck (pull_request) Successful in 9m10s
Checks / typetest (pull_request) Successful in 10m16s
Checks / build (pull_request) Successful in 11m12s
Node Tests / node-tests (pull_request) Successful in 10m57s
Browser Tests / browser-tests (pull_request) Successful in 12m28s
Checks / format (push) Successful in 3m15s
Checks / lint (push) Successful in 3m46s
Checks / typetest (push) Successful in 9m41s
Checks / typecheck (push) Successful in 11m54s
Checks / build (push) Successful in 11m59s
Node Tests / node-tests (push) Failing after 9m54s
Browser Tests / browser-tests (push) Successful in 14m28s
Extracts the useClient and useLocalStorage hooks into @webnet/react,
and the download, upload, readBlob, and fmtSize utilities into @webnet/utils,
so they can be shared by tailshare and other apps without living inside
a single app package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 00:42:38 +00:00
codingetandClaude 564dbfad3f feat(tsconnect-worker): main-thread fallback with IpnClientHandle and connectWithFallback
Checks / lint (pull_request) Successful in 3m3s
Checks / format (pull_request) Successful in 3m12s
Checks / typetest (pull_request) Failing after 10m1s
Checks / build (pull_request) Failing after 10m49s
Checks / typecheck (pull_request) Failing after 10m50s
Browser Tests / browser-tests (pull_request) Failing after 12m36s
Node Tests / node-tests (pull_request) Failing after 14m20s
- IpnClientHandle interface: shared contract for both SharedWorker client and
  main-thread handle (connectionMode, store, state, running, fileOps, run,
  disconnect)
- IpnMainThreadHandle: wraps IPN + Redux store; all IpnClient methods proxy
  directly; run() fires user callbacks with current state and delegates future
  state changes via runWithStore; disconnect() releases the lock and shuts down
- connectMainThread: IndexedDB-backed state uses a web lock keyed to the DB
  name ("tsconnect-idb:<name>"), rejecting immediately if held; in-memory
  state skips the lock entirely
- connectWithFallback: tries SharedWorker first, falls back to main thread if
  unavailable, disabled (disableSharedWorker option), or on worker init failure
- useBuildIpnWorker updated to use connectWithFallback, returns IpnClientHandle
- Tests: 6 Node tests verifying main-thread init and fallback behaviour; 4
  browser tests (Chromium + Firefox) verifying worker path and explicit
  disable fallback via esbuild-bundled inline build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 23:40:45 +00:00
codingetandClaude 630f21c898 feat(fileops): bind ControlledFileOps state to ipn store
Add a ControlledFileOps interface (UserIPNFileOps + observable limits +
onChange) satisfied by InMemoryFileOps and FsaFileOps. Bind its state
into a new fileOps Redux slice so file storage metrics and limits are
observable via store.getState().fileOps and broadcast to all clients.

In tsconnect-worker: hoist the FsaFileOps instance to module scope,
wire bindFileOpsToStore after init, and handle a new setFileOpsConfig
call that lets clients reconfigure maxFiles/maxTotalSize/maxFileSize
at runtime. Limit changes propagate back via the existing action
broadcast path so all connected clients see updated state immediately.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 23:24:50 +00:00
codingetandClaude c89c478286 feat(test): add browser integration testing with Playwright
Uses Playwright as a library within node:test (not @playwright/test) to
keep the same runner and script conventions. Browser test files use the
*.browser.ts extension so the existing src/**/*.test.ts glob picks up zero
browser tests, leaving the regular test suite unaffected.

Key pieces:
- .npmrc: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 prevents binary downloads on
  npm ci; browsers are installed explicitly in CI via playwright install
- @webnet/browser-test-utils: new private package exporting forBrowsers()
  (iterates chromium + firefox, handles browser lifecycle within node:test
  suite/before/after) and a serveDirectory() helper (minimal http.createServer
  that serves a built dist/ or out/ directory so the browser can fetch ES
  modules via dynamic import())
- test:browser / test:browser:coverage scripts added to transport, vfs,
  tsconnect following the same c8 + lcov pattern as test:coverage
- turbo.json: test:browser and test:browser:coverage tasks depend on build +
  ^build (dist/ must exist before the browser can import from it)
- .gitea/workflows/test-browser.yml: CI pipeline that installs browsers with
  --with-deps then runs npm run test:browser

Integration tests:
- DataChannelTransport: real RTCPeerConnection loopback (both peers in one
  page context), exercises send/receive and close propagation
- FsaVFS: OPFS round-trip (writeFile/readFile), stat, readdir, delete
- IndexedDBState: multi-instance persistence (write via instance 1, open
  fresh instance 2 and verify IDB round-trip), empty-DB initialisation
- FsaFileOps: write/read/stat/remove cycle and rename/listFiles over OPFS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 12:47:30 +00:00
codingetandClaude 68313fdcda feat(vfs): extract @webnet/vfs package from @webnet/drive
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>
2026-06-19 16:20:27 +00:00
codingetandClaude 5ee74ffc9d feat(tsconnect-worker): add SharedWorker binding for tsconnect
Wraps the IPN and tsconnect-redux store in a SharedWorker, exposing every
IPN method to clients via message-port RPC with per-object MessageChannels
for Conn, TCPListener, PacketConn, drive handler, and SSH sessions.

Key design points:
- IndexedDBState: sync-read/write Map cache with async IDB flush, replacing
  the unavailable localStorage in SharedWorkerGlobalScope
- Redux store on the worker broadcasts every dispatched action to all clients;
  new clients receive a full state snapshot as synthetic actions on connect
- Web Locks API tracks client liveness: when the client's tab closes its lock
  is released and the worker closes all resources that client opened
  (Conn, TCPListener, PacketConn, drive handler)
- Graceful IPN shutdown when the last client leaves
- FsaFileOps (OPFS) available for Taildrop when fileOps: true in config
- Two tsconfigs: DOM lib for client code, WebWorker lib for worker.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 11:30:15 +00:00
codingetandClaude 859f048e30 test(tsconnect,taildrive): add unit tests for drive bridge and listDrivePeers
Covers bridgeDriveHandler request/response translation (headers, body
streaming, status, all Body union variants) and IPN.serveDrive /
listDrivePeers argument validation and JSON handling, using fake
RawIPN/JsDriveRequest/JsDriveResponse objects so no WASM build is needed.
Bumps the tailscale submodule pointer to the rebased feat/drive-wasm-bridge
tip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 19:24:15 +00:00
codingetandClaude 01a49a0784 feat(tsconnect,taildrive): add taildrive WebDAV server bridge and listDrivePeers
Add drive types to @webnet/tsconnect:
- DriveSharePermission, DrivePermissions, JsDriveRequest, JsDriveResponse,
  RawDriveHandler, IPNDrivePeer types in types.ts
- setDriveHandler / listDrivePeers on the raw IPN interface

Add IPN class methods in ipn.ts:
- serveDrive(fn): registers a WebDAV handler for /v0/drive peerapi endpoint
- listDrivePeers(): lists peers with PeerCapabilityTaildriveSharer in their ACL caps

Add new package @webnet/taildrive with ./server sub-export:
- bridgeDriveHandler(handler: Handler): RawDriveHandler adapts an
  @webnet/http Handler to the Go bridge's raw callback interface, streaming
  the request body chunk-by-chunk and piping the response body to write()/end()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 10:30:48 +00:00
codinget 5a6c6e97bc feat(test-app): expose http/router, websocket and drive 2026-06-08 19:18:39 +00:00
codingetandClaude 74f3b4c9a4 feat(drive): add @webnet/xml and @webnet/drive packages
@webnet/xml: minimal XML parse/serialize with conditional exports —
browser builds use the native DOMParser, Node.js builds use
@xmldom/xmldom. Custom recursive serializer keeps output predictable
and avoids XMLSerializer differences across environments.

@webnet/drive: WebDAV client + server built on @webnet/http and
@webnet/transport, running in both browser and Node.js via the same
transport abstraction used by the rest of the monorepo.

- AsyncVFS interface with optional readFileRange (Range header support),
  copy/move, and setProps (dead property storage for PROPPATCH)
- MemoryVFS: in-memory implementation for testing
- NodeVFS: wraps node:fs/promises
- FsaVFS / OpfsVFS: browser File System Access API and OPFS
- createDAVHandler(vfs, opts): Handler compatible with @webnet/http
  Server; supports GET/HEAD/PUT/DELETE/MKCOL/COPY/MOVE/PROPFIND/
  PROPPATCH with correct 207 Multi-Status XML responses; LOCK/UNLOCK
  stub 501; readOnly option; prefix stripping
- DAVClient implements AsyncVFS so it can be used as the backing store
  for another DAV server instance; accepts RawDialer or ConnectionPool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 01:15:07 +00:00
codingetandClaude 8ec924adf7 feat(tsconnect): align Conn/TCPListener with @webnet/transport types
Adds @webnet/transport as a dependency. Conn now implements RawTransport
and TCPListener implements RawListener. close() on both returns void
instead of boolean (the raw Go return had no meaning to callers).
Removes the ./redux and ./react sub-exports (split to separate packages).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:32:55 +00:00
codingetandClaude e764d59ddc feat(websocket): split WebSocket into @webnet/websocket package
Moves upgradeWebSocket, connectWebSocket, and WebSocketConnection out of
@webnet/http into a new @webnet/websocket package. The new package depends
on @webnet/http (for ServerRequest/Response and client connection types)
and @webnet/transport (for RawTransport/RawDialer); the reverse dependency
no longer exists so @webnet/http consumers who don't need WebSocket pay
nothing for it.

Sub-exports ./server and ./client mirror the previous @webnet/http/websocket
structure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 13:54:07 +00:00
codingetandClaude 1fbe2c844c feat(transport): extract @webnet/transport package
Pulls RawTransport, RawDialer, RawListener, Reader, Writer, and PairSync
out of @webnet/http into a new protocol-agnostic @webnet/transport package.
Node and loopback transport implementations move there as sub-exports
(./node, ./loopback); @webnet/http re-exports the types to keep existing
internal imports resolving.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 12:45:31 +00:00
codingetandClaude 1f9ffc106b test(http): push branch coverage higher by targeting remaining uncovered paths
Cover previously missing branches:
- common/pair.ts: PairSync.close() idempotence and getA()-after-close
- common/reader.ts: _triggerFinishOk/Ko catch blocks (handler throws),
  BasicBodyReader #chunks pre-buffer path (slice returns multiple arrays),
  ChunkedBodyReader already-closed guard and truncated-chunk sanity check
- common/buffer.ts: WriteBuffer.flush() partial write of a second buffer
- client/objects.ts: method defaults to POST when body is truthy
- client/fetch.ts: rejectConnection called when conn.request() throws
- node/transport.ts: fast-fail read() path with null #readError (socket
  destroyed without error event)
- server/connection.ts: console.warn for missing requireHeaders entry,
  non-TimeoutError parse error propagates out of handle()

Result: 100% functions, 97.81% branches — remaining gaps are dead code
(defensive throws/catches that cannot fire under normal API usage).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:38:38 +00:00
codingetandClaude 0665ff0acd feat: add turborepo for dependency-aware parallel builds
Replaces `npm run build --workspaces --if-present` with `turbo run build`,
which resolves build order from each package's package.json dependencies
and parallelises independent packages automatically.

- turbo.json: declares build task with `^build` topological dependency
- package.json: switches build script to turbo, adds packageManager field
  (required by turbo v2), adds turbo to devDependencies
- .gitignore: add .turbo/ cache directory
- tailscale: bump submodule for tsconfig types-pinning fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 20:15:43 +00:00
codinget 1237206ec2 feat(tsconnect): add redux and react bindings 2026-04-18 19:37:46 +00:00
codingetandClaude 67cb334928 feat(example-app): add webpack + TypeScript + React 19 + SCSS scaffold
Vanilla webpack 5 app with:
- babel-loader + @babel/preset-typescript for TS/TSX compilation
- @pmmmwh/react-refresh-webpack-plugin for React Fast Refresh in dev
- sass-loader + css-loader + style-loader for SCSS support
- HtmlWebpackPlugin templating
- Dev server on port 3000 (npm run dev), production build (npm run build)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 19:52:50 +00:00
codinget 4741a6f267 feat(http): implement http server library inspired by koa 2026-04-12 22:37:56 +00:00
codingetandClaude 3dffc32f7a chore(http): configure for Node.js and add node:test harness
- Switch to module/moduleResolution: NodeNext and verbatimModuleSyntax
- Drop DOM lib, add @types/node, bump target to ES2022
- Add test script (node --experimental-strip-types --test) and typecheck script
- Add mockTransport helper and smoke tests in connection.test.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 19:18:31 +00:00
codingetandClaude 61ec60b46c feat(http): initialize @webnet/http package and wire into test-app
- 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>
2026-04-11 22:19:55 +00:00
codingetandClaude 341ece4f55 chore: add prettier, eslint, dpdm, lint-staged, commitlint; bump TS to 6
- Add Prettier with .prettierrc.json and format all source files
- Add ESLint v9 flat config with @eslint/js + typescript-eslint
- Add dpdm for circular dependency checking
- Add lint-staged (runs eslint + prettier on staged files)
- Add commitlint with conventional commits config
- Add husky pre-commit (lint-staged) and commit-msg (commitlint) hooks
- Bump TypeScript to ^6.0.2 in both packages
- Fix tsconnect moduleResolution: node → bundler (node10 deprecated in TS 6)
- Remove unused IPNConfig import and stale eslint-disable in ipn.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 20:35:51 +00:00
codinget f445f03282 feat(test-app): add vite test app for manual browser testing
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.
2026-04-10 13:58:44 +00:00
codingetandClaude b60522507f feat: initialize npm monorepo with tsconnect wasm package
Set up npm workspaces monorepo with the tailscale/tailscale repo as a
git submodule. Adds packages/tsconnect which builds the tsconnect wasm
via tailscale's Go toolchain and compiles a TypeScript wrapper that
initializes the wasm, captures newIPN from the global scope, removes it
to avoid pollution, and re-exports it with proper module-scoped types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 23:19:25 +00:00