n8n-nodes-onedrive-business 1.0.0 → 1.1.2
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.
|
@@ -119,6 +119,39 @@ class OneDriveBusinessTrigger {
|
|
|
119
119
|
placeholder: 'contoso.sharepoint.com,site-id,web-id',
|
|
120
120
|
description: 'SharePoint site ID',
|
|
121
121
|
},
|
|
122
|
+
// Watch Scope
|
|
123
|
+
{
|
|
124
|
+
displayName: 'Watch Scope',
|
|
125
|
+
name: 'watchScope',
|
|
126
|
+
type: 'options',
|
|
127
|
+
options: [
|
|
128
|
+
{
|
|
129
|
+
name: 'Entire Drive',
|
|
130
|
+
value: 'drive',
|
|
131
|
+
description: 'Monitor changes in the entire drive',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'Specific Folder',
|
|
135
|
+
value: 'folder',
|
|
136
|
+
description: 'Monitor changes in a specific folder and its subfolders',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
default: 'drive',
|
|
140
|
+
description: 'Scope of monitoring',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
displayName: 'Folder Path',
|
|
144
|
+
name: 'watchPath',
|
|
145
|
+
type: 'string',
|
|
146
|
+
displayOptions: {
|
|
147
|
+
show: {
|
|
148
|
+
watchScope: ['folder'],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
default: '',
|
|
152
|
+
placeholder: '/Documents/ProjectX',
|
|
153
|
+
description: 'Path relative to the root (e.g., /MyFolder). Leave empty for root.',
|
|
154
|
+
},
|
|
122
155
|
// Event Selection
|
|
123
156
|
{
|
|
124
157
|
displayName: 'Trigger On',
|
|
@@ -206,18 +239,26 @@ class OneDriveBusinessTrigger {
|
|
|
206
239
|
await stateStore.initialize(tenantId, driveId, driveLocation.userId, driveLocation.siteId);
|
|
207
240
|
// Create webhook handler
|
|
208
241
|
const webhookHandler = new WebhookHandler_1.WebhookHandler(graphClient, stateStore, subscriptionResource);
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
// Store subscription ID in workflow static data
|
|
212
|
-
const webhookData = this.getWorkflowStaticData('node');
|
|
213
|
-
webhookData.subscriptionId = subscription.id;
|
|
214
|
-
// Store instances for webhook handling
|
|
242
|
+
// Store instances for webhook handling BEFORE creating subscription
|
|
243
|
+
// This is critical because Microsoft verifies the webhook during creation
|
|
215
244
|
OneDriveBusinessTrigger.instances.set(nodeId, {
|
|
216
245
|
stateStore,
|
|
217
246
|
webhookHandler,
|
|
218
247
|
});
|
|
219
|
-
|
|
220
|
-
|
|
248
|
+
try {
|
|
249
|
+
// Create subscription
|
|
250
|
+
const subscription = await webhookHandler.createOrRenewSubscription(webhookUrl);
|
|
251
|
+
// Store subscription ID in workflow static data
|
|
252
|
+
const webhookData = this.getWorkflowStaticData('node');
|
|
253
|
+
webhookData.subscriptionId = subscription.id;
|
|
254
|
+
console.log(`OneDrive Business Trigger activated for node ${nodeId}`);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
// Cleanup if subscription fails
|
|
259
|
+
OneDriveBusinessTrigger.instances.delete(nodeId);
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
221
262
|
},
|
|
222
263
|
async delete() {
|
|
223
264
|
const webhookData = this.getWorkflowStaticData('node');
|
|
@@ -287,6 +328,19 @@ class OneDriveBusinessTrigger {
|
|
|
287
328
|
siteId: driveType === 'site' ? this.getNodeParameter('siteId') : undefined,
|
|
288
329
|
};
|
|
289
330
|
const drivePath = (0, helpers_1.resolveDrivePath)(driveLocation);
|
|
331
|
+
// Get watch scope settings
|
|
332
|
+
const watchScope = this.getNodeParameter('watchScope');
|
|
333
|
+
let watchPath;
|
|
334
|
+
if (watchScope === 'folder') {
|
|
335
|
+
watchPath = this.getNodeParameter('watchPath');
|
|
336
|
+
// Normalize path: ensure it doesn't end with / (unless it's just /)
|
|
337
|
+
if (watchPath && watchPath.length > 1 && watchPath.endsWith('/')) {
|
|
338
|
+
watchPath = watchPath.slice(0, -1);
|
|
339
|
+
}
|
|
340
|
+
// Ensure no leading slash for logic comparison later (Microsoft usually returns paths without leading slash in parentReference? Need to check)
|
|
341
|
+
// Microsoft parentReference.path is usually: /drives/{drive-id}/root:/path/to/folder
|
|
342
|
+
// So we will leave it as is for now and handle normalization in DeltaProcessor
|
|
343
|
+
}
|
|
290
344
|
// Initialize state store
|
|
291
345
|
const credentials = await this.getCredentials('oneDriveBusinessOAuth2Api');
|
|
292
346
|
const tenantId = credentials.tenantId;
|
|
@@ -306,7 +360,7 @@ class OneDriveBusinessTrigger {
|
|
|
306
360
|
// Create delta processor
|
|
307
361
|
const deltaProcessor = new DeltaProcessor_1.DeltaProcessor(graphClient, instance.stateStore, drivePath);
|
|
308
362
|
// Process delta query
|
|
309
|
-
const triggerEvents = await deltaProcessor.processDeltaQuery(eventFilter);
|
|
363
|
+
const triggerEvents = await deltaProcessor.processDeltaQuery(eventFilter, watchPath);
|
|
310
364
|
// Clean up old versions periodically
|
|
311
365
|
const state = instance.stateStore.getState();
|
|
312
366
|
const processedCount = Object.keys(state.processedVersions).length;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M41.8 17H41c-7.3 0-13.4 5.3-14.7 12.3-.6-.1-1.2-.2-1.8-.2-6.5 0-11.9 4.6-13.2 10.8C5 40.8 0 46.1 0 52.5c0 6.4 5.2 11.5 11.5 11.5h31.9c11.4 0 20.6-9.2 20.6-20.6 0-10.4-7.7-19-17.7-20.3-1.1-9.9-9.5-17.6-19.5-17.6z" fill="#0078D4"/></svg>
|
|
@@ -44,7 +44,13 @@ export declare class DeltaProcessor {
|
|
|
44
44
|
*
|
|
45
45
|
* This is the main entry point called by the trigger node
|
|
46
46
|
*/
|
|
47
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Process delta query and return triggerable events
|
|
49
|
+
*
|
|
50
|
+
* This is the main entry point called by the trigger node
|
|
51
|
+
*/
|
|
52
|
+
processDeltaQuery(eventFilter: Set<string>, // e.g., ['file.created', 'file.updated']
|
|
53
|
+
watchPath?: string): Promise<TriggerEvent[]>;
|
|
48
54
|
/**
|
|
49
55
|
* Fetch all pages from delta query
|
|
50
56
|
* Handles @odata.nextLink pagination and stores @odata.deltaLink
|
|
@@ -55,6 +61,10 @@ export declare class DeltaProcessor {
|
|
|
55
61
|
* Returns a TriggerEvent if the item should trigger a workflow
|
|
56
62
|
*/
|
|
57
63
|
private processItem;
|
|
64
|
+
/**
|
|
65
|
+
* Check if item is within the watched path
|
|
66
|
+
*/
|
|
67
|
+
private isItemInScope;
|
|
58
68
|
/**
|
|
59
69
|
* Classify event as created vs updated
|
|
60
70
|
*
|
|
@@ -48,8 +48,13 @@ class DeltaProcessor {
|
|
|
48
48
|
*
|
|
49
49
|
* This is the main entry point called by the trigger node
|
|
50
50
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Process delta query and return triggerable events
|
|
53
|
+
*
|
|
54
|
+
* This is the main entry point called by the trigger node
|
|
55
|
+
*/
|
|
56
|
+
async processDeltaQuery(eventFilter, // e.g., ['file.created', 'file.updated']
|
|
57
|
+
watchPath) {
|
|
53
58
|
const events = [];
|
|
54
59
|
try {
|
|
55
60
|
// Get current state
|
|
@@ -70,7 +75,7 @@ class DeltaProcessor {
|
|
|
70
75
|
const items = await this.fetchAllDeltaPages(deltaEndpoint);
|
|
71
76
|
// Process each item
|
|
72
77
|
for (const item of items) {
|
|
73
|
-
const event = await this.processItem(item, eventFilter);
|
|
78
|
+
const event = await this.processItem(item, eventFilter, watchPath);
|
|
74
79
|
if (event) {
|
|
75
80
|
events.push(event);
|
|
76
81
|
}
|
|
@@ -114,7 +119,7 @@ class DeltaProcessor {
|
|
|
114
119
|
* Process a single delta item
|
|
115
120
|
* Returns a TriggerEvent if the item should trigger a workflow
|
|
116
121
|
*/
|
|
117
|
-
async processItem(item, eventFilter) {
|
|
122
|
+
async processItem(item, eventFilter, watchPath) {
|
|
118
123
|
// Skip deleted items (unless specifically requested)
|
|
119
124
|
if (item.deleted) {
|
|
120
125
|
return null;
|
|
@@ -123,6 +128,10 @@ class DeltaProcessor {
|
|
|
123
128
|
if (!item.parentReference) {
|
|
124
129
|
return null;
|
|
125
130
|
}
|
|
131
|
+
// Check scope (folder filtering)
|
|
132
|
+
if (watchPath && !this.isItemInScope(item, watchPath)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
126
135
|
// Determine event type
|
|
127
136
|
const eventType = this.classifyEvent(item);
|
|
128
137
|
// Check if this event type is enabled
|
|
@@ -154,6 +163,42 @@ class DeltaProcessor {
|
|
|
154
163
|
timestamp: new Date().toISOString(),
|
|
155
164
|
};
|
|
156
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if item is within the watched path
|
|
168
|
+
*/
|
|
169
|
+
isItemInScope(item, watchPath) {
|
|
170
|
+
if (!item.parentReference || !item.parentReference.path) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
// Extract relative path from parentReference.path
|
|
174
|
+
// Format: /drives/{drive-id}/root:/path/to/folder
|
|
175
|
+
const parts = item.parentReference.path.split('root:');
|
|
176
|
+
if (parts.length < 2) {
|
|
177
|
+
// Item is in root
|
|
178
|
+
// If watchPath is /, then it's a match. Otherwise no.
|
|
179
|
+
return watchPath === '/' || watchPath === '';
|
|
180
|
+
}
|
|
181
|
+
let relativePath = parts[1]; // /path/to/folder
|
|
182
|
+
// Ensure paths start with / for consistent comparison
|
|
183
|
+
if (!relativePath.startsWith('/')) {
|
|
184
|
+
relativePath = '/' + relativePath;
|
|
185
|
+
}
|
|
186
|
+
// Ensure watchPath starts with /
|
|
187
|
+
let normalizedWatchPath = watchPath;
|
|
188
|
+
if (!normalizedWatchPath.startsWith('/')) {
|
|
189
|
+
normalizedWatchPath = '/' + normalizedWatchPath;
|
|
190
|
+
}
|
|
191
|
+
// Check if relative path matches or is a subfolder
|
|
192
|
+
// 1. Exact match: relativePath === normalizedWatchPath
|
|
193
|
+
// 2. Subfolder: relativePath startsWith normalizedWatchPath + '/'
|
|
194
|
+
if (relativePath === normalizedWatchPath) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (relativePath.startsWith(normalizedWatchPath + '/')) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
157
202
|
/**
|
|
158
203
|
* Classify event as created vs updated
|
|
159
204
|
*
|
|
@@ -80,7 +80,7 @@ class WebhookHandler {
|
|
|
80
80
|
const expirationDateTime = new Date();
|
|
81
81
|
expirationDateTime.setDate(expirationDateTime.getDate() + this.SUBSCRIPTION_DURATION_DAYS);
|
|
82
82
|
const subscriptionRequest = {
|
|
83
|
-
changeType: '
|
|
83
|
+
changeType: 'updated',
|
|
84
84
|
notificationUrl,
|
|
85
85
|
resource: this.subscriptionResource,
|
|
86
86
|
expirationDateTime: expirationDateTime.toISOString(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-onedrive-business",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "n8n custom node for OneDrive Business with robust deduplication and webhook triggers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"main": "index.js",
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc && npm run copy-icons",
|
|
26
|
-
"copy-icons": "copyfiles
|
|
26
|
+
"copy-icons": "copyfiles \"nodes/**/*.{png,svg}\" dist/",
|
|
27
27
|
"dev": "tsc --watch",
|
|
28
28
|
"format": "prettier nodes --write",
|
|
29
29
|
"lint": "tslint -p tsconfig.json -c tslint.json",
|