A lightweight, real-time collaborative web application for managing personal information (notes, calendar events, contacts) using Y.js CRDTs synchronized over MQTT.
- Real-time Collaboration: Multiple clients can edit simultaneously with conflict-free merging
- End-to-End Encryption: AES-GCM 256-bit encryption with HKDF key derivation
- Reliable Delivery: IndexedDB-backed message queue with ACK tracking
- MQTT Constraints: Handles message size limits and rate limiting
- Three Collections:
- Notes: Markdown editor with live preview
- Events: iCal-compliant calendar events
- Contacts: vCard-compliant contact management
- QR Code Sharing: Instant room sharing via QR code or URL
- Import/Export: JSON-based data portability
- Responsive UI: Bootstrap 5 design for desktop and mobile
# Install dependencies
yarn install
# Start dev server
yarn dev
# Build for production
yarn build- Open the app in your browser
- Configure connection (or use defaults):
- Broker:
wss://test.mosquitto.org:8081/mqtt(default) - Room:
default-room(default) - Secret: Custom or use default (shows security warning)
- Broker:
- Create notes, events, or contacts
- Share room via QR code to collaborate
Click the gear icon (⚙️) in the top-right to configure:
- MQTT broker URL
- Room name
- Encryption key (generate random or use custom)
- Collection visibility (show/hide Notes, Events, Contacts)
- Click gear icon → "Share Room"
- Scan QR code or copy URL
- Other users open the URL to join instantly
- Frontend: Preact + TypeScript + Vite
- State: Zustand + Y.js CRDTs
- Sync: Custom MQTT provider with encryption
- UI: Bootstrap 5 + Bootstrap Icons
- Markdown: marked
- QR: qrcode
UI Components
↓
Zustand Store (wraps Y.js with deepObserve)
↓
Y.js Document (CRDTs)
↓
MQTT Provider (encryption, fragmentation, ACKs)
↓
MQTT Broker (message relay)
- Binary frames: 47-byte header + encrypted payload
- Fragmentation: Chunks large updates (default: 64 KiB max)
- Rate limiting: Token bucket algorithm (default: 200 KiB/s)
- Encryption: AES-GCM 256-bit with HKDF key derivation
- Reliable delivery: IndexedDB queue with ACK tracking and exponential backoff retry
- Presence tracking: Periodic beacons to detect active peers
- Echo prevention: Ignores own messages via client ID matching
The app ships with a default encryption key (MZXW6YTBOI======) for easy deployment. This is insecure for production use!
A dismissible warning banner appears when using the default key. Always generate a custom key for private data.
Keys are derived using HKDF-SHA256:
- Input: Shared secret (base32 or base58)
- Salt: Room name (docId)
- Info: "mqtt-yjs-v1"
- Output: AES-GCM 256-bit key
Access control is implicit via shared encryption key knowledge. Anyone with the broker URL + room name + secret can join. This is by design for maximum simplicity.
{
id: string; // UUID
title: string;
content: string; // Markdown
created: string; // ISO 8601
modified: string; // ISO 8601
}{
id: string; // UUID
uid: string; // iCal UID
summary: string;
dtstart: string; // ISO 8601
dtend: string; // ISO 8601
location: string;
description: string;
created: string;
lastmod: string;
}{
id: string // UUID
uid: string // vCard UID (urn:uuid:...)
fn: string // Formatted name
n: { // Name components
family, given, middle, prefix, suffix
}
email: string[] // Multi-value
tel: string[] // Multi-value
adr: Address[] // Multi-value
org: string
title: string
photo: string
created: string
rev: string
}- Click gear icon → "Export Data"
- Downloads
myjs-export-YYYY-MM-DD.json - Contains all collections
- Click gear icon → "Import Data"
- Select JSON file
- Choose mode:
- Overwrite: Clears all data, replaces with import
- Append: Merges with existing (ID-based conflict resolution)
Settings are saved to localStorage:
- roomConfig: broker, room name, secret
- collectionVisibility: which collections are visible
- security-ack-{room}: security warning acknowledgment
- Modern evergreen browser (Chrome, Firefox, Safari, Edge)
- IndexedDB support
- Web Crypto API
- WebSocket support
URL: wss://test.mosquitto.org:8081/mqtt
Auth: None
Note: Public broker, not for sensitive data
Any MQTT broker with WebSocket support works:
- Mosquitto with websockets
- HiveMQ
- EMQX
- AWS IoT Core
src/
├── components/ # Preact components
│ └── layout/ # Header, modals
├── providers/ # MQTT Y.js provider
├── stores/ # Zustand stores
├── types/ # TypeScript types
├── utils/ # Crypto, IDB, QR, binary helpers
├── styles/ # CSS
├── app.tsx # Main app
└── main.tsx # Entry point
- Update data types in
src/types/collections.ts - Add CRUD methods to
src/stores/use-doc-store.ts - Create UI components in
src/components/ - Wire into
src/app.tsx
- Check broker URL is correct (wss:// for secure WebSockets)
- Verify broker is accessible (not behind firewall)
- Check browser console for MQTT errors
- Verify all clients use same room name and secret
- Check encryption key matches across clients
- Look for decryption errors in console
- Default rate limiting: 200 KiB/s
- Large documents (>1000 items) may slow deepObserve
- Consider splitting into multiple rooms for very large datasets
[Add your license here]
[Add contribution guidelines]
- Y.js for CRDTs
- MQTT.js for browser MQTT client
- Preact for lightweight React alternative
- Bootstrap for UI framework