@sockethub/platform-caldav 1.0.0-beta.4 → 1.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ it is used.
18
18
 
19
19
  ## Discovery cache
20
20
 
21
- Each `query`, `create`, `update`, and `delete` needs the account's calendar list to
21
+ Each `query`, `sync`, `create`, `update`, and `delete` needs the account's calendar list to
22
22
  check the target. The platform caches that discovery result in memory for
23
23
  `packageConfig.discoveryCacheTtlMs` (default `300000`, 5 minutes; `0`
24
24
  disables the cache). Entries are keyed by an HMAC of the protocol, service URL,
@@ -82,6 +82,8 @@ never forwarded to another origin.
82
82
  - `fetch`: discover calendars and their event/task support.
83
83
  - `query`: read items from one discovered calendar, optionally filtering by
84
84
  component type or UTC date range.
85
+ - `sync`: report what changed in one discovered calendar since a sync token
86
+ (RFC 6578 WebDAV Sync).
85
87
  - `read`: list the items in a published `.ics` feed without credentials,
86
88
  with the same optional filters as `query`.
87
89
  - `create`: create an event or task with collision protection.
@@ -134,6 +136,71 @@ Each returned item contains its resource `id`, `uid`, `etag`, and
134
136
  `updateSupported`. Keep the ETag for later update or delete requests. Use date
135
137
  ranges for large calendars; responses larger than 10 MiB are rejected.
136
138
 
139
+ ### Sync changes
140
+
141
+ `sync` reports changes without downloading the whole calendar each time. Send
142
+ it without an `object` (or without `object.syncToken`) for the initial sync:
143
+
144
+ ```json
145
+ {
146
+ "type": "sync",
147
+ "actor": { "id": "caldav:alice", "type": "person" },
148
+ "target": {
149
+ "id": "https://calendar.example/calendars/alice/personal/",
150
+ "type": "calendar"
151
+ }
152
+ }
153
+ ```
154
+
155
+ The response is a collection with every event and task, plus a token:
156
+
157
+ ```json
158
+ {
159
+ "type": "collection",
160
+ "summary": "CalDAV changes",
161
+ "totalItems": 1,
162
+ "items": [
163
+ {
164
+ "id": "https://calendar.example/calendars/alice/personal/a.ics",
165
+ "type": "task",
166
+ "uid": "a",
167
+ "name": "Pay rent",
168
+ "etag": "\"2\"",
169
+ "updateSupported": true
170
+ }
171
+ ],
172
+ "removed": [],
173
+ "syncToken": "http://sabre.io/ns/sync/42",
174
+ "truncated": false
175
+ }
176
+ ```
177
+
178
+ Store `syncToken` as an opaque string per calendar and send it back next time
179
+ as `"object": { "syncToken": "..." }`. The response then holds only the items
180
+ added or changed since that token, with their current ETag and content, and
181
+ `removed` lists the ids of items deleted since then. Items in the same shape
182
+ as `query` results replace the stored copy with the same `id`.
183
+
184
+ When `truncated` is `true`, the server limited the result. The returned token
185
+ covers exactly the changes delivered, so store it and sync again until
186
+ `truncated` is `false`.
187
+
188
+ Calendar resources that hold neither an event nor a task, such as journals, are
189
+ left out of `items`.
190
+
191
+ Handle these errors:
192
+
193
+ - `caldav:sync-token-invalid`: the server no longer accepts the token (it
194
+ expired, or the calendar was reset). Drop the stored token and sync again
195
+ without one.
196
+ - `caldav:sync-unsupported`: the server does not offer WebDAV Sync. Fall back
197
+ to `query`, which returns a full snapshot.
198
+ - `caldav:sync-failed`: any other sync error.
199
+
200
+ When a server rejects a token without saying why (a 400, 409, or 412 with
201
+ no `DAV:` precondition), the platform reports `caldav:sync-token-invalid`. A
202
+ retry without a token then reports the real cause.
203
+
137
204
  ### Read a published iCalendar feed
138
205
 
139
206
  Many calendars are shared as a read-only `.ics` link (often shown as a
@@ -257,7 +324,8 @@ Query again, reconcile the change, and retry with the new ETag.
257
324
  ## Interoperability and security
258
325
 
259
326
  The implementation follows WebDAV/CalDAV discovery, calendar-query REPORT,
260
- iCalendar content-line escaping/folding, and conditional PUT/DELETE semantics.
327
+ iCalendar content-line escaping/folding, conditional PUT/DELETE semantics, and
328
+ WebDAV Sync (RFC 6578).
261
329
 
262
330
  ### Server and provider compatibility
263
331
 
@@ -269,8 +337,8 @@ until they are included in automated or repeatable manual testing.
269
337
 
270
338
  | Service | Deployment | Verification | Authentication exercised | Notes |
271
339
  | --- | --- | --- | --- | --- |
272
- | Radicale 3.7.7 | Self-hosted | Automated integration | Basic | Complete discovery and event/task create, query, update, and delete lifecycle. |
273
- | Baïkal 0.10.1 | Self-hosted | Automated integration | Digest MD5 with `qop=auth` | Complete lifecycle through Baïkal's `dav.php/` endpoint, including its encoded ETags. |
340
+ | Radicale 3.7.7 | Self-hosted | Automated integration | Basic | Complete discovery and event/task create, query, update, and delete lifecycle, and incremental sync. |
341
+ | Baïkal 0.10.1 | Self-hosted | Automated integration | Digest MD5 with `qop=auth` | Complete lifecycle and incremental sync through Baïkal's `dav.php/` endpoint, including its encoded ETags. |
274
342
  | Nextcloud | Self-hosted or hosted | Manually verified during initial development | Basic with username and password or app password | Use the final `/remote.php/dav/` endpoint when cross-origin discovery redirects are involved. OAuth is not covered by this verification. |
275
343
  | ownCloud | Self-hosted or hosted | Not yet verified | Not yet verified | Expected to work when standard discovery and a supported authentication method are enabled. |
276
344
  | DAViCal | Self-hosted | Not yet verified | Not yet verified | Expected to work when standard discovery and a supported authentication method are enabled. |
@@ -300,17 +368,15 @@ administrator must explicitly enable `allowPrivateAddresses` or
300
368
  | Shared and delegated calendars | Read-only discovery only | Calendars returned from the user's calendar home can be queried, but creating shares, accepting invitations, proxy delegation, and ACL management are not implemented. |
301
369
  | Calendar collection management | Not supported | The platform does not create, rename, or delete calendar collections. |
302
370
  | Free/busy queries | Not supported | CalDAV free-busy REPORT and scheduling availability are not implemented. |
303
- | Incremental sync | Not supported | WebDAV sync tokens (RFC 6578) are not used yet; queries return snapshots. |
371
+ | Incremental sync | Supported | `sync` uses RFC 6578 WebDAV Sync tokens, including truncated results. Members returned without calendar data are fetched with one `calendar-multiget`. |
304
372
  | Managed attachments | Not supported | iCalendar URL and inline attachment properties are supported, but CalDAV managed attachments are not. |
305
373
 
306
374
  <!-- markdownlint-enable MD013 -->
307
375
 
308
- Queries currently return calendar snapshots. CalDAV sync tokens (RFC 6578) are
309
- the intended follow-up for efficient incremental synchronization.
310
-
311
376
  Stable errors include `caldav:authentication-failed`,
312
377
  `caldav:unsupported-authentication`,
313
378
  `caldav:connection-failed`, `caldav:invalid-calendar`,
314
379
  `caldav:invalid-resource`, `caldav:unsupported-component`,
315
- `caldav:conflict`, `caldav:not-found`, `caldav:invalid-feed`, and
316
- `caldav:feed-failed`.
380
+ `caldav:conflict`, `caldav:not-found`, `caldav:invalid-feed`,
381
+ `caldav:feed-failed`, `caldav:sync-token-invalid`,
382
+ `caldav:sync-unsupported`, and `caldav:sync-failed`.
package/dist/dav.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type DavAuthentication, DavClient, DavFailure, type DavNetworkOptions } from "@sockethub/dav";
2
- import type { CalendarDescription, CalendarItem, QueryInput } from "./types.js";
2
+ import type { CalendarDescription, CalendarItem, CalendarSyncResult, QueryInput } from "./types.js";
3
3
  export declare const CalDavFailure: typeof DavFailure;
4
4
  export declare class CalDavClient extends DavClient {
5
5
  constructor(url: string, authentication: DavAuthentication, timeoutMs?: number, networkOptions?: DavNetworkOptions);
@@ -10,6 +10,11 @@ export declare class CalDavClient extends DavClient {
10
10
  etag?: string;
11
11
  }>;
12
12
  query(calendar: CalendarDescription, query?: QueryInput): Promise<CalendarItem[]>;
13
+ /**
14
+ * Incremental sync (RFC 6578). Without `syncToken`, lists every event and
15
+ * task in the calendar; with one, lists only what changed since then.
16
+ */
17
+ sync(calendar: CalendarDescription, syncToken?: string): Promise<CalendarSyncResult>;
13
18
  update(id: string, etag: string, body: string): Promise<{
14
19
  id: string;
15
20
  etag?: string;
package/dist/index.d.ts CHANGED
@@ -30,6 +30,17 @@ export default class CalDav implements PlatformInterface {
30
30
  read(job: ActivityStream, done: PlatformCallback): void;
31
31
  create(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
32
32
  query(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
33
+ /**
34
+ * Incremental calendar sync (RFC 6578 WebDAV Sync). Without
35
+ * `object.syncToken`, returns every event and task plus a token; with
36
+ * one, returns only items changed and ids removed since that token.
37
+ * Store the returned `syncToken` opaquely and send it next time. When
38
+ * `truncated` is true, sync again with the new token before relying on
39
+ * the result being complete. `caldav:sync-token-invalid` means start
40
+ * over without a token; `caldav:sync-unsupported` means fall back to
41
+ * `query`.
42
+ */
43
+ sync(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
33
44
  update(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
34
45
  delete(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
35
46
  private client;