diff --git a/docs.json b/docs.json index fdafde7..9004070 100644 --- a/docs.json +++ b/docs.json @@ -84,6 +84,7 @@ "iroh-services/quickstart", "iroh-services/projects", "iroh-services/access", + "iroh-services/tickets", "iroh-services/support" ] }, diff --git a/iroh-services/tickets.mdx b/iroh-services/tickets.mdx new file mode 100644 index 0000000..9eb9591 --- /dev/null +++ b/iroh-services/tickets.mdx @@ -0,0 +1,183 @@ +--- +title: "Hosted Tickets" +description: "Automatically exchange tickets between app instances using Iroh Services" +--- + +Tickets tell your application what to do. They bundle connection details and +application-specific data into a single token that one app instance can create +and others can consume. The tickets service gives you a central place to +publish and discover these tickets, so your users don't need to copy-paste +strings or scan QR codes to get connected. + + +New to tickets? Read the [Tickets concept page](/concepts/tickets) first for +background on how tickets work in iroh. + + +## The Problem + +Iroh tickets are powerful — they contain everything needed to connect to a peer +and start working. But getting a ticket from one device to another has always +required an outside channel: a text message, a QR code, a shared clipboard. + +For many applications, that manual handoff is friction you'd rather remove. If +your users are already signed into a service, why should they need to text +themselves a ticket? + +## How It Works + +The tickets service is a central server where your application can **publish** +and **fetch** tickets scoped to your [project](/iroh-services/projects). Any +endpoint connected to your project can list and retrieve tickets published by +other endpoints in the same project. + +The workflow is simple: + +1. **Alice publishes a ticket** — her app creates a ticket and publishes it to + the service with a name +2. **Bob lists or fetches tickets** — his app queries the service and gets + Alice's ticket back +3. **Bob connects to Alice** — using the ticket's connection details, Bob's app + connects directly to Alice + +Tickets are **live** — they're only available while the publishing endpoint is +online. When Alice's endpoint disconnects, her tickets are automatically removed. + +## Defining a Ticket Type + +Tickets use the exact same format you'd use for a QR code or copy-paste. The +key principle is: **tickets tell your application what to do, and there are many +ways to get tickets between app instances**. The tickets service is one of those +ways, but the ticket format itself is transport-agnostic. + +Your application should define a custom ticket type that carries +whatever data your app needs: + +```rust +use iroh::EndpointId; +use iroh_tickets::Ticket; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct TopicTicket { + message: String, + endpoint_id: EndpointId, +} + +impl Ticket for TopicTicket { + const KIND: &'static str = "coolapp"; + + fn to_bytes(&self) -> Vec { + postcard::to_stdvec(&self).expect("serialization failed") + } + + fn from_bytes(bytes: &[u8]) -> Result { + Ok(postcard::from_bytes(bytes)?) + } +} +``` + +The `KIND` constant is a short string prefix that identifies your ticket type. +This is what you'd see at the start of a ticket string — the same string whether +the ticket comes from a QR code, a URL, or the tickets service. + +Design your ticket type to carry the information your app needs to act on it. +Include endpoint IDs for connection details, content hashes for data references, +topic names, capabilities — whatever makes sense for your use case. + +## Publishing Tickets + +To publish a ticket, create your n0des client and call `publish_ticket` with a +name and your ticket value: + +```rust +use iroh::Endpoint; +use iroh_n0des::Client; + +let endpoint = Endpoint::bind().await?; +let client = Client::builder(&endpoint) + .api_secret_from_env()? + .build() + .await?; + +let ticket = TopicTicket { + message: "cool_pokemon".to_string(), + endpoint_id: endpoint.id(), +}; + +client.publish_ticket("alice_cool_pokemon", ticket).await?; +``` + +Ticket names are strings scoped to your project. Choose a naming convention +that works for your application — for example, prefixing with a username or +device identifier. + +## Fetching & Listing Tickets + +Other endpoints in your project can fetch a specific ticket by name, or list +all available tickets: + +```rust +// Fetch a specific ticket by name +let ticket = client + .fetch_ticket::("alice_cool_pokemon") + .await?; + +if let Some(published) = ticket { + println!("found ticket: {:?}", published.ticket); + println!("published by: {}", published.name); +} + +// List all tickets in the project +let all_tickets = client + .fetch_tickets::(0, 100) + .await?; + +for t in &all_tickets { + println!("{}: {:?}", t.name, t.ticket); +} +``` + +## Tickets Go Offline With You + +Published tickets are live — they're available as long as the publishing endpoint +is connected. When an endpoint disconnects, its tickets are automatically +cleaned up. This keeps the ticket list fresh and avoids stale entries pointing +to peers that are no longer reachable. + +```rust +// Alice publishes a ticket +alice_client.publish_ticket("my_topic", ticket).await?; + +// Bob can see it +let tickets = bob_client.fetch_tickets::(0, 100).await?; +assert_eq!(tickets.len(), 1); + +// Alice goes offline +alice_endpoint.close().await; + +// After a short delay, the ticket is cleaned up +let tickets = bob_client.fetch_tickets::(0, 100).await?; +assert_eq!(tickets.len(), 0); +``` + +## One Format, Many Channels + +The ticket format is the same regardless of how it's delivered. A `TopicTicket` +published through the tickets service serializes to the same string as one +shared via a QR code or pasted into a chat. This means your application can +support multiple discovery methods without changing its ticket handling: + +- **Tickets service** for automatic discovery between signed-in users +- **QR codes** for in-person device pairing +- **Deep links** for sharing via messaging apps +- **Copy-paste** for developer workflows and debugging + +Your app only needs to know how to *handle* a ticket — how the ticket arrives +is a separate concern. + +## Next Steps + +- [Tickets concept page](/concepts/tickets): How tickets work under the hood +- [Projects](/iroh-services/projects): Understanding project scoping +- [Quickstart](/iroh-services/quickstart): Set up your first Iroh Services app