@timesheet/plugin-google-calendar 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/handlers/handleSyncBatch.d.ts +4 -0
- package/dist/handlers/handleSyncBatch.js +51 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/lib/googleCalendarClient.d.ts +1 -0
- package/dist/lib/googleCalendarClient.js +46 -17
- package/dist/lib/taskSync.js +1 -5
- package/manifest.json +11 -3
- package/package.json +2 -2
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SyncModeInput } from '@timesheet/integration-sdk';
|
|
2
|
+
import { GoogleCalendarConfig } from '../lib/types';
|
|
3
|
+
import { GoogleCalendarSyncResult } from '../lib/taskSync';
|
|
4
|
+
export declare const handleSyncBatch: import("@timesheet/integration-sdk").IntegrationHandler<SyncModeInput, GoogleCalendarSyncResult, GoogleCalendarConfig>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleSyncBatch = void 0;
|
|
4
|
+
const integration_sdk_1 = require("@timesheet/integration-sdk");
|
|
5
|
+
const taskSync_1 = require("../lib/taskSync");
|
|
6
|
+
exports.handleSyncBatch = (0, integration_sdk_1.defineHandler)(async (input, context) => {
|
|
7
|
+
context.logger.info('Processing sync batch', {
|
|
8
|
+
sinceVersion: input.sinceVersion,
|
|
9
|
+
headVersion: input.headVersion,
|
|
10
|
+
changeCount: input.changes.length,
|
|
11
|
+
hasMore: input.hasMore
|
|
12
|
+
});
|
|
13
|
+
let syncedCount = 0;
|
|
14
|
+
const errors = [];
|
|
15
|
+
for (const change of input.changes) {
|
|
16
|
+
if (change.entityType !== 'task') {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const event = change.op === 'DELETE' ? 'task.delete' : 'task.update';
|
|
21
|
+
const result = await (0, taskSync_1.syncTaskToGoogleCalendar)({
|
|
22
|
+
event,
|
|
23
|
+
taskId: change.entityId,
|
|
24
|
+
item: change.item
|
|
25
|
+
}, context);
|
|
26
|
+
if (result.status === 'synced' || result.status === 'deleted') {
|
|
27
|
+
syncedCount++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
context.logger.error('Failed to sync change', {
|
|
32
|
+
entityId: change.entityId,
|
|
33
|
+
op: change.op,
|
|
34
|
+
error: String(err)
|
|
35
|
+
});
|
|
36
|
+
errors.push({ entityId: change.entityId, error: String(err) });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
system: 'google-calendar',
|
|
41
|
+
status: errors.length > 0 ? 'partial' : 'completed',
|
|
42
|
+
syncedCount,
|
|
43
|
+
details: {
|
|
44
|
+
sinceVersion: input.sinceVersion,
|
|
45
|
+
headVersion: input.headVersion,
|
|
46
|
+
totalChanges: input.changes.length,
|
|
47
|
+
hasMore: input.hasMore,
|
|
48
|
+
errors: errors.length > 0 ? errors : undefined
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { syncTaskToExternal } from './handlers/syncTaskToExternal';
|
|
2
2
|
export { syncTaskFromExternal } from './handlers/syncTaskFromExternal';
|
|
3
|
+
export { handleSyncBatch } from './handlers/handleSyncBatch';
|
|
3
4
|
export { handleWebhook } from './handlers/handleWebhook';
|
|
4
5
|
export { runFullSync } from './handlers/runFullSync';
|
|
5
6
|
export { testConnection } from './handlers/testConnection';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PLUGIN_NAME = exports.PLUGIN_SYSTEM = exports.listExternalProjects = exports.testConnection = exports.runFullSync = exports.handleWebhook = exports.syncTaskFromExternal = exports.syncTaskToExternal = void 0;
|
|
3
|
+
exports.PLUGIN_NAME = exports.PLUGIN_SYSTEM = exports.listExternalProjects = exports.testConnection = exports.runFullSync = exports.handleWebhook = exports.handleSyncBatch = exports.syncTaskFromExternal = exports.syncTaskToExternal = void 0;
|
|
4
4
|
var syncTaskToExternal_1 = require("./handlers/syncTaskToExternal");
|
|
5
5
|
Object.defineProperty(exports, "syncTaskToExternal", { enumerable: true, get: function () { return syncTaskToExternal_1.syncTaskToExternal; } });
|
|
6
6
|
var syncTaskFromExternal_1 = require("./handlers/syncTaskFromExternal");
|
|
7
7
|
Object.defineProperty(exports, "syncTaskFromExternal", { enumerable: true, get: function () { return syncTaskFromExternal_1.syncTaskFromExternal; } });
|
|
8
|
+
var handleSyncBatch_1 = require("./handlers/handleSyncBatch");
|
|
9
|
+
Object.defineProperty(exports, "handleSyncBatch", { enumerable: true, get: function () { return handleSyncBatch_1.handleSyncBatch; } });
|
|
8
10
|
var handleWebhook_1 = require("./handlers/handleWebhook");
|
|
9
11
|
Object.defineProperty(exports, "handleWebhook", { enumerable: true, get: function () { return handleWebhook_1.handleWebhook; } });
|
|
10
12
|
var runFullSync_1 = require("./handlers/runFullSync");
|
|
@@ -64,14 +64,28 @@ class GoogleCalendarClient {
|
|
|
64
64
|
}
|
|
65
65
|
async stopWatch(channelId, resourceId) {
|
|
66
66
|
const token = await this.getAccessToken();
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const timeoutId = setTimeout(() => controller.abort(), GoogleCalendarClient.REQUEST_TIMEOUT_MS);
|
|
69
|
+
let response;
|
|
70
|
+
try {
|
|
71
|
+
response = await fetch('https://www.googleapis.com/calendar/v3/channels/stop', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
Authorization: `Bearer ${token}`,
|
|
75
|
+
'Content-Type': 'application/json'
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({ id: channelId, resourceId }),
|
|
78
|
+
signal: controller.signal
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
clearTimeout(timeoutId);
|
|
83
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
84
|
+
throw new Error(`Google Calendar API channels/stop timed out after ${GoogleCalendarClient.REQUEST_TIMEOUT_MS}ms`);
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
clearTimeout(timeoutId);
|
|
75
89
|
// 404 means channel already expired — not an error
|
|
76
90
|
if (!response.ok && response.status !== 404) {
|
|
77
91
|
const errorText = await response.text();
|
|
@@ -80,15 +94,29 @@ class GoogleCalendarClient {
|
|
|
80
94
|
}
|
|
81
95
|
async request(method, path, query, body, retried = false) {
|
|
82
96
|
const token = await this.getAccessToken();
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
const timeoutId = setTimeout(() => controller.abort(), GoogleCalendarClient.REQUEST_TIMEOUT_MS);
|
|
99
|
+
let response;
|
|
100
|
+
try {
|
|
101
|
+
response = await fetch(this.buildUrl(path, query), {
|
|
102
|
+
method,
|
|
103
|
+
headers: {
|
|
104
|
+
Authorization: `Bearer ${token}`,
|
|
105
|
+
Accept: 'application/json',
|
|
106
|
+
'Content-Type': 'application/json'
|
|
107
|
+
},
|
|
108
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
109
|
+
signal: controller.signal
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
clearTimeout(timeoutId);
|
|
114
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
115
|
+
throw new Error(`Google Calendar API ${method} ${path} timed out after ${GoogleCalendarClient.REQUEST_TIMEOUT_MS}ms`);
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
clearTimeout(timeoutId);
|
|
92
120
|
if (response.status === 401 && !retried) {
|
|
93
121
|
const refreshed = await this.refreshAccessToken();
|
|
94
122
|
if (refreshed) {
|
|
@@ -118,3 +146,4 @@ class GoogleCalendarClient {
|
|
|
118
146
|
}
|
|
119
147
|
}
|
|
120
148
|
exports.GoogleCalendarClient = GoogleCalendarClient;
|
|
149
|
+
GoogleCalendarClient.REQUEST_TIMEOUT_MS = 30000;
|
package/dist/lib/taskSync.js
CHANGED
|
@@ -411,11 +411,7 @@ function getHeader(input, name) {
|
|
|
411
411
|
return value === undefined || value === null ? undefined : String(value);
|
|
412
412
|
}
|
|
413
413
|
async function ensureWatchChannels(context) {
|
|
414
|
-
|
|
415
|
-
const contextAny = context;
|
|
416
|
-
const metadata = contextAny['metadata'];
|
|
417
|
-
const webhooks = metadata?.['webhooks'];
|
|
418
|
-
const webhookUrl = webhooks?.['integration-webhook'];
|
|
414
|
+
const webhookUrl = context.metadata?.webhooks?.['integration-webhook'];
|
|
419
415
|
if (!webhookUrl) {
|
|
420
416
|
context.logger.info('No webhook URL available — skipping watch channel registration');
|
|
421
417
|
return;
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "google-calendar-sync",
|
|
3
3
|
"name": "Google Calendar Sync",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "Synchronize Timesheet tasks with Google Calendar",
|
|
6
6
|
"longDescription": "Bidirectional synchronization between Timesheet tasks and Google Calendar events.",
|
|
7
7
|
"icon": "google-calendar",
|
|
@@ -65,11 +65,12 @@
|
|
|
65
65
|
{
|
|
66
66
|
"id": "task-changed",
|
|
67
67
|
"type": "event",
|
|
68
|
+
"mode": "sync",
|
|
68
69
|
"name": "Task Changed",
|
|
69
70
|
"description": "Sync task create/update/delete events to Google Calendar",
|
|
70
71
|
"events": ["task.create", "task.update", "task.delete"],
|
|
71
72
|
"configurable": true,
|
|
72
|
-
"actionId": "sync-
|
|
73
|
+
"actionId": "handle-sync-batch"
|
|
73
74
|
},
|
|
74
75
|
{
|
|
75
76
|
"id": "integration-webhook",
|
|
@@ -83,12 +84,14 @@
|
|
|
83
84
|
{
|
|
84
85
|
"id": "full-sync",
|
|
85
86
|
"type": "schedule",
|
|
87
|
+
"mode": "sync",
|
|
86
88
|
"name": "Scheduled Full Sync",
|
|
87
89
|
"description": "Run incremental synchronization for all mapped calendars",
|
|
88
90
|
"schedule": "0 2 * * *",
|
|
89
91
|
"timezone": "UTC",
|
|
90
92
|
"configurable": true,
|
|
91
|
-
"
|
|
93
|
+
"events": ["task.create", "task.update", "task.delete"],
|
|
94
|
+
"actionId": "handle-sync-batch"
|
|
92
95
|
},
|
|
93
96
|
{
|
|
94
97
|
"id": "manual-sync",
|
|
@@ -108,6 +111,11 @@
|
|
|
108
111
|
"name": "Sync task to Google Calendar",
|
|
109
112
|
"handler": "syncTaskToExternal"
|
|
110
113
|
},
|
|
114
|
+
{
|
|
115
|
+
"id": "handle-sync-batch",
|
|
116
|
+
"name": "Handle sync batch",
|
|
117
|
+
"handler": "handleSyncBatch"
|
|
118
|
+
},
|
|
111
119
|
{
|
|
112
120
|
"id": "sync-task-from-external",
|
|
113
121
|
"name": "Sync task from Google Calendar",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timesheet/plugin-google-calendar",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -13,6 +13,6 @@
|
|
|
13
13
|
"prepublishOnly": "npm run build"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@timesheet/integration-sdk": "^0.
|
|
16
|
+
"@timesheet/integration-sdk": "^0.3.0"
|
|
17
17
|
}
|
|
18
18
|
}
|