Skip to content

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.

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 diagram showing connection string decoding, website handshake, initial resource sync, and subscription establishment

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"
}

When a viewer pastes the connection string:

  1. Decode Base64: Extract JSON structure
  2. Parse Components: Get user key, device key, UCAN token
  3. Device Discovery: Find node via Iroh discovery using device key
  4. QUIC Connection: Establish encrypted transport connection

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:

  1. Parse and validate UCAN token structure
  2. Verify cryptographic signature
  3. Check token expiration
  4. Extract folder_id from capabilities
  5. Verify folder exists and is accessible

WebsiteHandshakeResponse - Sent by node:

WebsiteHandshakeResponse {
node_user: User, // Publisher's identity
node_device: Device, // Node's device info
}

After handshake, viewer requests folder contents:

ResourceRequest Message:

WebsiteMessage::ResourceRequest {
ucan_token: String, // Same folder UCAN
}

Node Processing:

  1. Re-validate UCAN token
  2. Extract folder_id from capabilities: extract_folder_id_from_ucan()
  3. Query database for all resources in folder
  4. Prepare each resource for viewer

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/read permission

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(),
),
];

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(),
})

Viewer processes and stores content:

  1. Validate Resource UCANs: Check signatures and permissions
  2. Decrypt AES Keys: Use viewer’s PGP private key
  3. Store Resources: Save to local encrypted database
  4. Mark Connection: Set first_sync = true for publisher
  5. Ready State: Content available for viewing and interaction
  • 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
  • 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

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

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)

After successful connection:

  1. Content Available: Viewer can access all resources in folder
  2. Interaction Enabled: Submit forms, write comments based on permissions
  3. Sync Ready: Incremental sync keeps content up-to-date
  4. 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.