# Self-Hosted Diagram Sharing with Express-PouchDB ## Requirements (Confirmed) - **Storage**: In-memory (ephemeral) - lost on server restart - **Content**: Copy current diagram entities when creating share - **Expiration**: No expiration - links work until server restart - **Encryption**: None - keep it simple, anyone with link can access ## Architecture ``` ┌─────────────────────────────────────────────────┐ │ Express Server (port 3001) │ ├─────────────────────────────────────────────────┤ │ /api/share/* → Share management API │ │ /pouchdb/* → express-pouchdb (sync) │ │ /share/:uuid → Client share route │ │ /api/* → Existing API routes │ │ /* → Vite static files │ └─────────────────────────────────────────────────┘ │ └── In-Memory PouchDB (per share UUID) ``` ## Implementation Steps ### Phase 1: Server-Side Setup #### 1.1 Add Dependencies ```bash npm install express-pouchdb pouchdb-adapter-memory ``` #### 1.2 Create PouchDB Server Service **New file: `server/services/pouchdbServer.js`** - Initialize PouchDB with memory adapter - Track active share databases in a Map - Export `getShareDB(shareId)`, `shareExists(shareId)`, `createPouchDBMiddleware()` #### 1.3 Create Share API **New file: `server/api/share.js`** - `POST /api/share/create` - Generate UUID, create in-memory DB, copy entities - `GET /api/share/:id/exists` - Check if share exists - `GET /api/share/stats` - Debug endpoint for active shares #### 1.4 Update API Router **Edit: `server/api/index.js`** - Add `import shareRouter from "./share.js"` - Mount at `router.use("/share", shareRouter)` #### 1.5 Mount Express-PouchDB **Edit: `server.js`** - Import `createPouchDBMiddleware` from pouchdbServer.js - Mount at `app.use("/pouchdb", createPouchDBMiddleware())` --- ### Phase 2: Client-Side Integration #### 2.1 Update URL Parsing **Edit: `src/util/functions/getPath.ts`** - Add `getPathInfo()` function returning `{ dbName, isShare, shareId }` - Detect `/share/:uuid` pattern #### 2.2 Update PouchDB Persistence Manager **Edit: `src/integration/database/pouchdbPersistenceManager.ts`** In `initLocal()`: - Call `getPathInfo()` to detect share URLs - If share: use `share-{uuid}` as local DB name, call `beginShareSync()` Add new method `beginShareSync(shareId)`: - Check share exists via `/api/share/:id/exists` - Connect to `${origin}/pouchdb/share-${shareId}` - Set up presence with `share-${shareId}` as DB name - Begin live sync (no encryption) #### 2.3 Add React Route **Edit: `src/react/webRouter.tsx`** - Add route `{ path: "/share/:uuid", element: }` - No ProtectedRoute wrapper (public access) #### 2.4 Add Share Button Handler **Edit: `src/react/pages/vrExperience.tsx`** - Add `isShare` prop - Add `handleShare()` function: 1. Get all entities from local PouchDB 2. POST to `/api/share/create` with entities 3. Copy resulting URL to clipboard 4. Show confirmation --- ### Phase 3: Presence Integration The WebSocket presence system already routes by database name. Since shares use `share-{uuid}` as the database name, presence works automatically. **Edit: `server/server.js`** (WebSocket server) - Update `originIsAllowed()` to allow localhost for development --- ## Files to Modify | File | Action | Purpose | |------|--------|---------| | `package.json` | Edit | Add express-pouchdb, pouchdb-adapter-memory | | `server.js` | Edit | Mount /pouchdb middleware | | `server/api/index.js` | Edit | Add share router | | `server/services/pouchdbServer.js` | Create | PouchDB memory initialization | | `server/api/share.js` | Create | Share API endpoints | | `server/server.js` | Edit | Allow localhost origins | | `src/util/functions/getPath.ts` | Edit | Add getPathInfo() | | `src/integration/database/pouchdbPersistenceManager.ts` | Edit | Add share sync logic | | `src/react/webRouter.tsx` | Edit | Add /share/:uuid route | | `src/react/pages/vrExperience.tsx` | Edit | Add share button handler | | `src/util/featureConfig.ts` | Edit | Enable shareCollaborate feature | --- ## User Flow ### Creating a Share 1. User has a diagram open at `/db/public/mydiagram` 2. Clicks "Share" button 3. Client fetches all entities from local PouchDB 4. POSTs to `/api/share/create` with entities 5. Server creates in-memory DB, copies entities, returns UUID 6. Client copies `https://server.com/share/{uuid}` to clipboard 7. User shares link with collaborators ### Joining a Share 1. User navigates to `https://server.com/share/{uuid}` 2. React Router renders VrExperience with `isShare=true` 3. PouchdbPersistenceManager detects share URL 4. Checks `/api/share/:uuid/exists` - returns true 5. Creates local PouchDB `share-{uuid}` 6. Connects to `/pouchdb/share-{uuid}` for sync 7. Entities replicate to local, render in scene 8. Presence WebSocket connects with `share-{uuid}` as room --- ## Future Authentication (Not Implemented Now) Structure allows easy addition later: - express-pouchdb middleware can be wrapped with auth middleware - Share API can require JWT/session tokens - Could add password-protected shares - Could add read-only vs read-write permissions