import { z } from 'zod'; /** * Types and schemas for the short-lived collaboration tokens (issue #34, * ADR 0003/0007). These are browser-safe (no Node built-ins) so the web app * can import them from the package barrel. The signing/verifying helpers live * in `./token-crypto` (Node `crypto`) and are imported only by the api and the * collab server. */ export const collabTokenModeSchema = z.enum(['rw', 'ro']); export type CollabTokenMode = z.infer; /** * PostgreSQL `LISTEN/NOTIFY` channel over which the api announces that the * access situation of a pond changed (issue #39, permissions.md §Performance). * The notification payload is the pond id. The api emits it whenever a * permission-relevant change happens (interim: pond soft-delete; from M5 on: * grant changes, #53); the collab server listens and re-validates every open * connection to that pond's pages by closing them so clients reconnect and * re-acquire a token reflecting the current access. */ export const POND_ACCESS_CHANGED_CHANNEL = 'pond_access_changed'; /** * PostgreSQL `NOTIFY` channel over which the api asks the collab server to * restore a page to an earlier version (issue #42, ADR 0013). The api does the * permission check, then emits this; the collab server owns the live document, * so it snapshots the current state (a `PRE_RESTORE` version) and applies the * restored content as a normal edit through the document, converging every open * client. Payload is a JSON {@link PageRestoreRequest}. */ export const PAGE_RESTORE_CHANNEL = 'page_restore'; /** * PostgreSQL `NOTIFY` channel over which the collab server announces that it * wrote an automatic version snapshot (issue #94): the api listens and fans * the change out to watchers as notifications — permission-checked there, * where the resolution lives. Payload is a JSON {@link PageVersionCreatedEvent}. */ export const PAGE_VERSION_CREATED_CHANNEL = 'page_version_created'; /** JSON payload carried on {@link PAGE_VERSION_CREATED_CHANNEL}. */ export interface PageVersionCreatedEvent { pageId: string; /** Everyone who contributed to the snapshot — all excluded from fan-out. */ contributorIds: string[]; } /** JSON payload carried on {@link PAGE_RESTORE_CHANNEL}. */ export interface PageRestoreRequest { pageId: string; versionId: string; /** The user who triggered the restore (recorded on the pre-restore snapshot). */ userId: string; } /** The application claims carried by a collaboration token. */ export const collabTokenClaimsSchema = z.object({ // `null` for an anonymous visitor holding a public read-only token (issue // #53); a user id for a signed-in participant (used for presence/versioning). userId: z.string().min(1).nullable(), pageId: z.string().min(1), mode: collabTokenModeSchema, }); export type CollabTokenClaims = z.infer; /** Response of `GET /pages/:id/collab-token`. */ export interface CollabTokenResponse { token: string; mode: CollabTokenMode; expiresInSeconds: number; } export type CollabTokenVerification = | { valid: true; claims: CollabTokenClaims } | { valid: false; reason: 'malformed' | 'bad_algorithm' | 'bad_signature' | 'expired' | 'invalid_claims'; };