@sockethub/platform-caldav 1.0.0-alpha.9 → 1.0.0-beta.1

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
@@ -1,14 +1,20 @@
1
1
  # `@sockethub/platform-caldav`
2
2
 
3
3
  Sockethub platform for discovering CalDAV calendars and reading, creating,
4
- updating, and deleting events (`VEVENT`) and tasks (`VTODO`).
4
+ updating, and deleting events (`VEVENT`) and tasks (`VTODO`). It can also read
5
+ published iCalendar (`.ics`) feeds, such as holiday calendars or read-only
6
+ subscription links, without a CalDAV account.
5
7
 
6
8
  Requests accept only public HTTPS services by default. Administrators can opt
7
9
  in to HTTP with `packageConfig.allowInsecureHttp` and private-network targets
8
10
  with `packageConfig.allowPrivateAddresses`; these are server settings and
9
- cannot be enabled by a connected client. Credentials and OAuth tokens are sent
10
- only to the configured origin. Every calendar and item target is checked
11
- against the authenticated account before it is used.
11
+ cannot be enabled by a connected client. Address checks use the shared
12
+ outbound URL guard, including IPv6 embeddings of private ranges and the
13
+ local-use NAT64 prefix `64:ff9b:1::/48`. See
14
+ [Outbound URL guard](../../docs/configuration.md#outbound-url-guard).
15
+ Credentials and OAuth tokens are sent only to the configured origin. Every
16
+ calendar and item target is checked against the authenticated account before
17
+ it is used.
12
18
 
13
19
  ## Authentication
14
20
 
@@ -63,6 +69,8 @@ never forwarded to another origin.
63
69
  - `fetch`: discover calendars and their event/task support.
64
70
  - `query`: read items from one discovered calendar, optionally filtering by
65
71
  component type or UTC date range.
72
+ - `read`: list the items in a published `.ics` feed without credentials,
73
+ with the same optional filters as `query`.
66
74
  - `create`: create an event or task with collision protection.
67
75
  - `update`: replace an item only when its ETag still matches.
68
76
  - `delete`: delete an item only when its ETag still matches.
@@ -113,6 +121,44 @@ Each returned item contains its resource `id`, `uid`, `etag`, and
113
121
  `updateSupported`. Keep the ETag for later update or delete requests. Use date
114
122
  ranges for large calendars; responses larger than 10 MiB are rejected.
115
123
 
124
+ ### Read a published iCalendar feed
125
+
126
+ Many calendars are shared as a read-only `.ics` link (often shown as a
127
+ `webcal://` URL). The `read` action fetches such a feed and returns its
128
+ events and tasks as a collection. It needs no credentials and no `fetch`
129
+ step; `webcal:` links are fetched over HTTPS.
130
+
131
+ ```json
132
+ {
133
+ "type": "read",
134
+ "actor": { "id": "caldav:alice", "type": "person" },
135
+ "target": {
136
+ "id": "https://example.org/holidays/2026.ics",
137
+ "type": "feed"
138
+ },
139
+ "object": {
140
+ "type": "event",
141
+ "startTime": "2026-08-01T00:00:00Z",
142
+ "endTime": "2026-09-01T00:00:00Z"
143
+ }
144
+ }
145
+ ```
146
+
147
+ The collection `summary` is the feed's `X-WR-CALNAME` when present. Items use
148
+ the same shape as `query` results, with `updateSupported` always `false` and
149
+ no `etag`; each item's `id` is the feed URL plus a fragment derived from its
150
+ UID, so it is unique within the feed but cannot be used for `update` or
151
+ `delete`. The optional `object` filters by component type and overlap with a
152
+ UTC date range, following the CalDAV time-range rules. Recurring items are not
153
+ expanded: a recurring event is returned once, with its `recurrence` rule,
154
+ whenever the rule can still produce an occurrence inside the range, and
155
+ rescheduled occurrences appear as additional items. Components the platform
156
+ cannot represent (for example an event without a summary) are skipped and
157
+ logged rather than failing the whole feed. An all-day event without an end
158
+ lasts one day when matching a range. Feeds larger than 10 MiB are rejected,
159
+ and the same HTTPS and private-address policies apply as for CalDAV servers,
160
+ including on every redirect.
161
+
116
162
  ### Create an event
117
163
 
118
164
  ```json
@@ -233,6 +279,7 @@ administrator must explicitly enable `allowPrivateAddresses` or
233
279
  | Calendar listing | Supported | Discovers accessible calendars in the user's calendar home and their advertised `VEVENT`/`VTODO` component support. |
234
280
  | Events and tasks | Supported | Reads and writes `VEVENT` and `VTODO`. `VJOURNAL` and other component types are not supported. |
235
281
  | Calendar queries | Supported | Supports component and UTC time-range filters. Without a range, a query returns a calendar snapshot. |
282
+ | Published iCalendar feeds | Read-only | Reads `.ics` and `webcal://` subscription links without credentials, with component and time-range filters. Recurrences are not expanded. |
236
283
  | Conditional create, update, and delete | Supported | Uses ETags with `If-None-Match` and `If-Match` to prevent accidental overwrites. |
237
284
  | Recurrence | Partially supported | Common recurrence rules are parsed and generated. Resources with recurrence exceptions, exclusion dates, or multiple primary components are readable but rejected for update when rewriting could lose data. |
238
285
  | Scheduling data | Partially supported | Organizer and attendee properties are represented, but CalDAV scheduling inbox/outbox delivery, invitation responses, and server-side email are not implemented. |
@@ -251,4 +298,5 @@ Stable errors include `caldav:authentication-failed`,
251
298
  `caldav:unsupported-authentication`,
252
299
  `caldav:connection-failed`, `caldav:invalid-calendar`,
253
300
  `caldav:invalid-resource`, `caldav:unsupported-component`,
254
- `caldav:conflict`, and `caldav:not-found`.
301
+ `caldav:conflict`, `caldav:not-found`, `caldav:invalid-feed`, and
302
+ `caldav:feed-failed`.
package/dist/ics.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { CalendarItem, QueryInput } from "./types.js";
2
+ export interface ICalendarFeed {
3
+ name?: string;
4
+ items: CalendarItem[];
5
+ skipped: number;
6
+ }
7
+ /**
8
+ * Whether an item overlaps the requested time range, following the CalDAV
9
+ * time-range rules: an item matches when it starts before the range ends and
10
+ * ends after the range starts (a zero-length item matches when it starts at
11
+ * or after the range start). Recurring items are never expanded: they match
12
+ * while the rule can still produce an occurrence inside the range: a bounded
13
+ * rule (UNTIL or COUNT) stops matching once its last possible occurrence,
14
+ * including the master duration, has ended. Items without any time match
15
+ * only when no range is given. An all-day event with no end lasts one day
16
+ * (RFC 5545 section 3.6.1).
17
+ */
18
+ export declare function matchesRange(item: CalendarItem, query: QueryInput): boolean;
19
+ /**
20
+ * Parse every event and task in a published iCalendar file (an `.ics`
21
+ * subscription) into the item model used by CalDAV queries. Components the
22
+ * parser cannot represent are counted in `skipped` rather than failing the
23
+ * whole feed, since subscriptions are read-only and often third-party.
24
+ */
25
+ export declare function parseICalendarFeed(body: string, sourceUrl: string, query?: QueryInput): ICalendarFeed;
package/dist/index.d.ts CHANGED
@@ -1,19 +1,47 @@
1
1
  import type { ActivityStream, PlatformCallback, PlatformInterface, PlatformSchemaStruct, PlatformSession, StatelessPlatformConfig } from "@sockethub/schemas";
2
+ import { type Agent } from "undici";
2
3
  import type { CalDavCredentials } from "./types.js";
4
+ /**
5
+ * `fetch` that honours an undici `dispatcher`. Defaults to undici's own
6
+ * `fetch` so the request and the guarded dispatcher come from the same build.
7
+ */
8
+ type FeedFetch = (url: URL, init: RequestInit & {
9
+ dispatcher?: Agent;
10
+ }) => Promise<Response>;
3
11
  export declare function assertCalendarResource(calendarId: string, resourceId: string): void;
4
12
  export default class CalDav implements PlatformInterface {
5
13
  private readonly log;
14
+ private readonly fetchImpl;
15
+ private feedDispatcher?;
6
16
  config: StatelessPlatformConfig;
7
- constructor(session: PlatformSession);
17
+ constructor(session: PlatformSession, fetchImpl?: FeedFetch);
8
18
  get schema(): PlatformSchemaStruct;
9
19
  isInitialized(): boolean;
10
20
  cleanup(done: PlatformCallback): void;
11
21
  fetch(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
22
+ /**
23
+ * Read a published iCalendar (`.ics`) file such as a holiday calendar or a
24
+ * read-only subscription link. No CalDAV account is involved, so this
25
+ * action takes no credentials; `webcal:` links are fetched over HTTPS.
26
+ */
27
+ read(job: ActivityStream, done: PlatformCallback): void;
12
28
  create(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
13
29
  query(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
14
30
  update(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
15
31
  delete(job: ActivityStream, credentials: CalDavCredentials, done: PlatformCallback): void;
16
32
  private client;
33
+ /**
34
+ * The SSRF-guarded dispatcher for feed reads, created once per instance
35
+ * (its policy comes from packageConfig, fixed before the first job) so
36
+ * connections are pooled across reads.
37
+ */
38
+ private dispatcher;
39
+ /**
40
+ * Fetch a feed, following redirects by hand so every hop is re-checked
41
+ * against the scheme policy; the guarded dispatcher only vets addresses.
42
+ */
43
+ private fetchFeed;
44
+ private feedUrl;
17
45
  private calendar;
18
46
  private assertResource;
19
47
  private mutationResponse;