@dytsou/calendar-build 2.0.1 → 2.1.0
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 +1 -1
- package/worker.js +286 -4
package/package.json
CHANGED
package/worker.js
CHANGED
|
@@ -225,11 +225,269 @@ function injectConsoleFilter(html) {
|
|
|
225
225
|
return consoleFilterScript + html;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Normalize date to ISO string for comparison
|
|
230
|
+
*/
|
|
231
|
+
function normalizeDate(dateValue) {
|
|
232
|
+
if (!dateValue) return null;
|
|
233
|
+
if (typeof dateValue === 'string') {
|
|
234
|
+
// Parse and return ISO string
|
|
235
|
+
const date = new Date(dateValue);
|
|
236
|
+
return isNaN(date.getTime()) ? null : date.toISOString();
|
|
237
|
+
}
|
|
238
|
+
if (dateValue instanceof Date) {
|
|
239
|
+
return dateValue.toISOString();
|
|
240
|
+
}
|
|
241
|
+
// Try to convert to date
|
|
242
|
+
const date = new Date(dateValue);
|
|
243
|
+
return isNaN(date.getTime()) ? null : date.toISOString();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Extract event time fields (handles various field name formats)
|
|
248
|
+
*/
|
|
249
|
+
function getEventTime(event, field) {
|
|
250
|
+
// Try common field name variations (including iCal and calendar.js formats)
|
|
251
|
+
const variations = {
|
|
252
|
+
start: ['start', 'startDate', 'start_time', 'startTime', 'dtstart', 'start_date', 'dateStart', 'date_start'],
|
|
253
|
+
end: ['end', 'endDate', 'end_time', 'endTime', 'dtend', 'end_date', 'dateEnd', 'date_end']
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const fieldNames = variations[field] || [field];
|
|
257
|
+
for (const name of fieldNames) {
|
|
258
|
+
if (event[name] !== undefined && event[name] !== null) {
|
|
259
|
+
return event[name];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Extract event title (handles various field name formats)
|
|
267
|
+
*/
|
|
268
|
+
function getEventTitle(event) {
|
|
269
|
+
return event.title || event.summary || event.name || '';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Extract event description (handles various field name formats)
|
|
274
|
+
*/
|
|
275
|
+
function getEventDescription(event) {
|
|
276
|
+
return event.description || event.desc || '';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Extract calendar identifier (handles various field name formats)
|
|
281
|
+
*/
|
|
282
|
+
function getCalendarId(event) {
|
|
283
|
+
return event.calendar || event.calendarId || event.calendar_id || event.source || 'default';
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Merge consecutive events within the same calendar
|
|
288
|
+
* Events are consecutive if event1.end === event2.start (exact match)
|
|
289
|
+
*/
|
|
290
|
+
function mergeConsecutiveEvents(events) {
|
|
291
|
+
if (!Array.isArray(events) || events.length === 0) {
|
|
292
|
+
return events;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Group events by calendar identifier
|
|
296
|
+
const eventsByCalendar = {};
|
|
297
|
+
for (const event of events) {
|
|
298
|
+
const calendarId = getCalendarId(event);
|
|
299
|
+
if (!eventsByCalendar[calendarId]) {
|
|
300
|
+
eventsByCalendar[calendarId] = [];
|
|
301
|
+
}
|
|
302
|
+
eventsByCalendar[calendarId].push(event);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const mergedEvents = [];
|
|
306
|
+
|
|
307
|
+
// Process each calendar group separately
|
|
308
|
+
for (const calendarId in eventsByCalendar) {
|
|
309
|
+
const calendarEvents = eventsByCalendar[calendarId];
|
|
310
|
+
|
|
311
|
+
// Sort events by start time
|
|
312
|
+
calendarEvents.sort((a, b) => {
|
|
313
|
+
const startA = getEventTime(a, 'start');
|
|
314
|
+
const startB = getEventTime(b, 'start');
|
|
315
|
+
if (!startA || !startB) return 0;
|
|
316
|
+
return new Date(startA) - new Date(startB);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Merge consecutive events
|
|
320
|
+
let currentMerge = null;
|
|
321
|
+
|
|
322
|
+
for (let i = 0; i < calendarEvents.length; i++) {
|
|
323
|
+
const event = calendarEvents[i];
|
|
324
|
+
const startTime = getEventTime(event, 'start');
|
|
325
|
+
const endTime = getEventTime(event, 'end');
|
|
326
|
+
|
|
327
|
+
if (!startTime || !endTime) {
|
|
328
|
+
// If event is missing time info, add as-is
|
|
329
|
+
if (currentMerge) {
|
|
330
|
+
mergedEvents.push(currentMerge);
|
|
331
|
+
currentMerge = null;
|
|
332
|
+
}
|
|
333
|
+
mergedEvents.push(event);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (currentMerge === null) {
|
|
338
|
+
// Start a new merge group
|
|
339
|
+
currentMerge = { ...event };
|
|
340
|
+
} else {
|
|
341
|
+
// Check if this event is consecutive to the current merge
|
|
342
|
+
const currentEndTime = getEventTime(currentMerge, 'end');
|
|
343
|
+
// Normalize dates for comparison (handle different date formats)
|
|
344
|
+
const normalizedEndTime = normalizeDate(currentEndTime);
|
|
345
|
+
const normalizedStartTime = normalizeDate(startTime);
|
|
346
|
+
|
|
347
|
+
if (normalizedEndTime && normalizedStartTime && normalizedEndTime === normalizedStartTime) {
|
|
348
|
+
// Merge: combine titles and extend end time
|
|
349
|
+
const currentTitle = getEventTitle(currentMerge);
|
|
350
|
+
const eventTitle = getEventTitle(event);
|
|
351
|
+
const combinedTitle = currentTitle && eventTitle
|
|
352
|
+
? `${currentTitle} + ${eventTitle}`
|
|
353
|
+
: currentTitle || eventTitle;
|
|
354
|
+
|
|
355
|
+
// Update title field (try common variations)
|
|
356
|
+
if (currentMerge.title !== undefined) currentMerge.title = combinedTitle;
|
|
357
|
+
if (currentMerge.summary !== undefined) currentMerge.summary = combinedTitle;
|
|
358
|
+
if (currentMerge.name !== undefined) currentMerge.name = combinedTitle;
|
|
359
|
+
if (!currentMerge.title && !currentMerge.summary && !currentMerge.name) {
|
|
360
|
+
currentMerge.title = combinedTitle;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Combine descriptions
|
|
364
|
+
const currentDesc = getEventDescription(currentMerge);
|
|
365
|
+
const eventDesc = getEventDescription(event);
|
|
366
|
+
if (currentDesc || eventDesc) {
|
|
367
|
+
const combinedDesc = currentDesc && eventDesc
|
|
368
|
+
? `${currentDesc}\n\n${eventDesc}`
|
|
369
|
+
: currentDesc || eventDesc;
|
|
370
|
+
|
|
371
|
+
if (currentMerge.description !== undefined) currentMerge.description = combinedDesc;
|
|
372
|
+
if (currentMerge.desc !== undefined) currentMerge.desc = combinedDesc;
|
|
373
|
+
if (!currentMerge.description && !currentMerge.desc) {
|
|
374
|
+
currentMerge.description = combinedDesc;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Update end time - preserve original field name format
|
|
379
|
+
const originalEndField = getEventTime(currentMerge, 'end') !== null
|
|
380
|
+
? (currentMerge.end !== undefined ? 'end' :
|
|
381
|
+
currentMerge.endDate !== undefined ? 'endDate' :
|
|
382
|
+
currentMerge.end_time !== undefined ? 'end_time' :
|
|
383
|
+
currentMerge.endTime !== undefined ? 'endTime' :
|
|
384
|
+
currentMerge.dtend !== undefined ? 'dtend' :
|
|
385
|
+
currentMerge.end_date !== undefined ? 'end_date' :
|
|
386
|
+
currentMerge.dateEnd !== undefined ? 'dateEnd' :
|
|
387
|
+
currentMerge.date_end !== undefined ? 'date_end' : 'end')
|
|
388
|
+
: 'end';
|
|
389
|
+
|
|
390
|
+
// Update all possible end time fields to ensure compatibility
|
|
391
|
+
if (currentMerge.end !== undefined) currentMerge.end = endTime;
|
|
392
|
+
if (currentMerge.endDate !== undefined) currentMerge.endDate = endTime;
|
|
393
|
+
if (currentMerge.end_time !== undefined) currentMerge.end_time = endTime;
|
|
394
|
+
if (currentMerge.endTime !== undefined) currentMerge.endTime = endTime;
|
|
395
|
+
if (currentMerge.dtend !== undefined) currentMerge.dtend = endTime;
|
|
396
|
+
if (currentMerge.end_date !== undefined) currentMerge.end_date = endTime;
|
|
397
|
+
if (currentMerge.dateEnd !== undefined) currentMerge.dateEnd = endTime;
|
|
398
|
+
if (currentMerge.date_end !== undefined) currentMerge.date_end = endTime;
|
|
399
|
+
if (!currentMerge.end && !currentMerge.endDate && !currentMerge.end_time &&
|
|
400
|
+
!currentMerge.endTime && !currentMerge.dtend && !currentMerge.end_date &&
|
|
401
|
+
!currentMerge.dateEnd && !currentMerge.date_end) {
|
|
402
|
+
currentMerge.end = endTime;
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
// Not consecutive, save current merge and start new one
|
|
406
|
+
mergedEvents.push(currentMerge);
|
|
407
|
+
currentMerge = { ...event };
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Add the last merge group if any
|
|
413
|
+
if (currentMerge !== null) {
|
|
414
|
+
mergedEvents.push(currentMerge);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return mergedEvents;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Process calendar events JSON and merge consecutive events
|
|
423
|
+
*/
|
|
424
|
+
function processCalendarEventsJson(jsonData) {
|
|
425
|
+
if (Array.isArray(jsonData)) {
|
|
426
|
+
// Format: [{event1}, {event2}, ...]
|
|
427
|
+
return mergeConsecutiveEvents(jsonData);
|
|
428
|
+
} else if (jsonData && typeof jsonData === 'object') {
|
|
429
|
+
// Check for nested structures
|
|
430
|
+
if (Array.isArray(jsonData.events)) {
|
|
431
|
+
// Format: {events: [...], ...}
|
|
432
|
+
return {
|
|
433
|
+
...jsonData,
|
|
434
|
+
events: mergeConsecutiveEvents(jsonData.events)
|
|
435
|
+
};
|
|
436
|
+
} else if (Array.isArray(jsonData.calendars)) {
|
|
437
|
+
// Format: {calendars: [{events: [...]}, ...], ...}
|
|
438
|
+
return {
|
|
439
|
+
...jsonData,
|
|
440
|
+
calendars: jsonData.calendars.map(calendar => {
|
|
441
|
+
if (Array.isArray(calendar.events)) {
|
|
442
|
+
return {
|
|
443
|
+
...calendar,
|
|
444
|
+
events: mergeConsecutiveEvents(calendar.events)
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return calendar;
|
|
448
|
+
})
|
|
449
|
+
};
|
|
450
|
+
} else if (Array.isArray(jsonData.data)) {
|
|
451
|
+
// Format: {data: [...], ...}
|
|
452
|
+
return {
|
|
453
|
+
...jsonData,
|
|
454
|
+
data: mergeConsecutiveEvents(jsonData.data)
|
|
455
|
+
};
|
|
456
|
+
} else if (Array.isArray(jsonData.items)) {
|
|
457
|
+
// Format: {items: [...], ...}
|
|
458
|
+
return {
|
|
459
|
+
...jsonData,
|
|
460
|
+
items: mergeConsecutiveEvents(jsonData.items)
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
// Unknown structure, try to find any array of events
|
|
464
|
+
for (const key in jsonData) {
|
|
465
|
+
if (Array.isArray(jsonData[key]) && jsonData[key].length > 0) {
|
|
466
|
+
// Check if it looks like events (has time fields)
|
|
467
|
+
const firstItem = jsonData[key][0];
|
|
468
|
+
if (firstItem && typeof firstItem === 'object') {
|
|
469
|
+
const hasTimeField = getEventTime(firstItem, 'start') !== null ||
|
|
470
|
+
getEventTime(firstItem, 'end') !== null;
|
|
471
|
+
if (hasTimeField) {
|
|
472
|
+
return {
|
|
473
|
+
...jsonData,
|
|
474
|
+
[key]: mergeConsecutiveEvents(jsonData[key])
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Unknown format, return as-is
|
|
483
|
+
return jsonData;
|
|
484
|
+
}
|
|
485
|
+
|
|
228
486
|
/**
|
|
229
487
|
* Sanitize response body to hide calendar URLs in error messages
|
|
230
488
|
* Only sanitizes HTML and JSON responses to avoid breaking JavaScript code
|
|
231
489
|
*/
|
|
232
|
-
async function sanitizeResponse(response) {
|
|
490
|
+
async function sanitizeResponse(response, pathname) {
|
|
233
491
|
const contentType = response.headers.get('content-type') || '';
|
|
234
492
|
|
|
235
493
|
// Only sanitize HTML and JSON responses (error messages)
|
|
@@ -250,6 +508,27 @@ async function sanitizeResponse(response) {
|
|
|
250
508
|
|
|
251
509
|
let sanitizedBody = body;
|
|
252
510
|
|
|
511
|
+
// For JSON responses, check if it's a calendar events endpoint
|
|
512
|
+
if (contentType.includes('application/json')) {
|
|
513
|
+
// Check for calendar events endpoints - open-web-calendar uses /calendar.json
|
|
514
|
+
// Also check for any .json file that might contain calendar events
|
|
515
|
+
const isCalendarEventsEndpoint = pathname === '/calendar.events.json' ||
|
|
516
|
+
pathname === '/calendar.json' ||
|
|
517
|
+
pathname.endsWith('.events.json') ||
|
|
518
|
+
pathname.endsWith('.json');
|
|
519
|
+
|
|
520
|
+
if (isCalendarEventsEndpoint) {
|
|
521
|
+
try {
|
|
522
|
+
const jsonData = JSON.parse(body);
|
|
523
|
+
const processedData = processCalendarEventsJson(jsonData);
|
|
524
|
+
sanitizedBody = JSON.stringify(processedData);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
// If JSON parsing fails, continue with original body
|
|
527
|
+
console.error('Failed to parse calendar events JSON:', error);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
253
532
|
// For HTML responses, inject console filtering
|
|
254
533
|
if (contentType.includes('text/html')) {
|
|
255
534
|
sanitizedBody = injectConsoleFilter(sanitizedBody);
|
|
@@ -390,7 +669,8 @@ export default {
|
|
|
390
669
|
});
|
|
391
670
|
|
|
392
671
|
// Sanitize response to hide calendar URLs in error messages
|
|
393
|
-
|
|
672
|
+
// Pass pathname for calendar events processing
|
|
673
|
+
return await sanitizeResponse(response, pathname);
|
|
394
674
|
}
|
|
395
675
|
|
|
396
676
|
// Handle main calendar page requests - always add calendar URLs from secret
|
|
@@ -444,7 +724,8 @@ export default {
|
|
|
444
724
|
});
|
|
445
725
|
|
|
446
726
|
// Sanitize response to hide calendar URLs in error messages
|
|
447
|
-
|
|
727
|
+
// Pass pathname for calendar events processing
|
|
728
|
+
return await sanitizeResponse(response, pathname);
|
|
448
729
|
}
|
|
449
730
|
|
|
450
731
|
// For all other requests (static resources, etc.), proxy directly
|
|
@@ -469,7 +750,8 @@ export default {
|
|
|
469
750
|
});
|
|
470
751
|
|
|
471
752
|
// Sanitize response to hide calendar URLs in error messages
|
|
472
|
-
|
|
753
|
+
// Pass pathname for calendar events processing
|
|
754
|
+
return await sanitizeResponse(response, pathname);
|
|
473
755
|
} catch (error) {
|
|
474
756
|
return new Response(`Error: ${error.message}`, {
|
|
475
757
|
status: 500,
|