File Change Events
Cloudreve provides a real-time file change notification service based on Server-Sent Events (SSE). Clients can subscribe to file change events to receive notifications when files or folders are created, modified, renamed, or deleted.
Endpoint
GET /api/v4/file/eventsRefer to the Events stream API documentation for the complete API schema.
Auth: JWT Required
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
uri | string | query | No | URI of the folder to watch, e.g., cloudreve://my |
X-Cr-Client-Id | string | header | No | Unique client identifier (must be UUID), used for subscription management |
Server-Sent Events
This endpoint returns a text/event-stream response. The connection remains open, and the server pushes events to the client as they occur. Each event follows the SSE format:
event: <event-type>
data: <json-data>Clients should use an SSE-compatible library or the native EventSource API to consume this endpoint.
Event Types
| Event Type | Description |
|---|---|
subscribed | Initial subscription established successfully |
resumed | Subscription resumed after reconnection (with the same X-Cr-Client-Id) |
event | File change notification containing one or more change events |
keep-alive | Heartbeat to maintain connection |
reconnect-required | Server requests the client to disconnect and re-establish the connection |
Client Identification
The X-Cr-Client-Id header serves multiple purposes:
Filtering Self-Generated Events
When a client makes changes to files (upload, rename, delete, etc.), those changes trigger events. By providing a consistent X-Cr-Client-Id, the server filters out events that originated from the same client, preventing redundant processing.
Subscription Recovery
If a client disconnects (due to network issues, app backgrounding, etc.), the server temporarily stores events for that client ID. When the client reconnects with the same X-Cr-Client-Id:
- The server sends a
resumedevent instead ofsubscribed - Stored events are delivered to the client
- The client can seamlessly continue without missing changes
TIP
Generate a persistent UUID for each client instance and reuse it across reconnections to take full advantage of subscription recovery.
Reconnection Strategy
Clients should implement automatic reconnection with exponential backoff:
- On connection failure or unexpected close, wait before reconnecting
- Start with a short delay (e.g., 1 second)
- Double the delay after each failed attempt, up to a maximum (e.g., 32 seconds)
- Reset the delay after a successful connection
When receiving a reconnect-required event, the client should:
- Close the current connection gracefully
- Reconnect immediately without backoff delay
- Continue using the same
X-Cr-Client-Idto resume the subscription
Event Debouncing and Merging
The server implements intelligent event debouncing and merging to reduce noise and bandwidth:
- Debouncing: Rapid successive changes to the same file are batched together
- Delay: Events are held briefly to allow related operations to be grouped
- Merging: Redundant or superseded events are consolidated (e.g., multiple modifications become a single event, create followed by delete cancels out)
This means clients may receive fewer events than the actual number of operations, but each event reflects the meaningful final state of the affected files.
Example
const clientId = localStorage.getItem('clientId') || crypto.randomUUID();
localStorage.setItem('clientId', clientId);
function connect() {
const url = new URL('/api/v4/file/events', 'https://your-site.com');
url.searchParams.set('uri', 'cloudreve://my');
const eventSource = new EventSource(url, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-Cr-Client-Id': clientId
}
});
eventSource.addEventListener('subscribed', (e) => {
console.log('Subscription established');
});
eventSource.addEventListener('resumed', (e) => {
console.log('Subscription resumed');
});
eventSource.addEventListener('event', (e) => {
const events = JSON.parse(e.data);
for (const event of events) {
console.log(`${event.type}: ${event.from} -> ${event.to}`);
}
});
eventSource.addEventListener('reconnect-required', (e) => {
eventSource.close();
connect(); // Reconnect immediately
});
eventSource.onerror = (e) => {
eventSource.close();
setTimeout(connect, 5000); // Reconnect with delay
};
}
connect();