Viewer Connections
Unlike Livnote’s peer-to-peer collaboration model where both parties have equal permissions, Sthalam uses a publisher-node-viewer architecture. Viewers connect to the publisher’s sovereign node to access published content with specific, granular permissions.
Connection Architecture
Section titled “Connection Architecture”Publisher ↔ Node ↔ Viewer:
- Publisher creates and publishes content to their sovereign node
- Node hosts content and handles viewer connections
- Viewers connect to node to access and interact with content
This differs from Livnote where peers connect directly for collaborative editing.
Viewer Handshake Protocol
Section titled “Viewer Handshake Protocol”
Connection String Structure
Section titled “Connection String Structure”The publisher shares a base64-encoded connection string containing:
{ "user_public_key": "publisher_pgp_public_key", "device_public_key": "node_device_public_key", "ucan_token": "folder_access_token", "ucan_pub_key": "ucan_verification_key"}Step 1: Initiating Connection
Section titled “Step 1: Initiating Connection”When a viewer pastes the connection string:
- Decode Base64: Extract JSON structure
- Parse Components: Get user key, device key, UCAN token
- Device Discovery: Find node via Iroh discovery using device key
- QUIC Connection: Establish encrypted transport connection
Step 2: Website Handshake
Section titled “Step 2: Website Handshake”The viewer handshake differs from Livnote’s first-time connection:
WebsiteHandshakeRequest - Sent by viewer:
WebsiteHandshakeRequest { viewer_user: User, // Viewer's identity viewer_device: Device, // Viewer's device info ucan_token: String, // Folder access token}Node Validation:
- Parse and validate UCAN token structure
- Verify cryptographic signature
- Check token expiration
- Extract
folder_idfrom capabilities - Verify folder exists and is accessible
WebsiteHandshakeResponse - Sent by node:
WebsiteHandshakeResponse { node_user: User, // Publisher's identity node_device: Device, // Node's device info}Step 3: Resource Request
Section titled “Step 3: Resource Request”After handshake, viewer requests folder contents:
ResourceRequest Message:
WebsiteMessage::ResourceRequest { ucan_token: String, // Same folder UCAN}Node Processing:
- Re-validate UCAN token
- Extract
folder_idfrom capabilities:extract_folder_id_from_ucan() - Query database for all resources in folder
- Prepare each resource for viewer
Step 4: Resource Preparation
Section titled “Step 4: Resource Preparation”For each resource in the folder, the node creates viewer-specific permissions:
Folder Sharing (services/src/node_service.rs:50-144):
- Generate read-only folder UCAN for viewer
- Delegate from publisher’s folder token
- Use
crud/readpermission
Resource Sharing (services/src/node_service.rs:146-289):
- For each resource, generate resource-specific UCAN
- Create 3-document permissions:
blocksuite_doc-crud/read(main content, read-only)thread_comments_doc-crud/write(comments, bidirectional)form_submissions_doc-crud/append(submissions, append-only)
- Re-encrypt resource AES key for viewer’s PGP public key (later releases: generate new AES key per viewer)
- Package as
ResourceSyncData
Permission Generation Example:
let permissions = vec![ ( format!("sthalam:resource:{}:blocksuite_doc", resource_id), "crud/read".to_string(), ), ( format!("sthalam:resource:{}:thread_comments_doc", resource_id), "crud/write".to_string(), ), ( format!("sthalam:resource:{}:form_submissions_doc", resource_id), "crud/append".to_string(), ),];Step 5: Content Distribution
Section titled “Step 5: Content Distribution”Node sends all folder contents to viewer:
Folder Sync:
Message::FolderSync( FolderSyncMessage::UnknownFoldersPayload { folder_data: vec![folder_with_viewer_token], })Resource Distribution: For each resource in folder:
Message::ResourceAdditionRequest(ResourceSyncData { resource: resource_metadata, resource_keys: vec![viewer_encrypted_key], share_records: vec![viewer_share_record], vector_clocks: viewer_vector_clocks,})Completion Signal:
Message::Website(WebsiteMessage::InitialSyncComplete { folder_id: folder_id, resource_count: resources.len(),})Step 6: Viewer Storage
Section titled “Step 6: Viewer Storage”Viewer processes and stores content:
- Validate Resource UCANs: Check signatures and permissions
- Decrypt AES Keys: Use viewer’s PGP private key
- Store Resources: Save to local encrypted database
- Mark Connection: Set
first_sync = truefor publisher - Ready State: Content available for viewing and interaction
Connection vs. Livnote
Section titled “Connection vs. Livnote”Livnote (Peer-to-Peer):
Section titled “Livnote (Peer-to-Peer):”- FirstConnectRequest/Response: Full bidirectional identity exchange
- Equal Permissions: Both peers can create, share, and collaborate
- Direct Connections: Peers connect directly to each other
- User Sync: Manifest-based synchronization of shared users
Sthalam (Publisher-Viewer):
Section titled “Sthalam (Publisher-Viewer):”- WebsiteHandshake: Asymmetric handshake (viewer requests, node provides)
- Granular Permissions: Viewer receives specific read/write/append permissions per document
- Node-Mediated: Viewer connects to node, not directly to publisher
- Website Sync: Resource-based synchronization with permission validation
Subscription Behavior
Section titled “Subscription Behavior”After initial connection, viewers are subscribed to the publisher’s folder:
Pull-Based Updates:
- New resources added to folder → Viewer receives when polling node during incremental sync
- Existing resources updated → Viewer requests changes via state vectors
- No push notifications → Viewer must actively poll node to receive new content
Persistent Connection:
- Connection information stored locally
- Subsequent connections use simpler incremental sync
- No need to re-handshake on reconnection
Security Properties
Section titled “Security Properties”Token-Based Authorization: UCAN tokens encode all permissions
Capability Validation: Every sync operation validates resource UCAN tokens
Granular Access: Different permissions for content, comments, and submissions
Proof Chains: Resource UCANs chain back to folder UCAN and publisher’s root authority
Encrypted Data: All content encrypted with AES keys, re-encrypted per viewer (later releases: new AES key per viewer)
What Happens Next
Section titled “What Happens Next”After successful connection:
- Content Available: Viewer can access all resources in folder
- Interaction Enabled: Submit forms, write comments based on permissions
- Sync Ready: Incremental sync keeps content up-to-date
- Subscription Active: Automatic delivery of new publisher content
The viewer connection model enables sovereign content distribution—publishers maintain full control while viewers gain secure, permission-based access to published content.