Fix: non-destructive calendar updates, attendee support, RFC 5545 compliance#53
Conversation
- Parse attendees/organizer from CalDAV events (PARTSTAT, ROLE, CUTYPE, RSVP) - Rewrite updateCalendarEvent to patch-in-place instead of rebuilding VEVENT, preserving all existing data (attendees, reminders, X-GOOGLE-*, etc.) - Add participant support to createCalendarEvent with ORGANIZER auto-generation - Add all-day event support (VALUE=DATE) on create and update - Parse DURATION and compute end time for DURATION-based events - Handle recurring events safely: require confirmRecurring for time changes that would orphan exception VEVENTs, using rrule for expansion - Preserve timezone (TZID) on floating-time updates - Remove JMAP calendar fallback (dead code), call CalDAV directly - Fix extractVEvent to return null instead of full input on no match - Add quote-aware iCal parsing (findValueBoundary) for properties with quoted colons in parameters (DELEGATED-FROM, ALTREP, etc.) - Validate attendee emails to prevent iCal property injection - SEQUENCE increment only for scheduling-significant changes with attendees - Orphaned VTIMEZONE cleanup after time changes - 355 tests (all passing), 97% statement coverage on caldav-client.ts
|
Couldn't see attendees, and that sent me down one hell of a rabbit hole when I found other issues. Got everything working with CalDAV and intentionally disabled JMAP calendar support. Reasoning was that JMAP isn't actually supported by FastMail, existing implementation appears to have bugs, and when Fastmail DO enable support, significant testing and likely bugfixing will be required. IMO - safer to explicitly disable it until the the JMAP implementation is enabled, so that it can be properly tested and feature parity can be ensured. Tried to closely follow the RFC specs, or other existing implementations where possible. As per the pull request - i have brought in an additional dependency to try and intelligently handle calendar entries in repeated series - but a workaround is feasible (albeit a bit of a blunt instrument). |
…phaned ORGANIZER (MadLlama25#56) update_calendar_event previously overwrote title/description/location to empty when passed "" (guarding only on !== undefined), and would crash on null. Empty, whitespace-only, and null values on those fields are now loud-rejected with a per-field error ("omit the field to leave it unchanged"). Dates already threw on bad input and are unchanged. To deliberately delete an optional field, pass it in the new clearFields array (allowed: description, location). Setting and clearing the same field in one call is rejected. Also fixes a related malformed-VEVENT bug: participants: [] removed all ATTENDEEs but left a dangling ORGANIZER. It now strips ORGANIZER too, gated on the empty case so the participant-add path still emits an ORGANIZER. Adds requireNonEmpty / validateClearFields helpers (exported, unit-tested) and flips the existing "preserves ORGANIZER" test to assert removal. Closes MadLlama25#56.
|
Pushed a follow-up commit (de56612) addressing #56 and a related bug found alongside it:
Both come with unit tests (the existing |
….11.0 Five findings from an adversarial review of the merged PR #53 code: - escapeICalText: normalize bare CR to LF before escaping and strip remaining control characters — a lone \r previously passed through verbatim and could act as a line terminator for downstream parsers (iCal property injection via title/description/location). - normalizeMasterVEventFirst: reorder VEVENT blocks so the master (no RECURRENCE-ID) is first before in-place patching. Component order is not guaranteed by RFC; an exception-first payload previously had its exception patched and skipped the recurring-event guard. - parseICalDateAsUTC: orphaned-exception detection now compares RECURRENCE-IDs and RRULE occurrences in a single UTC frame. With TZ != UTC, naive datetimes were parsed in local time while rrule used naive-as-UTC, flagging valid exceptions as orphans (deleted once the user passed confirmRecurring). - replaceICalProperty: insert new properties before the first sub-component (VALARM) per RFC 5545 ABNF eventprop *alarmc. - Quoted TZID params handled in formatDateTimeProperty and removeOrphanedVTimezones (plus fold-aware reference scanning). 15 new unit tests pin these behaviors. Version synced to 1.11.0 across package.json, manifest.json, src/index.ts, and README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
updateCalendarEventdestroyed all non-standard properties (attendees, reminders, recurrence rules, X-GOOGLE-* etc.) by rebuilding VEVENT from scratch. Now patches in-place, preserving everything not explicitly changed.createCalendarEventsilently failed for all-day events (produced midnight UTC instead ofVALUE=DATE)startDate/endDatenot passed through,participants: []sent when none provided). Replaced with direct CalDAV calls; JMAP calendar code retained with bug fixes and TODO markers for when Fastmail enables support.confirmRecurringflag required for time changes, orphaned exceptions pruned via RRULE expansionWhat changed
src/caldav-client.ts(+966 lines)parseAllICalProperties,parseAttendee,findValueBoundary(quote-aware),replaceICalProperty,removeAllICalProperties,insertBeforeEndVEvent,removeOrphanedVTimezones,removeExceptionVEvents,parseICalDuration,formatDateTimeProperty,validateAttendeeEmail,quoteParamValue,detectLineEnding,toICalUTC(with date-only guard),foldICalLine(with configurable line ending)extractVEventreturnsnullinstead of full input when no VEVENT found (was silently parsing VTIMEZONE properties)Participantinterface andCalendarEventextended withorganizer?andparticipants?parseCalendarObjecttakes optional{ includeParticipants }(onlyget_calendar_eventenables it, keeping list responses compact)src/caldav-client.test.ts(+1475 lines, 171 new tests)Comprehensive coverage including CRLF/LF preservation, VALARM sub-component skipping, injection prevention, recurring event orphan detection, quote-aware parsing, and all edge cases.
src/index.tsupdate_calendar_eventanddelete_calendar_eventtool schemas and handlers addedcreate_calendar_eventnow passesparticipantsthrough to CalDAV (was silently dropped)get_calendar_eventusesgetCalendarEventByIdwith participant parsingsrc/contacts-calendar.tsparticipants: []being sent when none provided (JMAP bug)package.jsonrruledependency for RRULE expansion during recurring event orphan detectiontslib), built-in TypeScript typesREADME.mdFASTMAIL_CALDAV_DISPLAY_NAMEenv var documentedSupersedes
This PR supersedes #45 (iCal encoding fix) — those fixes are included and extended here.
Test plan
npx tsc --noEmitpassesvalidateSavePathfailures on Windows unrelated to this PR)npm run buildcompilesget_calendar_eventon event with Google-synced attendees — participants visible with PARTSTAT/ROLE/CUTYPEupdate_calendar_eventchanging only title — attendees survive, timezone preserved, SEQUENCE unchangedcreate_calendar_eventwith participants — ORGANIZER and ATTENDEE lines presentcreate_calendar_eventwith date-only start — all-day event, not midnight UTC