@openinc/parse-server-opendash 4.0.28 → 4.0.30

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.
Files changed (23) hide show
  1. package/dist/features/openservice/schedules/calendarSync/functions/eventToSchedule.d.ts +11 -5
  2. package/dist/features/openservice/schedules/calendarSync/functions/eventToSchedule.js +62 -23
  3. package/dist/features/openservice/schedules/calendarSync/functions/scheduleToEvent.d.ts +2 -1
  4. package/dist/features/openservice/schedules/calendarSync/functions/scheduleToEvent.js +62 -33
  5. package/dist/features/openservice/schedules/calendarSync/service/CalendarManager.d.ts +27 -22
  6. package/dist/features/openservice/schedules/calendarSync/service/CalendarManager.js +101 -48
  7. package/dist/features/openservice/schedules/calendarSync/service/CalendarSyncPoller.d.ts +20 -0
  8. package/dist/features/openservice/schedules/calendarSync/service/CalendarSyncPoller.js +197 -0
  9. package/dist/features/openservice/schedules/calendarSync/service/calendarMetaHelper.d.ts +36 -0
  10. package/dist/features/openservice/schedules/calendarSync/service/calendarMetaHelper.js +89 -0
  11. package/dist/features/openservice/schedules/calendarSync/types/ConfigValues.d.ts +8 -0
  12. package/dist/features/openservice/schedules/calendarSync/types/ConfigValues.js +2 -0
  13. package/dist/features/openservice/schedules/calendarSync/types/Event.d.ts +1 -1
  14. package/dist/features/openservice/schedules/initSchedulesFeature.js +6 -0
  15. package/dist/hooks/Config_open_service.js +2 -0
  16. package/dist/hooks/Maintenance_Schedule.js +47 -22
  17. package/dist/types/Meta_Config.d.ts +4 -3
  18. package/dist/types/Meta_Entry.d.ts +4 -3
  19. package/dist/types/custom/MetaField.d.ts +117 -0
  20. package/dist/types/custom/MetaField.js +21 -0
  21. package/dist/types/index.d.ts +2 -0
  22. package/dist/types/index.js +4 -2
  23. package/package.json +5 -5
@@ -1,11 +1,17 @@
1
1
  import { GraphEventResponse } from "../types/Event.js";
2
2
  export type ScheduleCronTimestamp = {
3
- startData: Date;
4
- endData?: Date;
3
+ startDate: Date;
4
+ endDate?: Date;
5
5
  number: number;
6
6
  unit: "days" | "weeks" | "months";
7
7
  };
8
- export declare function eventToSchedule(event: GraphEventResponse): {
9
- timestamp: ScheduleCronTimestamp;
8
+ export type NotifyBeforeDue = {
9
+ value: number;
10
+ unit: "minutes" | "hours" | "days" | "weeks";
11
+ };
12
+ export type EventToScheduleResult = {
10
13
  schedule_type: "timestamp";
11
- } | undefined;
14
+ timestamp: ScheduleCronTimestamp;
15
+ notifyBeforeDue?: NotifyBeforeDue;
16
+ };
17
+ export declare function eventToSchedule(event: GraphEventResponse): EventToScheduleResult | undefined;
@@ -1,10 +1,47 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.eventToSchedule = eventToSchedule;
7
+ const dayjs_1 = __importDefault(require("dayjs"));
8
+ // Parses only the date portion of an Outlook dateTime string to avoid
9
+ // timezone misinterpretation (Outlook dateTime is local to the event's
10
+ // timezone, not UTC).
11
+ function parseDateOnly(dateTime) {
12
+ return (0, dayjs_1.default)(dateTime.slice(0, 10));
13
+ }
14
+ function minutesToNotifyBeforeDue(minutes) {
15
+ if (minutes % (60 * 24 * 7) === 0)
16
+ return { value: minutes / (60 * 24 * 7), unit: "weeks" };
17
+ if (minutes % (60 * 24) === 0)
18
+ return { value: minutes / (60 * 24), unit: "days" };
19
+ if (minutes % 60 === 0)
20
+ return { value: minutes / 60, unit: "hours" };
21
+ return { value: minutes, unit: "minutes" };
22
+ }
4
23
  function eventToSchedule(event) {
24
+ const notifyBeforeDue = event.isReminderOn && typeof event.reminderMinutesBeforeStart === "number"
25
+ ? minutesToNotifyBeforeDue(event.reminderMinutesBeforeStart)
26
+ : undefined;
5
27
  const recurrence = event?.recurrence;
6
- if (!recurrence)
7
- return undefined;
28
+ // Single (non-recurring) event: treat as one-day schedule regardless of
29
+ // the event's time or duration — our schedules are always all-day.
30
+ if (!recurrence) {
31
+ if (!event.start?.dateTime)
32
+ return undefined;
33
+ const date = parseDateOnly(event.start.dateTime).toDate();
34
+ return {
35
+ schedule_type: "timestamp",
36
+ timestamp: {
37
+ startDate: date,
38
+ endDate: date,
39
+ number: 1,
40
+ unit: "days",
41
+ },
42
+ notifyBeforeDue,
43
+ };
44
+ }
8
45
  const pattern = recurrence.pattern;
9
46
  const range = recurrence.range;
10
47
  if (!pattern || !range || !pattern.type)
@@ -21,39 +58,41 @@ function eventToSchedule(event) {
21
58
  case "relativeMonthly":
22
59
  unit = "months";
23
60
  break;
61
+ case "absoluteYearly":
62
+ case "relativeYearly":
63
+ // Map yearly recurrence to a 12-month interval
64
+ unit = "months";
65
+ break;
24
66
  default:
25
- // unsupported recurrence type
26
67
  return undefined;
27
68
  }
28
69
  const interval = typeof pattern.interval === "number" && pattern.interval > 0
29
70
  ? pattern.interval
30
71
  : 1;
31
- // Prefer explicit range.startDate, fallback to event.start.dateTime
32
- let startData;
33
- if (range?.startDate) {
34
- // range.startDate is YYYY-MM-DD
35
- startData = new Date(range.startDate + "T00:00:00Z");
36
- }
37
- else if (event.start?.dateTime) {
38
- startData = new Date(event.start.dateTime);
72
+ // Yearly patterns don't carry an interval in Outlook (always 1 year)
73
+ const effectiveInterval = pattern.type === "absoluteYearly" || pattern.type === "relativeYearly"
74
+ ? 12
75
+ : interval;
76
+ // range.startDate is always YYYY-MM-DD
77
+ const startDate = parseDateOnly(range.startDate);
78
+ let endDate;
79
+ if (range.type === "endDate") {
80
+ endDate = parseDateOnly(range.endDate);
39
81
  }
40
- // For end, prefer range.endDate. If range.type === 'noEnd' leave undefined.
41
- let endData;
42
- if (range?.type === "endDate" && range?.endDate) {
43
- endData = new Date(range.endDate + "T00:00:00Z");
82
+ else if (range.type === "numbered") {
83
+ // Compute end date from occurrence count
84
+ const occurrences = range.numberOfOccurrences ?? 1;
85
+ endDate = startDate.add((occurrences - 1) * effectiveInterval, unit);
44
86
  }
45
- else if (event.end?.dateTime) {
46
- endData = new Date(event.end.dateTime);
47
- }
48
- if (!startData)
49
- return undefined;
87
+ // range.type === "noEnd" → endDate stays undefined
50
88
  return {
51
89
  schedule_type: "timestamp",
52
90
  timestamp: {
53
- startData,
54
- endData,
55
- number: interval,
91
+ startDate: startDate.toDate(),
92
+ endDate: endDate?.toDate(),
93
+ number: effectiveInterval,
56
94
  unit,
57
95
  },
96
+ notifyBeforeDue,
58
97
  };
59
98
  }
@@ -1,3 +1,4 @@
1
1
  import { Maintenance_Schedule } from "../../../../../types/index.js";
2
+ import { CalendarEventSnapshot } from "../service/calendarMetaHelper.js";
2
3
  import { GraphEvent } from "../types/Event.js";
3
- export declare function scheduleToEvent(schedule: Maintenance_Schedule): Promise<GraphEvent | undefined>;
4
+ export declare function scheduleToEvent(schedule: Maintenance_Schedule, originalEvent?: CalendarEventSnapshot): Promise<GraphEvent | undefined>;
@@ -1,13 +1,36 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.scheduleToEvent = scheduleToEvent;
4
- async function scheduleToEvent(schedule) {
5
- console.log("Converting Schedule to Event...");
7
+ const dayjs_1 = __importDefault(require("dayjs"));
8
+ function notifyBeforeDueToMinutes(notify) {
9
+ switch (notify.unit) {
10
+ case "weeks":
11
+ return notify.value * 60 * 24 * 7;
12
+ case "days":
13
+ return notify.value * 60 * 24;
14
+ case "hours":
15
+ return notify.value * 60;
16
+ case "minutes":
17
+ return notify.value;
18
+ case "seconds":
19
+ return Math.round(notify.value / 60);
20
+ case "milliseconds":
21
+ return Math.round(notify.value / 60000);
22
+ default:
23
+ return notify.value;
24
+ }
25
+ }
26
+ async function scheduleToEvent(schedule, originalEvent) {
6
27
  try {
7
- await schedule.fetchWithInclude("template");
28
+ await schedule.fetchWithInclude(["template"], {
29
+ useMasterKey: true,
30
+ });
8
31
  }
9
32
  catch (error) {
10
- console.error("Error fetching template:", error);
33
+ console.error(`[scheduleToEvent] Failed to fetch template for schedule ${schedule.id}:`, error);
11
34
  }
12
35
  const template = schedule.get("template");
13
36
  const templateCron = template?.get("cron");
@@ -16,33 +39,39 @@ async function scheduleToEvent(schedule) {
16
39
  const end = scheduleCron?.timestamp?.endDate ?? templateCron?.timestamp?.endDate;
17
40
  const interval = scheduleCron?.timestamp?.number ?? templateCron?.timestamp?.number;
18
41
  const unit = scheduleCron?.timestamp?.unit ?? templateCron?.timestamp?.unit;
19
- if (!start || !end || !interval || !unit) {
20
- console.log("Insufficient data to create event from schedule.");
42
+ if (!start || !interval || !unit) {
21
43
  return;
22
44
  }
45
+ const description = schedule.get("description");
46
+ const notifyBeforeDue = schedule.get("notifyBeforeDue");
47
+ // If the original Outlook event had specific times (not all-day), preserve
48
+ // them so we don't silently convert a timed event to all-day on update.
49
+ const preserveTimes = originalEvent != null && !originalEvent.isAllDay;
50
+ const toDateOnly = (d) => d.toISOString().slice(0, 10);
23
51
  const event = {
24
52
  subject: schedule.get("title"),
25
- start: {
26
- // for single-day/all-day events we use date-only; keep full ISO otherwise
27
- dateTime: start.toISOString().slice(0, 19),
28
- timeZone: "UTC",
29
- },
30
- end: {
31
- dateTime: end.toISOString().slice(0, 19),
32
- timeZone: "UTC",
33
- },
34
- isAllDay: true,
53
+ body: { contentType: "Text", content: description ?? "" },
54
+ start: preserveTimes
55
+ ? originalEvent.start
56
+ : { dateTime: toDateOnly(start), timeZone: "UTC" },
57
+ end: preserveTimes
58
+ ? originalEvent.end
59
+ : {
60
+ dateTime: toDateOnly((0, dayjs_1.default)(start).add(1, "day").toDate()),
61
+ timeZone: "UTC",
62
+ },
63
+ isAllDay: preserveTimes ? undefined : true,
64
+ isReminderOn: notifyBeforeDue != null,
65
+ reminderMinutesBeforeStart: notifyBeforeDue
66
+ ? notifyBeforeDueToMinutes(notifyBeforeDue)
67
+ : undefined,
35
68
  };
36
- // Helper to format date as YYYY-MM-DD for recurrence range
37
- const toDateOnly = (d) => d.toISOString().slice(0, 10);
38
- // determine pattern type and additional fields
39
69
  let pattern = { interval };
40
70
  if (unit === "days") {
41
71
  pattern.type = "daily";
42
72
  }
43
73
  else if (unit === "weeks") {
44
74
  pattern.type = "weekly";
45
- // include the weekday of the start date so weekly recurrences occur on that weekday
46
75
  const dayNames = [
47
76
  "Sunday",
48
77
  "Monday",
@@ -57,32 +86,32 @@ async function scheduleToEvent(schedule) {
57
86
  }
58
87
  else if (unit === "months") {
59
88
  pattern.type = "absoluteMonthly";
60
- // Graph uses dayOfMonth for absoluteMonthly
61
89
  pattern.dayOfMonth = start.getUTCDate();
62
90
  }
63
91
  else {
64
- // unsupported unit -> skip recurrence
65
92
  pattern = null;
66
93
  }
67
94
  if (pattern) {
68
- event.recurrence = {
69
- pattern,
70
- range: {
95
+ const range = end
96
+ ? {
71
97
  type: "endDate",
72
98
  startDate: toDateOnly(start),
73
99
  endDate: toDateOnly(end),
74
- },
75
- };
76
- // if event is all-day, set start/end to date-only values (Graph expects dates)
77
- if (event.isAllDay) {
100
+ }
101
+ : {
102
+ type: "noEnd",
103
+ startDate: toDateOnly(start),
104
+ };
105
+ event.recurrence = { pattern, range };
106
+ // All-day events need date-only strings for start/end in the Graph payload.
107
+ // Timed events already have their original times set above — don't touch them.
108
+ if (!preserveTimes) {
78
109
  event.start.dateTime = toDateOnly(start);
79
- // Graph's endDate in event object is exclusive for all-day events; keep as provided for recurrence range
80
- // Set end to next day to represent an all-day event that begins and ends the same day
81
110
  const endForEvent = new Date(start);
82
111
  endForEvent.setUTCDate(endForEvent.getUTCDate() + 1);
83
- event.end.dateTime = toDateOnly(endForEvent);
112
+ if (event.end)
113
+ event.end.dateTime = toDateOnly(endForEvent);
84
114
  }
85
115
  }
86
- console.log("Schedule converted to Event:", event);
87
116
  return event;
88
117
  }
@@ -3,32 +3,37 @@ import { GraphEvent, GraphEventResponse } from "../types/Event.js";
3
3
  * Manager for calendar operations via Microsoft Graph API
4
4
  */
5
5
  export declare class CalendarManager {
6
- isValid: boolean;
6
+ tenantID?: string;
7
+ clientID?: string;
8
+ clientSecret?: string;
9
+ userMail?: string;
10
+ constructor(config: {
11
+ tenantId: string;
12
+ clientID: string;
13
+ clientSecret: string;
14
+ userMail: string;
15
+ });
16
+ get isValid(): true | string;
7
17
  private getAccess;
8
- /**
9
- * Create a new event in the calendar
10
- * @param event The event data to create
11
- * @returns the created event response
12
- */
13
18
  createEvent(event: GraphEvent): Promise<GraphEventResponse | undefined>;
14
- /**
15
- * Update an event in the calendar
16
- * @param eventId The ID of the event to update
17
- * @param event The updated event data
18
- * @returns
19
- */
20
- updateEvent(eventId: string, event: Partial<GraphEvent>): Promise<unknown>;
21
- /**
22
- * Delete an event from the calendar
23
- * @param eventId The ID of the event to delete
24
- * @returns whether the deletion was successful
25
- */
19
+ updateEvent(eventId: string, event: Partial<GraphEvent>): Promise<GraphEventResponse | undefined>;
26
20
  deleteEvent(eventId: string): Promise<boolean | undefined>;
27
- /**
28
- * Fetch all events in calendar
29
- * @returns the list of events
30
- */
21
+ fetchEvent(eventId: string): Promise<GraphEventResponse | null>;
31
22
  fetchEvents(): Promise<{
32
23
  value: GraphEventResponse[];
33
24
  } | undefined>;
25
+ /**
26
+ * Fetch changed events since the last delta sync.
27
+ * On the first call (no deltaLink), returns all current events and a deltaLink
28
+ * for subsequent incremental calls.
29
+ * @param deltaLink token from the previous call; omit for the initial full sync
30
+ */
31
+ fetchDelta(deltaLink?: string): Promise<{
32
+ events: (GraphEventResponse & {
33
+ "@removed"?: {
34
+ reason: string;
35
+ };
36
+ })[];
37
+ deltaLink: string;
38
+ } | null>;
34
39
  }
@@ -1,27 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CalendarManager = void 0;
4
- const index_js_1 = require("../../../../config/index.js");
5
- const tenantID = index_js_1.ConfigInstance.getInstance().get("MICROSOFT_TENANT_ID");
6
- const clientID = index_js_1.ConfigInstance.getInstance().get("MICROSOFT_CLIENT_ID");
7
- const clientSecret = index_js_1.ConfigInstance.getInstance().get("MICROSOFT_CLIENT_SECRET");
8
- const userMail = index_js_1.ConfigInstance.getInstance().get("MICROSOFT_USER_EMAIL");
9
4
  /**
10
5
  * Manager for calendar operations via Microsoft Graph API
11
6
  */
12
7
  class CalendarManager {
13
- isValid = !!(tenantID && clientID && clientSecret && userMail);
8
+ tenantID;
9
+ clientID;
10
+ clientSecret;
11
+ userMail;
12
+ constructor(config) {
13
+ this.tenantID = config.tenantId;
14
+ this.clientID = config.clientID;
15
+ this.clientSecret = config.clientSecret;
16
+ this.userMail = config.userMail;
17
+ }
18
+ get isValid() {
19
+ if (!this.tenantID)
20
+ return "Missing Microsoft Tenant ID";
21
+ if (!this.clientID)
22
+ return "Missing Microsoft Client ID";
23
+ if (!this.clientSecret)
24
+ return "Missing Microsoft Client Secret";
25
+ if (!this.userMail)
26
+ return "Missing Microsoft User Email";
27
+ return true;
28
+ }
14
29
  async getAccess() {
15
- if (!this.isValid)
30
+ if (this.isValid !== true)
16
31
  return null;
17
- console.log("Getting access token...");
18
32
  try {
19
33
  const params = new URLSearchParams();
20
- params.append("client_id", clientID);
21
- params.append("client_secret", clientSecret);
34
+ params.append("client_id", this.clientID);
35
+ params.append("client_secret", this.clientSecret);
22
36
  params.append("scope", "https://graph.microsoft.com/.default");
23
37
  params.append("grant_type", "client_credentials");
24
- const response = await fetch(`https://login.microsoftonline.com/${tenantID}/oauth2/v2.0/token`, {
38
+ const response = await fetch(`https://login.microsoftonline.com/${this.tenantID}/oauth2/v2.0/token`, {
25
39
  method: "POST",
26
40
  headers: {
27
41
  "Content-Type": "application/x-www-form-urlencoded",
@@ -29,26 +43,27 @@ class CalendarManager {
29
43
  body: params.toString(),
30
44
  });
31
45
  const data = await response.json();
32
- console.log("Access token fetched successfully:", data);
46
+ if (!response.ok) {
47
+ console.error(`[CalendarManager] Failed to obtain access token: HTTP ${response.status}`);
48
+ return null;
49
+ }
33
50
  return data;
34
51
  }
35
52
  catch (error) {
36
- console.error("Error fetching access token:", error);
53
+ console.error("[CalendarManager] Error fetching access token:", error);
37
54
  return null;
38
55
  }
39
56
  }
40
- /**
41
- * Create a new event in the calendar
42
- * @param event The event data to create
43
- * @returns the created event response
44
- */
45
57
  async createEvent(event) {
46
- if (!userMail || !tenantID || !clientID || !clientSecret)
58
+ if (!this.userMail ||
59
+ !this.tenantID ||
60
+ !this.clientID ||
61
+ !this.clientSecret)
47
62
  return;
48
63
  const accessData = await this.getAccess();
49
64
  if (!accessData?.access_token)
50
65
  return;
51
- const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userMail)}/events`, {
66
+ const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events`, {
52
67
  method: "POST",
53
68
  headers: {
54
69
  Authorization: `Bearer ${accessData.access_token}`,
@@ -56,21 +71,17 @@ class CalendarManager {
56
71
  },
57
72
  body: JSON.stringify(event),
58
73
  });
59
- const data = (await res.json());
60
- console.log("Event creation response:", data);
61
- return data;
74
+ if (!res.ok) {
75
+ console.error(`[CalendarManager] Failed to create event: HTTP ${res.status}`);
76
+ return;
77
+ }
78
+ return (await res.json());
62
79
  }
63
- /**
64
- * Update an event in the calendar
65
- * @param eventId The ID of the event to update
66
- * @param event The updated event data
67
- * @returns
68
- */
69
80
  async updateEvent(eventId, event) {
70
81
  const accessData = await this.getAccess();
71
82
  if (!accessData?.access_token)
72
83
  return;
73
- const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userMail)}/events/${eventId}`, {
84
+ const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events/${eventId}`, {
74
85
  method: "PATCH",
75
86
  headers: {
76
87
  Authorization: `Bearer ${accessData.access_token}`,
@@ -78,46 +89,88 @@ class CalendarManager {
78
89
  },
79
90
  body: JSON.stringify(event),
80
91
  });
81
- const data = await res.json();
82
- console.log("Event update response:", data);
83
- return data;
92
+ if (!res.ok) {
93
+ console.error(`[CalendarManager] Failed to update event ${eventId}: HTTP ${res.status}`);
94
+ return;
95
+ }
96
+ return (await res.json());
84
97
  }
85
- /**
86
- * Delete an event from the calendar
87
- * @param eventId The ID of the event to delete
88
- * @returns whether the deletion was successful
89
- */
90
98
  async deleteEvent(eventId) {
91
99
  const accessData = await this.getAccess();
92
100
  if (!accessData?.access_token)
93
101
  return;
94
102
  // on successful deletion, Microsoft Graph returns 204 No Content, so no JSON to parse
95
- const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userMail)}/events/${eventId}`, {
103
+ const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events/${eventId}`, {
96
104
  method: "DELETE",
97
105
  headers: {
98
106
  Authorization: `Bearer ${accessData.access_token}`,
99
107
  },
100
108
  });
101
- console.log("Event deletion response:", res);
109
+ if (!res.ok) {
110
+ console.error(`[CalendarManager] Failed to delete event ${eventId}: HTTP ${res.status}`);
111
+ }
102
112
  return res.ok;
103
113
  }
104
- /**
105
- * Fetch all events in calendar
106
- * @returns the list of events
107
- */
114
+ async fetchEvent(eventId) {
115
+ const accessData = await this.getAccess();
116
+ if (!accessData?.access_token)
117
+ return null;
118
+ const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events/${eventId}`, { headers: { Authorization: `Bearer ${accessData.access_token}` } });
119
+ if (!res.ok) {
120
+ console.error(`[CalendarManager] Failed to fetch event ${eventId}: HTTP ${res.status}`);
121
+ return null;
122
+ }
123
+ return (await res.json());
124
+ }
108
125
  async fetchEvents() {
109
126
  const accessData = await this.getAccess();
110
127
  if (!accessData?.access_token)
111
128
  return;
112
- const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userMail)}/events`, {
129
+ const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events`, {
113
130
  method: "GET",
114
131
  headers: {
115
132
  Authorization: `Bearer ${accessData.access_token}`,
116
133
  },
117
134
  });
118
- const data = await res.json();
119
- console.log("Calendar events:", data);
120
- return data;
135
+ if (!res.ok) {
136
+ console.error(`[CalendarManager] Failed to fetch events: HTTP ${res.status}`);
137
+ return;
138
+ }
139
+ return (await res.json());
140
+ }
141
+ /**
142
+ * Fetch changed events since the last delta sync.
143
+ * On the first call (no deltaLink), returns all current events and a deltaLink
144
+ * for subsequent incremental calls.
145
+ * @param deltaLink token from the previous call; omit for the initial full sync
146
+ */
147
+ async fetchDelta(deltaLink) {
148
+ const accessData = await this.getAccess();
149
+ if (!accessData?.access_token)
150
+ return null;
151
+ const events = [];
152
+ let nextUrl = deltaLink ||
153
+ `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events/delta`;
154
+ let newDeltaLink = "";
155
+ while (nextUrl) {
156
+ const res = await fetch(nextUrl, {
157
+ headers: { Authorization: `Bearer ${accessData.access_token}` },
158
+ });
159
+ if (!res.ok) {
160
+ console.error(`[CalendarManager] fetchDelta HTTP ${res.status}:`, await res.text());
161
+ return null;
162
+ }
163
+ const data = (await res.json());
164
+ events.push(...(data.value ?? []));
165
+ nextUrl = data["@odata.nextLink"];
166
+ if (data["@odata.deltaLink"])
167
+ newDeltaLink = data["@odata.deltaLink"];
168
+ }
169
+ if (!newDeltaLink) {
170
+ console.error("[CalendarManager] fetchDelta did not return a deltaLink.");
171
+ return null;
172
+ }
173
+ return { events, deltaLink: newDeltaLink };
121
174
  }
122
175
  }
123
176
  exports.CalendarManager = CalendarManager;
@@ -0,0 +1,20 @@
1
+ import { Config } from "../../../../../types/index.js";
2
+ export declare class CalendarSyncPoller {
3
+ private timer;
4
+ private config;
5
+ constructor(config: Config);
6
+ private get manager();
7
+ /**
8
+ * Start polling the Microsoft Calendar delta feed.
9
+ * @param intervalMs polling interval in milliseconds (default: 5 minutes)
10
+ */
11
+ start(intervalMs?: number): void;
12
+ stop(): void;
13
+ private poll;
14
+ private processEvent;
15
+ private updateScheduleFromEvent;
16
+ private createScheduleFromEvent;
17
+ private handleDeleted;
18
+ private loadDeltaLink;
19
+ private saveDeltaLink;
20
+ }