caldav-adapter 8.3.5 → 8.3.6
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "caldav-adapter",
|
|
3
3
|
"description": "CalDAV server for Node.js and Koa. Modernized and maintained for Forward Email.",
|
|
4
|
-
"version": "8.3.
|
|
4
|
+
"version": "8.3.6",
|
|
5
5
|
"author": "Sanders DeNardi and Forward Email LLC",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Sanders DeNardi <sedenardi@gmail.com> (http://www.sandersdenardi.com/)",
|
|
@@ -12,21 +12,6 @@ module.exports = function (options) {
|
|
|
12
12
|
|
|
13
13
|
return async function (ctx, events, calendar, children) {
|
|
14
14
|
const eventActions = _.map(events, async (event) => {
|
|
15
|
-
//
|
|
16
|
-
// For deleted events without href, we cannot reliably construct the
|
|
17
|
-
// correct URL that the client originally used. Returning an incorrect
|
|
18
|
-
// URL causes Apple Calendar to fail with "Couldn't get a calendar item
|
|
19
|
-
// to remove" errors because the URL doesn't match its local cache.
|
|
20
|
-
//
|
|
21
|
-
// Skip these events in sync-collection responses. The client will
|
|
22
|
-
// eventually clean them up through other sync mechanisms.
|
|
23
|
-
//
|
|
24
|
-
// See: https://www.rfc-editor.org/rfc/rfc6578.html (sync-collection)
|
|
25
|
-
//
|
|
26
|
-
if (event.deleted_at && !event.href) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
15
|
const misses = [];
|
|
31
16
|
const propActions = _.map(children, async (child) => {
|
|
32
17
|
return tags.getResponse({
|
|
@@ -63,6 +48,8 @@ module.exports = function (options) {
|
|
|
63
48
|
// Priority order:
|
|
64
49
|
// 1. event.href - the original resource path (if stored by the data layer)
|
|
65
50
|
// 2. Constructed from event.eventId - fallback for backwards compatibility
|
|
51
|
+
// This fallback produces the same URLs that were returned to clients
|
|
52
|
+
// before the href field was added, so it matches what clients cached.
|
|
66
53
|
//
|
|
67
54
|
// See: https://www.rfc-editor.org/rfc/rfc6578.html (sync-collection)
|
|
68
55
|
//
|
|
@@ -80,7 +67,6 @@ module.exports = function (options) {
|
|
|
80
67
|
return resp;
|
|
81
68
|
});
|
|
82
69
|
const responses = await Promise.all(eventActions);
|
|
83
|
-
|
|
84
|
-
return { responses: _.compact(responses) };
|
|
70
|
+
return { responses };
|
|
85
71
|
};
|
|
86
72
|
};
|
|
@@ -95,17 +95,15 @@ test('event-response uses event.href when available instead of constructing from
|
|
|
95
95
|
t.is(responseObj['D:href'], '/cal/user/calendar/original@event.ics');
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
test('event-response returns 404 status for deleted events
|
|
98
|
+
test('event-response returns 404 status for deleted events', async (t) => {
|
|
99
99
|
const options = createMockOptions();
|
|
100
100
|
const eventResponse = eventResponseFactory(options);
|
|
101
101
|
const ctx = createMockCtx('/cal/user/calendar');
|
|
102
102
|
const calendar = createMockCalendar();
|
|
103
103
|
|
|
104
|
-
// Deleted event WITH href - should be included in response
|
|
105
104
|
const events = [
|
|
106
105
|
{
|
|
107
106
|
eventId: 'deleted-event',
|
|
108
|
-
href: '/cal/user/calendar/deleted-event.ics',
|
|
109
107
|
deleted_at: new Date(),
|
|
110
108
|
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
111
109
|
}
|
|
@@ -127,13 +125,15 @@ test('event-response returns 404 status for deleted events with href', async (t)
|
|
|
127
125
|
t.true(responseObj['D:status'].includes('404'));
|
|
128
126
|
});
|
|
129
127
|
|
|
130
|
-
test('event-response
|
|
128
|
+
test('event-response returns 404 for deleted events without href using fallback URL', async (t) => {
|
|
131
129
|
const options = createMockOptions();
|
|
132
130
|
const eventResponse = eventResponseFactory(options);
|
|
133
131
|
const ctx = createMockCtx('/cal/user/calendar');
|
|
134
132
|
const calendar = createMockCalendar();
|
|
135
133
|
|
|
136
|
-
// Deleted event WITHOUT href - should
|
|
134
|
+
// Deleted event WITHOUT href - should use fallback URL construction
|
|
135
|
+
// This is critical for backwards compatibility with events created
|
|
136
|
+
// before the href field was added
|
|
137
137
|
const events = [
|
|
138
138
|
{
|
|
139
139
|
eventId: 'deleted-event-no-href',
|
|
@@ -147,8 +147,13 @@ test('event-response skips deleted events without href', async (t) => {
|
|
|
147
147
|
const result = await eventResponse(ctx, events, calendar, children);
|
|
148
148
|
|
|
149
149
|
t.truthy(result.responses);
|
|
150
|
-
// Should be
|
|
151
|
-
t.is(result.responses.length,
|
|
150
|
+
// Should NOT be skipped - fallback URL construction works for backwards compatibility
|
|
151
|
+
t.is(result.responses.length, 1);
|
|
152
|
+
|
|
153
|
+
const responseObj = result.responses[0];
|
|
154
|
+
t.is(responseObj['D:href'], '/cal/user/calendar/deleted-event-no-href.ics');
|
|
155
|
+
t.truthy(responseObj['D:status']);
|
|
156
|
+
t.true(responseObj['D:status'].includes('404'));
|
|
152
157
|
});
|
|
153
158
|
|
|
154
159
|
test('event-response uses href for deleted events when available (critical for sync)', async (t) => {
|
|
@@ -225,7 +230,6 @@ test('event-response handles multiple events correctly', async (t) => {
|
|
|
225
230
|
},
|
|
226
231
|
{
|
|
227
232
|
eventId: 'event3',
|
|
228
|
-
href: '/cal/user/calendar/event3.ics',
|
|
229
233
|
deleted_at: new Date(),
|
|
230
234
|
ical: 'BEGIN:VCALENDAR\nEND:VCALENDAR'
|
|
231
235
|
}
|
|
@@ -244,7 +248,7 @@ test('event-response handles multiple events correctly', async (t) => {
|
|
|
244
248
|
// Event 2: uses href
|
|
245
249
|
t.is(result.responses[1]['D:href'], '/cal/user/calendar/custom-path.ics');
|
|
246
250
|
|
|
247
|
-
// Event 3: deleted
|
|
251
|
+
// Event 3: deleted, uses eventId (no href) - fallback URL construction
|
|
248
252
|
t.is(result.responses[2]['D:href'], '/cal/user/calendar/event3.ics');
|
|
249
253
|
t.true(result.responses[2]['D:status'].includes('404'));
|
|
250
254
|
});
|
|
@@ -279,8 +283,8 @@ test('event-response handles multiple events with mixed deleted states', async (
|
|
|
279
283
|
const result = await eventResponse(ctx, events, calendar, children);
|
|
280
284
|
|
|
281
285
|
t.truthy(result.responses);
|
|
282
|
-
//
|
|
283
|
-
t.is(result.responses.length,
|
|
286
|
+
// All 3 responses - deleted without href is NO LONGER skipped
|
|
287
|
+
t.is(result.responses.length, 3);
|
|
284
288
|
|
|
285
289
|
// Active event
|
|
286
290
|
t.is(result.responses[0]['D:href'], '/cal/user/calendar/active-event.ics');
|
|
@@ -291,6 +295,10 @@ test('event-response handles multiple events with mixed deleted states', async (
|
|
|
291
295
|
'/cal/user/calendar/deleted-with-href.ics'
|
|
292
296
|
);
|
|
293
297
|
t.true(result.responses[1]['D:status'].includes('404'));
|
|
298
|
+
|
|
299
|
+
// Deleted event without href - uses fallback URL construction
|
|
300
|
+
t.is(result.responses[2]['D:href'], '/cal/user/calendar/deleted-no-href.ics');
|
|
301
|
+
t.true(result.responses[2]['D:status'].includes('404'));
|
|
294
302
|
});
|
|
295
303
|
|
|
296
304
|
test('event-response handles email-like eventId with @ symbol', async (t) => {
|