@prismatic-io/components-google-calendar 0.0.0-stage → 0.0.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 +64 -2
- package/dist/icon.png +0 -0
- package/dist/index.js +249 -0
- package/dist/oauth-button.png +0 -0
- package/npm-dist/icon.png +0 -0
- package/npm-dist/index.cjs +1617 -0
- package/npm-dist/index.d.cts +1016 -0
- package/npm-dist/oauth-button.png +0 -0
- package/package.json +54 -4
|
@@ -0,0 +1,1617 @@
|
|
|
1
|
+
let _prismatic_io_spectral = require("@prismatic-io/spectral");
|
|
2
|
+
let _prismatic_io_spectral_http = require("@prismatic-io/spectral/http");
|
|
3
|
+
let _googleapis_calendar = require("@googleapis/calendar");
|
|
4
|
+
let uuid = require("uuid");
|
|
5
|
+
//#region src/connections.ts
|
|
6
|
+
const googleConnection = (0, _prismatic_io_spectral.oauth2Connection)({
|
|
7
|
+
oauth2Type: _prismatic_io_spectral.OAuth2Type.AuthorizationCode,
|
|
8
|
+
key: "oauth2",
|
|
9
|
+
display: {
|
|
10
|
+
label: "OAuth 2.0",
|
|
11
|
+
description: "OAuth 2.0 connection for Google Calendar"
|
|
12
|
+
},
|
|
13
|
+
inputs: {
|
|
14
|
+
authorizeUrl: {
|
|
15
|
+
label: "Authorize URL",
|
|
16
|
+
placeholder: "Enter OAuth 2.0 Authorization URL",
|
|
17
|
+
type: "string",
|
|
18
|
+
required: true,
|
|
19
|
+
shown: false,
|
|
20
|
+
comments: "The OAuth 2.0 Authorization URL for Google Calendar API.",
|
|
21
|
+
default: "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent"
|
|
22
|
+
},
|
|
23
|
+
tokenUrl: {
|
|
24
|
+
label: "Token URL",
|
|
25
|
+
placeholder: "Enter OAuth 2.0 Token URL",
|
|
26
|
+
type: "string",
|
|
27
|
+
required: true,
|
|
28
|
+
shown: false,
|
|
29
|
+
comments: "The OAuth 2.0 Token URL for Google Calendar API.",
|
|
30
|
+
default: "https://oauth2.googleapis.com/token"
|
|
31
|
+
},
|
|
32
|
+
scopes: {
|
|
33
|
+
label: "Scopes",
|
|
34
|
+
placeholder: "Enter OAuth scopes (space-delimited)",
|
|
35
|
+
type: "string",
|
|
36
|
+
required: false,
|
|
37
|
+
shown: true,
|
|
38
|
+
comments: "Space-delimited list of OAuth 2.0 scopes. See [Google Calendar API scopes](https://developers.google.com/calendar/api/auth) for available options.",
|
|
39
|
+
default: "https://www.googleapis.com/auth/calendar"
|
|
40
|
+
},
|
|
41
|
+
clientId: {
|
|
42
|
+
label: "Client ID",
|
|
43
|
+
placeholder: "Enter Client ID",
|
|
44
|
+
type: "string",
|
|
45
|
+
required: true,
|
|
46
|
+
example: "123456789012-abc123def456ghi789jkl012mno345pq.apps.googleusercontent.com",
|
|
47
|
+
comments: "Client ID from your Google Cloud Platform OAuth 2.0 credentials.",
|
|
48
|
+
shown: true
|
|
49
|
+
},
|
|
50
|
+
clientSecret: {
|
|
51
|
+
label: "Client Secret",
|
|
52
|
+
placeholder: "Enter Client Secret",
|
|
53
|
+
type: "password",
|
|
54
|
+
example: "GOCSPX-abcd1234efgh5678ijkl",
|
|
55
|
+
comments: "Client Secret from your Google Cloud Platform OAuth 2.0 credentials.",
|
|
56
|
+
required: true,
|
|
57
|
+
shown: true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
var connections_default = [googleConnection];
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/client.ts
|
|
64
|
+
const validateConnection = (connection) => {
|
|
65
|
+
if (connection.key !== googleConnection.key) throw new _prismatic_io_spectral.ConnectionError(connection, "Unknown Connection type provided.");
|
|
66
|
+
if (!connection.token?.access_token) throw new _prismatic_io_spectral.ConnectionError(connection, "Received valid Connection type but did not find valid access token.");
|
|
67
|
+
};
|
|
68
|
+
const createClient = ({ connection }) => {
|
|
69
|
+
validateConnection(connection);
|
|
70
|
+
const oauth2Client = new _googleapis_calendar.auth.OAuth2(_prismatic_io_spectral.util.types.toString(connection.fields.clientId), _prismatic_io_spectral.util.types.toString(connection.fields.clientSecret));
|
|
71
|
+
const token = _prismatic_io_spectral.util.types.toString(connection.token.access_token);
|
|
72
|
+
oauth2Client.setCredentials({ access_token: token });
|
|
73
|
+
return (0, _googleapis_calendar.calendar)({
|
|
74
|
+
version: "v3",
|
|
75
|
+
auth: oauth2Client
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const getAccessToken = ({ connection }) => {
|
|
79
|
+
validateConnection(connection);
|
|
80
|
+
return _prismatic_io_spectral.util.types.toString(connection.token.access_token);
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/utils/cleanInputs.ts
|
|
84
|
+
const YYYY_MM_DD = /^\d{4}-\d{2}-\d{2}$/;
|
|
85
|
+
const lookBackDateClean = (value) => {
|
|
86
|
+
const raw = _prismatic_io_spectral.util.types.toString(value).trim();
|
|
87
|
+
if (!raw) return "";
|
|
88
|
+
if (!YYYY_MM_DD.test(raw)) throw new Error("Look-back Date must be in YYYY-MM-DD format.");
|
|
89
|
+
const parsed = /* @__PURE__ */ new Date(`${raw}T00:00:00Z`);
|
|
90
|
+
if (Number.isNaN(parsed.getTime())) throw new Error("Look-back Date is not a valid calendar date.");
|
|
91
|
+
const [y, m, d] = raw.split("-").map(Number);
|
|
92
|
+
if (parsed.getUTCFullYear() !== y || parsed.getUTCMonth() + 1 !== m || parsed.getUTCDate() !== d) throw new Error("Look-back Date is not a valid calendar date.");
|
|
93
|
+
if (parsed.getTime() > Date.now()) throw new Error("Look-back Date cannot be a future date.");
|
|
94
|
+
return raw;
|
|
95
|
+
};
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/inputs.ts
|
|
98
|
+
const calendarId = (0, _prismatic_io_spectral.input)({
|
|
99
|
+
label: "Calendar ID",
|
|
100
|
+
type: "string",
|
|
101
|
+
required: true,
|
|
102
|
+
placeholder: "Enter Calendar ID",
|
|
103
|
+
example: "en.usa#holiday@group.v.calendar.google.com",
|
|
104
|
+
comments: "The unique identifier of the calendar. Use 'primary' for the user's primary calendar.",
|
|
105
|
+
clean: _prismatic_io_spectral.util.types.toString,
|
|
106
|
+
dataSource: "selectCalendar"
|
|
107
|
+
});
|
|
108
|
+
const startTime = (0, _prismatic_io_spectral.input)({
|
|
109
|
+
label: "Start Time",
|
|
110
|
+
type: "string",
|
|
111
|
+
required: true,
|
|
112
|
+
placeholder: "Enter start time (ISO 8601 format)",
|
|
113
|
+
example: "2026-05-28T17:00:00-07:00",
|
|
114
|
+
comments: "The start time of the event in ISO 8601 format with timezone offset."
|
|
115
|
+
});
|
|
116
|
+
const timeZone = (0, _prismatic_io_spectral.input)({
|
|
117
|
+
label: "Time Zone",
|
|
118
|
+
type: "string",
|
|
119
|
+
required: true,
|
|
120
|
+
placeholder: "Enter timezone (IANA format)",
|
|
121
|
+
example: "America/Chicago",
|
|
122
|
+
comments: "The timezone of the event in IANA Time Zone Database format. See [IANA timezone list](https://www.iana.org/time-zones)."
|
|
123
|
+
});
|
|
124
|
+
const endTime = (0, _prismatic_io_spectral.input)({
|
|
125
|
+
label: "End Time",
|
|
126
|
+
type: "string",
|
|
127
|
+
required: true,
|
|
128
|
+
placeholder: "Enter end time (ISO 8601 format)",
|
|
129
|
+
example: "2026-05-28T19:00:00-07:00",
|
|
130
|
+
comments: "The end time of the event in ISO 8601 format with timezone offset."
|
|
131
|
+
});
|
|
132
|
+
const summary = (0, _prismatic_io_spectral.input)({
|
|
133
|
+
label: "Summary",
|
|
134
|
+
type: "string",
|
|
135
|
+
required: true,
|
|
136
|
+
placeholder: "Enter event summary",
|
|
137
|
+
example: "Team Standup Meeting",
|
|
138
|
+
comments: "The title or summary of the event."
|
|
139
|
+
});
|
|
140
|
+
const description = (0, _prismatic_io_spectral.input)({
|
|
141
|
+
label: "Description",
|
|
142
|
+
type: "string",
|
|
143
|
+
required: true,
|
|
144
|
+
placeholder: "Enter event description",
|
|
145
|
+
example: "Discuss sprint progress and blockers",
|
|
146
|
+
comments: "A detailed description of the event."
|
|
147
|
+
});
|
|
148
|
+
const attendees = (0, _prismatic_io_spectral.input)({
|
|
149
|
+
label: "Attendees",
|
|
150
|
+
type: "code",
|
|
151
|
+
default: JSON.stringify([{
|
|
152
|
+
email: "john.doe@example.com",
|
|
153
|
+
optional: false
|
|
154
|
+
}, {
|
|
155
|
+
email: "jane.smith@example.com",
|
|
156
|
+
optional: true
|
|
157
|
+
}], null, 2),
|
|
158
|
+
language: "json",
|
|
159
|
+
required: false,
|
|
160
|
+
comments: "Array of attendee objects with email addresses and optional flags. See [Google Calendar Events API](https://developers.google.com/calendar/api/v3/reference/events/insert) for full schema.",
|
|
161
|
+
clean: (value) => value ? _prismatic_io_spectral.util.types.toObject(value) : void 0
|
|
162
|
+
});
|
|
163
|
+
const remindMethod = (0, _prismatic_io_spectral.input)({
|
|
164
|
+
label: "Remind Method",
|
|
165
|
+
type: "string",
|
|
166
|
+
model: [{
|
|
167
|
+
label: "Email",
|
|
168
|
+
value: "email"
|
|
169
|
+
}, {
|
|
170
|
+
label: "Popup",
|
|
171
|
+
value: "popup"
|
|
172
|
+
}],
|
|
173
|
+
required: false,
|
|
174
|
+
placeholder: "Select reminder method",
|
|
175
|
+
comments: "How to send the event reminder. Only used when 'Default Reminder' is false."
|
|
176
|
+
});
|
|
177
|
+
const remindMinutes = (0, _prismatic_io_spectral.input)({
|
|
178
|
+
label: "Remind Before (minutes)",
|
|
179
|
+
type: "string",
|
|
180
|
+
required: false,
|
|
181
|
+
placeholder: "Enter minutes before event",
|
|
182
|
+
example: "30",
|
|
183
|
+
comments: "Number of minutes before the event to send the reminder. Only used when 'Default Reminder' is false."
|
|
184
|
+
});
|
|
185
|
+
const eventLocation = (0, _prismatic_io_spectral.input)({
|
|
186
|
+
label: "Event Location",
|
|
187
|
+
type: "string",
|
|
188
|
+
required: false,
|
|
189
|
+
placeholder: "Enter event location",
|
|
190
|
+
example: "Conference Room A, Building 3",
|
|
191
|
+
comments: "The physical or virtual location of the event."
|
|
192
|
+
});
|
|
193
|
+
const eventId = (0, _prismatic_io_spectral.input)({
|
|
194
|
+
label: "Event ID",
|
|
195
|
+
type: "string",
|
|
196
|
+
required: true,
|
|
197
|
+
placeholder: "Enter Event ID",
|
|
198
|
+
dataSource: "selectEvent",
|
|
199
|
+
example: "20260101_q8ue475rr4p7opsd4c0lr7g5pg",
|
|
200
|
+
comments: "The unique identifier of the event."
|
|
201
|
+
});
|
|
202
|
+
const useDefaultReminder = (0, _prismatic_io_spectral.input)({
|
|
203
|
+
label: "Default Reminder",
|
|
204
|
+
type: "boolean",
|
|
205
|
+
required: true,
|
|
206
|
+
comments: "When true, the event uses the default reminder settings from the calendar."
|
|
207
|
+
});
|
|
208
|
+
const maxResults = (0, _prismatic_io_spectral.input)({
|
|
209
|
+
label: "Max Results",
|
|
210
|
+
type: "string",
|
|
211
|
+
required: false,
|
|
212
|
+
placeholder: "Enter maximum number of results (1-250)",
|
|
213
|
+
comments: "Maximum number of results to return (1-250).",
|
|
214
|
+
example: "50",
|
|
215
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toInt(value) : void 0
|
|
216
|
+
});
|
|
217
|
+
const pageToken = (0, _prismatic_io_spectral.input)({
|
|
218
|
+
label: "Page Token",
|
|
219
|
+
type: "string",
|
|
220
|
+
required: false,
|
|
221
|
+
placeholder: "Enter page token from previous response",
|
|
222
|
+
comments: "Pagination token returned from a previous request to retrieve the next page of results.",
|
|
223
|
+
example: "lslTXFcbLQKkb0vP9Kgh5hy0Y0OnC7Z9ZPHPwPmMnxSk3eiDRMkct7D8E",
|
|
224
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
|
|
225
|
+
});
|
|
226
|
+
const syncToken = (0, _prismatic_io_spectral.input)({
|
|
227
|
+
label: "Sync Token",
|
|
228
|
+
type: "string",
|
|
229
|
+
required: false,
|
|
230
|
+
placeholder: "Enter sync token from previous response",
|
|
231
|
+
comments: "Token for retrieving only resources modified since the last sync request.",
|
|
232
|
+
example: "lslTXFcbLQKkb0vP9Kgh5hy0Y0OnC7Z9ZPHPwPmMnxSk3eiDRMkct7D8E",
|
|
233
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
|
|
234
|
+
});
|
|
235
|
+
const connectionInput = (0, _prismatic_io_spectral.input)({
|
|
236
|
+
label: "Connection",
|
|
237
|
+
type: "connection",
|
|
238
|
+
required: true,
|
|
239
|
+
comments: "The Google Calendar connection to use."
|
|
240
|
+
});
|
|
241
|
+
const addConferenceEvent = (0, _prismatic_io_spectral.input)({
|
|
242
|
+
label: "Add Conference Event",
|
|
243
|
+
type: "boolean",
|
|
244
|
+
default: "false",
|
|
245
|
+
required: false,
|
|
246
|
+
comments: "When true, creates a Google Meet conference link for the event.",
|
|
247
|
+
clean: _prismatic_io_spectral.util.types.toBool
|
|
248
|
+
});
|
|
249
|
+
const sendUpdates = (0, _prismatic_io_spectral.input)({
|
|
250
|
+
label: "Send Updates",
|
|
251
|
+
type: "string",
|
|
252
|
+
model: [
|
|
253
|
+
{
|
|
254
|
+
label: "",
|
|
255
|
+
value: ""
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
label: "All",
|
|
259
|
+
value: "all"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
label: "External Only",
|
|
263
|
+
value: "externalOnly"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
label: "None",
|
|
267
|
+
value: "none"
|
|
268
|
+
}
|
|
269
|
+
],
|
|
270
|
+
required: false,
|
|
271
|
+
default: "",
|
|
272
|
+
comments: "Whether to send notifications about the creation of the new event. Note that some emails might still be sent. The default is false.",
|
|
273
|
+
clean: _prismatic_io_spectral.util.types.toString
|
|
274
|
+
});
|
|
275
|
+
const selectCalendarInputs = { connection: connectionInput };
|
|
276
|
+
const selectEventInputs = {
|
|
277
|
+
connection: connectionInput,
|
|
278
|
+
calendarId: {
|
|
279
|
+
...calendarId,
|
|
280
|
+
comments: "Calendar ID to get events from.",
|
|
281
|
+
dataSource: void 0
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const fetchAll = (0, _prismatic_io_spectral.input)({
|
|
285
|
+
label: "Fetch All",
|
|
286
|
+
type: "boolean",
|
|
287
|
+
required: false,
|
|
288
|
+
comments: "When true, fetches all pages of results, ignoring the 'Max Results' and 'Page Token' inputs.",
|
|
289
|
+
clean: _prismatic_io_spectral.util.types.toBool
|
|
290
|
+
});
|
|
291
|
+
const calendarChangeEventsInputs = {
|
|
292
|
+
connection: connectionInput,
|
|
293
|
+
calendarId: {
|
|
294
|
+
...calendarId,
|
|
295
|
+
comments: "The calendar to monitor for changes."
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const lookBackDate = (0, _prismatic_io_spectral.input)({
|
|
299
|
+
label: "Look-back Date",
|
|
300
|
+
placeholder: "Enter look-back date (YYYY-MM-DD)",
|
|
301
|
+
type: "string",
|
|
302
|
+
required: false,
|
|
303
|
+
comments: "The date the initial sync starts from, in YYYY-MM-DD format. Cannot be a future date. Leave empty to start from the first recurrence with no backfill. When set, the initial sync seeds each record modified on or after this date once.",
|
|
304
|
+
example: "2026-01-01",
|
|
305
|
+
clean: lookBackDateClean
|
|
306
|
+
});
|
|
307
|
+
const pollEventsTriggerInputs = {
|
|
308
|
+
connection: connectionInput,
|
|
309
|
+
calendarId: {
|
|
310
|
+
...calendarId,
|
|
311
|
+
comments: "The calendar to poll for event changes."
|
|
312
|
+
},
|
|
313
|
+
lookBackDate
|
|
314
|
+
};
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/parseReturn.ts
|
|
317
|
+
const parseReturn = (config) => {
|
|
318
|
+
Object.fromEntries(Object.entries(config).filter(([key]) => !["paramsSerializer", "validateStatus"].includes(key)));
|
|
319
|
+
};
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/actions/createCalendar.ts
|
|
322
|
+
const createCalendar = (0, _prismatic_io_spectral.action)({
|
|
323
|
+
display: {
|
|
324
|
+
label: "Create Calendar",
|
|
325
|
+
description: "Create a new calendar"
|
|
326
|
+
},
|
|
327
|
+
perform: async (_context, params) => {
|
|
328
|
+
const { config, ...base } = await createClient({ connection: params.googleConnection }).calendars.insert({ requestBody: {
|
|
329
|
+
summary: _prismatic_io_spectral.util.types.toString(params.summary),
|
|
330
|
+
timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone),
|
|
331
|
+
description: _prismatic_io_spectral.util.types.toString(params.description)
|
|
332
|
+
} });
|
|
333
|
+
return { data: {
|
|
334
|
+
config: parseReturn(config),
|
|
335
|
+
...base
|
|
336
|
+
} };
|
|
337
|
+
},
|
|
338
|
+
inputs: {
|
|
339
|
+
summary,
|
|
340
|
+
description,
|
|
341
|
+
timeZone,
|
|
342
|
+
googleConnection: connectionInput
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/actions/createEvent.ts
|
|
347
|
+
const createEvent = (0, _prismatic_io_spectral.action)({
|
|
348
|
+
display: {
|
|
349
|
+
label: "Create Event",
|
|
350
|
+
description: "Create a new event in a given calendar"
|
|
351
|
+
},
|
|
352
|
+
perform: async (_context, params) => {
|
|
353
|
+
const useDefaultReminder = _prismatic_io_spectral.util.types.toBool(params.useDefaultReminder);
|
|
354
|
+
const remindMethod = _prismatic_io_spectral.util.types.toString(params.remindMethod);
|
|
355
|
+
const remindMinutes = _prismatic_io_spectral.util.types.toString(params.remindMinutes);
|
|
356
|
+
const { config, ...base } = await createClient({ connection: params.connection }).events.insert({
|
|
357
|
+
calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId),
|
|
358
|
+
...params.addConferenceEvent && { conferenceDataVersion: 1 },
|
|
359
|
+
requestBody: {
|
|
360
|
+
...params.addConferenceEvent && { conferenceData: { createRequest: {
|
|
361
|
+
requestId: (0, uuid.v4)(),
|
|
362
|
+
conferenceSolutionKey: { type: "hangoutsMeet" }
|
|
363
|
+
} } },
|
|
364
|
+
summary: _prismatic_io_spectral.util.types.toString(params.summary),
|
|
365
|
+
location: _prismatic_io_spectral.util.types.toString(params.eventLocation),
|
|
366
|
+
description: _prismatic_io_spectral.util.types.toString(params.description),
|
|
367
|
+
start: {
|
|
368
|
+
dateTime: _prismatic_io_spectral.util.types.toString(params.startTime),
|
|
369
|
+
timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone)
|
|
370
|
+
},
|
|
371
|
+
end: {
|
|
372
|
+
dateTime: _prismatic_io_spectral.util.types.toString(params.endTime),
|
|
373
|
+
timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone)
|
|
374
|
+
},
|
|
375
|
+
attendees: params.attendees,
|
|
376
|
+
reminders: {
|
|
377
|
+
...!useDefaultReminder && remindMethod && remindMinutes && { overrides: [{
|
|
378
|
+
method: remindMethod,
|
|
379
|
+
minutes: _prismatic_io_spectral.util.types.toInt(remindMinutes)
|
|
380
|
+
}] },
|
|
381
|
+
useDefault: useDefaultReminder
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
sendUpdates: params.sendUpdates || void 0
|
|
385
|
+
});
|
|
386
|
+
return { data: {
|
|
387
|
+
config: parseReturn(config),
|
|
388
|
+
...base
|
|
389
|
+
} };
|
|
390
|
+
},
|
|
391
|
+
inputs: {
|
|
392
|
+
calendarId,
|
|
393
|
+
summary,
|
|
394
|
+
description,
|
|
395
|
+
timeZone,
|
|
396
|
+
startTime,
|
|
397
|
+
endTime,
|
|
398
|
+
eventLocation,
|
|
399
|
+
attendees,
|
|
400
|
+
remindMethod,
|
|
401
|
+
useDefaultReminder,
|
|
402
|
+
remindMinutes,
|
|
403
|
+
addConferenceEvent,
|
|
404
|
+
connection: connectionInput,
|
|
405
|
+
sendUpdates
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/actions/deleteCalendar.ts
|
|
410
|
+
const deleteCalendar = (0, _prismatic_io_spectral.action)({
|
|
411
|
+
display: {
|
|
412
|
+
label: "Delete Calendar",
|
|
413
|
+
description: "Delete an existing calendar by Id"
|
|
414
|
+
},
|
|
415
|
+
perform: async (_context, params) => {
|
|
416
|
+
const { config, ...base } = await createClient({ connection: params.connection }).calendarList.delete({ calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId) });
|
|
417
|
+
return { data: {
|
|
418
|
+
config: parseReturn(config),
|
|
419
|
+
...base
|
|
420
|
+
} };
|
|
421
|
+
},
|
|
422
|
+
inputs: {
|
|
423
|
+
calendarId,
|
|
424
|
+
connection: connectionInput
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/actions/deleteEvent.ts
|
|
429
|
+
const deleteEvent = (0, _prismatic_io_spectral.action)({
|
|
430
|
+
display: {
|
|
431
|
+
label: "Delete Event",
|
|
432
|
+
description: "Delete an event by an Id"
|
|
433
|
+
},
|
|
434
|
+
perform: async (_context, params) => {
|
|
435
|
+
const { config, ...base } = await createClient({ connection: params.connection }).events.delete({
|
|
436
|
+
calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId),
|
|
437
|
+
eventId: _prismatic_io_spectral.util.types.toString(params.eventId),
|
|
438
|
+
sendUpdates: params.sendUpdates || void 0
|
|
439
|
+
});
|
|
440
|
+
return { data: {
|
|
441
|
+
config: parseReturn(config),
|
|
442
|
+
...base
|
|
443
|
+
} };
|
|
444
|
+
},
|
|
445
|
+
inputs: {
|
|
446
|
+
calendarId,
|
|
447
|
+
eventId,
|
|
448
|
+
connection: connectionInput,
|
|
449
|
+
sendUpdates: {
|
|
450
|
+
...sendUpdates,
|
|
451
|
+
comments: "Guests who should receive notifications about the deletion of the event."
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/actions/getCalendar.ts
|
|
457
|
+
const getCalendar = (0, _prismatic_io_spectral.action)({
|
|
458
|
+
display: {
|
|
459
|
+
label: "Get Calendar",
|
|
460
|
+
description: "Get the information and metadata of a calendar by Id"
|
|
461
|
+
},
|
|
462
|
+
perform: async (_context, params) => {
|
|
463
|
+
const { config, ...base } = await createClient({ connection: params.connection }).calendars.get({ calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId) });
|
|
464
|
+
return { data: {
|
|
465
|
+
config: parseReturn(config),
|
|
466
|
+
...base
|
|
467
|
+
} };
|
|
468
|
+
},
|
|
469
|
+
inputs: {
|
|
470
|
+
connection: connectionInput,
|
|
471
|
+
calendarId
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/actions/getEvent.ts
|
|
476
|
+
const getEvent = (0, _prismatic_io_spectral.action)({
|
|
477
|
+
display: {
|
|
478
|
+
label: "Get Event",
|
|
479
|
+
description: "Get the information and metadata of an event by Id"
|
|
480
|
+
},
|
|
481
|
+
perform: async (_context, params) => {
|
|
482
|
+
const { config, ...base } = await createClient({ connection: params.connection }).events.get({
|
|
483
|
+
calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId),
|
|
484
|
+
eventId: _prismatic_io_spectral.util.types.toString(params.eventId)
|
|
485
|
+
});
|
|
486
|
+
return { data: {
|
|
487
|
+
config: parseReturn(config),
|
|
488
|
+
...base
|
|
489
|
+
} };
|
|
490
|
+
},
|
|
491
|
+
inputs: {
|
|
492
|
+
calendarId,
|
|
493
|
+
eventId,
|
|
494
|
+
connection: connectionInput
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/helpers/listAllCalendars.ts
|
|
499
|
+
const listAllCalendars = async (params) => {
|
|
500
|
+
const client = createClient({ connection: params.connection });
|
|
501
|
+
const MAX_RESULTS = 250;
|
|
502
|
+
if (params.fetchAll) params.pageToken = void 0;
|
|
503
|
+
const maxResults = params.fetchAll ? MAX_RESULTS : _prismatic_io_spectral.util.types.toInt(params.maxResults) || void 0;
|
|
504
|
+
let calendarList = await client.calendarList.list({
|
|
505
|
+
pageToken: _prismatic_io_spectral.util.types.toString(params.pageToken) || void 0,
|
|
506
|
+
maxResults
|
|
507
|
+
});
|
|
508
|
+
let nextPageToken = calendarList.data.nextPageToken;
|
|
509
|
+
if (!params.fetchAll || !nextPageToken) return calendarList;
|
|
510
|
+
const allItems = [...calendarList.data.items];
|
|
511
|
+
while (nextPageToken) {
|
|
512
|
+
calendarList = await client.calendarList.list({
|
|
513
|
+
pageToken: nextPageToken,
|
|
514
|
+
maxResults
|
|
515
|
+
});
|
|
516
|
+
allItems.push(...calendarList.data.items);
|
|
517
|
+
nextPageToken = calendarList.data.nextPageToken;
|
|
518
|
+
}
|
|
519
|
+
calendarList.data.items = allItems;
|
|
520
|
+
return calendarList;
|
|
521
|
+
};
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region src/actions/listCalendar.ts
|
|
524
|
+
const listCalendar = (0, _prismatic_io_spectral.action)({
|
|
525
|
+
display: {
|
|
526
|
+
label: "List Calendars",
|
|
527
|
+
description: "List all calendars"
|
|
528
|
+
},
|
|
529
|
+
perform: async (_context, params) => {
|
|
530
|
+
const { config, ...base } = await listAllCalendars(params);
|
|
531
|
+
return { data: {
|
|
532
|
+
config: parseReturn(config),
|
|
533
|
+
...base
|
|
534
|
+
} };
|
|
535
|
+
},
|
|
536
|
+
inputs: {
|
|
537
|
+
pageToken,
|
|
538
|
+
maxResults,
|
|
539
|
+
connection: connectionInput,
|
|
540
|
+
fetchAll
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
//#endregion
|
|
544
|
+
//#region src/helpers/listAllEvents.ts
|
|
545
|
+
const listAllEvents = async (params) => {
|
|
546
|
+
const client = createClient({ connection: params.googleConnection });
|
|
547
|
+
const MAX_RESULTS = 250;
|
|
548
|
+
if (params.fetchAll) params.pageToken = void 0;
|
|
549
|
+
const maxResults = params.fetchAll ? MAX_RESULTS : _prismatic_io_spectral.util.types.toInt(params.maxResults) || void 0;
|
|
550
|
+
const requestParams = {
|
|
551
|
+
calendarId: params.calendarId,
|
|
552
|
+
maxResults,
|
|
553
|
+
pageToken: params.pageToken,
|
|
554
|
+
syncToken: params.syncToken,
|
|
555
|
+
maxAttendees: params.maxAttendees,
|
|
556
|
+
timeZone: params.timeZone,
|
|
557
|
+
q: params.q,
|
|
558
|
+
timeMax: params.timeMax,
|
|
559
|
+
timeMin: params.timeMin,
|
|
560
|
+
orderBy: params.orderBy,
|
|
561
|
+
singleEvents: params.singleEvents,
|
|
562
|
+
updatedMin: params.updatedMin,
|
|
563
|
+
showDeleted: params.showDeleted,
|
|
564
|
+
showHiddenInvitations: params.showHiddenInvitations
|
|
565
|
+
};
|
|
566
|
+
let eventsList = await client.events.list(requestParams);
|
|
567
|
+
let nextPageToken = eventsList.data.nextPageToken;
|
|
568
|
+
if (!params.fetchAll || !nextPageToken) return eventsList;
|
|
569
|
+
const allItems = [...eventsList.data.items];
|
|
570
|
+
while (nextPageToken) {
|
|
571
|
+
eventsList = await client.events.list({
|
|
572
|
+
...requestParams,
|
|
573
|
+
pageToken: nextPageToken
|
|
574
|
+
});
|
|
575
|
+
allItems.push(...eventsList.data.items);
|
|
576
|
+
nextPageToken = eventsList.data.nextPageToken;
|
|
577
|
+
}
|
|
578
|
+
eventsList.data.items = allItems;
|
|
579
|
+
return eventsList;
|
|
580
|
+
};
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/actions/listEvents.ts
|
|
583
|
+
const listEvents = (0, _prismatic_io_spectral.action)({
|
|
584
|
+
display: {
|
|
585
|
+
label: "List Events",
|
|
586
|
+
description: "List all events in a given calendar"
|
|
587
|
+
},
|
|
588
|
+
perform: async (_context, params) => {
|
|
589
|
+
const { config, ...base } = await listAllEvents(params);
|
|
590
|
+
return { data: {
|
|
591
|
+
config: parseReturn(config),
|
|
592
|
+
...base
|
|
593
|
+
} };
|
|
594
|
+
},
|
|
595
|
+
inputs: {
|
|
596
|
+
googleConnection: connectionInput,
|
|
597
|
+
calendarId,
|
|
598
|
+
fetchAll,
|
|
599
|
+
maxResults,
|
|
600
|
+
pageToken,
|
|
601
|
+
syncToken,
|
|
602
|
+
maxAttendees: (0, _prismatic_io_spectral.input)({
|
|
603
|
+
label: "Max Attendees",
|
|
604
|
+
type: "string",
|
|
605
|
+
required: false,
|
|
606
|
+
placeholder: "Enter maximum attendees",
|
|
607
|
+
example: "10",
|
|
608
|
+
comments: "Maximum number of attendees to include in the response. If there are more attendees, only the participant is returned.",
|
|
609
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toInt(value) : void 0
|
|
610
|
+
}),
|
|
611
|
+
orderBy: (0, _prismatic_io_spectral.input)({
|
|
612
|
+
label: "Order By",
|
|
613
|
+
type: "string",
|
|
614
|
+
model: [{
|
|
615
|
+
label: "Start Time",
|
|
616
|
+
value: "startTime"
|
|
617
|
+
}, {
|
|
618
|
+
label: "Updated",
|
|
619
|
+
value: "updated"
|
|
620
|
+
}],
|
|
621
|
+
required: false,
|
|
622
|
+
placeholder: "Select sort order",
|
|
623
|
+
comments: "The order of events returned in the result. Default is an unspecified, stable order.",
|
|
624
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
|
|
625
|
+
}),
|
|
626
|
+
q: (0, _prismatic_io_spectral.input)({
|
|
627
|
+
label: "Query",
|
|
628
|
+
type: "string",
|
|
629
|
+
required: false,
|
|
630
|
+
placeholder: "Enter search terms",
|
|
631
|
+
example: "team meeting",
|
|
632
|
+
comments: "Free text search terms to find events matching in summary, description, location, or attendee fields.",
|
|
633
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
|
|
634
|
+
}),
|
|
635
|
+
showDeleted: (0, _prismatic_io_spectral.input)({
|
|
636
|
+
label: "Show Deleted",
|
|
637
|
+
type: "boolean",
|
|
638
|
+
default: "false",
|
|
639
|
+
required: false,
|
|
640
|
+
comments: "When true, includes deleted events in the response.",
|
|
641
|
+
clean: _prismatic_io_spectral.util.types.toBool
|
|
642
|
+
}),
|
|
643
|
+
showHiddenInvitations: (0, _prismatic_io_spectral.input)({
|
|
644
|
+
label: "Show Hidden Invitations",
|
|
645
|
+
type: "boolean",
|
|
646
|
+
required: false,
|
|
647
|
+
comments: "When true, includes hidden invitations in the response.",
|
|
648
|
+
clean: _prismatic_io_spectral.util.types.toBool
|
|
649
|
+
}),
|
|
650
|
+
singleEvents: (0, _prismatic_io_spectral.input)({
|
|
651
|
+
label: "Single Events",
|
|
652
|
+
type: "boolean",
|
|
653
|
+
default: "false",
|
|
654
|
+
required: false,
|
|
655
|
+
comments: "When true, expands recurring events into instances and returns only single one-off events and instances, not the underlying recurring events.",
|
|
656
|
+
clean: _prismatic_io_spectral.util.types.toBool
|
|
657
|
+
}),
|
|
658
|
+
timeMin: (0, _prismatic_io_spectral.input)({
|
|
659
|
+
label: "Time Min",
|
|
660
|
+
type: "string",
|
|
661
|
+
required: false,
|
|
662
|
+
placeholder: "Enter start time (ISO 8601 format)",
|
|
663
|
+
example: "2026-06-03T10:00:00-07:00",
|
|
664
|
+
comments: "Lower bound for filtering events by end time. Must be in ISO 8601 format with timezone offset.",
|
|
665
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
|
|
666
|
+
}),
|
|
667
|
+
timeMax: (0, _prismatic_io_spectral.input)({
|
|
668
|
+
label: "Time Max",
|
|
669
|
+
type: "string",
|
|
670
|
+
required: false,
|
|
671
|
+
placeholder: "Enter end time (ISO 8601 format)",
|
|
672
|
+
example: "2026-06-03T10:00:00-07:00",
|
|
673
|
+
comments: "Upper bound for filtering events by start time. Must be in ISO 8601 format with timezone offset.",
|
|
674
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
|
|
675
|
+
}),
|
|
676
|
+
updatedMin: (0, _prismatic_io_spectral.input)({
|
|
677
|
+
label: "Updated Min",
|
|
678
|
+
type: "string",
|
|
679
|
+
required: false,
|
|
680
|
+
placeholder: "Enter minimum update time (RFC 3339)",
|
|
681
|
+
example: "2026-01-01T00:00:00Z",
|
|
682
|
+
comments: "Lower bound for filtering by last modification time (RFC 3339 format). Deleted entries since this time are always included regardless of 'Show Deleted' setting.",
|
|
683
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
|
|
684
|
+
}),
|
|
685
|
+
timeZone: (0, _prismatic_io_spectral.input)({
|
|
686
|
+
label: "Time Zone",
|
|
687
|
+
type: "string",
|
|
688
|
+
required: false,
|
|
689
|
+
placeholder: "Enter timezone (IANA format)",
|
|
690
|
+
example: "America/Chicago",
|
|
691
|
+
comments: "Time zone used in the response.",
|
|
692
|
+
clean: (value) => value !== "" ? _prismatic_io_spectral.util.types.toString(value) : void 0
|
|
693
|
+
})
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region src/actions/rawRequest.ts
|
|
698
|
+
const { debugRequest: _, ...rawRequestInputs } = _prismatic_io_spectral_http.inputs;
|
|
699
|
+
const rawRequest = (0, _prismatic_io_spectral.action)({
|
|
700
|
+
display: {
|
|
701
|
+
label: "Raw Request",
|
|
702
|
+
description: "Send raw HTTP request to Google Calendar"
|
|
703
|
+
},
|
|
704
|
+
inputs: {
|
|
705
|
+
connection: connectionInput,
|
|
706
|
+
...rawRequestInputs,
|
|
707
|
+
url: {
|
|
708
|
+
...rawRequestInputs.url,
|
|
709
|
+
comments: "Input the path only (/colors), The base URL is already included (https://www.googleapis.com/calendar/v3). For example, to connect to https://www.googleapis.com/calendar/v3/colors, only /colors is entered in this field.",
|
|
710
|
+
example: "/colors"
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
perform: async (context, { connection, ...rawRequestInputs }) => {
|
|
714
|
+
const accessToken = getAccessToken({ connection });
|
|
715
|
+
try {
|
|
716
|
+
const { data } = await (0, _prismatic_io_spectral_http.sendRawRequest)("https://www.googleapis.com/calendar/v3", {
|
|
717
|
+
...rawRequestInputs,
|
|
718
|
+
debugRequest: context.debug.enabled
|
|
719
|
+
}, { Authorization: `Bearer ${accessToken}` });
|
|
720
|
+
return { data };
|
|
721
|
+
} catch (error) {
|
|
722
|
+
const handled = (0, _prismatic_io_spectral_http.handleErrors)(error);
|
|
723
|
+
const serialized = _prismatic_io_spectral.util.types.toJSON(handled);
|
|
724
|
+
throw new Error(serialized);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region src/actions/index.ts
|
|
730
|
+
var actions_default = {
|
|
731
|
+
listCalendar,
|
|
732
|
+
createCalendar,
|
|
733
|
+
getCalendar,
|
|
734
|
+
deleteCalendar,
|
|
735
|
+
listEvents,
|
|
736
|
+
createEvent,
|
|
737
|
+
getEvent,
|
|
738
|
+
updateEvent: (0, _prismatic_io_spectral.action)({
|
|
739
|
+
display: {
|
|
740
|
+
label: "Update Event",
|
|
741
|
+
description: "Update the information and metadata of an existing event"
|
|
742
|
+
},
|
|
743
|
+
perform: async (_context, params) => {
|
|
744
|
+
const reminderMethod = _prismatic_io_spectral.util.types.toString(params.remindMethod);
|
|
745
|
+
const remindMinutes = _prismatic_io_spectral.util.types.toInt(params.remindMinutes);
|
|
746
|
+
const reminderInfoNeeded = reminderMethod && remindMinutes;
|
|
747
|
+
const { config, ...base } = await createClient({ connection: params.connection }).events.update({
|
|
748
|
+
calendarId: _prismatic_io_spectral.util.types.toString(params.calendarId),
|
|
749
|
+
eventId: _prismatic_io_spectral.util.types.toString(params.eventId),
|
|
750
|
+
requestBody: {
|
|
751
|
+
summary: _prismatic_io_spectral.util.types.toString(params.summary) || void 0,
|
|
752
|
+
location: _prismatic_io_spectral.util.types.toString(params.eventLocation) || void 0,
|
|
753
|
+
description: _prismatic_io_spectral.util.types.toString(params.description) || void 0,
|
|
754
|
+
start: {
|
|
755
|
+
dateTime: _prismatic_io_spectral.util.types.toString(params.startTime),
|
|
756
|
+
timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone)
|
|
757
|
+
},
|
|
758
|
+
end: {
|
|
759
|
+
dateTime: _prismatic_io_spectral.util.types.toString(params.endTime),
|
|
760
|
+
timeZone: _prismatic_io_spectral.util.types.toString(params.timeZone)
|
|
761
|
+
},
|
|
762
|
+
attendees: params.attendees,
|
|
763
|
+
reminders: {
|
|
764
|
+
overrides: _prismatic_io_spectral.util.types.toBool(params.useDefaultReminder) !== true && reminderInfoNeeded ? [{
|
|
765
|
+
method: _prismatic_io_spectral.util.types.toString(params.remindMethod),
|
|
766
|
+
minutes: _prismatic_io_spectral.util.types.toInt(params.remindMinutes)
|
|
767
|
+
}] : void 0,
|
|
768
|
+
useDefault: _prismatic_io_spectral.util.types.toBool(params.useDefaultReminder)
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
sendUpdates: params.sendUpdates || void 0
|
|
772
|
+
});
|
|
773
|
+
return { data: {
|
|
774
|
+
config: parseReturn(config),
|
|
775
|
+
...base
|
|
776
|
+
} };
|
|
777
|
+
},
|
|
778
|
+
inputs: {
|
|
779
|
+
calendarId,
|
|
780
|
+
eventId,
|
|
781
|
+
summary: {
|
|
782
|
+
...summary,
|
|
783
|
+
required: false
|
|
784
|
+
},
|
|
785
|
+
description: {
|
|
786
|
+
...description,
|
|
787
|
+
required: false
|
|
788
|
+
},
|
|
789
|
+
timeZone,
|
|
790
|
+
startTime,
|
|
791
|
+
endTime,
|
|
792
|
+
eventLocation: {
|
|
793
|
+
...eventLocation,
|
|
794
|
+
required: false
|
|
795
|
+
},
|
|
796
|
+
attendees,
|
|
797
|
+
useDefaultReminder,
|
|
798
|
+
remindMethod,
|
|
799
|
+
remindMinutes,
|
|
800
|
+
connection: connectionInput,
|
|
801
|
+
sendUpdates: {
|
|
802
|
+
...sendUpdates,
|
|
803
|
+
comments: "Guests who should receive notifications about the event update (for example, title changes, etc.)."
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}),
|
|
807
|
+
deleteEvent,
|
|
808
|
+
rawRequest
|
|
809
|
+
};
|
|
810
|
+
//#endregion
|
|
811
|
+
//#region src/dataSources/index.ts
|
|
812
|
+
var dataSources_default = {
|
|
813
|
+
selectCalendar: (0, _prismatic_io_spectral.dataSource)({
|
|
814
|
+
display: {
|
|
815
|
+
label: "Select Calendar",
|
|
816
|
+
description: "A list of selectable calendars"
|
|
817
|
+
},
|
|
818
|
+
inputs: selectCalendarInputs,
|
|
819
|
+
perform: async (_context, { connection }) => {
|
|
820
|
+
const { data: calendarList } = await listAllCalendars({
|
|
821
|
+
connection,
|
|
822
|
+
fetchAll: true
|
|
823
|
+
});
|
|
824
|
+
const pickListOptions = calendarList.items.map((calendar) => ({
|
|
825
|
+
key: calendar.id,
|
|
826
|
+
label: calendar.summary
|
|
827
|
+
}));
|
|
828
|
+
pickListOptions.sort((a, b) => a.label.localeCompare(b.label));
|
|
829
|
+
return { result: pickListOptions };
|
|
830
|
+
},
|
|
831
|
+
dataSourceType: "picklist",
|
|
832
|
+
examplePayload: { result: [{
|
|
833
|
+
label: "Holidays in United States",
|
|
834
|
+
key: "en.usa#holiday@group.v.calendar.google.com"
|
|
835
|
+
}] }
|
|
836
|
+
}),
|
|
837
|
+
selectEvent: (0, _prismatic_io_spectral.dataSource)({
|
|
838
|
+
display: {
|
|
839
|
+
label: "Select Event",
|
|
840
|
+
description: "A list of selectable events"
|
|
841
|
+
},
|
|
842
|
+
inputs: selectEventInputs,
|
|
843
|
+
perform: async (_context, { connection: googleConnection, calendarId }) => {
|
|
844
|
+
const pickListOptions = (await listAllEvents({
|
|
845
|
+
googleConnection,
|
|
846
|
+
calendarId,
|
|
847
|
+
fetchAll: true
|
|
848
|
+
})).data.items.map((event) => ({
|
|
849
|
+
key: event.id,
|
|
850
|
+
label: `${event.summary} (${event.start.date} to ${event.end.date})`
|
|
851
|
+
}));
|
|
852
|
+
pickListOptions.sort((a, b) => a.label.localeCompare(b.label));
|
|
853
|
+
return { result: pickListOptions };
|
|
854
|
+
},
|
|
855
|
+
dataSourceType: "picklist",
|
|
856
|
+
examplePayload: { result: [{
|
|
857
|
+
label: "New Year's Day (2020-01-01 to 2020-01-02)",
|
|
858
|
+
key: "20200101_q8ue475rr4p7opsd4c0lr7g5pg"
|
|
859
|
+
}] }
|
|
860
|
+
})
|
|
861
|
+
};
|
|
862
|
+
//#endregion
|
|
863
|
+
//#region src/examplePayloads.ts
|
|
864
|
+
const pollEventsTriggerPayload = {
|
|
865
|
+
branch: "default",
|
|
866
|
+
payload: {
|
|
867
|
+
headers: {},
|
|
868
|
+
queryParameters: null,
|
|
869
|
+
rawBody: {
|
|
870
|
+
data: null,
|
|
871
|
+
contentType: null
|
|
872
|
+
},
|
|
873
|
+
paginationState: void 0,
|
|
874
|
+
body: { data: {
|
|
875
|
+
changes: {
|
|
876
|
+
summary: {
|
|
877
|
+
totalChanges: 0,
|
|
878
|
+
created: 0,
|
|
879
|
+
updated: 0,
|
|
880
|
+
deleted: 0,
|
|
881
|
+
syncTokenAvailable: true
|
|
882
|
+
},
|
|
883
|
+
createdEvents: [],
|
|
884
|
+
updatedEvents: [],
|
|
885
|
+
deletedEvents: [],
|
|
886
|
+
allChanges: []
|
|
887
|
+
},
|
|
888
|
+
calendarId: "example@group.calendar.google.com",
|
|
889
|
+
syncToken: "exampleSyncToken"
|
|
890
|
+
} },
|
|
891
|
+
pathFragment: "",
|
|
892
|
+
webhookUrls: {},
|
|
893
|
+
webhookApiKeys: {},
|
|
894
|
+
invokeUrl: "",
|
|
895
|
+
executionId: "RXhhbXBsZUV4ZWN1dGlvblJlc3VsdElkMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY=",
|
|
896
|
+
customer: {
|
|
897
|
+
id: "testCustomerId",
|
|
898
|
+
name: "Test Customer",
|
|
899
|
+
externalId: "testCustomerExternalId"
|
|
900
|
+
},
|
|
901
|
+
instance: {
|
|
902
|
+
id: "SW5zdGFuY2U6OGRiNWQ4NmYtMjBkYS00MzI5LWI2MmMtM2ZhOWFiNDUwYjUw",
|
|
903
|
+
name: "Google Calendar - Polling Events"
|
|
904
|
+
},
|
|
905
|
+
user: {
|
|
906
|
+
id: "testUserId",
|
|
907
|
+
email: "testUserEmail@example.com",
|
|
908
|
+
name: "Test User",
|
|
909
|
+
externalId: "testUserExternalId"
|
|
910
|
+
},
|
|
911
|
+
integration: {
|
|
912
|
+
id: "SW50ZWdyYXRpb246ZDk2YzcxNmItODFmMS00NmQ4LTk0NzItNDU3NDZmMzg2Mjgw",
|
|
913
|
+
name: "Google Calendar - DEV",
|
|
914
|
+
versionSequenceId: "testIntegrationVersionSequenceId",
|
|
915
|
+
externalVersion: ""
|
|
916
|
+
},
|
|
917
|
+
flow: {
|
|
918
|
+
id: "SW50ZWdyYXRpb25GbG93OmM4Y2IzY2UwLTFjYTUtNDVhZC04Yjc1LWNmNDc4YWE1Y2RlYQ==",
|
|
919
|
+
name: "Poll Events",
|
|
920
|
+
stableId: "test-flow-stable-id"
|
|
921
|
+
},
|
|
922
|
+
startedAt: "2026-01-07 14:22:38.313677+00",
|
|
923
|
+
globalDebug: false
|
|
924
|
+
},
|
|
925
|
+
polledNoChanges: false
|
|
926
|
+
};
|
|
927
|
+
const calendarChangeEventsPayload = {
|
|
928
|
+
branch: "Push Notifications",
|
|
929
|
+
payload: {
|
|
930
|
+
headers: {
|
|
931
|
+
Accept: "*/*",
|
|
932
|
+
"Accept-Encoding": "gzip, deflate, br",
|
|
933
|
+
Host: "hooks.example.com",
|
|
934
|
+
"User-Agent": "APIs-Google; (+https://developers.google.com/webmasters/APIs-Google.html)",
|
|
935
|
+
"X-Amz-Cf-Id": "ExampleCloudFrontId123456789012345678901234567890123456==",
|
|
936
|
+
"X-Amzn-Trace-Id": "Root=1-12345678-abcdef0123456789abcdef01",
|
|
937
|
+
"X-Forwarded-For": "192.0.2.1, 198.51.100.1",
|
|
938
|
+
"X-Goog-Changed": "content,properties",
|
|
939
|
+
"X-Goog-Channel-Expiration": "Wed, 07 Jan 2026 07:10:37 GMT",
|
|
940
|
+
"X-Goog-Channel-ID": "12345678-abcd-1234-abcd-123456789012",
|
|
941
|
+
"X-Goog-Message-Number": "123456",
|
|
942
|
+
"X-Goog-Resource-ID": "ExampleResourceId1234567890",
|
|
943
|
+
"X-Goog-Resource-State": "update",
|
|
944
|
+
"X-Goog-Resource-URI": "https://www.googleapis.com/calendar/v3/calendars/primary/events?alt=json"
|
|
945
|
+
},
|
|
946
|
+
queryParameters: null,
|
|
947
|
+
rawBody: {
|
|
948
|
+
data: null,
|
|
949
|
+
contentType: "application/octet-stream"
|
|
950
|
+
},
|
|
951
|
+
body: {
|
|
952
|
+
data: {
|
|
953
|
+
calendar: {
|
|
954
|
+
id: "primary",
|
|
955
|
+
summary: "Example Calendar",
|
|
956
|
+
description: "This is an example calendar",
|
|
957
|
+
timeZone: "America/Chicago"
|
|
958
|
+
},
|
|
959
|
+
events: {
|
|
960
|
+
totalEvents: 2,
|
|
961
|
+
recentEvents: [{
|
|
962
|
+
id: "example_event_1",
|
|
963
|
+
summary: "Team Meeting",
|
|
964
|
+
start: {
|
|
965
|
+
dateTime: "2026-01-07T10:00:00-06:00",
|
|
966
|
+
timeZone: "America/Chicago"
|
|
967
|
+
},
|
|
968
|
+
end: {
|
|
969
|
+
dateTime: "2026-01-07T11:00:00-06:00",
|
|
970
|
+
timeZone: "America/Chicago"
|
|
971
|
+
},
|
|
972
|
+
updated: "2026-01-07T08:30:00.000Z"
|
|
973
|
+
}, {
|
|
974
|
+
id: "example_event_2",
|
|
975
|
+
summary: "Lunch Break",
|
|
976
|
+
start: {
|
|
977
|
+
dateTime: "2026-01-07T12:00:00-06:00",
|
|
978
|
+
timeZone: "America/Chicago"
|
|
979
|
+
},
|
|
980
|
+
end: {
|
|
981
|
+
dateTime: "2026-01-07T13:00:00-06:00",
|
|
982
|
+
timeZone: "America/Chicago"
|
|
983
|
+
},
|
|
984
|
+
updated: "2026-01-07T08:15:00.000Z"
|
|
985
|
+
}]
|
|
986
|
+
},
|
|
987
|
+
notification: {
|
|
988
|
+
resourceState: "update",
|
|
989
|
+
changedFields: "content,properties",
|
|
990
|
+
resourceId: "ExampleResourceId1234567890",
|
|
991
|
+
timestamp: "2026-01-07T14:26:35.618Z"
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
contentType: "application/octet-stream"
|
|
995
|
+
},
|
|
996
|
+
pathFragment: "",
|
|
997
|
+
webhookUrls: { "Calendar Change Events": "https://hooks.example.com/trigger/RXhhbXBsZUluc3RhbmNlRmxvd0NvbmZpZ0lkMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=" },
|
|
998
|
+
webhookApiKeys: { "Calendar Change Events": [] },
|
|
999
|
+
invokeUrl: "https://hooks.example.com/trigger/RXhhbXBsZUluc3RhbmNlRmxvd0NvbmZpZ0lkMTIzNDU2Nzg5MDEyMzQ1Njc4OTA=",
|
|
1000
|
+
executionId: "RXhhbXBsZUV4ZWN1dGlvblJlc3VsdElkMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY=",
|
|
1001
|
+
customer: {
|
|
1002
|
+
id: "testCustomerId",
|
|
1003
|
+
name: "Test Customer",
|
|
1004
|
+
externalId: "testCustomerExternalId"
|
|
1005
|
+
},
|
|
1006
|
+
instance: {
|
|
1007
|
+
id: "SW5zdGFuY2U6OGRiNWQ4NmYtMjBkYS00MzI5LWI2MmMtM2ZhOWFiNDUwYjUw",
|
|
1008
|
+
name: "Google Calendar - DEV - Calendar Change Events"
|
|
1009
|
+
},
|
|
1010
|
+
user: {
|
|
1011
|
+
id: "testUserId",
|
|
1012
|
+
email: "testUserEmail@example.com",
|
|
1013
|
+
name: "Test User",
|
|
1014
|
+
externalId: "testUserExternalId"
|
|
1015
|
+
},
|
|
1016
|
+
integration: {
|
|
1017
|
+
id: "SW50ZWdyYXRpb246ZDk2YzcxNmItODFmMS00NmQ4LTk0NzItNDU3NDZmMzg2Mjgw",
|
|
1018
|
+
name: "Google Calendar - DEV",
|
|
1019
|
+
versionSequenceId: "testIntegrationVersionSequenceId",
|
|
1020
|
+
externalVersion: ""
|
|
1021
|
+
},
|
|
1022
|
+
flow: {
|
|
1023
|
+
id: "SW50ZWdyYXRpb25GbG93OmM4Y2IzY2UwLTFjYTUtNDVhZC04Yjc1LWNmNDc4YWE1Y2RlYQ==",
|
|
1024
|
+
name: "Calendar Change Events",
|
|
1025
|
+
stableId: "test-flow-stable-id"
|
|
1026
|
+
},
|
|
1027
|
+
startedAt: "2026-01-07 14:22:38.313677+00",
|
|
1028
|
+
globalDebug: false
|
|
1029
|
+
},
|
|
1030
|
+
response: {
|
|
1031
|
+
statusCode: 200,
|
|
1032
|
+
contentType: "application/json"
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
//#endregion
|
|
1036
|
+
//#region src/utils/resolvePollingRecordChanges.ts
|
|
1037
|
+
const resolvePollingRecordChanges = (data) => {
|
|
1038
|
+
const changesObject = data ?? {
|
|
1039
|
+
createdEvents: [],
|
|
1040
|
+
updatedEvents: [],
|
|
1041
|
+
deletedEvents: [],
|
|
1042
|
+
allChanges: []
|
|
1043
|
+
};
|
|
1044
|
+
return [
|
|
1045
|
+
...(changesObject.createdEvents ?? []).map((record) => ({
|
|
1046
|
+
changeType: "created",
|
|
1047
|
+
record
|
|
1048
|
+
})),
|
|
1049
|
+
...(changesObject.updatedEvents ?? []).map((record) => ({
|
|
1050
|
+
changeType: "updated",
|
|
1051
|
+
record
|
|
1052
|
+
})),
|
|
1053
|
+
...(changesObject.deletedEvents ?? []).map((record) => ({
|
|
1054
|
+
changeType: "deleted",
|
|
1055
|
+
record
|
|
1056
|
+
}))
|
|
1057
|
+
];
|
|
1058
|
+
};
|
|
1059
|
+
//#endregion
|
|
1060
|
+
//#region src/utils/index.ts
|
|
1061
|
+
function getBase64FromUrl(url) {
|
|
1062
|
+
const lastPathSegmentMatch = url.match(/\/([^/]+)$/);
|
|
1063
|
+
return lastPathSegmentMatch ? lastPathSegmentMatch[1] : "";
|
|
1064
|
+
}
|
|
1065
|
+
//#endregion
|
|
1066
|
+
//#region src/helpers/stopWatch.ts
|
|
1067
|
+
const stopWatch = async ({ calendar, channelId, resourceId }) => {
|
|
1068
|
+
await calendar.channels.stop({ requestBody: {
|
|
1069
|
+
id: channelId,
|
|
1070
|
+
resourceId
|
|
1071
|
+
} });
|
|
1072
|
+
};
|
|
1073
|
+
const stopWatchWithErrorHandling = async (params, logger) => {
|
|
1074
|
+
try {
|
|
1075
|
+
await stopWatch(params);
|
|
1076
|
+
logger.info("Previous Calendar watch stopped successfully");
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
const error = err;
|
|
1079
|
+
logger.warn("Failed to stop previous Calendar watch (continuing):", error?.errors?.[0]?.reason || error?.message);
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
//#endregion
|
|
1083
|
+
//#region src/helpers/manageWatch.ts
|
|
1084
|
+
const manageWatch = async ({ calendar, calendarId, webhookAddress, newChannelId, previousWatch, logger }) => {
|
|
1085
|
+
if (previousWatch?.channelId && previousWatch?.resourceId) await stopWatchWithErrorHandling({
|
|
1086
|
+
calendar,
|
|
1087
|
+
channelId: previousWatch.channelId,
|
|
1088
|
+
resourceId: previousWatch.resourceId
|
|
1089
|
+
}, logger);
|
|
1090
|
+
const expiration = Date.now() + 10080 * 60 * 1e3;
|
|
1091
|
+
try {
|
|
1092
|
+
const res = await calendar.events.watch({
|
|
1093
|
+
calendarId,
|
|
1094
|
+
requestBody: {
|
|
1095
|
+
id: newChannelId,
|
|
1096
|
+
type: "web_hook",
|
|
1097
|
+
address: webhookAddress,
|
|
1098
|
+
expiration: _prismatic_io_spectral.util.types.toString(expiration)
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
logger.info("New Calendar watch created successfully");
|
|
1102
|
+
if (!res.data.id || !res.data.resourceId || !res.data.expiration) throw new Error("Calendar watch creation returned incomplete data");
|
|
1103
|
+
return {
|
|
1104
|
+
channelId: res.data.id,
|
|
1105
|
+
resourceId: res.data.resourceId,
|
|
1106
|
+
expiration: res.data.expiration
|
|
1107
|
+
};
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
logger.error("Failed to create Calendar watch. Does the calendar still exist?");
|
|
1110
|
+
throw error;
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/helpers/calendarChangeEventsCreate.ts
|
|
1115
|
+
const calendarChangeEventsCreate = async (context, { connection, calendarId }) => {
|
|
1116
|
+
const calendar = createClient({ connection });
|
|
1117
|
+
const integrationFlowName = context.flow.name;
|
|
1118
|
+
const address = context.webhookUrls[integrationFlowName];
|
|
1119
|
+
const stateKey = getBase64FromUrl(address);
|
|
1120
|
+
const newChannelId = (0, uuid.v4)();
|
|
1121
|
+
const previousWatch = context.crossFlowState[stateKey];
|
|
1122
|
+
context.logger.info(`Started Calendar Change Events Trigger deploy for ${integrationFlowName}`);
|
|
1123
|
+
context.logger.info(`Creating watch for calendar ${calendarId}`);
|
|
1124
|
+
const watchResult = await manageWatch({
|
|
1125
|
+
calendar,
|
|
1126
|
+
calendarId,
|
|
1127
|
+
webhookAddress: address,
|
|
1128
|
+
newChannelId,
|
|
1129
|
+
logger: context.logger,
|
|
1130
|
+
previousWatch
|
|
1131
|
+
});
|
|
1132
|
+
context.crossFlowState[stateKey] = watchResult;
|
|
1133
|
+
context.logger.info("Establishing initial sync token for incremental sync");
|
|
1134
|
+
const syncStateKey = `${stateKey}_syncToken`;
|
|
1135
|
+
const lastListExecutedAtStateKey = `${stateKey}_lastListExecutedAt`;
|
|
1136
|
+
try {
|
|
1137
|
+
const response = await listAllEvents({
|
|
1138
|
+
googleConnection: connection,
|
|
1139
|
+
calendarId,
|
|
1140
|
+
fetchAll: true,
|
|
1141
|
+
singleEvents: true,
|
|
1142
|
+
showDeleted: true
|
|
1143
|
+
});
|
|
1144
|
+
if (response.data.nextSyncToken) {
|
|
1145
|
+
context.crossFlowState[syncStateKey] = response.data.nextSyncToken;
|
|
1146
|
+
context.crossFlowState[lastListExecutedAtStateKey] = (/* @__PURE__ */ new Date()).toISOString();
|
|
1147
|
+
context.logger.info("Initial sync token established. Future notifications will track changes from this point.");
|
|
1148
|
+
} else throw new Error("No sync token found");
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
context.logger.error("Failed to establish initial sync token:", error instanceof Error ? error.message : "Unknown error");
|
|
1151
|
+
throw error;
|
|
1152
|
+
}
|
|
1153
|
+
context.logger.info(`Finished Calendar Change Events Trigger deploy for ${integrationFlowName}`);
|
|
1154
|
+
};
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region src/helpers/calendarChangeEventsDelete.ts
|
|
1157
|
+
const calendarChangeEventsDelete = async (context, { connection }) => {
|
|
1158
|
+
const integrationFlowName = context.flow.name;
|
|
1159
|
+
context.logger.info(`Started Calendar Change Events Trigger delete for ${integrationFlowName}`);
|
|
1160
|
+
const address = context.webhookUrls[integrationFlowName];
|
|
1161
|
+
const stateKey = getBase64FromUrl(address);
|
|
1162
|
+
const syncStateKey = `${stateKey}_syncToken`;
|
|
1163
|
+
const lastListExecutedAtStateKey = `${stateKey}_lastListExecutedAt`;
|
|
1164
|
+
const previousWatch = context.crossFlowState[stateKey];
|
|
1165
|
+
if (previousWatch?.channelId && previousWatch?.resourceId) await stopWatchWithErrorHandling({
|
|
1166
|
+
calendar: createClient({ connection }),
|
|
1167
|
+
channelId: previousWatch.channelId,
|
|
1168
|
+
resourceId: previousWatch.resourceId
|
|
1169
|
+
}, context.logger);
|
|
1170
|
+
else context.logger.info("No previous Calendar watch found in state, nothing to stop");
|
|
1171
|
+
delete context.crossFlowState[stateKey];
|
|
1172
|
+
delete context.crossFlowState[syncStateKey];
|
|
1173
|
+
delete context.crossFlowState[lastListExecutedAtStateKey];
|
|
1174
|
+
context.logger.info(`Finished Calendar Change Events Trigger delete for ${integrationFlowName}`);
|
|
1175
|
+
};
|
|
1176
|
+
//#endregion
|
|
1177
|
+
//#region src/helpers/retrieveIncrementalChanges.ts
|
|
1178
|
+
const retrieveIncrementalChanges = async (connection, calendarId, syncToken) => {
|
|
1179
|
+
const response = await listAllEvents({
|
|
1180
|
+
googleConnection: connection,
|
|
1181
|
+
calendarId,
|
|
1182
|
+
syncToken,
|
|
1183
|
+
fetchAll: true,
|
|
1184
|
+
singleEvents: true,
|
|
1185
|
+
showDeleted: true
|
|
1186
|
+
});
|
|
1187
|
+
return {
|
|
1188
|
+
events: response.data.items || [],
|
|
1189
|
+
nextSyncToken: response.data.nextSyncToken
|
|
1190
|
+
};
|
|
1191
|
+
};
|
|
1192
|
+
//#endregion
|
|
1193
|
+
//#region src/helpers/calendarChangeEventsPerform.ts
|
|
1194
|
+
const calendarChangeEventsPerform = async (context, payload, { connection, calendarId }) => {
|
|
1195
|
+
const headers = _prismatic_io_spectral.util.types.lowerCaseHeaders(payload.headers);
|
|
1196
|
+
const invokeType = headers["prismatic-invoke-type"];
|
|
1197
|
+
const integrationFlowName = context.flow.name;
|
|
1198
|
+
const address = context.webhookUrls[integrationFlowName];
|
|
1199
|
+
const stateKey = getBase64FromUrl(address);
|
|
1200
|
+
const previousWatch = context.crossFlowState[stateKey];
|
|
1201
|
+
if (!previousWatch) throw new Error("No previous watch found in state");
|
|
1202
|
+
if (invokeType === "Scheduled") {
|
|
1203
|
+
if (context.debug.enabled) context.logger.info("Scheduled renewal triggered");
|
|
1204
|
+
const watchResult = await manageWatch({
|
|
1205
|
+
calendar: createClient({ connection }),
|
|
1206
|
+
calendarId,
|
|
1207
|
+
webhookAddress: address,
|
|
1208
|
+
newChannelId: (0, uuid.v4)(),
|
|
1209
|
+
logger: context.logger,
|
|
1210
|
+
previousWatch
|
|
1211
|
+
});
|
|
1212
|
+
context.crossFlowState[stateKey] = watchResult;
|
|
1213
|
+
payload.body.data = watchResult;
|
|
1214
|
+
return {
|
|
1215
|
+
payload,
|
|
1216
|
+
branch: "Log Messages",
|
|
1217
|
+
response: {
|
|
1218
|
+
statusCode: 200,
|
|
1219
|
+
contentType: "application/json"
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
let changeDetails = null;
|
|
1224
|
+
const resourceState = headers["x-goog-resource-state"];
|
|
1225
|
+
const resourceId = headers["x-goog-resource-id"];
|
|
1226
|
+
const changedFields = headers["x-goog-changed"];
|
|
1227
|
+
const isValidChannel = headers["x-goog-channel-id"] === previousWatch.channelId;
|
|
1228
|
+
const isValidResource = resourceId === previousWatch.resourceId;
|
|
1229
|
+
if (!isValidChannel || !isValidResource) throw new Error("Unauthorized webhook call: Channel or resource ID mismatch");
|
|
1230
|
+
if (resourceState === "sync") {
|
|
1231
|
+
if (context.debug.enabled) context.logger.info("Sync notification received");
|
|
1232
|
+
payload.body.data = { notification: {
|
|
1233
|
+
resourceState,
|
|
1234
|
+
resourceId,
|
|
1235
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1236
|
+
type: "sync"
|
|
1237
|
+
} };
|
|
1238
|
+
return {
|
|
1239
|
+
payload,
|
|
1240
|
+
branch: "Log Messages",
|
|
1241
|
+
response: {
|
|
1242
|
+
statusCode: 200,
|
|
1243
|
+
contentType: "application/json"
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
if (context.debug.enabled) context.logger.info("Notification received");
|
|
1248
|
+
if (resourceState === "exists") {
|
|
1249
|
+
const calendar = createClient({ connection });
|
|
1250
|
+
let calendarInfo = null;
|
|
1251
|
+
let changes = null;
|
|
1252
|
+
let syncExpired = false;
|
|
1253
|
+
const calendarResponse = await calendar.calendars.get({ calendarId });
|
|
1254
|
+
calendarInfo = {
|
|
1255
|
+
id: calendarResponse.data.id,
|
|
1256
|
+
summary: calendarResponse.data.summary,
|
|
1257
|
+
description: calendarResponse.data.description,
|
|
1258
|
+
timeZone: calendarResponse.data.timeZone
|
|
1259
|
+
};
|
|
1260
|
+
const syncStateKey = `${stateKey}_syncToken`;
|
|
1261
|
+
const previousSyncToken = context.crossFlowState[syncStateKey];
|
|
1262
|
+
let changedEvents = [];
|
|
1263
|
+
let newSyncToken;
|
|
1264
|
+
if (!previousSyncToken) throw new Error("No sync token found in state");
|
|
1265
|
+
try {
|
|
1266
|
+
if (context.debug.enabled) context.logger.info("Using sync token to fetch incremental changes");
|
|
1267
|
+
const eventsResponse = await retrieveIncrementalChanges(connection, calendarId, previousSyncToken);
|
|
1268
|
+
if (eventsResponse.events && eventsResponse.events.length > 0) changedEvents = eventsResponse.events.map((event) => {
|
|
1269
|
+
let changeType;
|
|
1270
|
+
if (event.status === "cancelled") changeType = "deleted";
|
|
1271
|
+
else if (!event.created || !event.updated) changeType = "unknown";
|
|
1272
|
+
else {
|
|
1273
|
+
const createdTime = new Date(event.created).getTime();
|
|
1274
|
+
changeType = new Date(event.updated).getTime() - createdTime < 1e3 ? "created" : "updated";
|
|
1275
|
+
}
|
|
1276
|
+
return {
|
|
1277
|
+
changeType,
|
|
1278
|
+
id: event.id,
|
|
1279
|
+
summary: event.summary || "[No title]",
|
|
1280
|
+
status: event.status,
|
|
1281
|
+
start: event.start,
|
|
1282
|
+
end: event.end,
|
|
1283
|
+
created: event.created,
|
|
1284
|
+
updated: event.updated,
|
|
1285
|
+
organizer: event.organizer?.email,
|
|
1286
|
+
attendees: event.attendees?.map((a) => ({
|
|
1287
|
+
email: a.email,
|
|
1288
|
+
responseStatus: a.responseStatus,
|
|
1289
|
+
optional: a.optional
|
|
1290
|
+
})),
|
|
1291
|
+
location: event.location,
|
|
1292
|
+
description: event.description,
|
|
1293
|
+
recurringEventId: event.recurringEventId,
|
|
1294
|
+
htmlLink: event.htmlLink
|
|
1295
|
+
};
|
|
1296
|
+
});
|
|
1297
|
+
newSyncToken = eventsResponse.nextSyncToken;
|
|
1298
|
+
} catch (e) {
|
|
1299
|
+
const syncError = e;
|
|
1300
|
+
if (syncError?.response?.status === 410) {
|
|
1301
|
+
context.logger.warn("Sync token expired, re-establishing baseline");
|
|
1302
|
+
syncExpired = true;
|
|
1303
|
+
const lastListExecutedAtStateKey = `${stateKey}_lastListExecutedAt`;
|
|
1304
|
+
const lastListExecutedAt = context.crossFlowState[lastListExecutedAtStateKey];
|
|
1305
|
+
const response = await listAllEvents({
|
|
1306
|
+
googleConnection: connection,
|
|
1307
|
+
calendarId,
|
|
1308
|
+
updatedMin: lastListExecutedAt,
|
|
1309
|
+
fetchAll: true,
|
|
1310
|
+
singleEvents: true,
|
|
1311
|
+
showDeleted: true
|
|
1312
|
+
});
|
|
1313
|
+
context.crossFlowState[lastListExecutedAtStateKey] = (/* @__PURE__ */ new Date()).toISOString();
|
|
1314
|
+
newSyncToken = response.data.nextSyncToken;
|
|
1315
|
+
} else throw syncError;
|
|
1316
|
+
}
|
|
1317
|
+
if (!newSyncToken) throw new Error("No new sync token found to continue incremental sync");
|
|
1318
|
+
context.crossFlowState[syncStateKey] = newSyncToken;
|
|
1319
|
+
if (context.debug.enabled) context.logger.info("Updated sync token for next incremental sync");
|
|
1320
|
+
if (syncExpired) {
|
|
1321
|
+
changes = {
|
|
1322
|
+
summary: {
|
|
1323
|
+
totalChanges: 0,
|
|
1324
|
+
created: 0,
|
|
1325
|
+
updated: 0,
|
|
1326
|
+
deleted: 0,
|
|
1327
|
+
syncTokenAvailable: !!previousSyncToken,
|
|
1328
|
+
syncTokenUpdated: !!newSyncToken
|
|
1329
|
+
},
|
|
1330
|
+
createdEvents: [],
|
|
1331
|
+
updatedEvents: [],
|
|
1332
|
+
deletedEvents: [],
|
|
1333
|
+
allChanges: []
|
|
1334
|
+
};
|
|
1335
|
+
changeDetails = {
|
|
1336
|
+
calendar: calendarInfo,
|
|
1337
|
+
changes,
|
|
1338
|
+
notification: {
|
|
1339
|
+
resourceState: "resync",
|
|
1340
|
+
reason: "sync_token_expired",
|
|
1341
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
} else {
|
|
1345
|
+
const createdEvents = changedEvents.filter((e) => e.changeType === "created");
|
|
1346
|
+
const updatedEvents = changedEvents.filter((e) => e.changeType === "updated");
|
|
1347
|
+
const deletedEvents = changedEvents.filter((e) => e.changeType === "deleted");
|
|
1348
|
+
changes = {
|
|
1349
|
+
summary: {
|
|
1350
|
+
totalChanges: changedEvents.length,
|
|
1351
|
+
created: createdEvents.length,
|
|
1352
|
+
updated: updatedEvents.length,
|
|
1353
|
+
deleted: deletedEvents.length,
|
|
1354
|
+
syncTokenAvailable: !!previousSyncToken,
|
|
1355
|
+
syncTokenUpdated: !!newSyncToken
|
|
1356
|
+
},
|
|
1357
|
+
createdEvents,
|
|
1358
|
+
updatedEvents,
|
|
1359
|
+
deletedEvents,
|
|
1360
|
+
allChanges: changedEvents
|
|
1361
|
+
};
|
|
1362
|
+
changeDetails = {
|
|
1363
|
+
calendar: calendarInfo,
|
|
1364
|
+
changes,
|
|
1365
|
+
notification: {
|
|
1366
|
+
resourceState,
|
|
1367
|
+
changedFields,
|
|
1368
|
+
resourceId,
|
|
1369
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
} else changeDetails = {
|
|
1374
|
+
calendar: null,
|
|
1375
|
+
changes: null,
|
|
1376
|
+
notification: {
|
|
1377
|
+
resourceState,
|
|
1378
|
+
changedFields,
|
|
1379
|
+
resourceId,
|
|
1380
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
payload.body.data = changeDetails;
|
|
1384
|
+
return {
|
|
1385
|
+
payload,
|
|
1386
|
+
branch: "Push Notifications",
|
|
1387
|
+
response: {
|
|
1388
|
+
statusCode: 200,
|
|
1389
|
+
contentType: "application/json"
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
};
|
|
1393
|
+
//#endregion
|
|
1394
|
+
//#region src/helpers/pollEventsTriggerPerform.ts
|
|
1395
|
+
const classifyEvent = (event) => {
|
|
1396
|
+
if (event.status === "cancelled") return "deleted";
|
|
1397
|
+
if (!event.created || !event.updated) return "unknown";
|
|
1398
|
+
const createdTime = new Date(event.created).getTime();
|
|
1399
|
+
return new Date(event.updated).getTime() - createdTime < 1e3 ? "created" : "updated";
|
|
1400
|
+
};
|
|
1401
|
+
const processEvents = (events) => {
|
|
1402
|
+
const createdEvents = [];
|
|
1403
|
+
const updatedEvents = [];
|
|
1404
|
+
const deletedEvents = [];
|
|
1405
|
+
return {
|
|
1406
|
+
processedEvents: events.map((event) => {
|
|
1407
|
+
const changeType = classifyEvent(event);
|
|
1408
|
+
const processed = {
|
|
1409
|
+
changeType,
|
|
1410
|
+
id: event.id,
|
|
1411
|
+
summary: event.summary || "[No title]",
|
|
1412
|
+
status: event.status,
|
|
1413
|
+
start: event.start,
|
|
1414
|
+
end: event.end,
|
|
1415
|
+
created: event.created,
|
|
1416
|
+
updated: event.updated,
|
|
1417
|
+
organizer: event.organizer?.email,
|
|
1418
|
+
attendees: event.attendees?.map((a) => ({
|
|
1419
|
+
email: a.email,
|
|
1420
|
+
responseStatus: a.responseStatus,
|
|
1421
|
+
optional: a.optional
|
|
1422
|
+
})),
|
|
1423
|
+
location: event.location,
|
|
1424
|
+
description: event.description,
|
|
1425
|
+
recurringEventId: event.recurringEventId,
|
|
1426
|
+
htmlLink: event.htmlLink
|
|
1427
|
+
};
|
|
1428
|
+
if (changeType === "created") createdEvents.push(processed);
|
|
1429
|
+
else if (changeType === "updated") updatedEvents.push(processed);
|
|
1430
|
+
else if (changeType === "deleted") deletedEvents.push(processed);
|
|
1431
|
+
return processed;
|
|
1432
|
+
}),
|
|
1433
|
+
createdEvents,
|
|
1434
|
+
updatedEvents,
|
|
1435
|
+
deletedEvents
|
|
1436
|
+
};
|
|
1437
|
+
};
|
|
1438
|
+
const pollEventsTriggerPerform = async (context, payload, { connection, calendarId, lookBackDate }) => {
|
|
1439
|
+
const pollState = context.polling.getState();
|
|
1440
|
+
const isBatching = context.batch?.enabled === true;
|
|
1441
|
+
const incoming = payload.paginationState;
|
|
1442
|
+
const newLastPolledAt = incoming?.drainStartedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1443
|
+
let syncToken = incoming?.syncToken ?? pollState.syncToken;
|
|
1444
|
+
let events = [];
|
|
1445
|
+
let nextPageToken;
|
|
1446
|
+
let drained = true;
|
|
1447
|
+
try {
|
|
1448
|
+
if (incoming?.pageToken) {
|
|
1449
|
+
const response = await listAllEvents({
|
|
1450
|
+
googleConnection: connection,
|
|
1451
|
+
calendarId,
|
|
1452
|
+
pageToken: incoming.pageToken,
|
|
1453
|
+
fetchAll: false,
|
|
1454
|
+
maxResults: 50,
|
|
1455
|
+
singleEvents: true,
|
|
1456
|
+
showDeleted: true
|
|
1457
|
+
});
|
|
1458
|
+
events = response.data.items || [];
|
|
1459
|
+
nextPageToken = response.data.nextPageToken;
|
|
1460
|
+
if (response.data.nextSyncToken) syncToken = response.data.nextSyncToken;
|
|
1461
|
+
drained = !nextPageToken;
|
|
1462
|
+
} else if (syncToken) if (isBatching) {
|
|
1463
|
+
const response = await listAllEvents({
|
|
1464
|
+
googleConnection: connection,
|
|
1465
|
+
calendarId,
|
|
1466
|
+
syncToken,
|
|
1467
|
+
fetchAll: false,
|
|
1468
|
+
maxResults: 50,
|
|
1469
|
+
singleEvents: true,
|
|
1470
|
+
showDeleted: true
|
|
1471
|
+
});
|
|
1472
|
+
events = response.data.items || [];
|
|
1473
|
+
nextPageToken = response.data.nextPageToken;
|
|
1474
|
+
if (response.data.nextSyncToken) syncToken = response.data.nextSyncToken;
|
|
1475
|
+
drained = !nextPageToken;
|
|
1476
|
+
} else {
|
|
1477
|
+
const result = await retrieveIncrementalChanges(connection, calendarId, syncToken);
|
|
1478
|
+
events = result.events;
|
|
1479
|
+
syncToken = result.nextSyncToken;
|
|
1480
|
+
}
|
|
1481
|
+
else {
|
|
1482
|
+
if (context.debug.enabled) context.logger.info("No sync token found, performing initial sync");
|
|
1483
|
+
const response = await listAllEvents({
|
|
1484
|
+
googleConnection: connection,
|
|
1485
|
+
calendarId,
|
|
1486
|
+
updatedMin: lookBackDate ? (/* @__PURE__ */ new Date(`${lookBackDate}T00:00:00Z`)).toISOString() : newLastPolledAt,
|
|
1487
|
+
fetchAll: !isBatching,
|
|
1488
|
+
maxResults: isBatching ? 50 : void 0,
|
|
1489
|
+
singleEvents: true,
|
|
1490
|
+
showDeleted: true
|
|
1491
|
+
});
|
|
1492
|
+
syncToken = response.data.nextSyncToken;
|
|
1493
|
+
nextPageToken = response.data.nextPageToken;
|
|
1494
|
+
events = response.data.items || [];
|
|
1495
|
+
drained = !nextPageToken;
|
|
1496
|
+
if (!isBatching) events = lookBackDate ? events : [];
|
|
1497
|
+
}
|
|
1498
|
+
} catch (e) {
|
|
1499
|
+
const syncError = e;
|
|
1500
|
+
if (syncError.response?.status === 410) {
|
|
1501
|
+
if (context.debug.enabled) context.logger.warn("Sync token is no longer valid, performing incremental sync from last polled date");
|
|
1502
|
+
const resyncTimeMin = pollState.lastPolledAt;
|
|
1503
|
+
if (context.debug.enabled) context.logger.info(`Resyncing from ${resyncTimeMin}`);
|
|
1504
|
+
const initialResponse = await listAllEvents({
|
|
1505
|
+
googleConnection: connection,
|
|
1506
|
+
calendarId,
|
|
1507
|
+
updatedMin: resyncTimeMin,
|
|
1508
|
+
fetchAll: !isBatching,
|
|
1509
|
+
maxResults: isBatching ? 50 : void 0,
|
|
1510
|
+
singleEvents: true,
|
|
1511
|
+
showDeleted: true
|
|
1512
|
+
});
|
|
1513
|
+
syncToken = initialResponse.data.nextSyncToken;
|
|
1514
|
+
nextPageToken = initialResponse.data.nextPageToken;
|
|
1515
|
+
events = initialResponse.data.items || [];
|
|
1516
|
+
drained = !nextPageToken;
|
|
1517
|
+
if (!syncToken && drained) throw new Error("No sync token found after resync");
|
|
1518
|
+
} else throw syncError;
|
|
1519
|
+
}
|
|
1520
|
+
const { processedEvents, createdEvents, updatedEvents, deletedEvents } = processEvents(events);
|
|
1521
|
+
let nextPaginationState = null;
|
|
1522
|
+
if (!drained) nextPaginationState = {
|
|
1523
|
+
...syncToken ? { syncToken } : {},
|
|
1524
|
+
pageToken: nextPageToken,
|
|
1525
|
+
drainStartedAt: newLastPolledAt
|
|
1526
|
+
};
|
|
1527
|
+
const stateUpdate = {
|
|
1528
|
+
syncToken,
|
|
1529
|
+
lastPolledAt: drained ? newLastPolledAt : pollState.lastPolledAt
|
|
1530
|
+
};
|
|
1531
|
+
context.polling.setState(stateUpdate);
|
|
1532
|
+
const changes = {
|
|
1533
|
+
summary: {
|
|
1534
|
+
totalChanges: processedEvents.length,
|
|
1535
|
+
created: createdEvents.length,
|
|
1536
|
+
updated: updatedEvents.length,
|
|
1537
|
+
deleted: deletedEvents.length,
|
|
1538
|
+
syncTokenAvailable: !!syncToken
|
|
1539
|
+
},
|
|
1540
|
+
createdEvents,
|
|
1541
|
+
updatedEvents,
|
|
1542
|
+
deletedEvents,
|
|
1543
|
+
allChanges: processedEvents
|
|
1544
|
+
};
|
|
1545
|
+
const polledNoChanges = processedEvents.length === 0 && nextPaginationState === null && !incoming;
|
|
1546
|
+
return {
|
|
1547
|
+
payload: {
|
|
1548
|
+
...payload,
|
|
1549
|
+
paginationState: nextPaginationState ?? void 0,
|
|
1550
|
+
body: { data: {
|
|
1551
|
+
changes,
|
|
1552
|
+
calendarId,
|
|
1553
|
+
syncToken
|
|
1554
|
+
} }
|
|
1555
|
+
},
|
|
1556
|
+
polledNoChanges
|
|
1557
|
+
};
|
|
1558
|
+
};
|
|
1559
|
+
//#endregion
|
|
1560
|
+
//#region src/triggers/index.ts
|
|
1561
|
+
var triggers_default = {
|
|
1562
|
+
calendarChangeEvents: (0, _prismatic_io_spectral.trigger)({
|
|
1563
|
+
display: {
|
|
1564
|
+
label: "Calendar Change Events",
|
|
1565
|
+
description: "Receive change notifications for a Google Calendar. Automatically creates and manages a Google Calendar push notification subscription when the instance is deployed, and removes the subscription when the instance is deleted."
|
|
1566
|
+
},
|
|
1567
|
+
perform: calendarChangeEventsPerform,
|
|
1568
|
+
inputs: calendarChangeEventsInputs,
|
|
1569
|
+
synchronousResponseSupport: "invalid",
|
|
1570
|
+
scheduleSupport: "valid",
|
|
1571
|
+
allowsBranching: true,
|
|
1572
|
+
staticBranchNames: ["Push Notifications", "Log Messages"],
|
|
1573
|
+
examplePayload: calendarChangeEventsPayload,
|
|
1574
|
+
webhookLifecycleHandlers: {
|
|
1575
|
+
create: calendarChangeEventsCreate,
|
|
1576
|
+
delete: calendarChangeEventsDelete
|
|
1577
|
+
}
|
|
1578
|
+
}),
|
|
1579
|
+
pollEventsTrigger: (0, _prismatic_io_spectral.pollingTrigger)({
|
|
1580
|
+
display: {
|
|
1581
|
+
label: "New and Updated Events",
|
|
1582
|
+
description: "Retrieves existing and ongoing events in a specified Google Calendar. Load history once, check for changes on a schedule, or both."
|
|
1583
|
+
},
|
|
1584
|
+
inputs: pollEventsTriggerInputs,
|
|
1585
|
+
triggerResolverSupport: "valid",
|
|
1586
|
+
batchConfig: { batchSize: 50 },
|
|
1587
|
+
triggerResolver: {
|
|
1588
|
+
resolveItems: (_context, { payload }) => resolvePollingRecordChanges(payload.body.data?.changes),
|
|
1589
|
+
getNextPaginationState: (_context, { payload }) => payload.paginationState ?? null
|
|
1590
|
+
},
|
|
1591
|
+
perform: pollEventsTriggerPerform,
|
|
1592
|
+
examplePayload: pollEventsTriggerPayload
|
|
1593
|
+
})
|
|
1594
|
+
};
|
|
1595
|
+
//#endregion
|
|
1596
|
+
//#region src/index.ts
|
|
1597
|
+
var src_default = (0, _prismatic_io_spectral.component)({
|
|
1598
|
+
key: "google-calendar",
|
|
1599
|
+
public: true,
|
|
1600
|
+
documentationUrl: "https://prismatic.io/docs/components/google-calendar/",
|
|
1601
|
+
display: {
|
|
1602
|
+
label: "Google Calendar",
|
|
1603
|
+
description: "Manage calendars and events in Google Calendar",
|
|
1604
|
+
iconPath: "icon.png",
|
|
1605
|
+
category: "Application Connectors"
|
|
1606
|
+
},
|
|
1607
|
+
actions: actions_default,
|
|
1608
|
+
hooks: { error: _prismatic_io_spectral_http.handleErrors },
|
|
1609
|
+
connections: connections_default,
|
|
1610
|
+
dataSources: dataSources_default,
|
|
1611
|
+
triggers: triggers_default
|
|
1612
|
+
});
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region npm-dist/.entry.ts
|
|
1615
|
+
var _entry_default = (0, _prismatic_io_spectral.callableComponent)(src_default);
|
|
1616
|
+
//#endregion
|
|
1617
|
+
module.exports = _entry_default;
|