Skip to main content

max / makenotwork

3.0 KB · 90 lines History Blame Raw
1 # SyncKit SSE Push Notifications
2
3 SyncKit provides a Server-Sent Events (SSE) endpoint that notifies connected clients in real time when new data is available. Instead of polling, your app can subscribe to a persistent connection and pull only when something changes.
4
5 ## Endpoint
6
7 ```
8 GET /api/sync/subscribe?app_id={app_id}
9 Authorization: Bearer <token>
10 ```
11
12 Requires a valid SyncKit JWT token. The `app_id` query parameter specifies which sync app to subscribe to.
13
14 ## Event Format
15
16 When another device pushes changes, all other connected devices for the same app and user receive a `changed` event:
17
18 ```
19 event: changed
20 data:
21 ```
22
23 The event carries no payload (data is always E2E encrypted and never sent over SSE). On receiving `changed`, initiate a pull to fetch actual changes.
24
25 ## Keepalive
26
27 The server sends a comment line every 30 seconds to keep the connection alive and prevent proxy timeouts:
28
29 ```
30 : keepalive
31 ```
32
33 This is a standard SSE comment and is ignored by conforming clients.
34
35 ## Client Integration
36
37 ### Basic Flow
38
39 1. Authenticate and obtain a SyncKit JWT token
40 2. Open an SSE connection to `/api/sync/subscribe?app_id={app_id}`
41 3. On receiving a `changed` event, call the pull endpoint to fetch new changes
42 4. Reconnect on connection drop (most SSE libraries handle this automatically)
43
44 ### With the Rust SDK
45
46 The `synckit-client` crate provides `SyncNotifyStream` for SSE integration. `SyncKitClient::subscribe` opens the connection; `next_change()` yields `Some(())` for each `changed` event and `None` when the stream ends:
47
48 ```rust
49 let mut stream = client.subscribe().await?;
50
51 // In your sync loop:
52 while stream.next_change().await.is_some() {
53 // The server signaled new changes, pull them.
54 let (changes, new_cursor, _has_more) = client.pull(device_id, cursor).await?;
55 cursor = new_cursor;
56 }
57 // next_change() returned None: the stream ended. Reconnect (typically after a
58 // short delay), refreshing the token first if it has expired.
59 ```
60
61 Keepalive comments are parsed and ignored for you. The stream does **not** auto-reconnect: when `next_change()` returns `None`, the caller reconnects.
62
63 ### Selective Sync
64
65 Combine SSE notifications with `PullFilter` to pull only specific tables or time ranges:
66
67 ```rust
68 let filter = PullFilter {
69 tables: Some(vec!["tasks".into()]),
70 since: Some(last_sync_time),
71 ..Default::default()
72 };
73
74 let (changes, new_cursor, has_more) =
75 client.pull_filtered(device_id, cursor, filter).await?;
76 ```
77
78 This reduces bandwidth and processing when your app only needs a subset of changes.
79
80 ## Design Notes
81
82 - One SSE connection per (app, user) pair
83 - The broadcast channel buffers up to 16 messages. If a client falls behind, missed events are skipped. The next pull will catch up regardless.
84 - SSE works through standard HTTP proxies and load balancers. The 30-second keepalive stays within common proxy timeout defaults.
85
86 ## See Also
87
88 - [SyncKit Cloud Sync]./synckit.md: Full sync API reference
89 - [OAuth2 PKCE]./oauth.md: Authentication for SyncKit apps
90