diff --git a/hugo-site/content/apps/_index.md b/hugo-site/content/apps/_index.md index 75d1ae89..d869c7cf 100644 --- a/hugo-site/content/apps/_index.md +++ b/hugo-site/content/apps/_index.md @@ -75,7 +75,8 @@ still at the RFC stage. Decentralized live social feed (posts, profiles, follows, likes), designed as a high-churn live surface that complements [Atlas](#atlas) for durable -discovery and archival. +discovery and archival. Built with TypeScript and Vite, Raven is also the +reference frontend for the [TypeScript SDK](/build/manual/typescript-sdk/). {{< app-screenshot light="/images/apps/raven.png" alt="Raven microblogging interface" >}} diff --git a/hugo-site/content/build/_index.md b/hugo-site/content/build/_index.md index 1bbe74a1..e7286b52 100644 --- a/hugo-site/content/build/_index.md +++ b/hugo-site/content/build/_index.md @@ -14,17 +14,20 @@ peer-to-peer network with no servers to maintain. - [Tutorial](/build/manual/tutorial/) - Build your first Freenet app - [Manual](/build/manual/) - Architecture, components, and reference +- [TypeScript SDK](/build/manual/typescript-sdk/) - Connect a browser or Node.js UI to a node with `@freenetorg/freenet-stdlib` ## AI-Assisted Development -Install the [freenet-dapp-builder](https://github.com/freenet/freenet-agent-skills/tree/main/skills/dapp-builder) skill for Claude Code: +Install the [dapp-builder](https://github.com/freenet/freenet-agent-skills/tree/main/skills/dapp-builder) skill for Claude Code: ```bash /plugin marketplace add freenet/freenet-agent-skills -/plugin install freenet-dapp-builder +/plugin install freenet@freenet-agent-skills ``` -This skill guides you through building contracts, delegates, and UI for Freenet apps. +This installs the `freenet` plugin, whose `dapp-builder` skill guides you through building +contracts, delegates, and UI for Freenet apps. The plugin also bundles `local-dev` and other +development skills. ## Developer Tools diff --git a/hugo-site/content/build/manual/_index.md b/hugo-site/content/build/manual/_index.md index 6aceedd0..1f087840 100644 --- a/hugo-site/content/build/manual/_index.md +++ b/hugo-site/content/build/manual/_index.md @@ -19,10 +19,11 @@ usage. Use the table of contents below to navigate through the manual. 2. [Components](#components) 3. [Architecture](#architecture) 4. [Developer Guide](#developer-guide) -5. [Examples](#examples) -6. [Community and Support](#community-and-support) -7. [Reference](#reference) -8. [Further reading](#further-reading) +5. [Client SDKs](#client-sdks) +6. [Examples](#examples) +7. [Community and Support](#community-and-support) +8. [Reference](#reference) +9. [Further reading](#further-reading) --- @@ -34,7 +35,7 @@ Learn the basics of Freenet and its purpose. --- -## Components +## Components {#components} Explore the key components of Freenet: @@ -60,15 +61,31 @@ Understand Freenet's architecture and how it works: Resources for building on Freenet: - [Publish a Website](publish-a-website): Host a static website on Freenet -- no coding required. -- [Remote Access to a Node](remote-access): Safely reach your local node's API from another device (SSH tunnel, Tailscale). +- [Remote Access to a Node](remote-access): Safely reach your local node's API from another device + (SSH tunnel, Tailscale). - [Tutorial: Create an App](tutorial): Step-by-step guide to creating a decentralized app. -- [Contract Interfaces](contract-interface): Reference for contract interfaces. +- [Contract Interfaces](contract-interface): The Rust contract-authoring API (`ContractInterface`). + Full API on [docs.rs](https://docs.rs/freenet-stdlib). - [Manifest Format](manifest): Details about the `freenet.toml` configuration format. --- +## Client SDKs {#client-sdks} + +Libraries for connecting a user interface to a Freenet node over WebSocket: + +- [TypeScript SDK](typescript-sdk): The browser/Node.js client -- `@freenetorg/freenet-stdlib`. + Recommended for most UIs. +- Rust client (Dioxus): use `freenet-stdlib` with the `net` feature; see + [docs.rs](https://docs.rs/freenet-stdlib) and [River](https://github.com/freenet/river) for the + reference implementation. + +--- + ## Examples {#examples} +- [Example Apps](example-app): Canonical apps to learn from -- freenet-ping (minimal Rust), Raven + (TypeScript + Vite), and River (Dioxus). - [Antiflood Tokens](examples/antiflood-tokens) - [Blind Trust Tokens](examples/blind-trust-tokens) @@ -97,8 +114,7 @@ Deep-dive articles on the design principles behind Freenet's architecture: - [Understanding Small World Networks](/build/manual/further-reading/small-world-networks/): the routing intuition behind the P2P network. How Freenet finds destinations in just a few hops without a central index. -- [Understanding Freenet's Delta-Sync](/build/manual/further-reading/delta-sync/): how shared - state stays consistent across the network using mergeable, additive updates rather than full - snapshots. +- [Understanding Freenet's Delta-Sync](/build/manual/further-reading/delta-sync/): how shared state + stays consistent across the network using mergeable, additive updates rather than full snapshots. See [Further reading](/build/manual/further-reading/) for the full collection. diff --git a/hugo-site/content/build/manual/components/delegates.md b/hugo-site/content/build/manual/components/delegates.md index 711e53f1..ed9fbb3e 100644 --- a/hugo-site/content/build/manual/components/delegates.md +++ b/hugo-site/content/build/manual/components/delegates.md @@ -79,7 +79,7 @@ Beyond handling messages, delegates can create, read, and modify contracts; crea A **key manager delegate** stores private keys and signs data on request, possibly prompting the user for permission. An **inbox delegate** monitors an inbox contract, downloads new messages, decrypts them, and stores them privately for UIs to display. A **contacts delegate** stores and retrieves contact information. An **alerts delegate** watches for events (like mentions in a discussion) and notifies the user. -Delegates store their state encrypted on the local device. Recent prior values are kept as snapshots so accidental overwrites can be recovered. Cross-device synchronization is not yet implemented — see [issue #3050](https://github.com/freenet/freenet-core/issues/3050) for the planned vault-delegate primitive. +Delegates store their state encrypted on the local device. Recent prior values are kept as snapshots so accidental overwrites can be recovered. Cross-device synchronization is not yet implemented; see [issue #3050](https://github.com/freenet/freenet-core/issues/3050) for the planned vault-delegate primitive. For a real-world example, River's [chat delegate](https://github.com/freenet/river/tree/main/delegates/chat-delegate) stores per-room signing keys and signs messages, invitations, and room configurations on behalf of the UI, without ever exposing the keys. diff --git a/hugo-site/content/build/manual/components/overview.md b/hugo-site/content/build/manual/components/overview.md index 72d7989c..614c0184 100644 --- a/hugo-site/content/build/manual/components/overview.md +++ b/hugo-site/content/build/manual/components/overview.md @@ -11,9 +11,11 @@ aliases: Delegates, contracts, and user interfaces (UIs) each serve distinct roles in the Freenet ecosystem. [Contracts](/build/manual/components/contracts) control public data, or "shared state". -[Delegates](/build/manual/components/delegates) act as the user's agent and can store private data on the -user's behalf, while [User Interfaces](/build/manual/components/ui) provide an interface between these and -the user through a web browser. UIs are distributed through the P2P network via contracts. +[Delegates](/build/manual/components/delegates) act as the user's agent and can store private data +on the user's behalf, while [User Interfaces](/build/manual/components/ui) provide an interface +between these and the user through a web browser. UIs are distributed through the P2P network via +contracts and connect to the node through a client SDK; see +[Client SDKs](/build/manual/#client-sdks). ![Architectural Primitives Diagram](/components.svg) @@ -24,7 +26,8 @@ Its primary functions are: - Providing a user-friendly interface to access Freenet via a web browser - Host the user's [delegates](/build/manual/components/delegates) and the private data they store -- Host [contracts](/build/manual/components/contracts) and their associated data on behalf of the network +- Host [contracts](/build/manual/components/contracts) and their associated data on behalf of the + network - Manage communication between contracts, delegates, and UI components Built with Rust, the core is designed to be compact (ideally under 5 MB), efficient, and capable of diff --git a/hugo-site/content/build/manual/components/ui.md b/hugo-site/content/build/manual/components/ui.md index 45cd40a7..7c4f32d0 100644 --- a/hugo-site/content/build/manual/components/ui.md +++ b/hugo-site/content/build/manual/components/ui.md @@ -10,8 +10,8 @@ On the normal web, a user might visit `https://gmail.com/`, their browser will d user interface which then runs in their browser and connects back to the Gmail servers. On Freenet the user interface is downloaded from a Freenet contract, and it -[interacts](/build/manual/components/overview) with contracts and delegates by sending messages through -the Freenet core. +[interacts](/build/manual/components/overview) with contracts and delegates by sending messages +through the Freenet core. ![Delegate, Contract, and UI Diagram](/ui_delegate_contract.svg) @@ -20,4 +20,105 @@ over Freenet and run in a web browser. UIs can create, retrieve, and update cont WebSocket connection to the local Freenet peer, as well as communicate with delegates. Because UIs run in a web browser, they can be built using any web framework, such as React, Angular, -Vue.js, Bootstrap, and so on. +Vue.js, Bootstrap, and so on. There are two first-class paths: + +- **TypeScript + Vite**: a standard web frontend that talks to the node over WebSocket using the + published TypeScript SDK (below). This is the recommended path for most apps. +- **Dioxus (Rust to WebAssembly)**: a Rust UI framework that lets you share types and logic with + your contracts. See [River](https://github.com/freenet/river) for a complete example. + +## The TypeScript SDK + +The SDK is published to npm as +[`@freenetorg/freenet-stdlib`](https://www.npmjs.com/package/@freenetorg/freenet-stdlib). Install +the latest release (no version pin needed, npm fetches the current one): + +```bash +npm install @freenetorg/freenet-stdlib +``` + +The essentials are below; for the complete API (every request/response class, update types, +streaming, and delegates) see the [TypeScript SDK reference](/build/manual/typescript-sdk/). + +### Connecting to the node + +The SDK's `FreenetWsApi` opens a WebSocket to the local node. Derive the URL from the page location +rather than hardcoding a host or port, because the host is not known ahead of time when your app is +served from a Freenet container: + +```typescript +import { + FreenetWsApi, + ContractKey, + GetRequest, + SubscribeRequest, + UpdateRequest, + UpdateData, + UpdateDataType, + DeltaUpdate, +} from "@freenetorg/freenet-stdlib"; + +// The third argument is the auth token. When your app runs inside the +// Freenet web-container shell, leave it empty; the shell injects auth. +const wsUrl = new URL(`ws://${location.host}/v1/contract/command`); +const api = new FreenetWsApi(wsUrl, handler, ""); +``` + +`handler` is a `ResponseHandler` object: the node pushes results and live updates to its callbacks +(`onContractGet`, `onContractUpdateNotification`, `onErr`, `onOpen`, and more). A minimal handler: + +```typescript +const handler = { + onOpen: () => console.log("[freenet] connected"), + onContractGet: (r) => { + /* initial state */ + }, + onContractUpdateNotification: (n) => { + /* live delta from a subscription */ + }, + onContractPut: () => {}, + onContractUpdate: () => {}, + onContractNotFound: () => {}, + onDelegateResponse: () => {}, + onErr: (e) => console.error("[freenet]", e.cause), +}; +``` + +### Reading, subscribing, and updating + +The request methods are promise-based (`await`); each also fires the matching handler callback. The +default request timeout is 30 seconds. + +```typescript +const contractKey = ContractKey.fromInstanceId(""); + +// GET: fetch current state (pass true to also fetch the contract code) +const response = await api.get(new GetRequest(contractKey, true)); +const state = JSON.parse(new TextDecoder().decode(Uint8Array.from(response.state))); + +// SUBSCRIBE: receive real-time updates via onContractUpdateNotification +await api.subscribe(new SubscribeRequest(contractKey, [])); + +// UPDATE: send a delta +const deltaBytes = new TextEncoder().encode(JSON.stringify(myDelta)); +const delta = new DeltaUpdate(Array.from(deltaBytes)); +const update = new UpdateData(UpdateDataType.DeltaUpdate, delta); +await api.update(new UpdateRequest(contractKey, update)); +``` + +Reads are not correlated to their responses: the SDK settles `get()` promises in FIFO order, so do +not run multiple `get()` calls concurrently; serialize them. See +[Request ordering](/build/manual/typescript-sdk/#request-ordering) in the SDK reference. + +### Packaging notes + +When your UI is served from a Freenet web container it runs inside a sandboxed iframe with a strict +Content Security Policy: + +- Set `base: "./"` in your Vite config so assets resolve relative to the container URL. +- Prefer vendoring third-party CSS/JS locally (copy into `public/`); the gateway applies a Content + Security Policy, so verify any remote origins (such as a font CDN) against a real gateway. + +See the [tutorial](/build/manual/tutorial/) for an end-to-end walkthrough and +[Raven](https://github.com/freenet/raven) (a decentralized microblogging app) for a complete +TypeScript + Vite reference. diff --git a/hugo-site/content/build/manual/contract-interface.md b/hugo-site/content/build/manual/contract-interface.md index 94d9e468..74e70567 100644 --- a/hugo-site/content/build/manual/contract-interface.md +++ b/hugo-site/content/build/manual/contract-interface.md @@ -6,6 +6,9 @@ aliases: - /resources/manual/contract-interface/ --- +This page documents the Rust contract-authoring API. To connect a user interface to a node, see +[Client SDKs](/build/manual/#client-sdks). + ## Terms - [Contract State](/build/manual/glossary#contract-state) - data associated with a contract that can be diff --git a/hugo-site/content/build/manual/docker.md b/hugo-site/content/build/manual/docker.md index 645acd03..ed0fd087 100644 --- a/hugo-site/content/build/manual/docker.md +++ b/hugo-site/content/build/manual/docker.md @@ -70,5 +70,5 @@ CONTRACT_SRC_DIR=./web /location/of/freenet/docker/fdev.sh build From the base directory of the contract project. ```sh -/location/of/freenet/docker/fdev.sh publish --code target/wasm32-unknown-unknown/release/freenet_microblogging_web.wasm --state web/build/freenet/contract-state +/location/of/freenet/docker/fdev.sh publish --code target/wasm32-unknown-unknown/release/my_contract.wasm contract --state web/build/freenet/contract-state ``` diff --git a/hugo-site/content/build/manual/example-app.md b/hugo-site/content/build/manual/example-app.md index 799e924a..d5da8148 100644 --- a/hugo-site/content/build/manual/example-app.md +++ b/hugo-site/content/build/manual/example-app.md @@ -1,130 +1,66 @@ --- -title: "Example App" +title: "Example Apps" date: 2025-04-13 draft: false aliases: - /resources/manual/example-app/ --- -# Build freenet from source on Linux: +The fastest way to learn how a Freenet application fits together (contract, optional delegate, and +UI) is to read a working one. These are the canonical examples, from smallest to most complete: -There is a single line command to build all freenet on Linux. +- **[freenet-ping](https://github.com/freenet/freenet-core/tree/main/apps/freenet-ping)**: the + smallest useful app. A Rust contract plus a small CLI that publishes, updates, and subscribes. + Best for understanding the contract lifecycle without any UI. +- **[Raven](https://github.com/freenet/raven)**: a decentralized microblogging app and a complete + **TypeScript + Vite** frontend built on the + [`@freenetorg/freenet-stdlib`](https://www.npmjs.com/package/@freenetorg/freenet-stdlib) SDK. The + reference for a browser frontend. +- **[River](https://github.com/freenet/river)**: a production decentralized group-chat app with a + **Dioxus** (Rust → WebAssembly) UI and a chat delegate. The reference for the patterns in the + [tutorial](/build/manual/tutorial/). -``` -git clone https://github.com/freenet/freenet-core && .\build-all.sh -``` - -then you could test it by building and deploying any web application: - -``` -.\build-examples.sh -``` - -. - -# Build freenet from source on Linux from command line: - -You could also run this: - -```bash -wget https://sh.rustup.rs ; sh index.html -y &&\ - source "$HOME/.cargo/env" &&\ - rustup default stable &&\ - rustup target add wasm32-unknown-unknown &&\ - (sh curl -L https://git.io/n-install | bash) ; ~/n/bin/n latest ; ~/n/bin/npm install -g typescript webpack &&\ - git clone https://github.com/freenet/freenet-core/ &&\ - cd freenet-core &&\ - git submodule update --init --recursive &&\ - export CARGO_TARGET_DIR="$(pwd)/target" &&\ - cd stdlib/typescript/ &&\ - npm run dev.package &&\ - cd ../.. &&\ - cargo install --path crates/core --force &&\ - cargo install --path crates/fdev --force &&\ - cd ./modules/identity-management/ &&\ - make build &&\ - cd ../antiflood-tokens/ &&\ - rm Cargo.lock ; make build &&\ - cd ../../apps/freenet-email-app &&\ - make build -``` +For a step-by-step walkthrough of building your own app, follow the +[tutorial](/build/manual/tutorial/). -Let's decompose this: - -Install rust: +## Prerequisites ```bash -wget https://sh.rustup.rs ; sh index.html -y && source "$HOME/.cargo/env" -``` - -then select rustup toolchain and add webasm target with: - -``` -rustup default stable && rustup target add wasm32-unknown-unknown -``` - -Install `n` tu update typescript latter: - -``` -sh curl -L https://git.io/n-install | bash) -``` - -Update `npm`. Warning: required! You must do that to have the latest npm packages. - -``` -~/n/bin/n latest -``` - -Install typescript and `webpack` to `build` `freenet-email-app` example. - -``` -~/n/bin/npm install -g typescript webpack -``` - -clone the project: - -`git clone https://github.com/freenet/freenet-core/ && cd freenet-core && git submodule update --init --recursive` - -set the build environment part (mandatory): +# Rust toolchain + the WebAssembly target +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +rustup target add wasm32-unknown-unknown +# The Freenet node and the fdev developer tool +git clone https://github.com/freenet/freenet-core.git +cd freenet-core +cargo install --path crates/core # `freenet` (the node) +cargo install --path crates/fdev # `fdev` (publish/dev tooling) ``` -export CARGO_TARGET_DIR="$(pwd)/target" -``` - -build typescript stdlib. -``` -cd stdlib/typescript/ && npm run dev.package && cd ../.. -``` +A TypeScript UI (such as Raven) additionally needs [npm](https://www.npmjs.com/); the SDK is pulled +in with `npm install @freenetorg/freenet-stdlib`. -Finally compile freenet: +## Running freenet-ping -``` -cargo install --path crates/core --force -cargo install --path crates/fdev --force -``` +`freenet-ping` ships a small Makefile. From `apps/freenet-ping` in the freenet-core checkout: -# Build and deploy freenet contract examples: +```bash +# Start a local node in another terminal first: +freenet local -Build identity managemenet and antiflood token freent modules: +# Build the contract and CLI +make -f run-ping.mk build +# Run against the local node's WebSocket API port +make -f run-ping.mk run WS_PORT=7509 ``` -cd ./modules/identity-management/ -make build -cd ../antiflood-tokens/ -``` - -Fix a compile issue with: -``` -rm Cargo.lock -``` +The app generates a random name and sends an update every second, logging the responses it receives +from the contract. See the +[freenet-ping README](https://github.com/freenet/freenet-core/tree/main/apps/freenet-ping) for the +full set of options. -``` -make build -``` +## Publishing a website -``` -cd ../../apps/freenet-email-app -make build -``` +To wrap a static site or a built UI in a signed web container and serve it over Freenet, see +[Publish a Website](/build/manual/publish-a-website/). diff --git a/hugo-site/content/build/manual/glossary.md b/hugo-site/content/build/manual/glossary.md index c46def72..7e7d1b61 100644 --- a/hugo-site/content/build/manual/glossary.md +++ b/hugo-site/content/build/manual/glossary.md @@ -8,10 +8,10 @@ aliases: ## Application -Software that uses Freenet as a back-end. This includes native software distributed independenly of +Software that uses Freenet as a back-end. This includes native software distributed independently of Freenet but which uses Freenet as a back-end (perhaps bundling Freenet), and -[web applications](/manual/glossary#web-application) that are distributed over Freenet and run in a -web browser. +[web applications](/build/manual/glossary#web-application) that are distributed over Freenet and run +in a web browser. ## Contract @@ -27,7 +27,7 @@ contract is to determine: A contract that contains an application or component as state, accessed through the web proxy. For example, if the contract id is `6C2KyVMtqw8D5wWa8Y7e14VmDNXXXv9CQ3m44PC9YbD2` then visiting -`http://localhost:PORT/contract/web/6C2KyVMtqw8D5wWa8Y7e14VmDNXXXv9CQ3m44PC9YbD2` will cause the +`http://localhost:7509/v1/contract/web/6C2KyVMtqw8D5wWa8Y7e14VmDNXXXv9CQ3m44PC9YbD2/` will cause the application/component to be retrieved from Freenet, decompressed, and sent to the browser where it can execute. @@ -49,8 +49,8 @@ Represents a modification to some state - similar to a [diff](https://en.wikiped source code. The exact format of a delta is determined by the contract. A contract will determine whether a delta is valid - perhaps by verifying it is signed by someone authorized to modify the contract state. A delta may be created in response to a -[State Summary](/manual/glossary#state-summary) as part of the -[State Synchronization](/manual/glossary#state-synchronization) mechanism. +[State Summary](/build/manual/glossary#state-summary) as part of the +[State Synchronization](/build/manual/glossary#state-synchronization) mechanism. ## Parameters @@ -66,9 +66,9 @@ contain the same state. In the original Freenet, this was known as a ## State Summary Given a contract state, this is a small piece of data that can be used to determine a -[delta](/manual/glossary#delta) between two contracts as part of the -[state synchronization](/manual/glossary#state-synchronization) mechanism. The format of a state -summary is determined by the state's contract. +[delta](/build/manual/glossary#delta) between two contracts as part of the +[state synchronization](/build/manual/glossary#state-synchronization) mechanism. The format of a +state summary is determined by the state's contract. ## State Synchronization @@ -81,7 +81,7 @@ efficiently merged over the network to ensure Software built on Freenet and distributed through Freenet. Applications run in the browser and can be built with tools like React, TypeScript, and Vue.js. An -application may use multiple components and [contracts](/manual/glossary#contract). +application may use multiple components and [contracts](/build/manual/glossary#contract). Applications are compressed and distributed via a -[container contract](/manual/glossary#container-contract). +[container contract](/build/manual/glossary#container-contract). diff --git a/hugo-site/content/build/manual/manifest.md b/hugo-site/content/build/manual/manifest.md index 0134d62d..a9e50e8d 100644 --- a/hugo-site/content/build/manual/manifest.md +++ b/hugo-site/content/build/manual/manifest.md @@ -11,7 +11,7 @@ the [TOML](https://toml.io/) format. Manifest files consist of the following sec - [type](#the-type-field): Contract type. - [lang](#the-lang-field): Contract source language. - [output_dir](#the-output_dir-field): Output path for build artifacts. -- [[webapp]](#the-contract-section): Configuration for UI component containers. +- [[webapp]](#the-webapp-section): Configuration for UI component containers. - [[state]](#the-state-section): Optionally seed a state. ## The `[contract]` section @@ -28,9 +28,9 @@ The type of the contract being packaged. Currently the following types are suppo - `standard`, the default type, it can be elided. This is just a standard [contract](/build/manual/glossary#contract). -- `webapp`, a web app [container contract](/build/manual/glossary#container-contract). Additionally to the - container contract the UI component source will be compiled and packaged as the state of the - contract. +- `webapp`, a web app [container contract](/build/manual/glossary#container-contract). Additionally + to the container contract the UI component source will be compiled and packaged as the state of + the contract. ### The `lang` field @@ -58,6 +58,12 @@ written to the relative directory `./build/freenet` from the manifest file direc An optional section, only specified in case of `webapp` contracts. +> **Two ways to package a UI.** The fields below let `fdev` build and bundle your web app while +> packaging the container contract. Alternatively, and what the modern **TypeScript + Vite** +> examples do, you build the UI yourself with your own tooling and publish the finished archive +> directly with `fdev publish --webapp-archive`. See [User Interface](/build/manual/components/ui/) +> and [Publish a Website](/build/manual/publish-a-website/). + ### The `lang` field ```toml @@ -135,7 +141,7 @@ posts = { path = "../contracts/posts" } An optional list of contract dependencies that will be embedded and available in the state of the contract. Each entry under this entry represents an alias to the contract code, it must include a -`path` field that specifies the relative location of the dependency from this manifesto directory. +`path` field that specifies the relative location of the dependency from this manifest directory. If dependencies are specified they will be compiled and appended to the contract state, under the `contracts` directory, and as such, become available from the HTTP gateway. A `dependencies.json` diff --git a/hugo-site/content/build/manual/tutorial.md b/hugo-site/content/build/manual/tutorial.md index 6c57e72f..0c330289 100644 --- a/hugo-site/content/build/manual/tutorial.md +++ b/hugo-site/content/build/manual/tutorial.md @@ -107,13 +107,14 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup target add wasm32-unknown-unknown ``` -### Freenet +### Freenet and fdev ```bash # Clone and build from source git clone https://github.com/freenet/freenet-core.git cd freenet-core -cargo install --path crates/core +cargo install --path crates/core # installs `freenet` (the node) +cargo install --path crates/fdev # installs `fdev` (the developer tool used to publish) ``` ### Build Tools @@ -170,14 +171,18 @@ ciborium = "0.2.2" serde = { version = "1.0", features = ["derive"] } # Freenet -freenet-scaffold = "0.2.1" -freenet-scaffold-macro = "0.2.1" -freenet-stdlib = { version = "0.1.30", features = ["contract"] } +freenet-scaffold = "0.2" +freenet-scaffold-macro = "0.2" +freenet-stdlib = { version = "0.8", features = ["contract"] } # Your shared types my-app-common = { path = "common" } ``` +> Rather than copy version numbers that age, let Cargo fill in the current releases for you: +> `cargo add freenet-stdlib --features contract` and `cargo add freenet-scaffold freenet-scaffold-macro`. +> The latest versions are on [crates.io](https://crates.io/crates/freenet-stdlib). + --- ## 5. Contract Development @@ -381,8 +386,40 @@ impl DelegateInterface for Delegate { ## 7. UI Development -The UI connects to the local Freenet Kernel via WebSocket to interact with contracts. River uses -[Dioxus](https://dioxuslabs.com), a Rust framework that compiles to WebAssembly, giving you a +The UI connects to the local Freenet Kernel via WebSocket to interact with contracts. You can use any +web framework. There are two first-class paths: **TypeScript + Vite**, a standard web frontend using +the published SDK, and **Dioxus**, a Rust framework that compiles to WebAssembly and shares code with +your contracts. River uses Dioxus. + +### TypeScript + Vite + +Install the SDK from npm (the current release, no version pin needed): + +```bash +npm install @freenetorg/freenet-stdlib +``` + +Connect to the node and interact with your contract. Derive the WebSocket URL from the page location +so the app works wherever it is served from: + +```typescript +import { FreenetWsApi, ContractKey, GetRequest, SubscribeRequest } from "@freenetorg/freenet-stdlib"; + +const wsUrl = new URL(`ws://${location.host}/v1/contract/command`); +const api = new FreenetWsApi(wsUrl, handler, ""); // empty auth: the container shell injects it + +const key = ContractKey.fromInstanceId(""); +await api.get(new GetRequest(key, true)); // promise-based; also fires handler.onContractGet +await api.subscribe(new SubscribeRequest(key, [])); // live updates via handler.onContractUpdateNotification +``` + +See [User Interface](/build/manual/components/ui/) for the full API (handler callbacks, updates, and +Vite/CSP notes) and [Raven](https://github.com/freenet/raven) (a decentralized microblogging app) +for a complete TypeScript + Vite reference. + +### Dioxus + +[Dioxus](https://dioxuslabs.com) is a Rust framework that compiles to WebAssembly, giving you a type-safe UI that shares code with your contracts. ### Basic Dioxus Component @@ -440,7 +477,7 @@ args = ["test", "--workspace"] # Start Freenet in local mode (no network) freenet local -# In another terminal, build and publish your contract +# In another terminal, build your contract (see Deployment below to publish) cargo make build ``` @@ -467,8 +504,10 @@ After building, publish your contract to the network: ```bash # The contract WASM is in target/wasm32-unknown-unknown/release/ -freenet publish \ +# `-p 7509` targets the local node's WebSocket API port (7509 is the default). +fdev -p 7509 publish \ --code target/wasm32-unknown-unknown/release/my_contract.wasm \ + contract \ --state initial_state.cbor ``` diff --git a/hugo-site/content/build/manual/typescript-sdk.md b/hugo-site/content/build/manual/typescript-sdk.md new file mode 100644 index 00000000..dcc5453c --- /dev/null +++ b/hugo-site/content/build/manual/typescript-sdk.md @@ -0,0 +1,195 @@ +--- +title: "TypeScript SDK" +date: 2026-06-05 +draft: false +--- + +[`@freenetorg/freenet-stdlib`](https://www.npmjs.com/package/@freenetorg/freenet-stdlib) is the +official TypeScript/JavaScript SDK for talking to a Freenet node from a browser or Node.js app. It +opens a WebSocket to the node and exposes a promise-based API for the contract and delegate +operations. + +> **This is a client SDK.** It connects a user interface to a node; it is not used to author +> contracts or delegates. To write contracts and delegates (in Rust), see +> [Contract Interfaces](/build/manual/contract-interface/) and the +> [`freenet-stdlib` API on docs.rs](https://docs.rs/freenet-stdlib). For the Rust UI client +> (Dioxus), use `freenet-stdlib` with the `net` feature. + +```bash +npm install @freenetorg/freenet-stdlib +``` + +No version pin is needed; npm installs the current release. In Node.js, also install the optional +`ws` package (`npm install ws`); browsers provide their own WebSocket. + +This page is the API reference. For the end-to-end app workflow see the +[tutorial](/build/manual/tutorial/), and for the UI-side overview see +[User Interface](/build/manual/components/ui/). [Raven](https://github.com/freenet/raven) (a +decentralized microblogging app) is a complete TypeScript + Vite reference. + +## Connecting: `FreenetWsApi` + +```typescript +import { FreenetWsApi, ResponseHandler } from "@freenetorg/freenet-stdlib"; + +const api = new FreenetWsApi(url, handler, authToken); +``` + +| Argument | Type | Notes | +| ----------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `url` | `URL` | The node's command WebSocket. Derive it from `location`: `new URL(\`ws://${location.host}/v1/contract/command\`)`. | +| `handler` | `ResponseHandler` | Callback object the node pushes results and live notifications to. | +| `authToken` | `string` (optional) | Leave empty (`""`) inside the Freenet web-container shell; the shell injects auth. Set it only for standalone/CLI clients. | + +The request methods are promise-based and also fire the matching `handler` callback. The default +request timeout is 30 seconds. + +```typescript +class FreenetWsApi { + async put(put: PutRequest): Promise; + async update(update: UpdateRequest): Promise; + async get(get: GetRequest): Promise; + async subscribe(subscribe: SubscribeRequest): Promise; + async disconnect(disconnect: DisconnectRequest): Promise; +} +``` + +## The `ResponseHandler` + +The node delivers results and subscription updates through this callback object. Required methods +are unmarked; optional ones are noted. + +```typescript +interface ResponseHandler { + onContractPut(response: PutResponse): void; + onContractGet(response: GetResponse): void; + onContractUpdate(response: UpdateResponse): void; + onContractUpdateNotification(response: UpdateNotification): void; // live updates from subscribe() + onContractNotFound(instanceId: ContractInstanceId): void; + onSubscribeResponse?(key: ContractKey, subscribed: boolean): void; // optional + onDelegateResponse(response: DelegateResponse): void; + onErr(response: HostError): void; + onOpen(): void; + onClose?(code: number, reason: string): void; // optional +} +``` + +## Requests + +| Class | Constructor | Purpose | +| ------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------- | +| `GetRequest` | `(key, fetchContract?, subscribe?, blockingSubscribe?)` | Fetch contract state (and optionally the contract code). | +| `PutRequest` | `(container?, wrappedState?, relatedContracts?, subscribe?, blockingSubscribe?)` | Publish/deploy a contract with its initial state. | +| `UpdateRequest` | `(key?, update?)` | Push a delta or state update. | +| `SubscribeRequest` | `(key?, summary?)` | Subscribe to a contract's updates. | +| `DisconnectRequest` | `(cause?)` | Disconnect gracefully. | +| `DelegateRequest` | `(type, request)` | Register/unregister a delegate or send it an application message. | + +## Responses + +| Class | Key fields | +| -------------------- | -------------------------------------------------------------------- | +| `PutResponse` | `key: ContractKey` | +| `GetResponse` | `key: ContractKey`, `contract: ContractContainer`, `state: number[]` | +| `UpdateResponse` | `key: ContractKey`, `summary: number[]` | +| `UpdateNotification` | `key: ContractKey`, `update: UpdateData` | +| `DelegateResponse` | `key?: DelegateKey`, `values?: OutboundDelegateMsg[]` | +| `HostError` | `{ cause: string }` | + +## Request ordering + +The SDK resolves `get()` promises from a single FIFO queue with no request correlation: the Nth +`GetResponse` (or not-found) to arrive settles the Nth pending `get()`, regardless of which contract +it was for. Do not run several `get()` calls concurrently, or responses will be delivered to the +wrong caller. Serialize reads instead: await one before starting the next, or chain them. Raven does +this with a `getChain` promise; see its +[`web/src/freenet-api.ts`](https://github.com/freenet/raven/blob/main/web/src/freenet-api.ts). + +## Keys and containers + +```typescript +import { + ContractKey, + ContractContainer, + ContractType, + WasmContractV1, +} from "@freenetorg/freenet-stdlib"; + +// A contract key from a base58 instance id (as printed by `fdev`) +const key = ContractKey.fromInstanceId("DCBi7HNZC3QUZRiZLFZDiEduv5KHgZfgBk8WwTiheGq1"); +key.encode(); // base58 string + +// Wrap WASM + parameters for a PutRequest +const contract = new WasmContractV1(contractCode, parameterBytes, key); +const container = new ContractContainer(ContractType.WasmContractV1, contract); +``` + +- `ContractInstanceId`: the 32-byte contract hash (a `Uint8Array`). +- `ContractKey`: wraps a `ContractInstanceId`; methods include `fromInstanceId()`, `bytes()`, + `codePart()`, `encode()`. Note that `fromInstanceId()` sets only the instance part of the key; the + node needs both parts, so for a contract with no separate parameters re-wrap it as + `new ContractKey(bytes, bytes)` (as Raven does). +- `ContractContainer` / `WasmContractV1`: carry the contract WASM and its parameters for a `put`. + +## Update data + +`UpdateData` is a discriminated union built with `UpdateDataType` plus one of six concrete update +types: + +```typescript +import { UpdateData, UpdateDataType, DeltaUpdate } from "@freenetorg/freenet-stdlib"; + +const deltaBytes = new TextEncoder().encode(JSON.stringify(myDelta)); +const update = new UpdateData(UpdateDataType.DeltaUpdate, new DeltaUpdate(Array.from(deltaBytes))); +await api.update(new UpdateRequest(key, update)); +``` + +| Class | Constructor | +| ---------------------------- | ------------------------------ | +| `StateUpdate` | `(state?)` | +| `DeltaUpdate` | `(delta?)` | +| `StateAndDeltaUpdate` | `(state?, delta?)` | +| `RelatedStateUpdate` | `(relatedTo?, state?)` | +| `RelatedDeltaUpdate` | `(relatedTo?, delta?)` | +| `RelatedStateAndDeltaUpdate` | `(relatedTo?, state?, delta?)` | + +## Delegates + +Message a delegate with `DelegateRequest` carrying `ApplicationMessages`. Messages your app sends +into the delegate (`InboundDelegateMsg`) are `ApplicationMessage` / `UserInputResponse`; messages +the delegate emits back (`OutboundDelegateMsg`, delivered in `DelegateResponse.values`) are +`ApplicationMessage` / `RequestUserInput` / `ContextUpdated`. + +To register or remove a delegate, wrap it in a `DelegateContainer` (`WasmDelegateV1`) and send a +`DelegateRequest` with `RegisterDelegate` / `UnregisterDelegate`. Delegate secret storage uses the +`GetSecretRequest`, `SetSecretRequest`, and `GetSecretResponse` types. + +These are the lower-level FlatBuffers types and live under the package subpaths +(`@freenetorg/freenet-stdlib/common`, `/client-request`, `/host-response`); this part of the API is +still stabilizing. Raven's +[`web/src/delegate-api.ts`](https://github.com/freenet/raven/blob/main/web/src/delegate-api.ts) +shows the current working approach for building and sending delegate messages. + +## Streaming large state + +Large state and deltas (above ~512 KB) are split into chunks on the wire. **`FreenetWsApi` +reassembles incoming chunks automatically** and delivers the complete payload through the normal +`ResponseHandler` callbacks, and it chunks large outgoing requests for you, so you do not normally +handle streaming yourself. + +The `streaming` module is exported for advanced or low-level use: + +- `ReassemblyBuffer`: manual chunk reassembly. `receiveChunk(streamId, index, total, data)` returns + the full `Uint8Array` once every chunk has arrived, otherwise `null`. +- `StreamError`: thrown on a protocol violation (zero or too many chunks, index out of range, stream + timeout). +- Sizing constants: `CHUNK_SIZE` (256 KB), `CHUNK_THRESHOLD` (512 KB), `MAX_TOTAL_CHUNKS` (256), + `MAX_CONCURRENT_STREAMS` (8). + +## Packaging for a web container + +When your UI is served from a Freenet web container it runs in a sandboxed iframe with a strict +Content Security Policy. Set `base: "./"` in your Vite config so assets resolve relative to the +container URL, and prefer vendoring third-party CSS/JS locally; the gateway applies a Content +Security Policy, so verify any remote origins against a real gateway. See +[User Interface](/build/manual/components/ui/) for details.