- Add /db/local/:db path type that stores diagrams locally without syncing - New diagrams now default to local storage (browser-only) - Share button creates public copy when sharing local diagrams - Add storage type badges (Local/Public/Private) in diagram manager - Add GitHub Actions workflow for automated builds - Block local- database requests at server with 404 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.5 KiB
5.5 KiB
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
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 entitiesGET /api/share/:id/exists- Check if share existsGET /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
createPouchDBMiddlewarefrom 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/:uuidpattern
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, callbeginShareSync()
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: <VrExperience isShare={true} /> } - No ProtectedRoute wrapper (public access)
2.4 Add Share Button Handler
Edit: src/react/pages/vrExperience.tsx
- Add
isShareprop - Add
handleShare()function:- Get all entities from local PouchDB
- POST to
/api/share/createwith entities - Copy resulting URL to clipboard
- 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
- User has a diagram open at
/db/public/mydiagram - Clicks "Share" button
- Client fetches all entities from local PouchDB
- POSTs to
/api/share/createwith entities - Server creates in-memory DB, copies entities, returns UUID
- Client copies
https://server.com/share/{uuid}to clipboard - User shares link with collaborators
Joining a Share
- User navigates to
https://server.com/share/{uuid} - React Router renders VrExperience with
isShare=true - PouchdbPersistenceManager detects share URL
- Checks
/api/share/:uuid/exists- returns true - Creates local PouchDB
share-{uuid} - Connects to
/pouchdb/share-{uuid}for sync - Entities replicate to local, render in scene
- 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