| layout | default |
|---|---|
| title | WebSocket API Reference |
| description | WebSocket events and message formats for Note Sync Now real-time sync |
| permalink | /docs/api/websocket/ |
This document describes the WebSocket events and message formats used for real-time synchronization in Note Sync Now.
Base URL: Determined by VITE_SOCKET_URL environment variable (client) or CORS_ORIGIN (server).
import { io } from 'socket.io-client';
const socket = io('http://localhost:3002', {
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5
});| Event | Direction | Description |
|---|---|---|
connect |
Server โ Client | Connection established |
disconnect |
Server โ Client | Connection closed |
connect_error |
Server โ Client | Connection error occurred |
reconnect |
Server โ Client | Successfully reconnected |
Join a synchronization chain/room.
Direction: Client โ Server
Payload:
{
"roomId": "abc123def456",
"deviceName": "My Laptop"
}Fields:
| Field | Type | Required | Description |
|---|---|---|---|
roomId |
string | Yes | 12-character room identifier |
deviceName |
string | No | Device display name |
Response (join-chain-response):
Direction: Server โ Client
{
"success": true,
"roomId": "abc123def456",
"members": 2,
"hasData": true,
"data": {
"encryptedData": "base64encoded...",
"timestamp": 1744780800000,
"version": 5
}
}Or error:
{
"success": false,
"error": "Invalid room ID format"
}Leave a synchronization chain.
Direction: Client โ Server
Payload:
{
"roomId": "abc123def456"
}Room membership updates broadcast.
Direction: Server โ Client
Payload:
{
"roomId": "abc123def456",
"members": [
{
"id": "socket-id-1",
"deviceName": "My Laptop",
"joinedAt": 1744780800000
},
{
"id": "socket-id-2",
"deviceName": "My Phone",
"joinedAt": 1744780801000
}
],
"count": 2
}Send encrypted content update to the server.
Direction: Client โ Server
Payload - Standard Update:
{
"roomId": "abc123def456",
"encryptedData": "base64encodedencryptedcontent...",
"timestamp": 1744780800000,
"version": 6,
"deviceId": "device-uuid"
}Payload - Chunked Update (First):
{
"roomId": "abc123def456",
"chunk": {
"id": "chunk-uuid",
"index": 0,
"total": 3,
"data": "base64encodedchunk..."
},
"isChunked": true
}Payload - Chunked Update (Continuation):
{
"roomId": "abc123def456",
"chunk": {
"id": "chunk-uuid",
"index": 1,
"total": 3,
"data": "base64encodedchunk..."
},
"isChunked": true
}Fields:
| Field | Type | Required | Description |
|---|---|---|---|
roomId |
string | Yes | Room identifier |
encryptedData |
string | Yes* | Base64-encoded encrypted content (*for non-chunked) |
timestamp |
number | Yes | Update timestamp (ms since epoch) |
version |
number | Yes | Content version number |
deviceId |
string | Yes | Unique device identifier |
isChunked |
boolean | No | Whether this is a chunked transfer |
chunk |
object | No* | Chunk information (*for chunked) |
Response (update-ack):
Direction: Server โ Client
{
"success": true,
"timestamp": 1744780800020,
"version": 6
}Or error:
{
"success": false,
"error": "Rate limit exceeded",
"retryAfter": 30
}Receive encrypted content update from another client.
Direction: Server โ Client
Payload:
{
"roomId": "abc123def456",
"encryptedData": "base64encodedencryptedcontent...",
"timestamp": 1744780800000,
"version": 6,
"deviceId": "sender-device-uuid"
}Request current room state (for reconnection or forced sync).
Direction: Client โ Server
Payload:
{
"roomId": "abc123def456",
"lastVersion": 5
}Fields:
| Field | Type | Required | Description |
|---|---|---|---|
roomId |
string | Yes | Room identifier |
lastVersion |
number | No | Last known version (for comparison) |
Response (request-sync-response):
{
"success": true,
"hasUpdate": true,
"data": {
"encryptedData": "base64encoded...",
"timestamp": 1744780900000,
"version": 7
}
}Or if no update:
{
"success": true,
"hasUpdate": false
}Direction: Server โ Client
Payload:
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many updates",
"details": {
"limit": 30,
"window": "1m",
"retryAfter": 30
}
}| Code | Description | HTTP Equivalent |
|---|---|---|
INVALID_ROOM_ID |
Room ID format is invalid | 400 |
ROOM_NOT_FOUND |
Room does not exist | 404 |
NOT_IN_ROOM |
Client not a room member | 403 |
INVALID_PAYLOAD |
Message format is invalid | 400 |
PAYLOAD_TOO_LARGE |
Content exceeds size limit | 413 |
RATE_LIMIT_EXCEEDED |
Too many requests | 429 |
ENCRYPTION_REQUIRED |
Encrypted data missing | 400 |
SERVER_ERROR |
Internal server error | 500 |
For content larger than 50KB, the client automatically splits content into chunks.
Client Server
โ โ
โ 1. chunk (index: 0, total: 3) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ
โ 2. chunk-ack (index: 0) โ
โ<โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ 3. chunk (index: 1, total: 3) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ
โ 4. chunk-ack (index: 1) โ
โ<โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ 5. chunk (index: 2, total: 3) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ
โ 6. ack (all chunks received) โ
โ<โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ 7. Reassemble & Forward โ
โ โโโโโโโโโโบ Other Clients
- Default: 50KB per chunk
- Maximum: 5MB total content size
- Timeout: 30 seconds for complete transfer
| Operation | Limit | Window |
|---|---|---|
| Updates (push-update) | 30 | 1 minute |
| Sync requests | 60 | 1 minute |
| Join attempts | 10 | 1 minute |
After each event, the server may include rate limit information:
{
"rateLimit": {
"limit": 30,
"remaining": 25,
"resetAt": 1744780860000
}
}class SyncClient {
constructor(serverUrl) {
this.socket = io(serverUrl, {
transports: ['websocket', 'polling'],
reconnection: true
});
this.setupEventHandlers();
}
setupEventHandlers() {
this.socket.on('connect', () => {
console.log('Connected to sync server');
});
this.socket.on('sync-update', (data) => {
this.handleSyncUpdate(data);
});
this.socket.on('error', (error) => {
console.error('Sync error:', error);
});
this.socket.on('disconnect', () => {
console.log('Disconnected from sync server');
});
}
joinChain(roomId, deviceName) {
return new Promise((resolve) => {
this.socket.emit('join-chain', { roomId, deviceName }, (response) => {
resolve(response);
});
});
}
pushUpdate(roomId, encryptedData, version) {
return new Promise((resolve) => {
this.socket.emit('push-update', {
roomId,
encryptedData,
timestamp: Date.now(),
version,
deviceId: this.deviceId
}, (response) => {
resolve(response);
});
});
}
disconnect() {
this.socket.disconnect();
}
}Last updated: 2026-04-16