n8n-nodes-onedrive-business 1.1.0 → 1.1.4
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.
|
@@ -239,18 +239,26 @@ class OneDriveBusinessTrigger {
|
|
|
239
239
|
await stateStore.initialize(tenantId, driveId, driveLocation.userId, driveLocation.siteId);
|
|
240
240
|
// Create webhook handler
|
|
241
241
|
const webhookHandler = new WebhookHandler_1.WebhookHandler(graphClient, stateStore, subscriptionResource);
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
// Store subscription ID in workflow static data
|
|
245
|
-
const webhookData = this.getWorkflowStaticData('node');
|
|
246
|
-
webhookData.subscriptionId = subscription.id;
|
|
247
|
-
// 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
|
|
248
244
|
OneDriveBusinessTrigger.instances.set(nodeId, {
|
|
249
245
|
stateStore,
|
|
250
246
|
webhookHandler,
|
|
251
247
|
});
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
}
|
|
254
262
|
},
|
|
255
263
|
async delete() {
|
|
256
264
|
const webhookData = this.getWorkflowStaticData('node');
|
|
@@ -350,9 +358,9 @@ class OneDriveBusinessTrigger {
|
|
|
350
358
|
OneDriveBusinessTrigger.instances.set(nodeId, instance);
|
|
351
359
|
}
|
|
352
360
|
// Create delta processor
|
|
353
|
-
const deltaProcessor = new DeltaProcessor_1.DeltaProcessor(graphClient, instance.stateStore, drivePath);
|
|
361
|
+
const deltaProcessor = new DeltaProcessor_1.DeltaProcessor(graphClient, instance.stateStore, drivePath, watchPath);
|
|
354
362
|
// Process delta query
|
|
355
|
-
const triggerEvents = await deltaProcessor.processDeltaQuery(eventFilter
|
|
363
|
+
const triggerEvents = await deltaProcessor.processDeltaQuery(eventFilter);
|
|
356
364
|
// Clean up old versions periodically
|
|
357
365
|
const state = instance.stateStore.getState();
|
|
358
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>
|
|
@@ -36,21 +36,16 @@ export declare class DeltaProcessor {
|
|
|
36
36
|
private graphClient;
|
|
37
37
|
private stateStore;
|
|
38
38
|
private drivePath;
|
|
39
|
+
private watchPath?;
|
|
39
40
|
private stabilityTracker;
|
|
40
41
|
private readonly STABILITY_WINDOW_MS;
|
|
41
|
-
constructor(graphClient: GraphClient, stateStore: StateStore, drivePath: string);
|
|
42
|
+
constructor(graphClient: GraphClient, stateStore: StateStore, drivePath: string, watchPath?: string);
|
|
42
43
|
/**
|
|
43
44
|
* Process delta query and return triggerable events
|
|
44
45
|
*
|
|
45
46
|
* This is the main entry point called by the trigger node
|
|
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
|
+
processDeltaQuery(eventFilter: Set<string>): Promise<TriggerEvent[]>;
|
|
54
49
|
/**
|
|
55
50
|
* Fetch all pages from delta query
|
|
56
51
|
* Handles @odata.nextLink pagination and stores @odata.deltaLink
|
|
@@ -61,10 +56,6 @@ export declare class DeltaProcessor {
|
|
|
61
56
|
* Returns a TriggerEvent if the item should trigger a workflow
|
|
62
57
|
*/
|
|
63
58
|
private processItem;
|
|
64
|
-
/**
|
|
65
|
-
* Check if item is within the watched path
|
|
66
|
-
*/
|
|
67
|
-
private isItemInScope;
|
|
68
59
|
/**
|
|
69
60
|
* Classify event as created vs updated
|
|
70
61
|
*
|
|
@@ -34,7 +34,7 @@ const helpers_1 = require("./helpers");
|
|
|
34
34
|
* Without this processor, ONE file upload would trigger FOUR workflow executions.
|
|
35
35
|
*/
|
|
36
36
|
class DeltaProcessor {
|
|
37
|
-
constructor(graphClient, stateStore, drivePath) {
|
|
37
|
+
constructor(graphClient, stateStore, drivePath, watchPath) {
|
|
38
38
|
// Stability tracking for files currently being uploaded
|
|
39
39
|
this.stabilityTracker = new Map();
|
|
40
40
|
// Stability window: wait 15 seconds after last modification
|
|
@@ -42,19 +42,15 @@ class DeltaProcessor {
|
|
|
42
42
|
this.graphClient = graphClient;
|
|
43
43
|
this.stateStore = stateStore;
|
|
44
44
|
this.drivePath = drivePath;
|
|
45
|
+
this.watchPath = watchPath;
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
47
48
|
* Process delta query and return triggerable events
|
|
48
49
|
*
|
|
49
50
|
* This is the main entry point called by the trigger node
|
|
50
51
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
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) {
|
|
52
|
+
async processDeltaQuery(eventFilter // e.g., ['file.created', 'file.updated']
|
|
53
|
+
) {
|
|
58
54
|
const events = [];
|
|
59
55
|
try {
|
|
60
56
|
// Get current state
|
|
@@ -69,13 +65,28 @@ class DeltaProcessor {
|
|
|
69
65
|
}
|
|
70
66
|
else {
|
|
71
67
|
// Initial delta query
|
|
72
|
-
|
|
68
|
+
if (this.watchPath && this.watchPath !== '' && this.watchPath !== '/') {
|
|
69
|
+
// Scope to specific folder
|
|
70
|
+
let path = this.watchPath;
|
|
71
|
+
if (!path.startsWith('/')) {
|
|
72
|
+
path = '/' + path;
|
|
73
|
+
}
|
|
74
|
+
// Syntax: /drive/root:/path/to/folder:/delta
|
|
75
|
+
// Note the colons wrapping the path
|
|
76
|
+
deltaEndpoint = `${this.drivePath}/root:${encodeURIComponent(path)}:/delta`;
|
|
77
|
+
console.log(`[DeltaProcessor] Initializing Scoped Delta Query: ${deltaEndpoint}`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Browse entire drive
|
|
81
|
+
deltaEndpoint = `${this.drivePath}/root/delta`;
|
|
82
|
+
console.log(`[DeltaProcessor] Initializing Root Delta Query`);
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
// Fetch delta changes (handle pagination)
|
|
75
86
|
const items = await this.fetchAllDeltaPages(deltaEndpoint);
|
|
76
87
|
// Process each item
|
|
77
88
|
for (const item of items) {
|
|
78
|
-
const event = await this.processItem(item, eventFilter
|
|
89
|
+
const event = await this.processItem(item, eventFilter);
|
|
79
90
|
if (event) {
|
|
80
91
|
events.push(event);
|
|
81
92
|
}
|
|
@@ -95,6 +106,8 @@ class DeltaProcessor {
|
|
|
95
106
|
const allItems = [];
|
|
96
107
|
let currentEndpoint = initialEndpoint;
|
|
97
108
|
while (currentEndpoint) {
|
|
109
|
+
// Log for debugging
|
|
110
|
+
console.log(`[DeltaProcessor] Fetching: ${currentEndpoint}`);
|
|
98
111
|
const response = await this.graphClient.get(currentEndpoint);
|
|
99
112
|
allItems.push(...response.value);
|
|
100
113
|
// Check for next page
|
|
@@ -119,19 +132,18 @@ class DeltaProcessor {
|
|
|
119
132
|
* Process a single delta item
|
|
120
133
|
* Returns a TriggerEvent if the item should trigger a workflow
|
|
121
134
|
*/
|
|
122
|
-
async processItem(item, eventFilter
|
|
135
|
+
async processItem(item, eventFilter) {
|
|
123
136
|
// Skip deleted items (unless specifically requested)
|
|
124
137
|
if (item.deleted) {
|
|
125
138
|
return null;
|
|
126
139
|
}
|
|
127
|
-
// Skip root folder
|
|
140
|
+
// Skip root folder (the folder itself, not its children)
|
|
128
141
|
if (!item.parentReference) {
|
|
129
142
|
return null;
|
|
130
143
|
}
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
144
|
+
// Note: We don't need manual scope checking (isItemInScope) anymore
|
|
145
|
+
// because the Delta Query itself is scoped to the target folder.
|
|
146
|
+
// Any item returned here IS in the scope.
|
|
135
147
|
// Determine event type
|
|
136
148
|
const eventType = this.classifyEvent(item);
|
|
137
149
|
// Check if this event type is enabled
|
|
@@ -163,42 +175,6 @@ class DeltaProcessor {
|
|
|
163
175
|
timestamp: new Date().toISOString(),
|
|
164
176
|
};
|
|
165
177
|
}
|
|
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
|
-
}
|
|
202
178
|
/**
|
|
203
179
|
* Classify event as created vs updated
|
|
204
180
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-onedrive-business",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
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",
|