# @momics/iroh-http-tauri
[](https://www.npmjs.com/package/@momics/iroh-http-tauri)
> Pre-v1.0. APIs may change between minor releases.
Tauri v2 plugin for [iroh-http](https://github.com/momics/iroh-http). Runs as a
Rust plugin with capability-based permissions. Your frontend JS only gets the
network access you grant.
## Install
**Frontend:**
```sh
npm install @momics/iroh-http-tauri
```
**Rust plugin** in `src-tauri/Cargo.toml`:
```toml
[dependencies]
tauri-plugin-iroh-http = "0.6"
```
**Register** in `src-tauri/src/lib.rs`:
```rust
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_iroh_http::init())
.run(tauri::generate_context!())
.unwrap();
}
```
To enable native `httpi://` URL resolution in the webview (see
[below](#httpi-scheme-handler)):
```rust
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_iroh_http::builder().with_scheme().build())
.run(tauri::generate_context!())
.unwrap();
}
```
### iOS
The plugin's network-interface enumeration links Apple's `SystemConfiguration`
framework, which iOS does not link automatically. Add it to your app's
`bundle.iOS.frameworks` and recreate the iOS project:
```jsonc
// src-tauri/tauri.conf.json
{
"bundle": {
"iOS": {
"frameworks": ["SystemConfiguration"]
}
}
}
```
```sh
npm run tauri ios init # regenerate the Xcode project so the framework applies
```
Without this, the Xcode link step fails with missing `_kSCNetwork*` /
`_kSCProp*` symbols.
If you use mDNS discovery (`node.browsePeers()` / `node.advertisePeer()` or the
generic `node.browse()` / `node.advertise()` APIs), iOS also gates local-network
access behind a user permission. Declare it and every Bonjour service type you
use in `src-tauri/Info.ios.plist`, which Tauri merges into the generated iOS
`Info.plist`:
```xml
NSLocalNetworkUsageDescription
Discover and connect to nearby peers on your local network.
NSBonjourServices
_iroh-http._udp
_printers._tcp
```
Without these, `NWBrowser` is denied with `NWError -65555 (NoAuth)` and browsing
silently restarts. Declare each service-name/protocol pair exactly as it appears
on the wire: for example, `serviceName: "printers", protocol: "tcp"` requires
`_printers._tcp`, while peer discovery always uses UDP.
For the full iOS + Android setup (including the Android `AndroidManifest.xml`
entries), see the
[Mobile mDNS / DNS-SD setup guide](../../docs/guidelines/mobile-mdns-setup.md).
### Android
The plugin merges `ACCESS_NETWORK_STATE` and `CHANGE_WIFI_MULTICAST_STATE` into
the application manifest. Before T extension 7, it shares one multicast lock
across active DNS-SD browse and advertisement sessions; newer foreground apps
use system-managed multicast. Ensure the final app also has `INTERNET` (normally
supplied by Tauri). Apps that later target Android 17 / API 37 must add the
platform's `ACCESS_LOCAL_NETWORK` runtime-permission flow; do not add it to
lower-target applications.
## Quick start
```ts
import { createNode } from "@momics/iroh-http-tauri";
const node = await createNode();
console.log("Node ID:", node.publicKey.toString());
node.serve({}, (req) => {
if (req.headers.get("Peer-Id") !== ALLOWED_PEER) {
return new Response("Forbidden", { status: 403 });
}
return new Response("hello");
});
const res = await node.fetch("httpi:///");
console.log(await res.text());
```
## Full API
The API is identical across Node.js, Deno, and Tauri: HTTP fetch/serve, QUIC
sessions, mDNS discovery, and Ed25519 crypto. See the
[API overview](../../docs/api-overview.md) for the complete reference.
## Permissions
Tauri's capability system controls what the frontend can access. Declare
permissions in `capabilities/default.json`:
| Permission | Covers |
| --------------------- | ------------------------------------------------------------------------------------- |
| `iroh-http:default` | `createNode()`, `close()`, node introspection |
| `iroh-http:fetch` | `node.fetch()` + body streaming |
| `iroh-http:serve` | `node.serve()` + body streaming |
| `iroh-http:connect` | Raw QUIC sessions (bidi streams, datagrams) |
| `iroh-http:discovery` | Local-network discovery: `advertisePeer`/`browsePeers` + generic `advertise`/`browse` |
| `iroh-http:crypto` | Key generation, signing, verification |
A typical app using fetch, serve, and local discovery:
```json
{
"permissions": [
"iroh-http:default",
"iroh-http:fetch",
"iroh-http:serve",
"iroh-http:discovery"
]
}
```
## `httpi://` scheme handler
Call `.with_scheme()` on the plugin builder to register `httpi://` as a native
URI scheme in the webview. Once an endpoint is created, standard browser APIs
resolve `httpi://` URLs directly through iroh-http-core — no JavaScript bridging
required.
```ts
// After createNode(), these all just work:
const res = await fetch("httpi:///path");
document.querySelector("img").src = "httpi:///photo.jpg";
document.querySelector("audio").src = "httpi:///track.flac"; // seeking supported
```
The handler auto-binds to the first endpoint created. There is nothing else to
configure.
> **GET only:** The scheme handler resolves GET requests. Non-GET callers
> receive `405 Method Not Allowed`. Use `node.fetch()` for POST, PUT, DELETE.
> **Platform note:** On macOS, Linux, and iOS the origin is
> `httpi:///path`. On Windows and Android, Tauri rewrites the origin to
> `http://httpi.localhost/path` — the handler accounts for this automatically.
### Response size limit
Tauri's custom-protocol responder takes a **complete body, not a stream**, so
the scheme handler must buffer each `httpi://` response fully in memory before
handing it to the webview. To stop a large or hostile peer from exhausting app
memory, the handler caps a single buffered response at **64 MiB by default**
(`DEFAULT_SCHEME_MAX_RESPONSE_BYTES`).
This cap is intentionally **per response, not per file**: media plays back
through many small `Range` requests, each well under the cap, so videos and
audio of any size stream fine as long as the serving peer supports `Range` (it
does by default). The limit only rejects a single _non-ranged_ response larger
than the cap.
Override it on the builder when you knowingly serve large non-ranged assets:
```rust
tauri::Builder::default()
.plugin(
tauri_plugin_iroh_http::builder()
.with_scheme()
.max_response_bytes(256 * 1024 * 1024) // 256 MiB
.build(),
);
```
The cap applies **only** to the `httpi://` scheme handler. The `node.fetch()`
IPC path streams the body and is bounded by your own consuming code, not by this
value.
## Tauri specifics
- Serve callbacks are delivered to the frontend via Tauri `Channel` events (push
model).
- All crypto functions are async (round-trip through the Rust plugin via Tauri
invoke).
- QUIC sessions require the `iroh-http:connect` permission.
- Local-network discovery requires the `iroh-http:discovery` permission.
- The `httpi://` scheme handler (opt-in via `.with_scheme()`) enables native URL
resolution without IPC overhead.
## Supported platforms
| Platform | Architecture | Minimum | Status |
| -------- | :---------------------: | :-----: | :----: |
| macOS | x86_64 | — | ✅ |
| macOS | aarch64 (Apple Silicon) | — | ✅ |
| Linux | x86_64 | — | ✅ |
| Linux | aarch64 | — | ✅ |
| Windows | x86_64 | — | ✅ |
| iOS | aarch64 | iOS 14 | ✅ |
| Android | arm64 / x86_64 | API 21 | ✅ |
Applications targeting Android 17 / API 37 or newer must additionally adopt
Android's app-owned `ACCESS_LOCAL_NETWORK` runtime-permission flow described
above.
## Other runtimes
| Runtime | Package |
| ------- | -------------------------------------------------------------------------------- |
| Node.js | [`@momics/iroh-http-node`](https://www.npmjs.com/package/@momics/iroh-http-node) |
| Deno | [`@momics/iroh-http-deno`](https://jsr.io/@momics/iroh-http-deno) |
## License
MIT OR Apache-2.0