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
- // Create subscription
210
- const subscription = await webhookHandler.createOrRenewSubscription(webhookUrl);
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
- console.log(`OneDrive Business Trigger activated for node ${nodeId}`);
220
- return true;
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
- processDeltaQuery(eventFilter: Set<string>): Promise<TriggerEvent[]>;
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
- async processDeltaQuery(eventFilter // e.g., ['file.created', 'file.updated']
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: 'created,updated',
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.0.0",
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 -u 1 \"nodes/**/*.{png,svg}\" dist/",
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",