From b8928c14b78cffeee2de0c0a36f1a87fc5e517db Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Sep 2025 17:01:48 -0700 Subject: [PATCH 1/7] Move web-transport-tauri into this repo. --- .envrc | 1 + .github/workflows/pr.yml | 19 +- .gitignore | 7 + Cargo.toml | 1 + flake.nix | 46 ++ justfile | 7 + package.json | 21 + pnpm-lock.yaml | 690 ++++++++++++++++++ pnpm-workspace.yaml | 3 + .../scripts => scripts}/package.ts | 5 +- .../scripts => scripts}/release.ts | 0 tsconfig.json | 15 + web-transport-tauri/.editorconfig | 15 + web-transport-tauri/.gitignore | 17 + web-transport-tauri/.rustfmt.toml | 4 + web-transport-tauri/Cargo.toml | 20 + web-transport-tauri/README.md | 4 + web-transport-tauri/build.rs | 8 + web-transport-tauri/package.json | 38 + .../commands/accept-bidirectional.toml | 13 + .../commands/accept-unidirectional.toml | 13 + .../autogenerated/commands/accept.toml | 13 + .../autogenerated/commands/close-stream.toml | 13 + .../autogenerated/commands/close.toml | 13 + .../autogenerated/commands/closed.toml | 13 + .../autogenerated/commands/connect.toml | 13 + .../commands/create-bidirectional.toml | 13 + .../commands/create-unidirectional.toml | 13 + .../autogenerated/commands/open.toml | 13 + .../autogenerated/commands/ping.toml | 13 + .../autogenerated/commands/read-stream.toml | 13 + .../autogenerated/commands/read.toml | 13 + .../autogenerated/commands/reset-stream.toml | 13 + .../autogenerated/commands/reset.toml | 13 + .../autogenerated/commands/write-stream.toml | 13 + .../autogenerated/commands/write.toml | 13 + .../permissions/autogenerated/reference.md | 466 ++++++++++++ web-transport-tauri/permissions/default.toml | 12 + .../permissions/schemas/schema.json | 510 +++++++++++++ web-transport-tauri/src/commands.rs | 45 ++ web-transport-tauri/src/desktop.rs | 221 ++++++ web-transport-tauri/src/error.rs | 36 + web-transport-tauri/src/index.ts | 14 + web-transport-tauri/src/lib.rs | 57 ++ web-transport-tauri/src/mobile.rs | 31 + web-transport-tauri/src/polyfill.ts | 180 +++++ web-transport-tauri/src/rpc.rs | 126 ++++ web-transport-tauri/src/rpc.ts | 111 +++ web-transport-tauri/tsconfig.json | 7 + web-transport-ws/package.json | 4 +- web-transport-ws/tsconfig.json | 12 +- 51 files changed, 2949 insertions(+), 25 deletions(-) create mode 100644 .envrc create mode 100644 flake.nix create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml rename {web-transport-ws/scripts => scripts}/package.ts (93%) rename {web-transport-ws/scripts => scripts}/release.ts (100%) create mode 100644 tsconfig.json create mode 100644 web-transport-tauri/.editorconfig create mode 100644 web-transport-tauri/.gitignore create mode 100644 web-transport-tauri/.rustfmt.toml create mode 100644 web-transport-tauri/Cargo.toml create mode 100644 web-transport-tauri/README.md create mode 100644 web-transport-tauri/build.rs create mode 100644 web-transport-tauri/package.json create mode 100644 web-transport-tauri/permissions/autogenerated/commands/accept-bidirectional.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/accept-unidirectional.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/accept.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/close-stream.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/close.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/closed.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/connect.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/create-bidirectional.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/create-unidirectional.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/open.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/ping.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/read-stream.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/read.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/reset-stream.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/reset.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/write-stream.toml create mode 100644 web-transport-tauri/permissions/autogenerated/commands/write.toml create mode 100644 web-transport-tauri/permissions/autogenerated/reference.md create mode 100644 web-transport-tauri/permissions/default.toml create mode 100644 web-transport-tauri/permissions/schemas/schema.json create mode 100644 web-transport-tauri/src/commands.rs create mode 100644 web-transport-tauri/src/desktop.rs create mode 100644 web-transport-tauri/src/error.rs create mode 100644 web-transport-tauri/src/index.ts create mode 100644 web-transport-tauri/src/lib.rs create mode 100644 web-transport-tauri/src/mobile.rs create mode 100644 web-transport-tauri/src/polyfill.ts create mode 100644 web-transport-tauri/src/rpc.rs create mode 100644 web-transport-tauri/src/rpc.ts create mode 100644 web-transport-tauri/tsconfig.json diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a68a3fe..a0966e1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,4 +1,4 @@ -name: Rust +name: CI on: pull_request: @@ -14,21 +14,16 @@ jobs: steps: - uses: actions/checkout@v3 - # Install Rust with clippy/rustfmt - - uses: actions-rust-lang/setup-rust-toolchain@v1 + # Install Nix + - uses: cachix/install-nix-action@v27 with: - target: wasm32-unknown-unknown - components: clippy, rustfmt + github_access_token: ${{ secrets.GITHUB_TOKEN }} - # Install Just to run CI scripts - - uses: extractions/setup-just@v3 - - # Cargo binstall is used to install tools faster than compiling them from source. - - uses: cargo-bins/cargo-binstall@main - - run: just setup-tools + # Use our flake to get all development tools (Rust, pnpm, just, cargo tools, etc.) + - run: nix develop --command echo "Development environment ready" # Set RUSTFLAGS - run: echo "RUSTFLAGS=--cfg=web_sys_unstable_apis" >> $GITHUB_ENV # Make sure u guys don't write bad code - - run: just check + - run: nix develop --command just check diff --git a/.gitignore b/.gitignore index a9d37c5..df038dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ target Cargo.lock + +# Node.js +node_modules/ +dist/ + +# Direnv +.direnv/ diff --git a/Cargo.toml b/Cargo.toml index 08726f6..a7401b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "web-transport", "web-transport-proto", "web-transport-quinn", + "web-transport-tauri", "web-transport-trait", "web-transport-wasm", "web-transport-ws", diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..bd31ccd --- /dev/null +++ b/flake.nix @@ -0,0 +1,46 @@ +{ + description = "Web Transport development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; }; + + rustTools = [ + pkgs.rustc + pkgs.cargo + pkgs.rustfmt + pkgs.clippy + pkgs.cargo-shear + pkgs.cargo-sort + pkgs.cargo-upgrades + pkgs.cargo-edit + ]; + + jsTools = [ + pkgs.nodejs_24 + pkgs.pnpm_10 + ]; + + tools = [ + pkgs.just + ]; + in + { + devShells.default = pkgs.mkShell { + packages = rustTools ++ jsTools ++ tools; + }; + } + ); +} \ No newline at end of file diff --git a/justfile b/justfile index df18ad6..0538737 100644 --- a/justfile +++ b/justfile @@ -37,6 +37,10 @@ check: # requires: cargo install cargo-sort cargo sort --workspace --check + # JavaScript/TypeScript checks + pnpm install --frozen-lockfile + pnpm -r run check + # Run any CI tests test: cargo test @@ -59,6 +63,9 @@ fix: # And of course, make sure the formatting is correct. cargo fmt --all + # JavaScript/TypeScript fixes + pnpm install + # Upgrade any tooling upgrade: rustup upgrade diff --git a/package.json b/package.json new file mode 100644 index 0000000..88e7563 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "web-transport-workspace", + "private": true, + "type": "module", + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "pnpm -r build", + "dev": "pnpm -r dev", + "check": "pnpm -r check", + "clean": "pnpm -r clean && rimraf dist node_modules/.cache", + "install-all": "pnpm install --frozen-lockfile" + }, + "devDependencies": { + "typescript": "^5.9.2", + "rimraf": "^6.0.1", + "@types/node": "^24.3.0", + "tsx": "^4.20.5" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..dec63a7 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,690 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.3.1 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + tsx: + specifier: ^4.20.5 + version: 4.20.5 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + + web-transport-tauri: + dependencies: + '@tauri-apps/api': + specifier: 2.8.0 + version: 2.8.0 + buffer: + specifier: ^6.0.3 + version: 6.0.3 + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.3.1 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + tsx: + specifier: ^4.20.5 + version: 4.20.5 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + + web-transport-ws: + devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.3.1 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + tsx: + specifier: ^4.20.5 + version: 4.20.5 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + ws: + specifier: ^8.16.0 + version: 8.18.3 + +packages: + + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@tauri-apps/api@2.8.0': + resolution: {integrity: sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw==} + + '@types/node@24.3.1': + resolution: {integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + + lru-cache@11.2.1: + resolution: {integrity: sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==} + engines: {node: 20 || >=22} + + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rimraf@6.0.1: + resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} + engines: {node: 20 || >=22} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + tsx@4.20.5: + resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + +snapshots: + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-x64@0.25.9': + optional: true + + '@esbuild/linux-arm64@0.25.9': + optional: true + + '@esbuild/linux-arm@0.25.9': + optional: true + + '@esbuild/linux-ia32@0.25.9': + optional: true + + '@esbuild/linux-loong64@0.25.9': + optional: true + + '@esbuild/linux-mips64el@0.25.9': + optional: true + + '@esbuild/linux-ppc64@0.25.9': + optional: true + + '@esbuild/linux-riscv64@0.25.9': + optional: true + + '@esbuild/linux-s390x@0.25.9': + optional: true + + '@esbuild/linux-x64@0.25.9': + optional: true + + '@esbuild/netbsd-arm64@0.25.9': + optional: true + + '@esbuild/netbsd-x64@0.25.9': + optional: true + + '@esbuild/openbsd-arm64@0.25.9': + optional: true + + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + + '@esbuild/sunos-x64@0.25.9': + optional: true + + '@esbuild/win32-arm64@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@tauri-apps/api@2.8.0': {} + + '@types/node@24.3.1': + dependencies: + undici-types: 7.10.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + base64-js@1.5.1: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + ieee754@1.2.1: {} + + is-fullwidth-code-point@3.0.0: {} + + isexe@2.0.0: {} + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + lru-cache@11.2.1: {} + + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minipass@7.1.2: {} + + package-json-from-dist@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@2.0.0: + dependencies: + lru-cache: 11.2.1 + minipass: 7.1.2 + + resolve-pkg-maps@1.0.0: {} + + rimraf@6.0.1: + dependencies: + glob: 11.0.3 + package-json-from-dist: 1.0.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + tsx@4.20.5: + dependencies: + esbuild: 0.25.9 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.2: {} + + undici-types@7.10.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + ws@8.18.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..e315c07 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "web-transport-ws" + - "web-transport-tauri" \ No newline at end of file diff --git a/web-transport-ws/scripts/package.ts b/scripts/package.ts similarity index 93% rename from web-transport-ws/scripts/package.ts rename to scripts/package.ts index b88fd01..6d49f21 100644 --- a/web-transport-ws/scripts/package.ts +++ b/scripts/package.ts @@ -2,7 +2,7 @@ // This creates a dist/ folder with the correct paths and dependencies for publishing // Split from release.ts to allow building packages without publishing -import { copyFileSync, readFileSync, writeFileSync } from "node:fs"; +import { copyFileSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; console.log("✍️ Rewriting package.json..."); @@ -60,6 +60,9 @@ if (pkg.dependencies) { pkg.devDependencies = undefined; pkg.scripts = undefined; +// Ensure dist directory exists +mkdirSync("dist", { recursive: true }); + // Write the rewritten package.json writeFileSync("dist/package.json", JSON.stringify(pkg, null, 2)); diff --git a/web-transport-ws/scripts/release.ts b/scripts/release.ts similarity index 100% rename from web-transport-ws/scripts/release.ts rename to scripts/release.ts diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8947e85 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "lib": ["esnext", "dom", "dom.iterable"], + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "declaration": true, + "isolatedModules": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} \ No newline at end of file diff --git a/web-transport-tauri/.editorconfig b/web-transport-tauri/.editorconfig new file mode 100644 index 0000000..79c0e4e --- /dev/null +++ b/web-transport-tauri/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = tab +indent_size = 4 +max_line_length = 120 + +# YAML doesn't support hard tabs 🙃 +[**.yml] +indent_style = space +indent_size = 2 diff --git a/web-transport-tauri/.gitignore b/web-transport-tauri/.gitignore new file mode 100644 index 0000000..50d8e32 --- /dev/null +++ b/web-transport-tauri/.gitignore @@ -0,0 +1,17 @@ +/.vs +.DS_Store +.Thumbs.db +*.sublime* +.idea/ +debug.log +package-lock.json +.vscode/settings.json +yarn.lock + +/.tauri +/target +Cargo.lock +node_modules/ + +dist-js +dist diff --git a/web-transport-tauri/.rustfmt.toml b/web-transport-tauri/.rustfmt.toml new file mode 100644 index 0000000..b3c42a2 --- /dev/null +++ b/web-transport-tauri/.rustfmt.toml @@ -0,0 +1,4 @@ +# i die on this hill +hard_tabs = true + +max_width = 120 diff --git a/web-transport-tauri/Cargo.toml b/web-transport-tauri/Cargo.toml new file mode 100644 index 0000000..a5674d8 --- /dev/null +++ b/web-transport-tauri/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "web-transport-tauri" +version = "0.1.0" +authors = ["Luke Curley "] +description = "A polyfill for WebTransport when using Tauri." +edition = "2021" +rust-version = "1.77.2" +exclude = ["/examples", "/dist-js", "/guest-js", "/node_modules"] +links = "web-transport-tauri" + +[dependencies] +bytes = { version = "1", features = ["serde"] } +hex = "0.4.3" +serde = "1.0" +tauri = { version = "2.5.0" } +thiserror = "2" +web-transport = "0.9" + +[build-dependencies] +tauri-plugin = { version = "2.2.0", features = ["build"] } diff --git a/web-transport-tauri/README.md b/web-transport-tauri/README.md new file mode 100644 index 0000000..f08fcba --- /dev/null +++ b/web-transport-tauri/README.md @@ -0,0 +1,4 @@ +# web-transport-tauri +A polyfill for WebTransport when using Tauri. + +The Javascript communicates with the Rust client (via a gross JSON RPC) to run WebTransport natively. diff --git a/web-transport-tauri/build.rs b/web-transport-tauri/build.rs new file mode 100644 index 0000000..4dc94f8 --- /dev/null +++ b/web-transport-tauri/build.rs @@ -0,0 +1,8 @@ +const COMMANDS: &[&str] = &["connect", "close", "closed", "accept", "open", "read", "write", "reset"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .android_path("android") + .ios_path("ios") + .build(); +} diff --git a/web-transport-tauri/package.json b/web-transport-tauri/package.json new file mode 100644 index 0000000..985ba2e --- /dev/null +++ b/web-transport-tauri/package.json @@ -0,0 +1,38 @@ +{ + "name": "@kixelated/web-transport-tauri", + "author": "Luke Curley ", + "version": "0.1.0", + "description": "WebTransport polyfill using Tauri", + "type": "module", + "license": "(MIT OR Apache-2.0)", + "repository": "github:kixelated/web-transport", + "exports": { + ".": "./src/index.ts" + }, + "types": "./src/index.ts", + "files": [ + "./src" + ], + "scripts": { + "build": "rimraf dist && tsc && tsx ../scripts/package.ts", + "dev": "tsc --watch", + "check": "tsc --noEmit" + }, + "dependencies": { + "@tauri-apps/api": "2.8.0", + "buffer": "^6.0.3" + }, + "devDependencies": { + "typescript": "^5.9.2", + "rimraf": "^6.0.1", + "@types/node": "^24.3.0", + "tsx": "^4.20.5" + }, + "keywords": [ + "webtransport", + "tauri", + "polyfill", + "quic", + "streams" + ] +} diff --git a/web-transport-tauri/permissions/autogenerated/commands/accept-bidirectional.toml b/web-transport-tauri/permissions/autogenerated/commands/accept-bidirectional.toml new file mode 100644 index 0000000..4a3e613 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/accept-bidirectional.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-accept-bidirectional" +description = "Enables the accept-bidirectional command without any pre-configured scope." +commands.allow = ["accept-bidirectional"] + +[[permission]] +identifier = "deny-accept-bidirectional" +description = "Denies the accept-bidirectional command without any pre-configured scope." +commands.deny = ["accept-bidirectional"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/accept-unidirectional.toml b/web-transport-tauri/permissions/autogenerated/commands/accept-unidirectional.toml new file mode 100644 index 0000000..71ae072 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/accept-unidirectional.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-accept-unidirectional" +description = "Enables the accept-unidirectional command without any pre-configured scope." +commands.allow = ["accept-unidirectional"] + +[[permission]] +identifier = "deny-accept-unidirectional" +description = "Denies the accept-unidirectional command without any pre-configured scope." +commands.deny = ["accept-unidirectional"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/accept.toml b/web-transport-tauri/permissions/autogenerated/commands/accept.toml new file mode 100644 index 0000000..80d6cfb --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/accept.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-accept" +description = "Enables the accept command without any pre-configured scope." +commands.allow = ["accept"] + +[[permission]] +identifier = "deny-accept" +description = "Denies the accept command without any pre-configured scope." +commands.deny = ["accept"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/close-stream.toml b/web-transport-tauri/permissions/autogenerated/commands/close-stream.toml new file mode 100644 index 0000000..c56bccd --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/close-stream.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-close-stream" +description = "Enables the close-stream command without any pre-configured scope." +commands.allow = ["close-stream"] + +[[permission]] +identifier = "deny-close-stream" +description = "Denies the close-stream command without any pre-configured scope." +commands.deny = ["close-stream"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/close.toml b/web-transport-tauri/permissions/autogenerated/commands/close.toml new file mode 100644 index 0000000..fad12d1 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/close.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-close" +description = "Enables the close command without any pre-configured scope." +commands.allow = ["close"] + +[[permission]] +identifier = "deny-close" +description = "Denies the close command without any pre-configured scope." +commands.deny = ["close"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/closed.toml b/web-transport-tauri/permissions/autogenerated/commands/closed.toml new file mode 100644 index 0000000..09c9187 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/closed.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-closed" +description = "Enables the closed command without any pre-configured scope." +commands.allow = ["closed"] + +[[permission]] +identifier = "deny-closed" +description = "Denies the closed command without any pre-configured scope." +commands.deny = ["closed"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/connect.toml b/web-transport-tauri/permissions/autogenerated/commands/connect.toml new file mode 100644 index 0000000..49ce9ad --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/connect.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-connect" +description = "Enables the connect command without any pre-configured scope." +commands.allow = ["connect"] + +[[permission]] +identifier = "deny-connect" +description = "Denies the connect command without any pre-configured scope." +commands.deny = ["connect"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/create-bidirectional.toml b/web-transport-tauri/permissions/autogenerated/commands/create-bidirectional.toml new file mode 100644 index 0000000..c07ae9e --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/create-bidirectional.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-create-bidirectional" +description = "Enables the create-bidirectional command without any pre-configured scope." +commands.allow = ["create-bidirectional"] + +[[permission]] +identifier = "deny-create-bidirectional" +description = "Denies the create-bidirectional command without any pre-configured scope." +commands.deny = ["create-bidirectional"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/create-unidirectional.toml b/web-transport-tauri/permissions/autogenerated/commands/create-unidirectional.toml new file mode 100644 index 0000000..1880494 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/create-unidirectional.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-create-unidirectional" +description = "Enables the create-unidirectional command without any pre-configured scope." +commands.allow = ["create-unidirectional"] + +[[permission]] +identifier = "deny-create-unidirectional" +description = "Denies the create-unidirectional command without any pre-configured scope." +commands.deny = ["create-unidirectional"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/open.toml b/web-transport-tauri/permissions/autogenerated/commands/open.toml new file mode 100644 index 0000000..4ea6dff --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/open.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open" +description = "Enables the open command without any pre-configured scope." +commands.allow = ["open"] + +[[permission]] +identifier = "deny-open" +description = "Denies the open command without any pre-configured scope." +commands.deny = ["open"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/ping.toml b/web-transport-tauri/permissions/autogenerated/commands/ping.toml new file mode 100644 index 0000000..1d13588 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/ping.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-ping" +description = "Enables the ping command without any pre-configured scope." +commands.allow = ["ping"] + +[[permission]] +identifier = "deny-ping" +description = "Denies the ping command without any pre-configured scope." +commands.deny = ["ping"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/read-stream.toml b/web-transport-tauri/permissions/autogenerated/commands/read-stream.toml new file mode 100644 index 0000000..071ff98 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/read-stream.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read-stream" +description = "Enables the read-stream command without any pre-configured scope." +commands.allow = ["read-stream"] + +[[permission]] +identifier = "deny-read-stream" +description = "Denies the read-stream command without any pre-configured scope." +commands.deny = ["read-stream"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/read.toml b/web-transport-tauri/permissions/autogenerated/commands/read.toml new file mode 100644 index 0000000..20fa10c --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/read.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read" +description = "Enables the read command without any pre-configured scope." +commands.allow = ["read"] + +[[permission]] +identifier = "deny-read" +description = "Denies the read command without any pre-configured scope." +commands.deny = ["read"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/reset-stream.toml b/web-transport-tauri/permissions/autogenerated/commands/reset-stream.toml new file mode 100644 index 0000000..c0abd8c --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/reset-stream.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-reset-stream" +description = "Enables the reset-stream command without any pre-configured scope." +commands.allow = ["reset-stream"] + +[[permission]] +identifier = "deny-reset-stream" +description = "Denies the reset-stream command without any pre-configured scope." +commands.deny = ["reset-stream"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/reset.toml b/web-transport-tauri/permissions/autogenerated/commands/reset.toml new file mode 100644 index 0000000..c32086d --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/reset.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-reset" +description = "Enables the reset command without any pre-configured scope." +commands.allow = ["reset"] + +[[permission]] +identifier = "deny-reset" +description = "Denies the reset command without any pre-configured scope." +commands.deny = ["reset"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/write-stream.toml b/web-transport-tauri/permissions/autogenerated/commands/write-stream.toml new file mode 100644 index 0000000..ea0a01a --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/write-stream.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write-stream" +description = "Enables the write-stream command without any pre-configured scope." +commands.allow = ["write-stream"] + +[[permission]] +identifier = "deny-write-stream" +description = "Denies the write-stream command without any pre-configured scope." +commands.deny = ["write-stream"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/write.toml b/web-transport-tauri/permissions/autogenerated/commands/write.toml new file mode 100644 index 0000000..73d1d38 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/commands/write.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write" +description = "Enables the write command without any pre-configured scope." +commands.allow = ["write"] + +[[permission]] +identifier = "deny-write" +description = "Denies the write command without any pre-configured scope." +commands.deny = ["write"] diff --git a/web-transport-tauri/permissions/autogenerated/reference.md b/web-transport-tauri/permissions/autogenerated/reference.md new file mode 100644 index 0000000..2bf2bc3 --- /dev/null +++ b/web-transport-tauri/permissions/autogenerated/reference.md @@ -0,0 +1,466 @@ +## Default Permission + +Default permissions for the plugin + +#### This default permission set includes the following: + +- `allow-connect` +- `allow-close` +- `allow-closed` +- `allow-accept` +- `allow-open` +- `allow-read` +- `allow-write` +- `allow-reset` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`web-transport-tauri:allow-accept-bidirectional` + + + +Enables the accept-bidirectional command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-accept-bidirectional` + + + +Denies the accept-bidirectional command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-accept-unidirectional` + + + +Enables the accept-unidirectional command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-accept-unidirectional` + + + +Denies the accept-unidirectional command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-accept` + + + +Enables the accept command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-accept` + + + +Denies the accept command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-close-stream` + + + +Enables the close-stream command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-close-stream` + + + +Denies the close-stream command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-close` + + + +Enables the close command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-close` + + + +Denies the close command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-closed` + + + +Enables the closed command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-closed` + + + +Denies the closed command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-connect` + + + +Enables the connect command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-connect` + + + +Denies the connect command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-create-bidirectional` + + + +Enables the create-bidirectional command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-create-bidirectional` + + + +Denies the create-bidirectional command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-create-unidirectional` + + + +Enables the create-unidirectional command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-create-unidirectional` + + + +Denies the create-unidirectional command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-open` + + + +Enables the open command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-open` + + + +Denies the open command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-ping` + + + +Enables the ping command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-ping` + + + +Denies the ping command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-read-stream` + + + +Enables the read-stream command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-read-stream` + + + +Denies the read-stream command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-read` + + + +Enables the read command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-read` + + + +Denies the read command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-reset-stream` + + + +Enables the reset-stream command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-reset-stream` + + + +Denies the reset-stream command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-reset` + + + +Enables the reset command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-reset` + + + +Denies the reset command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-write-stream` + + + +Enables the write-stream command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-write-stream` + + + +Denies the write-stream command without any pre-configured scope. + +
+ +`web-transport-tauri:allow-write` + + + +Enables the write command without any pre-configured scope. + +
+ +`web-transport-tauri:deny-write` + + + +Denies the write command without any pre-configured scope. + +
diff --git a/web-transport-tauri/permissions/default.toml b/web-transport-tauri/permissions/default.toml new file mode 100644 index 0000000..7eb52ea --- /dev/null +++ b/web-transport-tauri/permissions/default.toml @@ -0,0 +1,12 @@ +[default] +description = "Default permissions for the plugin" +permissions = [ + "allow-connect", + "allow-close", + "allow-closed", + "allow-accept", + "allow-open", + "allow-read", + "allow-write", + "allow-reset", +] diff --git a/web-transport-tauri/permissions/schemas/schema.json b/web-transport-tauri/permissions/schemas/schema.json new file mode 100644 index 0000000..b6c49f0 --- /dev/null +++ b/web-transport-tauri/permissions/schemas/schema.json @@ -0,0 +1,510 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the accept-bidirectional command without any pre-configured scope.", + "type": "string", + "const": "allow-accept-bidirectional", + "markdownDescription": "Enables the accept-bidirectional command without any pre-configured scope." + }, + { + "description": "Denies the accept-bidirectional command without any pre-configured scope.", + "type": "string", + "const": "deny-accept-bidirectional", + "markdownDescription": "Denies the accept-bidirectional command without any pre-configured scope." + }, + { + "description": "Enables the accept-unidirectional command without any pre-configured scope.", + "type": "string", + "const": "allow-accept-unidirectional", + "markdownDescription": "Enables the accept-unidirectional command without any pre-configured scope." + }, + { + "description": "Denies the accept-unidirectional command without any pre-configured scope.", + "type": "string", + "const": "deny-accept-unidirectional", + "markdownDescription": "Denies the accept-unidirectional command without any pre-configured scope." + }, + { + "description": "Enables the accept command without any pre-configured scope.", + "type": "string", + "const": "allow-accept", + "markdownDescription": "Enables the accept command without any pre-configured scope." + }, + { + "description": "Denies the accept command without any pre-configured scope.", + "type": "string", + "const": "deny-accept", + "markdownDescription": "Denies the accept command without any pre-configured scope." + }, + { + "description": "Enables the close-stream command without any pre-configured scope.", + "type": "string", + "const": "allow-close-stream", + "markdownDescription": "Enables the close-stream command without any pre-configured scope." + }, + { + "description": "Denies the close-stream command without any pre-configured scope.", + "type": "string", + "const": "deny-close-stream", + "markdownDescription": "Denies the close-stream command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Enables the closed command without any pre-configured scope.", + "type": "string", + "const": "allow-closed", + "markdownDescription": "Enables the closed command without any pre-configured scope." + }, + { + "description": "Denies the closed command without any pre-configured scope.", + "type": "string", + "const": "deny-closed", + "markdownDescription": "Denies the closed command without any pre-configured scope." + }, + { + "description": "Enables the connect command without any pre-configured scope.", + "type": "string", + "const": "allow-connect", + "markdownDescription": "Enables the connect command without any pre-configured scope." + }, + { + "description": "Denies the connect command without any pre-configured scope.", + "type": "string", + "const": "deny-connect", + "markdownDescription": "Denies the connect command without any pre-configured scope." + }, + { + "description": "Enables the create-bidirectional command without any pre-configured scope.", + "type": "string", + "const": "allow-create-bidirectional", + "markdownDescription": "Enables the create-bidirectional command without any pre-configured scope." + }, + { + "description": "Denies the create-bidirectional command without any pre-configured scope.", + "type": "string", + "const": "deny-create-bidirectional", + "markdownDescription": "Denies the create-bidirectional command without any pre-configured scope." + }, + { + "description": "Enables the create-unidirectional command without any pre-configured scope.", + "type": "string", + "const": "allow-create-unidirectional", + "markdownDescription": "Enables the create-unidirectional command without any pre-configured scope." + }, + { + "description": "Denies the create-unidirectional command without any pre-configured scope.", + "type": "string", + "const": "deny-create-unidirectional", + "markdownDescription": "Denies the create-unidirectional command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Enables the ping command without any pre-configured scope.", + "type": "string", + "const": "allow-ping", + "markdownDescription": "Enables the ping command without any pre-configured scope." + }, + { + "description": "Denies the ping command without any pre-configured scope.", + "type": "string", + "const": "deny-ping", + "markdownDescription": "Denies the ping command without any pre-configured scope." + }, + { + "description": "Enables the read-stream command without any pre-configured scope.", + "type": "string", + "const": "allow-read-stream", + "markdownDescription": "Enables the read-stream command without any pre-configured scope." + }, + { + "description": "Denies the read-stream command without any pre-configured scope.", + "type": "string", + "const": "deny-read-stream", + "markdownDescription": "Denies the read-stream command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Enables the reset-stream command without any pre-configured scope.", + "type": "string", + "const": "allow-reset-stream", + "markdownDescription": "Enables the reset-stream command without any pre-configured scope." + }, + { + "description": "Denies the reset-stream command without any pre-configured scope.", + "type": "string", + "const": "deny-reset-stream", + "markdownDescription": "Denies the reset-stream command without any pre-configured scope." + }, + { + "description": "Enables the reset command without any pre-configured scope.", + "type": "string", + "const": "allow-reset", + "markdownDescription": "Enables the reset command without any pre-configured scope." + }, + { + "description": "Denies the reset command without any pre-configured scope.", + "type": "string", + "const": "deny-reset", + "markdownDescription": "Denies the reset command without any pre-configured scope." + }, + { + "description": "Enables the write-stream command without any pre-configured scope.", + "type": "string", + "const": "allow-write-stream", + "markdownDescription": "Enables the write-stream command without any pre-configured scope." + }, + { + "description": "Denies the write-stream command without any pre-configured scope.", + "type": "string", + "const": "deny-write-stream", + "markdownDescription": "Denies the write-stream command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-connect`\n- `allow-close`\n- `allow-closed`\n- `allow-accept`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-reset`", + "type": "string", + "const": "default", + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-connect`\n- `allow-close`\n- `allow-closed`\n- `allow-accept`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-reset`" + } + ] + } + } +} \ No newline at end of file diff --git a/web-transport-tauri/src/commands.rs b/web-transport-tauri/src/commands.rs new file mode 100644 index 0000000..30cdc77 --- /dev/null +++ b/web-transport-tauri/src/commands.rs @@ -0,0 +1,45 @@ +use tauri::{command, AppHandle, Runtime}; + +use crate::rpc::*; +use crate::Result; +use crate::WebTransportExt; + +#[command] +pub(crate) async fn connect(app: AppHandle, payload: ConnectRequest) -> Result { + app.web_transport().connect(payload).await +} + +#[command] +pub(crate) fn close(app: AppHandle, payload: CloseRequest) -> Result { + app.web_transport().close(payload) +} + +#[command] +pub(crate) async fn closed(app: AppHandle, payload: ClosedRequest) -> Result { + app.web_transport().closed(payload).await +} + +#[command] +pub(crate) async fn accept(app: AppHandle, payload: AcceptRequest) -> Result { + app.web_transport().accept(payload).await +} + +#[command] +pub(crate) async fn open(app: AppHandle, payload: OpenRequest) -> Result { + app.web_transport().open(payload).await +} + +#[command] +pub(crate) async fn read(app: AppHandle, payload: ReadRequest) -> Result { + app.web_transport().read(payload).await +} + +#[command] +pub(crate) async fn write(app: AppHandle, payload: WriteRequest) -> Result { + app.web_transport().write(payload).await +} + +#[command] +pub(crate) async fn reset(app: AppHandle, payload: ResetRequest) -> Result { + app.web_transport().reset(payload) +} diff --git a/web-transport-tauri/src/desktop.rs b/web-transport-tauri/src/desktop.rs new file mode 100644 index 0000000..0316e2a --- /dev/null +++ b/web-transport-tauri/src/desktop.rs @@ -0,0 +1,221 @@ +use std::{ + collections::{hash_map, HashMap}, + sync::{ + atomic::{self, AtomicUsize}, + Arc, Mutex, + }, +}; + +use bytes::Bytes; +use serde::de::DeserializeOwned; +use tauri::{plugin::PluginApi, AppHandle, Runtime}; + +use crate::rpc::*; + +pub fn init( + _app: &AppHandle, + _api: PluginApi, +) -> crate::Result { + Ok(WebTransport { + sessions: Default::default(), + next_session: Default::default(), + }) +} + +/// Access to the web-transport APIs. +pub struct WebTransport { + sessions: Arc>>, + next_session: AtomicUsize, +} + +impl WebTransport { + pub async fn connect(&self, payload: ConnectRequest) -> crate::Result { + let builder = web_transport::ClientBuilder::new(); + + let builder = builder.with_congestion_control(match payload.congestion_control { + Some(CongestionControl::LowLatency) => web_transport::CongestionControl::LowLatency, + Some(CongestionControl::Throughput) => web_transport::CongestionControl::Throughput, + _ => web_transport::CongestionControl::Default, + }); + + let client = match payload.server_certificate_hashes { + Some(hashes) => { + let hashes = hashes.iter().map(hex::decode).collect::, _>>()?; + builder.with_server_certificate_hashes(hashes) + } + None => builder.with_system_roots(), + }?; + + let transport = client.connect(payload.url).await?; + let session = self.next_session.fetch_add(1, atomic::Ordering::Relaxed); + + match self.sessions.lock().unwrap().entry(session) { + hash_map::Entry::Vacant(e) => e.insert(Session::new(transport)), + hash_map::Entry::Occupied(_) => return Err(crate::Error::DuplicateSession), + }; + + Ok(ConnectResponse { session }) + } + + fn get(&self, id: usize) -> crate::Result { + self.sessions + .lock() + .unwrap() + .get(&id) + .cloned() + .ok_or(crate::Error::NoSession) + } + + pub fn close(&self, payload: CloseRequest) -> crate::Result { + let mut session = self + .sessions + .lock() + .unwrap() + .remove(&payload.session) + .ok_or(crate::Error::NoSession)?; + session + .inner + .close(payload.code, payload.reason.as_deref().unwrap_or("")); + + Ok(CloseResponse {}) + } + + pub async fn closed(&self, payload: ClosedRequest) -> crate::Result { + let session = self.get(payload.session)?; + session.inner.closed().await; + + Ok(ClosedResponse {}) + } + + pub async fn accept(&self, payload: AcceptRequest) -> crate::Result { + let mut session = self.get(payload.session)?; + let stream = if payload.bidirectional { + session.accept_bi().await? + } else { + session.accept_uni().await? + }; + Ok(AcceptResponse { stream }) + } + + pub async fn open(&self, payload: OpenRequest) -> crate::Result { + let mut session = self.get(payload.session)?; + let stream = if payload.bidirectional { + session.create_bi(payload.send_order).await? + } else { + session.create_uni(payload.send_order).await? + }; + Ok(OpenResponse { stream }) + } + + pub async fn read(&self, payload: ReadRequest) -> crate::Result { + let mut session = self.get(payload.session)?; + let data = session.recv(payload.stream).await?; + Ok(ReadResponse { data }) + } + + pub async fn write(&self, payload: WriteRequest) -> crate::Result { + let mut session = self.get(payload.session)?; + session.send(payload.stream, payload.data).await?; + Ok(WriteResponse {}) + } + + pub fn reset(&self, payload: ResetRequest) -> crate::Result { + let mut session = self.get(payload.session)?; + // TODO support reset in the middle of a read. + session.reset(payload.stream, payload.code)?; + Ok(ResetResponse {}) + } +} + +#[derive(Clone)] +pub struct Session { + pub inner: web_transport::Session, + send: Arc>>, + recv: Arc>>, + streams: Arc, +} + +impl Session { + pub fn new(inner: web_transport::Session) -> Self { + Self { + inner, + send: Default::default(), + recv: Default::default(), + streams: Default::default(), + } + } + + pub async fn accept_bi(&mut self) -> crate::Result { + let (send, recv) = self.inner.accept_bi().await?; + let id = self.streams.fetch_add(1, atomic::Ordering::Relaxed); + self.send.lock().unwrap().insert(id, send); + self.recv.lock().unwrap().insert(id, recv); + Ok(id) + } + + pub async fn accept_uni(&mut self) -> crate::Result { + let recv = self.inner.accept_uni().await?; + let id = self.streams.fetch_add(1, atomic::Ordering::Relaxed); + self.recv.lock().unwrap().insert(id, recv); + Ok(id) + } + + pub async fn create_bi(&mut self, send_order: Option) -> crate::Result { + let (mut send, recv) = self.inner.open_bi().await?; + if let Some(send_order) = send_order { + send.set_priority(send_order); + } + let id = self.streams.fetch_add(1, atomic::Ordering::Relaxed); + self.send.lock().unwrap().insert(id, send); + self.recv.lock().unwrap().insert(id, recv); + Ok(id) + } + + pub async fn create_uni(&mut self, send_order: Option) -> crate::Result { + let mut send = self.inner.open_uni().await?; + if let Some(send_order) = send_order { + send.set_priority(send_order); + } + let id = self.streams.fetch_add(1, atomic::Ordering::Relaxed); + self.send.lock().unwrap().insert(id, send); + Ok(id) + } + + pub async fn recv(&mut self, id: usize) -> crate::Result> { + let mut recv = self.recv.lock().unwrap().remove(&id).ok_or(crate::Error::NoStream)?; + + let buf = recv.read(usize::MAX).await?; + if buf.is_some() { + self.recv.lock().unwrap().insert(id, recv); + } + + Ok(buf) + } + + pub async fn send(&mut self, id: usize, mut data: Bytes) -> crate::Result<()> { + let mut send = self.send.lock().unwrap().remove(&id).ok_or(crate::Error::NoStream)?; + if data.is_empty() { + send.finish()?; + return Ok(()); + } + + while !data.is_empty() { + send.write_buf(&mut data).await?; + } + + self.send.lock().unwrap().insert(id, send); + Ok(()) + } + + pub fn reset(&mut self, id: usize, code: u32) -> crate::Result<()> { + if let Some(mut send) = self.send.lock().unwrap().remove(&id) { + send.reset(code); + } + + if let Some(mut recv) = self.recv.lock().unwrap().remove(&id) { + recv.stop(code); + } + + Ok(()) + } +} diff --git a/web-transport-tauri/src/error.rs b/web-transport-tauri/src/error.rs new file mode 100644 index 0000000..1f2f3a4 --- /dev/null +++ b/web-transport-tauri/src/error.rs @@ -0,0 +1,36 @@ +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + + #[error(transparent)] + WebTransport(#[from] web_transport::Error), + + #[error("duplicate session")] + DuplicateSession, + + #[error("no session found")] + NoSession, + + #[error("stream not found")] + NoStream, + + #[error("invalid hex")] + InvalidHex(#[from] hex::FromHexError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/web-transport-tauri/src/index.ts b/web-transport-tauri/src/index.ts new file mode 100644 index 0000000..db05045 --- /dev/null +++ b/web-transport-tauri/src/index.ts @@ -0,0 +1,14 @@ +import { isTauri } from "@tauri-apps/api/core"; +import WebTransportTauri from "./polyfill.ts"; + +// Install polyfill if WebTransport is not available, returning true if installed +export function install(): boolean { + if ("WebTransport" in globalThis) return false; + if (!isTauri()) return false; + + // biome-ignore lint/suspicious/noExplicitAny: polyfill + (globalThis as any).WebTransport = WebTransportTauri; + return true; +} + +export default WebTransportTauri; diff --git a/web-transport-tauri/src/lib.rs b/web-transport-tauri/src/lib.rs new file mode 100644 index 0000000..118da46 --- /dev/null +++ b/web-transport-tauri/src/lib.rs @@ -0,0 +1,57 @@ +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use rpc::*; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; +mod rpc; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +use desktop::WebTransport; +#[cfg(mobile)] +use mobile::WebTransport; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the web-transport APIs. +pub trait WebTransportExt { + fn web_transport(&self) -> &WebTransport; +} + +impl> crate::WebTransportExt for T { + fn web_transport(&self) -> &WebTransport { + self.state::().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("web-transport") + .invoke_handler(tauri::generate_handler![ + commands::connect, + commands::close, + commands::closed, + commands::accept, + commands::open, + commands::read, + commands::write, + commands::reset, + ]) + .setup(|app, api| { + #[cfg(mobile)] + let web_transport = mobile::init(app, api)?; + #[cfg(desktop)] + let web_transport = desktop::init(app, api)?; + app.manage(web_transport); + Ok(()) + }) + .build() +} diff --git a/web-transport-tauri/src/mobile.rs b/web-transport-tauri/src/mobile.rs new file mode 100644 index 0000000..ac08d8b --- /dev/null +++ b/web-transport-tauri/src/mobile.rs @@ -0,0 +1,31 @@ +use serde::de::DeserializeOwned; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_web_transport); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin("", "ExamplePlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_web_transport)?; + Ok(WebTransport(handle)) +} + +/// Access to the web-transport APIs. +pub struct WebTransport(PluginHandle); + +impl WebTransport { + pub fn ping(&self, payload: PingRequest) -> crate::Result { + self.0.run_mobile_plugin("ping", payload).map_err(Into::into) + } +} diff --git a/web-transport-tauri/src/polyfill.ts b/web-transport-tauri/src/polyfill.ts new file mode 100644 index 0000000..abecfc3 --- /dev/null +++ b/web-transport-tauri/src/polyfill.ts @@ -0,0 +1,180 @@ +import { Buffer } from "buffer"; +import * as rpc from "./rpc.ts"; + +export default class WebTransportTauri implements WebTransport { + readonly closed: Promise; + readonly datagrams = new WebTransportDatagramDuplexStreamTauri(); + readonly incomingBidirectionalStreams: ReadableStream; + readonly incomingUnidirectionalStreams: ReadableStream; + readonly ready: Promise; + + #url: URL; + #session: Promise; + + constructor(url: string | URL, options?: WebTransportOptions) { + this.#url = url instanceof URL ? url : new URL(url); + + // Ignore options.allowPooling and options.requireUnreliable for now. + + const hashes = options?.serverCertificateHashes?.map((hash) => { + if (hash.algorithm !== "sha-256") { + throw new Error("Only sha-256 is currently supported"); + } + + if (hash.value === undefined) { + throw new Error("Server certificate hash is required"); + } + + return Buffer.from(hash.value as ArrayBuffer).toString("hex"); + }); + + // Connect via Tauri backend + this.#session = rpc + .connect({ + url: this.#url.toString(), + congestionControl: options?.congestionControl, + serverCertificateHashes: hashes, + }) + .then((response) => response.session); + + this.ready = this.#session.then(() => {}); + + this.closed = this.#session.then((session) => + rpc.closed({ + session, + }), + ); + + this.incomingBidirectionalStreams = + new ReadableStream({ + pull: async (controller) => { + const accept = await rpc.accept({ + session: await this.#session, + bidirectional: true, + }); + + const readable = this.#createReadable(accept.stream); + const writable = this.#createWritable(accept.stream); + + controller.enqueue({ readable, writable }); + }, + }); + + this.incomingUnidirectionalStreams = new ReadableStream< + ReadableStream + >({ + pull: async (controller) => { + const accept = await rpc.accept({ + session: await this.#session, + bidirectional: false, + }); + + const readable = this.#createReadable(accept.stream); + controller.enqueue(readable); + }, + }); + } + + #createReadable(stream: number): ReadableStream { + return new ReadableStream({ + pull: async (controller) => { + const response = await rpc.read({ + session: await this.#session, + stream, + }); + + if (response.data) { + controller.enqueue(response.data); + } else { + controller.close(); + } + }, + cancel: async () => { + await rpc.reset({ + session: await this.#session, + stream, + code: 0, + }); + }, + }); + } + + #createWritable(stream: number): WritableStream { + return new WritableStream({ + write: async (chunk) => { + await rpc.write({ + session: await this.#session, + stream, + data: chunk, + }); + }, + close: async () => { + await rpc.write({ + session: await this.#session, + stream, + data: null, + }); + }, + }); + } + + close(closeInfo?: WebTransportCloseInfo): void { + this.#session.then((session) => + rpc.close({ + session, + code: closeInfo?.closeCode ?? 0, + reason: closeInfo?.reason ?? null, + }), + ); + } + + async createBidirectionalStream( + options?: WebTransportSendStreamOptions, + ): Promise { + const response = await rpc.open({ + session: await this.#session, + bidirectional: true, + sendOrder: options?.sendOrder, + }); + + return { + readable: this.#createReadable(response.stream), + writable: this.#createWritable(response.stream), + }; + } + + async createUnidirectionalStream( + options?: WebTransportSendStreamOptions, + ): Promise { + const response = await rpc.open({ + session: await this.#session, + bidirectional: false, + sendOrder: options?.sendOrder, + }); + + return this.#createWritable(response.stream); + } +} + +// TODO Implement this +export class WebTransportDatagramDuplexStreamTauri + implements WebTransportDatagramDuplexStream +{ + incomingHighWaterMark: number; + incomingMaxAge: number | null; + readonly maxDatagramSize: number; + outgoingHighWaterMark: number; + outgoingMaxAge: number | null; + readonly readable: ReadableStream; + readonly writable: WritableStream; + + constructor() { + this.incomingHighWaterMark = 1024; + this.incomingMaxAge = null; + this.maxDatagramSize = 1200; + this.outgoingHighWaterMark = 1024; + this.outgoingMaxAge = null; + this.readable = new ReadableStream({}); + this.writable = new WritableStream({}); + } +} diff --git a/web-transport-tauri/src/rpc.rs b/web-transport-tauri/src/rpc.rs new file mode 100644 index 0000000..ec4da82 --- /dev/null +++ b/web-transport-tauri/src/rpc.rs @@ -0,0 +1,126 @@ +use bytes::Bytes; +use serde::{Deserialize, Serialize}; +use tauri::Url; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] // matches the WebTransport API +pub enum CongestionControl { + Default, + LowLatency, + Throughput, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectRequest { + pub url: Url, + pub congestion_control: Option, + // Hex-encoded sha256 hashes. + pub server_certificate_hashes: Option>, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectResponse { + pub session: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloseRequest { + pub session: usize, + pub code: u32, + pub reason: Option, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloseResponse {} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClosedRequest { + pub session: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClosedResponse { + // TODO error code +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptRequest { + pub session: usize, + pub bidirectional: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptResponse { + pub stream: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OpenRequest { + pub session: usize, + pub bidirectional: bool, + pub send_order: Option, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OpenResponse { + pub stream: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ReadRequest { + pub session: usize, + pub stream: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ReadResponse { + // If None, then the stream is closed. + pub data: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WriteRequest { + pub session: usize, + pub stream: usize, + pub data: Bytes, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WriteResponse {} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FinishRequest { + pub session: usize, + pub stream: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FinishResponse {} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResetRequest { + pub session: usize, + pub stream: usize, + pub code: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResetResponse {} diff --git a/web-transport-tauri/src/rpc.ts b/web-transport-tauri/src/rpc.ts new file mode 100644 index 0000000..a2de236 --- /dev/null +++ b/web-transport-tauri/src/rpc.ts @@ -0,0 +1,111 @@ +import { invoke } from "@tauri-apps/api/core"; + +export interface ConnectRequest { + url: string; + + congestionControl?: "default" | "low-latency" | "throughput"; + // Only hex-encoded sha256 is currently supported. + serverCertificateHashes?: string[]; +} + +export interface ConnectResponse { + session: number; +} + +export async function connect( + payload: ConnectRequest, +): Promise { + return invoke("plugin:web-transport|connect", { + payload, + }); +} + +export async function close(payload: CloseRequest): Promise { + return invoke("plugin:web-transport|close", { payload }); +} + +export interface CloseRequest { + session: number; + code: number; + reason: string | null; +} + +export type CloseResponse = Record; + +export async function closed(payload: ClosedRequest): Promise { + return invoke("plugin:web-transport|closed", { payload }); +} + +export interface ClosedRequest { + session: number; +} + +export type ClosedResponse = Record; + +export async function accept(payload: AcceptRequest): Promise { + return invoke("plugin:web-transport|accept", { payload }); +} + +export interface AcceptRequest { + session: number; + bidirectional: boolean; +} + +export interface AcceptResponse { + stream: number; +} + +export async function open(payload: OpenRequest): Promise { + return invoke("plugin:web-transport|open", { payload }); +} + +export interface OpenRequest { + session: number; + bidirectional: boolean; + sendOrder?: number; +} + +export interface OpenResponse { + stream: number; +} + +export async function read(payload: ReadRequest): Promise { + return invoke("plugin:web-transport|read", { payload }); +} + +export interface ReadRequest { + session: number; + stream: number; +} + +export interface ReadResponse { + data: Uint8Array | null; +} + +export async function write(payload: WriteRequest): Promise { + return invoke("plugin:web-transport|write", { + payload, + }); +} + +export interface WriteRequest { + session: number; + stream: number; + data: Uint8Array | null; +} + +export type WriteResponse = Record; + +export async function reset(payload: ResetRequest): Promise { + return invoke("plugin:web-transport|reset", { + payload, + }); +} + +export interface ResetRequest { + session: number; + stream: number; + code: number; +} + +export type ResetResponse = Record; diff --git a/web-transport-tauri/tsconfig.json b/web-transport-tauri/tsconfig.json new file mode 100644 index 0000000..61b5446 --- /dev/null +++ b/web-transport-tauri/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*"] +} diff --git a/web-transport-ws/package.json b/web-transport-ws/package.json index 0dbd4c8..7826f45 100644 --- a/web-transport-ws/package.json +++ b/web-transport-ws/package.json @@ -14,11 +14,11 @@ "./src" ], "scripts": { - "build": "rimraf dist && tsc && tsx scripts/package.ts", + "build": "rimraf dist && tsc && tsx ../scripts/package.ts", "dev": "tsc --watch", "check": "tsc --noEmit", "example": "tsx examples/client.ts", - "release": "tsx scripts/release.ts" + "release": "tsx ../scripts/release.ts" }, "devDependencies": { "typescript": "^5.9.2", diff --git a/web-transport-ws/tsconfig.json b/web-transport-ws/tsconfig.json index fd3f548..61b5446 100644 --- a/web-transport-ws/tsconfig.json +++ b/web-transport-ws/tsconfig.json @@ -1,15 +1,7 @@ { + "extends": "../tsconfig.json", "compilerOptions": { - "lib": ["esnext", "dom", "dom.iterable"], - "target": "esnext", - "module": "esnext", - "moduleResolution": "bundler", - "outDir": "./dist", - "declaration": true, - "isolatedModules": true, - "strict": true, - "skipLibCheck": true, - "rewriteRelativeImportExtensions": true + "outDir": "./dist" }, "include": ["src/**/*"] } From 5764706db637d0d9c499b4cf61a8f03002329f4d Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Sep 2025 17:10:03 -0700 Subject: [PATCH 2/7] Add biome, nix, more linting stuff. --- biome.jsonc | 36 +++++++ flake.lock | 61 +++++++++++ flake.nix | 2 +- justfile | 18 +--- package.json | 41 +++---- pnpm-lock.yaml | 91 ++++++++++++++++ pnpm-workspace.yaml | 7 +- scripts/package.ts | 2 +- tsconfig.json | 28 ++--- web-transport-quinn/web/client.js | 116 ++++++++++---------- web-transport-quinn/web/package.json | 8 +- web-transport-tauri/package.json | 72 ++++++------- web-transport-tauri/src/index.ts | 10 +- web-transport-tauri/src/polyfill.ts | 45 ++++---- web-transport-tauri/src/rpc.ts | 4 +- web-transport-tauri/tsconfig.json | 10 +- web-transport-ws/examples/client.ts | 147 +++++++++++++------------ web-transport-ws/package.json | 70 ++++++------ web-transport-ws/src/frame.ts | 69 ++++++------ web-transport-ws/src/index.ts | 8 +- web-transport-ws/src/session.ts | 154 +++++++++++++-------------- web-transport-ws/src/stream.ts | 28 ++--- web-transport-ws/src/varint.ts | 102 +++++++++--------- web-transport-ws/tsconfig.json | 10 +- 24 files changed, 653 insertions(+), 486 deletions(-) create mode 100644 biome.jsonc create mode 100644 flake.lock diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 0000000..ec32ea6 --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,36 @@ +// This has to be in the root otherwise the VSCode plugin will not work out of the box. +{ + "$schema": "https://biomejs.dev/schemas/2.2.3/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "formatter": { + // We repeat the editorconfig settings here because biome has spotty mono-repo support. + // "useEditorconfig": true, + "lineWidth": 120, + "indentStyle": "tab", + "indentWidth": 4, + "lineEnding": "lf" + }, + "files": { + "includes": ["**", "!**/permissions/schemas/*.json"] + }, + "linter": { + "rules": { + "style": { + "useNodejsImportProtocol": "off" + }, + "suspicious": { + // Some runtimes need ts-ignore + "noTsIgnore": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..cdc86d7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1757034884, + "narHash": "sha256-PgLSZDBEWUHpfTRfFyklmiiLBE1i1aGCtz4eRA3POao=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ca77296380960cd497a765102eeb1356eb80fed0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index bd31ccd..1e02f11 100644 --- a/flake.nix +++ b/flake.nix @@ -24,13 +24,13 @@ pkgs.clippy pkgs.cargo-shear pkgs.cargo-sort - pkgs.cargo-upgrades pkgs.cargo-edit ]; jsTools = [ pkgs.nodejs_24 pkgs.pnpm_10 + pkgs.biome ]; tools = [ diff --git a/justfile b/justfile index 0538737..efb9e40 100644 --- a/justfile +++ b/justfile @@ -11,17 +11,11 @@ default: # Install any required dependencies. setup: - # Install cargo-binstall for faster tool installation. - cargo install cargo-binstall - just setup-tools - -# A separate entrypoint for CI. -setup-tools: - cargo binstall -y cargo-shear cargo-sort cargo-upgrades cargo-edit + cargo install -y cargo-shear cargo-sort cargo-upgrades cargo-edit # Run the CI checks check: - cargo check --all-targets --all-features + cargo test --all-targets --all-features cargo clippy --all-targets --all-features -- -D warnings # Do the same but explicitly use the WASM target. @@ -40,18 +34,13 @@ check: # JavaScript/TypeScript checks pnpm install --frozen-lockfile pnpm -r run check - -# Run any CI tests -test: - cargo test + pnpm exec biome check # Automatically fix some issues. fix: - cargo fix --allow-staged --all-targets --all-features cargo clippy --fix --allow-staged --all-targets --all-features # Do the same but explicitly use the WASM target. - cargo fix --allow-staged --all-targets --all-features --target wasm32-unknown-unknown -p web-transport cargo clippy --fix --allow-staged --all-targets --all-features --target wasm32-unknown-unknown -p web-transport # requires: cargo install cargo-shear @@ -65,6 +54,7 @@ fix: # JavaScript/TypeScript fixes pnpm install + pnpm exec biome check --fix # Upgrade any tooling upgrade: diff --git a/package.json b/package.json index 88e7563..a6afe9a 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,22 @@ { - "name": "web-transport-workspace", - "private": true, - "type": "module", - "engines": { - "node": ">=18" - }, - "scripts": { - "build": "pnpm -r build", - "dev": "pnpm -r dev", - "check": "pnpm -r check", - "clean": "pnpm -r clean && rimraf dist node_modules/.cache", - "install-all": "pnpm install --frozen-lockfile" - }, - "devDependencies": { - "typescript": "^5.9.2", - "rimraf": "^6.0.1", - "@types/node": "^24.3.0", - "tsx": "^4.20.5" - } -} \ No newline at end of file + "name": "web-transport-workspace", + "private": true, + "type": "module", + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "pnpm -r build", + "dev": "pnpm -r dev", + "check": "pnpm -r check", + "clean": "pnpm -r clean && rimraf dist node_modules/.cache", + "install-all": "pnpm install --frozen-lockfile" + }, + "devDependencies": { + "typescript": "^5.9.2", + "rimraf": "^6.0.1", + "@types/node": "^24.3.0", + "tsx": "^4.20.5", + "@biomejs/biome": "^2.2.2" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dec63a7..bd49298 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@biomejs/biome': + specifier: ^2.2.2 + version: 2.2.3 '@types/node': specifier: ^24.3.0 version: 24.3.1 @@ -63,6 +66,59 @@ importers: packages: + '@biomejs/biome@2.2.3': + resolution: {integrity: sha512-9w0uMTvPrIdvUrxazZ42Ib7t8Y2yoGLKLdNne93RLICmaHw7mcLv4PPb5LvZLJF3141gQHiCColOh/v6VWlWmg==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.2.3': + resolution: {integrity: sha512-OrqQVBpadB5eqzinXN4+Q6honBz+tTlKVCsbEuEpljK8ASSItzIRZUA02mTikl3H/1nO2BMPFiJ0nkEZNy3B1w==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.2.3': + resolution: {integrity: sha512-OCdBpb1TmyfsTgBAM1kPMXyYKTohQ48WpiN9tkt9xvU6gKVKHY4oVwteBebiOqyfyzCNaSiuKIPjmHjUZ2ZNMg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.2.3': + resolution: {integrity: sha512-q3w9jJ6JFPZPeqyvwwPeaiS/6NEszZ+pXKF+IczNo8Xj6fsii45a4gEEicKyKIytalV+s829ACZujQlXAiVLBQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.2.3': + resolution: {integrity: sha512-g/Uta2DqYpECxG+vUmTAmUKlVhnGEcY7DXWgKP8ruLRa8Si1QHsWknPY3B/wCo0KgYiFIOAZ9hjsHfNb9L85+g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.2.3': + resolution: {integrity: sha512-y76Dn4vkP1sMRGPFlNc+OTETBhGPJ90jY3il6jAfur8XWrYBQV3swZ1Jo0R2g+JpOeeoA0cOwM7mJG6svDz79w==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.2.3': + resolution: {integrity: sha512-LEtyYL1fJsvw35CxrbQ0gZoxOG3oZsAjzfRdvRBRHxOpQ91Q5doRVjvWW/wepgSdgk5hlaNzfeqpyGmfSD0Eyw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.2.3': + resolution: {integrity: sha512-Ms9zFYzjcJK7LV+AOMYnjN3pV3xL8Prxf9aWdDVL74onLn5kcvZ1ZMQswE5XHtnd/r/0bnUd928Rpbs14BzVmA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.2.3': + resolution: {integrity: sha512-gvCpewE7mBwBIpqk1YrUqNR4mCiyJm6UI3YWQQXkedSSEwzRdodRpaKhbdbHw1/hmTWOVXQ+Eih5Qctf4TCVOQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@esbuild/aix-ppc64@0.25.9': resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} @@ -414,6 +470,41 @@ packages: snapshots: + '@biomejs/biome@2.2.3': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.2.3 + '@biomejs/cli-darwin-x64': 2.2.3 + '@biomejs/cli-linux-arm64': 2.2.3 + '@biomejs/cli-linux-arm64-musl': 2.2.3 + '@biomejs/cli-linux-x64': 2.2.3 + '@biomejs/cli-linux-x64-musl': 2.2.3 + '@biomejs/cli-win32-arm64': 2.2.3 + '@biomejs/cli-win32-x64': 2.2.3 + + '@biomejs/cli-darwin-arm64@2.2.3': + optional: true + + '@biomejs/cli-darwin-x64@2.2.3': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.2.3': + optional: true + + '@biomejs/cli-linux-arm64@2.2.3': + optional: true + + '@biomejs/cli-linux-x64-musl@2.2.3': + optional: true + + '@biomejs/cli-linux-x64@2.2.3': + optional: true + + '@biomejs/cli-win32-arm64@2.2.3': + optional: true + + '@biomejs/cli-win32-x64@2.2.3': + optional: true + '@esbuild/aix-ppc64@0.25.9': optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e315c07..81a3177 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,6 @@ packages: - - "web-transport-ws" - - "web-transport-tauri" \ No newline at end of file + - web-transport-ws + - web-transport-tauri + +onlyBuiltDependencies: + - esbuild diff --git a/scripts/package.ts b/scripts/package.ts index 6d49f21..1cf8466 100644 --- a/scripts/package.ts +++ b/scripts/package.ts @@ -2,7 +2,7 @@ // This creates a dist/ folder with the correct paths and dependencies for publishing // Split from release.ts to allow building packages without publishing -import { copyFileSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"; +import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; console.log("✍️ Rewriting package.json..."); diff --git a/tsconfig.json b/tsconfig.json index 8947e85..178c955 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "lib": ["esnext", "dom", "dom.iterable"], - "target": "esnext", - "module": "esnext", - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "rewriteRelativeImportExtensions": true, - "declaration": true, - "isolatedModules": true, - "strict": true, - "skipLibCheck": true - }, - "include": ["src/**/*"] -} \ No newline at end of file + "compilerOptions": { + "lib": ["esnext", "dom", "dom.iterable"], + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "declaration": true, + "isolatedModules": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/web-transport-quinn/web/client.js b/web-transport-quinn/web/client.js index 24fe650..18c45d8 100644 --- a/web-transport-quinn/web/client.js +++ b/web-transport-quinn/web/client.js @@ -1,73 +1,75 @@ // @ts-ignore embed the certificate fingerprint using bundler -import fingerprintHex from 'bundle-text:../cert/localhost.hex'; +import fingerprintHex from "bundle-text:../cert/localhost.hex"; // Convert the hex to binary. -let fingerprint = []; +const fingerprint = []; for (let c = 0; c < fingerprintHex.length - 1; c += 2) { - fingerprint.push(parseInt(fingerprintHex.substring(c, c + 2), 16)); + fingerprint.push(parseInt(fingerprintHex.substring(c, c + 2), 16)); } -const params = new URLSearchParams(window.location.search) +const params = new URLSearchParams(window.location.search); -const url = params.get("url") || "https://localhost:4443" -const datagram = params.get("datagram") || false +const url = params.get("url") || "https://localhost:4443"; +const datagram = params.get("datagram") || false; function log(msg) { - const element = document.createElement("div"); - element.innerText = msg; + const element = document.createElement("div"); + element.innerText = msg; - document.body.appendChild(element); + document.body.appendChild(element); } async function run() { - // Connect using the hex fingerprint in the cert folder. - const transport = new WebTransport(url, { - serverCertificateHashes: [{ - "algorithm": "sha-256", - "value": new Uint8Array(fingerprint), - }], - }); - await transport.ready; - - log("connected"); - - let writer; - let reader; - - if (!datagram) { - // Create a bidirectional stream - const stream = await transport.createBidirectionalStream(); - log("created stream"); - - writer = stream.writable.getWriter(); - reader = stream.readable.getReader(); - } else { - log("using datagram"); - - // Create a datagram - writer = transport.datagrams.writable.getWriter(); - reader = transport.datagrams.readable.getReader(); - } - - // Create a message - const msg = 'Hello, world!'; - const encoded = new TextEncoder().encode(msg); - - await writer.write(encoded); - await writer.close(); - writer.releaseLock(); - - log("send: " + msg); - - // Read a message from it - // TODO handle partial reads - const { value } = await reader.read(); - - const recv = new TextDecoder().decode(value); - log("recv: " + recv); - - transport.close(); - log("closed"); + // Connect using the hex fingerprint in the cert folder. + const transport = new WebTransport(url, { + serverCertificateHashes: [ + { + algorithm: "sha-256", + value: new Uint8Array(fingerprint), + }, + ], + }); + await transport.ready; + + log("connected"); + + let writer; + let reader; + + if (!datagram) { + // Create a bidirectional stream + const stream = await transport.createBidirectionalStream(); + log("created stream"); + + writer = stream.writable.getWriter(); + reader = stream.readable.getReader(); + } else { + log("using datagram"); + + // Create a datagram + writer = transport.datagrams.writable.getWriter(); + reader = transport.datagrams.readable.getReader(); + } + + // Create a message + const msg = "Hello, world!"; + const encoded = new TextEncoder().encode(msg); + + await writer.write(encoded); + await writer.close(); + writer.releaseLock(); + + log(`send: ${msg}`); + + // Read a message from it + // TODO handle partial reads + const { value } = await reader.read(); + + const recv = new TextDecoder().decode(value); + log(`recv: ${recv}`); + + transport.close(); + log("closed"); } run(); diff --git a/web-transport-quinn/web/package.json b/web-transport-quinn/web/package.json index 337c1a2..6f698dc 100644 --- a/web-transport-quinn/web/package.json +++ b/web-transport-quinn/web/package.json @@ -1,6 +1,6 @@ { - "devDependencies": { - "@parcel/transformer-inline-string": "^2.10.3", - "parcel": "^2.10.3" - } + "devDependencies": { + "@parcel/transformer-inline-string": "^2.10.3", + "parcel": "^2.10.3" + } } diff --git a/web-transport-tauri/package.json b/web-transport-tauri/package.json index 985ba2e..d0928e1 100644 --- a/web-transport-tauri/package.json +++ b/web-transport-tauri/package.json @@ -1,38 +1,38 @@ { - "name": "@kixelated/web-transport-tauri", - "author": "Luke Curley ", - "version": "0.1.0", - "description": "WebTransport polyfill using Tauri", - "type": "module", - "license": "(MIT OR Apache-2.0)", - "repository": "github:kixelated/web-transport", - "exports": { - ".": "./src/index.ts" - }, - "types": "./src/index.ts", - "files": [ - "./src" - ], - "scripts": { - "build": "rimraf dist && tsc && tsx ../scripts/package.ts", - "dev": "tsc --watch", - "check": "tsc --noEmit" - }, - "dependencies": { - "@tauri-apps/api": "2.8.0", - "buffer": "^6.0.3" - }, - "devDependencies": { - "typescript": "^5.9.2", - "rimraf": "^6.0.1", - "@types/node": "^24.3.0", - "tsx": "^4.20.5" - }, - "keywords": [ - "webtransport", - "tauri", - "polyfill", - "quic", - "streams" - ] + "name": "@kixelated/web-transport-tauri", + "author": "Luke Curley ", + "version": "0.1.0", + "description": "WebTransport polyfill using Tauri", + "type": "module", + "license": "(MIT OR Apache-2.0)", + "repository": "github:kixelated/web-transport", + "exports": { + ".": "./src/index.ts" + }, + "types": "./src/index.ts", + "files": [ + "./src" + ], + "scripts": { + "build": "rimraf dist && tsc && tsx ../scripts/package.ts", + "dev": "tsc --watch", + "check": "tsc --noEmit" + }, + "dependencies": { + "@tauri-apps/api": "2.8.0", + "buffer": "^6.0.3" + }, + "devDependencies": { + "typescript": "^5.9.2", + "rimraf": "^6.0.1", + "@types/node": "^24.3.0", + "tsx": "^4.20.5" + }, + "keywords": [ + "webtransport", + "tauri", + "polyfill", + "quic", + "streams" + ] } diff --git a/web-transport-tauri/src/index.ts b/web-transport-tauri/src/index.ts index db05045..eeea814 100644 --- a/web-transport-tauri/src/index.ts +++ b/web-transport-tauri/src/index.ts @@ -3,12 +3,12 @@ import WebTransportTauri from "./polyfill.ts"; // Install polyfill if WebTransport is not available, returning true if installed export function install(): boolean { - if ("WebTransport" in globalThis) return false; - if (!isTauri()) return false; + if ("WebTransport" in globalThis) return false; + if (!isTauri()) return false; - // biome-ignore lint/suspicious/noExplicitAny: polyfill - (globalThis as any).WebTransport = WebTransportTauri; - return true; + // biome-ignore lint/suspicious/noExplicitAny: polyfill + (globalThis as any).WebTransport = WebTransportTauri; + return true; } export default WebTransportTauri; diff --git a/web-transport-tauri/src/polyfill.ts b/web-transport-tauri/src/polyfill.ts index abecfc3..b9c927f 100644 --- a/web-transport-tauri/src/polyfill.ts +++ b/web-transport-tauri/src/polyfill.ts @@ -45,24 +45,21 @@ export default class WebTransportTauri implements WebTransport { }), ); - this.incomingBidirectionalStreams = - new ReadableStream({ - pull: async (controller) => { - const accept = await rpc.accept({ - session: await this.#session, - bidirectional: true, - }); - - const readable = this.#createReadable(accept.stream); - const writable = this.#createWritable(accept.stream); - - controller.enqueue({ readable, writable }); - }, - }); - - this.incomingUnidirectionalStreams = new ReadableStream< - ReadableStream - >({ + this.incomingBidirectionalStreams = new ReadableStream({ + pull: async (controller) => { + const accept = await rpc.accept({ + session: await this.#session, + bidirectional: true, + }); + + const readable = this.#createReadable(accept.stream); + const writable = this.#createWritable(accept.stream); + + controller.enqueue({ readable, writable }); + }, + }); + + this.incomingUnidirectionalStreams = new ReadableStream>({ pull: async (controller) => { const accept = await rpc.accept({ session: await this.#session, @@ -128,9 +125,7 @@ export default class WebTransportTauri implements WebTransport { ); } - async createBidirectionalStream( - options?: WebTransportSendStreamOptions, - ): Promise { + async createBidirectionalStream(options?: WebTransportSendStreamOptions): Promise { const response = await rpc.open({ session: await this.#session, bidirectional: true, @@ -143,9 +138,7 @@ export default class WebTransportTauri implements WebTransport { }; } - async createUnidirectionalStream( - options?: WebTransportSendStreamOptions, - ): Promise { + async createUnidirectionalStream(options?: WebTransportSendStreamOptions): Promise { const response = await rpc.open({ session: await this.#session, bidirectional: false, @@ -157,9 +150,7 @@ export default class WebTransportTauri implements WebTransport { } // TODO Implement this -export class WebTransportDatagramDuplexStreamTauri - implements WebTransportDatagramDuplexStream -{ +export class WebTransportDatagramDuplexStreamTauri implements WebTransportDatagramDuplexStream { incomingHighWaterMark: number; incomingMaxAge: number | null; readonly maxDatagramSize: number; diff --git a/web-transport-tauri/src/rpc.ts b/web-transport-tauri/src/rpc.ts index a2de236..2775451 100644 --- a/web-transport-tauri/src/rpc.ts +++ b/web-transport-tauri/src/rpc.ts @@ -12,9 +12,7 @@ export interface ConnectResponse { session: number; } -export async function connect( - payload: ConnectRequest, -): Promise { +export async function connect(payload: ConnectRequest): Promise { return invoke("plugin:web-transport|connect", { payload, }); diff --git a/web-transport-tauri/tsconfig.json b/web-transport-tauri/tsconfig.json index 61b5446..dc58668 100644 --- a/web-transport-tauri/tsconfig.json +++ b/web-transport-tauri/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "./dist" - }, - "include": ["src/**/*"] + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*"] } diff --git a/web-transport-ws/examples/client.ts b/web-transport-ws/examples/client.ts index 153af9e..068a687 100644 --- a/web-transport-ws/examples/client.ts +++ b/web-transport-ws/examples/client.ts @@ -11,84 +11,83 @@ globalThis.WebTransport = WebTransportWs; globalThis.WebSocket = WebSocket; async function main() { - // @ts-expect-error too lazy to debug node types + // @ts-expect-error too lazy to debug node types const url = process.argv[2] || "http://localhost:3000"; console.log(`Connecting to ${url}...`); - // Create a new WebTransport connection - const transport = new WebTransport(url); - - // Add error handling for closed promise - transport.closed.then( - (info) => console.log("Transport closed with info:", info), - (error) => console.error("Transport closed with error:", error) - ); - - // Wait for the connection to be ready - await transport.ready; - console.log("✓ Connected successfully"); - - // Example 1: Send data on a unidirectional stream - console.log("\n--- Sending unidirectional stream ---"); - const sendStream = await transport.createUnidirectionalStream(); - const writer = sendStream.getWriter(); - - const message = "Hello from Node.js client!"; - await writer.write(new TextEncoder().encode(message)); - await writer.close(); - console.log(`✓ Sent: "${message}"`); - - // Example 2: Create and use a bidirectional stream - console.log("\n--- Creating bidirectional stream ---"); - const biStream = await transport.createBidirectionalStream(); - - // Send data - const biWriter = biStream.writable.getWriter(); - const request = "ping"; - await biWriter.write(new TextEncoder().encode(request)); - console.log(`✓ Sent: "${request}"`); - - // Read response - const biReader = biStream.readable.getReader(); - const { value, done } = await biReader.read(); - if (!done && value) { - const response = new TextDecoder().decode(value); - console.log(`✓ Received: "${response}"`); - } - - await biWriter.close(); - biReader.releaseLock(); - - // Example 3: Listen for incoming streams - console.log("\n--- Listening for incoming streams ---"); - - // Handle incoming unidirectional streams - const uni: ReadableStreamDefaultReader> = transport.incomingUnidirectionalStreams.getReader(); - - const { value: stream } = await uni.read(); - if (!stream) throw new Error("No stream received"); - - const reader = stream.getReader(); - const { value: data } = await reader.read(); - if (!data) throw new Error("No data received"); - - console.log( - `✓ Received uni stream: "${new TextDecoder().decode(data)}"`, - ); - reader.releaseLock(); - uni.releaseLock(); - - // Close the connection - console.log("\n--- Closing connection ---"); - transport.close({ - closeCode: 0, - reason: "Client finished", - }); - - // Wait for closed - const closeInfo = await transport.closed; - console.log(`✓ Connection closed: ${closeInfo.reason || "No reason"}`); + // Create a new WebTransport connection + const transport = new WebTransport(url); + + // Add error handling for closed promise + transport.closed.then( + (info) => console.log("Transport closed with info:", info), + (error) => console.error("Transport closed with error:", error), + ); + + // Wait for the connection to be ready + await transport.ready; + console.log("✓ Connected successfully"); + + // Example 1: Send data on a unidirectional stream + console.log("\n--- Sending unidirectional stream ---"); + const sendStream = await transport.createUnidirectionalStream(); + const writer = sendStream.getWriter(); + + const message = "Hello from Node.js client!"; + await writer.write(new TextEncoder().encode(message)); + await writer.close(); + console.log(`✓ Sent: "${message}"`); + + // Example 2: Create and use a bidirectional stream + console.log("\n--- Creating bidirectional stream ---"); + const biStream = await transport.createBidirectionalStream(); + + // Send data + const biWriter = biStream.writable.getWriter(); + const request = "ping"; + await biWriter.write(new TextEncoder().encode(request)); + console.log(`✓ Sent: "${request}"`); + + // Read response + const biReader = biStream.readable.getReader(); + const { value, done } = await biReader.read(); + if (!done && value) { + const response = new TextDecoder().decode(value); + console.log(`✓ Received: "${response}"`); + } + + await biWriter.close(); + biReader.releaseLock(); + + // Example 3: Listen for incoming streams + console.log("\n--- Listening for incoming streams ---"); + + // Handle incoming unidirectional streams + const uni: ReadableStreamDefaultReader> = + transport.incomingUnidirectionalStreams.getReader(); + + const { value: stream } = await uni.read(); + if (!stream) throw new Error("No stream received"); + + const reader = stream.getReader(); + const { value: data } = await reader.read(); + if (!data) throw new Error("No data received"); + + console.log(`✓ Received uni stream: "${new TextDecoder().decode(data)}"`); + reader.releaseLock(); + uni.releaseLock(); + + // Close the connection + console.log("\n--- Closing connection ---"); + transport.close({ + closeCode: 0, + reason: "Client finished", + }); + + // Wait for closed + const closeInfo = await transport.closed; + console.log(`✓ Connection closed: ${closeInfo.reason || "No reason"}`); } // Run the client diff --git a/web-transport-ws/package.json b/web-transport-ws/package.json index 7826f45..a0f0e9d 100644 --- a/web-transport-ws/package.json +++ b/web-transport-ws/package.json @@ -1,37 +1,37 @@ { - "name": "@kixelated/web-transport-ws", - "author": "Luke Curley ", - "version": "0.1.2", - "description": "WebTransport polyfill using WebSockets", - "type": "module", - "license": "(MIT OR Apache-2.0)", - "repository": "github:kixelated/web-transport", - "exports": { - ".": "./src/index.ts" - }, - "types": "./src/index.ts", - "files": [ - "./src" - ], - "scripts": { - "build": "rimraf dist && tsc && tsx ../scripts/package.ts", - "dev": "tsc --watch", - "check": "tsc --noEmit", - "example": "tsx examples/client.ts", - "release": "tsx ../scripts/release.ts" - }, - "devDependencies": { - "typescript": "^5.9.2", - "ws": "^8.16.0", - "rimraf": "^6.0.1", - "@types/node": "^24.3.0", - "tsx": "^4.20.5" - }, - "keywords": [ - "webtransport", - "websocket", - "polyfill", - "quic", - "streams" - ] + "name": "@kixelated/web-transport-ws", + "author": "Luke Curley ", + "version": "0.1.2", + "description": "WebTransport polyfill using WebSockets", + "type": "module", + "license": "(MIT OR Apache-2.0)", + "repository": "github:kixelated/web-transport", + "exports": { + ".": "./src/index.ts" + }, + "types": "./src/index.ts", + "files": [ + "./src" + ], + "scripts": { + "build": "rimraf dist && tsc && tsx ../scripts/package.ts", + "dev": "tsc --watch", + "check": "tsc --noEmit", + "example": "tsx examples/client.ts", + "release": "tsx ../scripts/release.ts" + }, + "devDependencies": { + "typescript": "^5.9.2", + "ws": "^8.16.0", + "rimraf": "^6.0.1", + "@types/node": "^24.3.0", + "tsx": "^4.20.5" + }, + "keywords": [ + "webtransport", + "websocket", + "polyfill", + "quic", + "streams" + ] } diff --git a/web-transport-ws/src/frame.ts b/web-transport-ws/src/frame.ts index 7387b4d..aad172b 100644 --- a/web-transport-ws/src/frame.ts +++ b/web-transport-ws/src/frame.ts @@ -12,15 +12,15 @@ export interface Data { id: Stream.Id; data: Uint8Array; fin: boolean; - // no offset, because everything is ordered - // no length, because WebSocket already provides this + // no offset, because everything is ordered + // no length, because WebSocket already provides this } export interface ResetStream { type: "reset_stream"; id: Stream.Id; code: VarInt; - // no final size, because there's no flow control + // no final size, because there's no flow control } export interface StopSending { @@ -32,7 +32,7 @@ export interface StopSending { export interface ConnectionClose { type: "connection_close"; code: VarInt; - // no reason size, because WebSocket already provides this. + // no reason size, because WebSocket already provides this. reason: string; } @@ -44,30 +44,25 @@ export interface Ping { type: "ping"; } -export type Any = - | Data - | ResetStream - | StopSending - | ConnectionClose; - +export type Any = Data | ResetStream | StopSending | ConnectionClose; export function encode(frame: Any): Uint8Array { switch (frame.type) { case "stream": { - // Calculate the maximum size of the buffer we'll need - let buffer = new Uint8Array(new ArrayBuffer(1 + 8 + frame.data.length), 0, 1); + // Calculate the maximum size of the buffer we'll need + let buffer = new Uint8Array(new ArrayBuffer(1 + 8 + frame.data.length), 0, 1); buffer[0] = frame.fin ? STREAM_FIN : STREAM; buffer = frame.id.value.encode(buffer); - buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength + frame.data.length); - buffer.set(frame.data, buffer.byteLength - frame.data.length); + buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength + frame.data.length); + buffer.set(frame.data, buffer.byteLength - frame.data.length); return buffer; } case "reset_stream": { - let buffer = new Uint8Array(new ArrayBuffer(1 + 8 + 8), 0, 1); + let buffer = new Uint8Array(new ArrayBuffer(1 + 8 + 8), 0, 1); buffer[0] = RESET_STREAM; buffer = frame.id.value.encode(buffer); @@ -76,7 +71,7 @@ export function encode(frame: Any): Uint8Array { } case "stop_sending": { - let buffer = new Uint8Array(new ArrayBuffer(1 + 8 + 8), 0, 1); + let buffer = new Uint8Array(new ArrayBuffer(1 + 8 + 8), 0, 1); buffer[0] = STOP_SENDING; buffer = frame.id.value.encode(buffer); @@ -86,13 +81,13 @@ export function encode(frame: Any): Uint8Array { case "connection_close": { const body = new TextEncoder().encode(frame.reason); - let buffer = new Uint8Array(new ArrayBuffer(1 + 8 + body.length), 0, 1); + let buffer = new Uint8Array(new ArrayBuffer(1 + 8 + body.length), 0, 1); buffer[0] = APPLICATION_CLOSE; buffer = frame.code.encode(buffer); - buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength + body.length); - buffer.set(body, buffer.byteLength - body.length); + buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength + body.length); + buffer.set(body, buffer.byteLength - body.length); return buffer; } @@ -100,21 +95,21 @@ export function encode(frame: Any): Uint8Array { } export function decode(buffer: Uint8Array): Any { - if (buffer.length === 0) { - throw new Error("Invalid frame: empty buffer"); - } + if (buffer.length === 0) { + throw new Error("Invalid frame: empty buffer"); + } - const frameType = buffer[0]; - buffer = buffer.slice(1); + const frameType = buffer[0]; + buffer = buffer.slice(1); - let v: VarInt; + let v: VarInt; if (frameType === RESET_STREAM) { - [ v, buffer ]= VarInt.decode(buffer); - const id = new Stream.Id(v); + [v, buffer] = VarInt.decode(buffer); + const id = new Stream.Id(v); - [ v, buffer ]= VarInt.decode(buffer); - const code = v; + [v, buffer] = VarInt.decode(buffer); + const code = v; return { type: "reset_stream", @@ -124,11 +119,11 @@ export function decode(buffer: Uint8Array): Any { } if (frameType === STOP_SENDING) { - [ v, buffer ]= VarInt.decode(buffer); - const id = new Stream.Id(v); + [v, buffer] = VarInt.decode(buffer); + const id = new Stream.Id(v); - [ v, buffer ]= VarInt.decode(buffer); - const code = v; + [v, buffer] = VarInt.decode(buffer); + const code = v; return { type: "stop_sending", @@ -138,8 +133,8 @@ export function decode(buffer: Uint8Array): Any { } if (frameType === APPLICATION_CLOSE) { - [ v, buffer ]= VarInt.decode(buffer); - const code = v; + [v, buffer] = VarInt.decode(buffer); + const code = v; const reason = new TextDecoder().decode(buffer); @@ -151,8 +146,8 @@ export function decode(buffer: Uint8Array): Any { } if (frameType === STREAM || frameType === STREAM_FIN) { - [ v, buffer ]= VarInt.decode(buffer); - const id = new Stream.Id(v); + [v, buffer] = VarInt.decode(buffer); + const id = new Stream.Id(v); return { type: "stream", diff --git a/web-transport-ws/src/index.ts b/web-transport-ws/src/index.ts index 6043091..a0588a1 100644 --- a/web-transport-ws/src/index.ts +++ b/web-transport-ws/src/index.ts @@ -2,10 +2,10 @@ import WebTransportWs from "./session.ts"; // Install polyfill if WebTransport is not available, returning true if installed export function install(): boolean { - if ("WebTransport" in globalThis) return false; - // biome-ignore lint/suspicious/noExplicitAny: polyfill - (globalThis as any).WebTransport = WebTransportWs; - return true + if ("WebTransport" in globalThis) return false; + // biome-ignore lint/suspicious/noExplicitAny: polyfill + (globalThis as any).WebTransport = WebTransportWs; + return true; } export default WebTransportWs; diff --git a/web-transport-ws/src/session.ts b/web-transport-ws/src/session.ts index 9fec5f6..0472dcd 100644 --- a/web-transport-ws/src/session.ts +++ b/web-transport-ws/src/session.ts @@ -19,22 +19,22 @@ export default class WebTransportWs implements WebTransport { readonly closed: Promise; #closedResolve!: (info: WebTransportCloseInfo) => void; - readonly incomingBidirectionalStreams: ReadableStream; - #incomingBidirectionalStreams!: ReadableStreamDefaultController; - readonly incomingUnidirectionalStreams: ReadableStream>; - #incomingUnidirectionalStreams!: ReadableStreamDefaultController>; + readonly incomingBidirectionalStreams: ReadableStream; + #incomingBidirectionalStreams!: ReadableStreamDefaultController; + readonly incomingUnidirectionalStreams: ReadableStream>; + #incomingUnidirectionalStreams!: ReadableStreamDefaultController>; - // TODO: Implement datagrams + // TODO: Implement datagrams readonly datagrams = new Datagrams(); constructor(url: string | URL, options?: WebTransportOptions) { - if (options?.requireUnreliable) { - throw new Error("not allowed to use WebSocket; requireUnreliable is true"); - } + if (options?.requireUnreliable) { + throw new Error("not allowed to use WebSocket; requireUnreliable is true"); + } - if (options?.serverCertificateHashes) { - console.warn("serverCertificateHashes is not supported; trying anyway"); - } + if (options?.serverCertificateHashes) { + console.warn("serverCertificateHashes is not supported; trying anyway"); + } url = WebTransportWs.#convertToWebSocketUrl(url); @@ -54,17 +54,17 @@ export default class WebTransportWs implements WebTransport { this.#ws.onerror = (event) => this.#handleError(event); this.#ws.onclose = (event) => this.#handleClose(event); - this.incomingBidirectionalStreams = new ReadableStream({ - start: (controller) => { - this.#incomingBidirectionalStreams = controller; - }, - }); - - this.incomingUnidirectionalStreams = new ReadableStream>({ - start: (controller) => { - this.#incomingUnidirectionalStreams = controller; - }, - }); + this.incomingBidirectionalStreams = new ReadableStream({ + start: (controller) => { + this.#incomingBidirectionalStreams = controller; + }, + }); + + this.incomingUnidirectionalStreams = new ReadableStream>({ + start: (controller) => { + this.#incomingUnidirectionalStreams = controller; + }, + }); } static #convertToWebSocketUrl(url: string | URL): string { @@ -101,16 +101,14 @@ export default class WebTransportWs implements WebTransport { if (this.#closed) return; this.#closed = new Error(`WebSocket error: ${event.type}`); - this.#close(1006, "WebSocket error"); + this.#close(1006, "WebSocket error"); } #handleClose(event: CloseEvent) { if (this.#closed) return; - this.#closed = new Error( - `Connection closed: ${event.code} ${event.reason}`, - ); - this.#close(event.code, event.reason); + this.#closed = new Error(`Connection closed: ${event.code} ${event.reason}`); + this.#close(event.code, event.reason); } #recvFrame(frame: Frame.Any) { @@ -121,9 +119,7 @@ export default class WebTransportWs implements WebTransport { } else if (frame.type === "stop_sending") { this.#handleStopSending(frame); } else if (frame.type === "connection_close") { - this.#closeReason = new Error( - `Connection closed: ${frame.code.value}: ${frame.reason}`, - ); + this.#closeReason = new Error(`Connection closed: ${frame.code.value}: ${frame.reason}`); this.#ws.close(); } else { const exhaustive: never = frame; @@ -142,15 +138,15 @@ export default class WebTransportWs implements WebTransport { if (!stream) { // We created the stream, we can skip it. if (frame.id.serverInitiated === this.#isServer) { - return; - } + return; + } if (!frame.id.canRecv(this.#isServer)) { throw new Error("received write-only stream"); } const reader = new ReadableStream({ start: (controller) => { - stream = controller; + stream = controller; this.#recvStreams.set(streamId, controller); }, cancel: () => { @@ -160,7 +156,7 @@ export default class WebTransportWs implements WebTransport { code: VarInt.from(0), }); - this.#recvStreams.delete(streamId); + this.#recvStreams.delete(streamId); }, }); @@ -182,14 +178,14 @@ export default class WebTransportWs implements WebTransport { ]); }, abort: (e) => { - console.warn("abort", e); + console.warn("abort", e); this.#sendPriorityFrame({ type: "reset_stream", id: frame.id, code: VarInt.from(0), }); - this.#sendStreams.delete(streamId); + this.#sendStreams.delete(streamId); }, close: async () => { await Promise.race([ @@ -202,23 +198,23 @@ export default class WebTransportWs implements WebTransport { this.closed, ]); - this.#sendStreams.delete(streamId); + this.#sendStreams.delete(streamId); }, }); - this.#incomingBidirectionalStreams.enqueue({ readable: reader, writable: writer }); + this.#incomingBidirectionalStreams.enqueue({ readable: reader, writable: writer }); } else { - this.#incomingUnidirectionalStreams.enqueue(reader); - } + this.#incomingUnidirectionalStreams.enqueue(reader); + } } - if (frame.data.byteLength > 0) { - stream?.enqueue(frame.data); - } + if (frame.data.byteLength > 0) { + stream?.enqueue(frame.data); + } if (frame.fin) { stream?.close(); - this.#recvStreams.delete(streamId); + this.#recvStreams.delete(streamId); } } @@ -227,8 +223,8 @@ export default class WebTransportWs implements WebTransport { const stream = this.#recvStreams.get(streamId); if (!stream) return; - stream.error(new Error(`RESET_STREAM: ${frame.code.value}`)); - this.#recvStreams.delete(streamId); + stream.error(new Error(`RESET_STREAM: ${frame.code.value}`)); + this.#recvStreams.delete(streamId); } #handleStopSending(frame: Frame.StopSending) { @@ -236,14 +232,14 @@ export default class WebTransportWs implements WebTransport { const stream = this.#sendStreams.get(streamId); if (!stream) return; - stream.error(new Error(`STOP_SENDING: ${frame.code.value}`)); - this.#sendStreams.delete(streamId); + stream.error(new Error(`STOP_SENDING: ${frame.code.value}`)); + this.#sendStreams.delete(streamId); - this.#sendPriorityFrame({ - type: "reset_stream", - id: frame.id, - code: frame.code, - }); + this.#sendPriorityFrame({ + type: "reset_stream", + id: frame.id, + code: frame.code, + }); } async #sendFrame(frame: Frame.Any) { @@ -253,12 +249,12 @@ export default class WebTransportWs implements WebTransport { } const chunk = Frame.encode(frame); - this.#ws.send(chunk); + this.#ws.send(chunk); } #sendPriorityFrame(frame: Frame.Any) { const chunk = Frame.encode(frame); - this.#ws.send(chunk); + this.#ws.send(chunk); } async createBidirectionalStream(): Promise { @@ -268,11 +264,7 @@ export default class WebTransportWs implements WebTransport { throw this.#closeReason || new Error("Connection closed"); } - const streamId = Stream.Id.create( - this.#nextBiStreamId++, - Stream.Dir.Bi, - this.#isServer, - ); + const streamId = Stream.Id.create(this.#nextBiStreamId++, Stream.Dir.Bi, this.#isServer); const writer = new WritableStream({ start: (controller) => { @@ -290,7 +282,7 @@ export default class WebTransportWs implements WebTransport { ]); }, abort: (e) => { - console.warn("abort", e); + console.warn("abort", e); this.#sendPriorityFrame({ type: "reset_stream", id: streamId, @@ -339,11 +331,7 @@ export default class WebTransportWs implements WebTransport { throw this.#closed; } - const streamId = Stream.Id.create( - this.#nextUniStreamId++, - Stream.Dir.Uni, - this.#isServer, - ); + const streamId = Stream.Id.create(this.#nextUniStreamId++, Stream.Dir.Uni, this.#isServer); const session = this; @@ -363,7 +351,7 @@ export default class WebTransportWs implements WebTransport { ]); }, abort(e) { - console.warn("abort", e); + console.warn("abort", e); session.#sendPriorityFrame({ type: "reset_stream", id: streamId, @@ -390,20 +378,32 @@ export default class WebTransportWs implements WebTransport { return writer; } - #close(code: number, reason: string) { + #close(code: number, reason: string) { this.#closedResolve({ closeCode: code, reason, }); - // Fail active streams so consumers unblock - try { this.#incomingBidirectionalStreams.close(); } catch {} - try { this.#incomingUnidirectionalStreams.close(); } catch {} - for (const c of this.#sendStreams.values()) { try { c.error(this.#closed); } catch {} } - for (const c of this.#recvStreams.values()) { try { c.error(this.#closed); } catch {} } - this.#sendStreams.clear(); - this.#recvStreams.clear(); - } + // Fail active streams so consumers unblock + try { + this.#incomingBidirectionalStreams.close(); + } catch {} + try { + this.#incomingUnidirectionalStreams.close(); + } catch {} + for (const c of this.#sendStreams.values()) { + try { + c.error(this.#closed); + } catch {} + } + for (const c of this.#recvStreams.values()) { + try { + c.error(this.#closed); + } catch {} + } + this.#sendStreams.clear(); + this.#recvStreams.clear(); + } close(info?: { closeCode?: number; reason?: string }) { if (this.#closed) return; @@ -421,7 +421,7 @@ export default class WebTransportWs implements WebTransport { this.#ws.close(); }, 100); - this.#close(code, reason); + this.#close(code, reason); } get congestionControl(): string { diff --git a/web-transport-ws/src/stream.ts b/web-transport-ws/src/stream.ts index 66fa8c2..a7ed6ab 100644 --- a/web-transport-ws/src/stream.ts +++ b/web-transport-ws/src/stream.ts @@ -3,47 +3,47 @@ import { VarInt } from "./varint.ts"; export const Dir = { Bi: 0, Uni: 1, -} as const +} as const; -export type DirType = (typeof Dir)[keyof typeof Dir] +export type DirType = (typeof Dir)[keyof typeof Dir]; export class Id { - readonly value: VarInt + readonly value: VarInt; constructor(value: VarInt) { - this.value = value + this.value = value; } static create(id: bigint, dir: DirType, isServer: boolean): Id { - let streamId = id << 2n + let streamId = id << 2n; if (dir === Dir.Uni) { - streamId |= 0x02n + streamId |= 0x02n; } if (isServer) { - streamId |= 0x01n + streamId |= 0x01n; } - return new Id(VarInt.from(streamId)) + return new Id(VarInt.from(streamId)); } get dir(): DirType { - return (this.value.value & 0x02n) !== 0n ? Dir.Uni : Dir.Bi + return (this.value.value & 0x02n) !== 0n ? Dir.Uni : Dir.Bi; } get serverInitiated(): boolean { - return (this.value.value & 0x01n) !== 0n + return (this.value.value & 0x01n) !== 0n; } canRecv(isServer: boolean): boolean { if (this.dir === Dir.Uni) { - return this.serverInitiated !== isServer + return this.serverInitiated !== isServer; } - return true + return true; } canSend(isServer: boolean): boolean { if (this.dir === Dir.Uni) { - return this.serverInitiated === isServer + return this.serverInitiated === isServer; } - return true + return true; } } diff --git a/web-transport-ws/src/varint.ts b/web-transport-ws/src/varint.ts index 4e694dd..8109465 100644 --- a/web-transport-ws/src/varint.ts +++ b/web-transport-ws/src/varint.ts @@ -1,97 +1,97 @@ export class VarInt { - static readonly MAX = (1n << 62n) - 1n - static readonly MAX_SIZE = 8 - readonly value: bigint + static readonly MAX = (1n << 62n) - 1n; + static readonly MAX_SIZE = 8; + readonly value: bigint; constructor(value: bigint) { if (value < 0n || value > VarInt.MAX) { - throw new Error(`VarInt value out of range: ${value}`) + throw new Error(`VarInt value out of range: ${value}`); } - this.value = value + this.value = value; } static from(value: number | bigint): VarInt { - return new VarInt(BigInt(value)) + return new VarInt(BigInt(value)); } size(): number { - const x = this.value - if (x < 2n ** 6n) return 1 - if (x < 2n ** 14n) return 2 - if (x < 2n ** 30n) return 4 - if (x < 2n ** 62n) return 8 - throw new Error("VarInt value too large") + const x = this.value; + if (x < 2n ** 6n) return 1; + if (x < 2n ** 14n) return 2; + if (x < 2n ** 30n) return 4; + if (x < 2n ** 62n) return 8; + throw new Error("VarInt value too large"); } - // Append to the provided buffer + // Append to the provided buffer encode(dst: Uint8Array): Uint8Array { - const x = this.value + const x = this.value; - const size = this.size() - if (dst.buffer.byteLength < dst.byteLength + size) { - throw new Error("destination buffer too small") - } + const size = this.size(); + if (dst.buffer.byteLength < dst.byteLength + size) { + throw new Error("destination buffer too small"); + } - const view = new DataView(dst.buffer, dst.byteOffset + dst.byteLength, size) + const view = new DataView(dst.buffer, dst.byteOffset + dst.byteLength, size); if (size === 1) { - view.setUint8(0, Number(x)) + view.setUint8(0, Number(x)); } else if (size === 2) { - view.setUint16(0, (0b01 << 14) | Number(x), false) + view.setUint16(0, (0b01 << 14) | Number(x), false); } else if (size === 4) { - view.setUint32(0, (0b10 << 30) | Number(x), false) + view.setUint32(0, (0b10 << 30) | Number(x), false); } else if (size === 8) { - view.setBigUint64(0, (0b11n << 62n) | x, false) + view.setBigUint64(0, (0b11n << 62n) | x, false); } else { - throw new Error("VarInt value too large") + throw new Error("VarInt value too large"); } - return new Uint8Array(dst.buffer, dst.byteOffset, dst.byteLength + size) + return new Uint8Array(dst.buffer, dst.byteOffset, dst.byteLength + size); } static decode(buffer: Uint8Array): [VarInt, Uint8Array] { - if (buffer.byteLength < 1) { - throw new Error("Unexpected end of buffer") - } + if (buffer.byteLength < 1) { + throw new Error("Unexpected end of buffer"); + } - const view = new DataView(buffer.buffer, buffer.byteOffset) - const firstByte = view.getUint8(0) - const tag = firstByte >> 6 + const view = new DataView(buffer.buffer, buffer.byteOffset); + const firstByte = view.getUint8(0); + const tag = firstByte >> 6; - let value: bigint - let bytesRead: number + let value: bigint; + let bytesRead: number; switch (tag) { case 0b00: - value = BigInt(firstByte & 0b00111111) - bytesRead = 1 - break + value = BigInt(firstByte & 0b00111111); + bytesRead = 1; + break; case 0b01: if (2 > buffer.length) { - throw new Error("Unexpected end of buffer") + throw new Error("Unexpected end of buffer"); } - value = BigInt(view.getUint16(0, false) & 0x3fff) - bytesRead = 2 - break + value = BigInt(view.getUint16(0, false) & 0x3fff); + bytesRead = 2; + break; case 0b10: if (4 > buffer.length) { - throw new Error("Unexpected end of buffer") + throw new Error("Unexpected end of buffer"); } - value = BigInt(view.getUint32(0, false) & 0x3fffffff) - bytesRead = 4 - break + value = BigInt(view.getUint32(0, false) & 0x3fffffff); + bytesRead = 4; + break; case 0b11: if (8 > buffer.length) { - throw new Error("Unexpected end of buffer") + throw new Error("Unexpected end of buffer"); } - value = view.getBigUint64(0, false) & 0x3fffffffffffffffn - bytesRead = 8 - break + value = view.getBigUint64(0, false) & 0x3fffffffffffffffn; + bytesRead = 8; + break; default: - throw new Error("Invalid VarInt tag") + throw new Error("Invalid VarInt tag"); } - const remaining = new Uint8Array(buffer.buffer, buffer.byteOffset + bytesRead, buffer.byteLength - bytesRead) - return [ new VarInt(value), remaining ] + const remaining = new Uint8Array(buffer.buffer, buffer.byteOffset + bytesRead, buffer.byteLength - bytesRead); + return [new VarInt(value), remaining]; } } diff --git a/web-transport-ws/tsconfig.json b/web-transport-ws/tsconfig.json index 61b5446..dc58668 100644 --- a/web-transport-ws/tsconfig.json +++ b/web-transport-ws/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "./dist" - }, - "include": ["src/**/*"] + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*"] } From 23fa7a0b447866768e7d7050fe0813a89f7b92ab Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Sep 2025 17:14:38 -0700 Subject: [PATCH 3/7] And readme. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f322ad..41c7c09 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ This project is broken up into quite a few different crates: - [web-transport](web-transport) provides a generic interface, delegating to [web-transport-quinn](web-transport-quinn) or [web-transport-wasm](web-transport-wasm) depending on the platform. - [web-transport-quinn](web-transport-quinn) mirrors the [Quinn API](https://docs.rs/quinn/latest/quinn/index.html), abstracting away the HTTP/3 setup. - [web-transport-wasm](web-transport-wasm) wraps the [browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API) -- [web-transport-ws](web-transport-ws) crudely implements the WebTransport API over WebSockets for backwards compatibility. Also includes a NPM package. +- [web-transport-tauri](web-transport-tauri) polyfills the WebTransport API using Tauri. The web client communicates with the Rust client via a JSON RPC. +- [web-transport-ws](web-transport-ws) polyfills the WebTransport API using WebSockets. The web client communicates with the Rust server. - [web-transport-trait](web-transport-trait) defines an async trait, currently implemented by [web-transport-quinn](web-transport-quinn) and [web-transport-ws](web-transport-ws). - [web-transport-proto](web-transport-proto) a bare minimum implementation of HTTP/3 just to establish the WebTransport session. From 3f2e2f25be8cf1b68074db4cb8e4ed33e43aeac8 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Sep 2025 17:21:09 -0700 Subject: [PATCH 4/7] Fix CI --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 1e02f11..320fd8a 100644 --- a/flake.nix +++ b/flake.nix @@ -35,6 +35,8 @@ tools = [ pkgs.just + pkgs.pkg-config + pkgs.glib ]; in { From 5ba901d8b2157378ab8e491b024693a79e2d749b Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Sep 2025 17:40:47 -0700 Subject: [PATCH 5/7] Fix CI maybe. --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index 320fd8a..e28934e 100644 --- a/flake.nix +++ b/flake.nix @@ -37,6 +37,7 @@ pkgs.just pkgs.pkg-config pkgs.glib + pkgs.gtk3 ]; in { From b270fa880aedb885a244566192b78a5fce683b08 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 9 Sep 2025 17:52:21 -0700 Subject: [PATCH 6/7] Remove unused permissons. --- .github/workflows/pr.yml | 4 +- justfile | 2 +- web-transport-tauri/Cargo.toml | 2 +- .../commands/accept-bidirectional.toml | 13 --- .../commands/accept-unidirectional.toml | 13 --- .../autogenerated/commands/close-stream.toml | 13 --- .../commands/create-bidirectional.toml | 13 --- .../commands/create-unidirectional.toml | 13 --- .../autogenerated/commands/ping.toml | 13 --- .../autogenerated/commands/read-stream.toml | 13 --- .../autogenerated/commands/reset-stream.toml | 13 --- .../autogenerated/commands/write-stream.toml | 13 --- .../permissions/schemas/schema.json | 108 ------------------ web-transport-tauri/src/desktop.rs | 4 + 14 files changed, 9 insertions(+), 228 deletions(-) delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/accept-bidirectional.toml delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/accept-unidirectional.toml delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/close-stream.toml delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/create-bidirectional.toml delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/create-unidirectional.toml delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/ping.toml delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/read-stream.toml delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/reset-stream.toml delete mode 100644 web-transport-tauri/permissions/autogenerated/commands/write-stream.toml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a0966e1..bbd72c6 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,4 +1,6 @@ name: CI +permissions: + contents: read on: pull_request: @@ -12,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Install Nix - uses: cachix/install-nix-action@v27 diff --git a/justfile b/justfile index efb9e40..d2f8507 100644 --- a/justfile +++ b/justfile @@ -11,7 +11,7 @@ default: # Install any required dependencies. setup: - cargo install -y cargo-shear cargo-sort cargo-upgrades cargo-edit + cargo install cargo-shear cargo-sort cargo-upgrades cargo-edit # Run the CI checks check: diff --git a/web-transport-tauri/Cargo.toml b/web-transport-tauri/Cargo.toml index a5674d8..742fadb 100644 --- a/web-transport-tauri/Cargo.toml +++ b/web-transport-tauri/Cargo.toml @@ -14,7 +14,7 @@ hex = "0.4.3" serde = "1.0" tauri = { version = "2.5.0" } thiserror = "2" -web-transport = "0.9" +web-transport = { version = "0.9.0", path = "../web-transport" } [build-dependencies] tauri-plugin = { version = "2.2.0", features = ["build"] } diff --git a/web-transport-tauri/permissions/autogenerated/commands/accept-bidirectional.toml b/web-transport-tauri/permissions/autogenerated/commands/accept-bidirectional.toml deleted file mode 100644 index 4a3e613..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/accept-bidirectional.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-accept-bidirectional" -description = "Enables the accept-bidirectional command without any pre-configured scope." -commands.allow = ["accept-bidirectional"] - -[[permission]] -identifier = "deny-accept-bidirectional" -description = "Denies the accept-bidirectional command without any pre-configured scope." -commands.deny = ["accept-bidirectional"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/accept-unidirectional.toml b/web-transport-tauri/permissions/autogenerated/commands/accept-unidirectional.toml deleted file mode 100644 index 71ae072..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/accept-unidirectional.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-accept-unidirectional" -description = "Enables the accept-unidirectional command without any pre-configured scope." -commands.allow = ["accept-unidirectional"] - -[[permission]] -identifier = "deny-accept-unidirectional" -description = "Denies the accept-unidirectional command without any pre-configured scope." -commands.deny = ["accept-unidirectional"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/close-stream.toml b/web-transport-tauri/permissions/autogenerated/commands/close-stream.toml deleted file mode 100644 index c56bccd..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/close-stream.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-close-stream" -description = "Enables the close-stream command without any pre-configured scope." -commands.allow = ["close-stream"] - -[[permission]] -identifier = "deny-close-stream" -description = "Denies the close-stream command without any pre-configured scope." -commands.deny = ["close-stream"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/create-bidirectional.toml b/web-transport-tauri/permissions/autogenerated/commands/create-bidirectional.toml deleted file mode 100644 index c07ae9e..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/create-bidirectional.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-create-bidirectional" -description = "Enables the create-bidirectional command without any pre-configured scope." -commands.allow = ["create-bidirectional"] - -[[permission]] -identifier = "deny-create-bidirectional" -description = "Denies the create-bidirectional command without any pre-configured scope." -commands.deny = ["create-bidirectional"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/create-unidirectional.toml b/web-transport-tauri/permissions/autogenerated/commands/create-unidirectional.toml deleted file mode 100644 index 1880494..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/create-unidirectional.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-create-unidirectional" -description = "Enables the create-unidirectional command without any pre-configured scope." -commands.allow = ["create-unidirectional"] - -[[permission]] -identifier = "deny-create-unidirectional" -description = "Denies the create-unidirectional command without any pre-configured scope." -commands.deny = ["create-unidirectional"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/ping.toml b/web-transport-tauri/permissions/autogenerated/commands/ping.toml deleted file mode 100644 index 1d13588..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/ping.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-ping" -description = "Enables the ping command without any pre-configured scope." -commands.allow = ["ping"] - -[[permission]] -identifier = "deny-ping" -description = "Denies the ping command without any pre-configured scope." -commands.deny = ["ping"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/read-stream.toml b/web-transport-tauri/permissions/autogenerated/commands/read-stream.toml deleted file mode 100644 index 071ff98..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/read-stream.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-read-stream" -description = "Enables the read-stream command without any pre-configured scope." -commands.allow = ["read-stream"] - -[[permission]] -identifier = "deny-read-stream" -description = "Denies the read-stream command without any pre-configured scope." -commands.deny = ["read-stream"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/reset-stream.toml b/web-transport-tauri/permissions/autogenerated/commands/reset-stream.toml deleted file mode 100644 index c0abd8c..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/reset-stream.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-reset-stream" -description = "Enables the reset-stream command without any pre-configured scope." -commands.allow = ["reset-stream"] - -[[permission]] -identifier = "deny-reset-stream" -description = "Denies the reset-stream command without any pre-configured scope." -commands.deny = ["reset-stream"] diff --git a/web-transport-tauri/permissions/autogenerated/commands/write-stream.toml b/web-transport-tauri/permissions/autogenerated/commands/write-stream.toml deleted file mode 100644 index ea0a01a..0000000 --- a/web-transport-tauri/permissions/autogenerated/commands/write-stream.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-write-stream" -description = "Enables the write-stream command without any pre-configured scope." -commands.allow = ["write-stream"] - -[[permission]] -identifier = "deny-write-stream" -description = "Denies the write-stream command without any pre-configured scope." -commands.deny = ["write-stream"] diff --git a/web-transport-tauri/permissions/schemas/schema.json b/web-transport-tauri/permissions/schemas/schema.json index b6c49f0..ffcba52 100644 --- a/web-transport-tauri/permissions/schemas/schema.json +++ b/web-transport-tauri/permissions/schemas/schema.json @@ -294,30 +294,6 @@ "PermissionKind": { "type": "string", "oneOf": [ - { - "description": "Enables the accept-bidirectional command without any pre-configured scope.", - "type": "string", - "const": "allow-accept-bidirectional", - "markdownDescription": "Enables the accept-bidirectional command without any pre-configured scope." - }, - { - "description": "Denies the accept-bidirectional command without any pre-configured scope.", - "type": "string", - "const": "deny-accept-bidirectional", - "markdownDescription": "Denies the accept-bidirectional command without any pre-configured scope." - }, - { - "description": "Enables the accept-unidirectional command without any pre-configured scope.", - "type": "string", - "const": "allow-accept-unidirectional", - "markdownDescription": "Enables the accept-unidirectional command without any pre-configured scope." - }, - { - "description": "Denies the accept-unidirectional command without any pre-configured scope.", - "type": "string", - "const": "deny-accept-unidirectional", - "markdownDescription": "Denies the accept-unidirectional command without any pre-configured scope." - }, { "description": "Enables the accept command without any pre-configured scope.", "type": "string", @@ -330,18 +306,6 @@ "const": "deny-accept", "markdownDescription": "Denies the accept command without any pre-configured scope." }, - { - "description": "Enables the close-stream command without any pre-configured scope.", - "type": "string", - "const": "allow-close-stream", - "markdownDescription": "Enables the close-stream command without any pre-configured scope." - }, - { - "description": "Denies the close-stream command without any pre-configured scope.", - "type": "string", - "const": "deny-close-stream", - "markdownDescription": "Denies the close-stream command without any pre-configured scope." - }, { "description": "Enables the close command without any pre-configured scope.", "type": "string", @@ -378,30 +342,6 @@ "const": "deny-connect", "markdownDescription": "Denies the connect command without any pre-configured scope." }, - { - "description": "Enables the create-bidirectional command without any pre-configured scope.", - "type": "string", - "const": "allow-create-bidirectional", - "markdownDescription": "Enables the create-bidirectional command without any pre-configured scope." - }, - { - "description": "Denies the create-bidirectional command without any pre-configured scope.", - "type": "string", - "const": "deny-create-bidirectional", - "markdownDescription": "Denies the create-bidirectional command without any pre-configured scope." - }, - { - "description": "Enables the create-unidirectional command without any pre-configured scope.", - "type": "string", - "const": "allow-create-unidirectional", - "markdownDescription": "Enables the create-unidirectional command without any pre-configured scope." - }, - { - "description": "Denies the create-unidirectional command without any pre-configured scope.", - "type": "string", - "const": "deny-create-unidirectional", - "markdownDescription": "Denies the create-unidirectional command without any pre-configured scope." - }, { "description": "Enables the open command without any pre-configured scope.", "type": "string", @@ -414,30 +354,6 @@ "const": "deny-open", "markdownDescription": "Denies the open command without any pre-configured scope." }, - { - "description": "Enables the ping command without any pre-configured scope.", - "type": "string", - "const": "allow-ping", - "markdownDescription": "Enables the ping command without any pre-configured scope." - }, - { - "description": "Denies the ping command without any pre-configured scope.", - "type": "string", - "const": "deny-ping", - "markdownDescription": "Denies the ping command without any pre-configured scope." - }, - { - "description": "Enables the read-stream command without any pre-configured scope.", - "type": "string", - "const": "allow-read-stream", - "markdownDescription": "Enables the read-stream command without any pre-configured scope." - }, - { - "description": "Denies the read-stream command without any pre-configured scope.", - "type": "string", - "const": "deny-read-stream", - "markdownDescription": "Denies the read-stream command without any pre-configured scope." - }, { "description": "Enables the read command without any pre-configured scope.", "type": "string", @@ -450,18 +366,6 @@ "const": "deny-read", "markdownDescription": "Denies the read command without any pre-configured scope." }, - { - "description": "Enables the reset-stream command without any pre-configured scope.", - "type": "string", - "const": "allow-reset-stream", - "markdownDescription": "Enables the reset-stream command without any pre-configured scope." - }, - { - "description": "Denies the reset-stream command without any pre-configured scope.", - "type": "string", - "const": "deny-reset-stream", - "markdownDescription": "Denies the reset-stream command without any pre-configured scope." - }, { "description": "Enables the reset command without any pre-configured scope.", "type": "string", @@ -474,18 +378,6 @@ "const": "deny-reset", "markdownDescription": "Denies the reset command without any pre-configured scope." }, - { - "description": "Enables the write-stream command without any pre-configured scope.", - "type": "string", - "const": "allow-write-stream", - "markdownDescription": "Enables the write-stream command without any pre-configured scope." - }, - { - "description": "Denies the write-stream command without any pre-configured scope.", - "type": "string", - "const": "deny-write-stream", - "markdownDescription": "Denies the write-stream command without any pre-configured scope." - }, { "description": "Enables the write command without any pre-configured scope.", "type": "string", diff --git a/web-transport-tauri/src/desktop.rs b/web-transport-tauri/src/desktop.rs index 0316e2a..ce9f5fd 100644 --- a/web-transport-tauri/src/desktop.rs +++ b/web-transport-tauri/src/desktop.rs @@ -77,6 +77,10 @@ impl WebTransport { .inner .close(payload.code, payload.reason.as_deref().unwrap_or("")); + // Clean up all stream resources + session.send.lock().unwrap().clear(); + session.recv.lock().unwrap().clear(); + Ok(CloseResponse {}) } From 8b88a54f835c5251a9fad388d8ff3c72f7bf6ec9 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 29 Sep 2025 11:26:39 -0700 Subject: [PATCH 7/7] dunno --- .../permissions/autogenerated/reference.md | 234 ------------------ 1 file changed, 234 deletions(-) diff --git a/web-transport-tauri/permissions/autogenerated/reference.md b/web-transport-tauri/permissions/autogenerated/reference.md index 2bf2bc3..549d407 100644 --- a/web-transport-tauri/permissions/autogenerated/reference.md +++ b/web-transport-tauri/permissions/autogenerated/reference.md @@ -22,58 +22,6 @@ Default permissions for the plugin - - - -`web-transport-tauri:allow-accept-bidirectional` - - - - -Enables the accept-bidirectional command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-accept-bidirectional` - - - - -Denies the accept-bidirectional command without any pre-configured scope. - - - - - - - -`web-transport-tauri:allow-accept-unidirectional` - - - - -Enables the accept-unidirectional command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-accept-unidirectional` - - - - -Denies the accept-unidirectional command without any pre-configured scope. - - - - @@ -103,32 +51,6 @@ Denies the accept command without any pre-configured scope. -`web-transport-tauri:allow-close-stream` - - - - -Enables the close-stream command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-close-stream` - - - - -Denies the close-stream command without any pre-configured scope. - - - - - - - `web-transport-tauri:allow-close` @@ -207,58 +129,6 @@ Denies the connect command without any pre-configured scope. -`web-transport-tauri:allow-create-bidirectional` - - - - -Enables the create-bidirectional command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-create-bidirectional` - - - - -Denies the create-bidirectional command without any pre-configured scope. - - - - - - - -`web-transport-tauri:allow-create-unidirectional` - - - - -Enables the create-unidirectional command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-create-unidirectional` - - - - -Denies the create-unidirectional command without any pre-configured scope. - - - - - - - `web-transport-tauri:allow-open` @@ -285,58 +155,6 @@ Denies the open command without any pre-configured scope. -`web-transport-tauri:allow-ping` - - - - -Enables the ping command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-ping` - - - - -Denies the ping command without any pre-configured scope. - - - - - - - -`web-transport-tauri:allow-read-stream` - - - - -Enables the read-stream command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-read-stream` - - - - -Denies the read-stream command without any pre-configured scope. - - - - - - - `web-transport-tauri:allow-read` @@ -363,32 +181,6 @@ Denies the read command without any pre-configured scope. -`web-transport-tauri:allow-reset-stream` - - - - -Enables the reset-stream command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-reset-stream` - - - - -Denies the reset-stream command without any pre-configured scope. - - - - - - - `web-transport-tauri:allow-reset` @@ -415,32 +207,6 @@ Denies the reset command without any pre-configured scope. -`web-transport-tauri:allow-write-stream` - - - - -Enables the write-stream command without any pre-configured scope. - - - - - - - -`web-transport-tauri:deny-write-stream` - - - - -Denies the write-stream command without any pre-configured scope. - - - - - - - `web-transport-tauri:allow-write`