n8n-nodes-onedrive-business-sp 1.1.1 → 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.
|
@@ -109,11 +109,51 @@ class MicrosoftOneDriveBusinessTrigger {
|
|
|
109
109
|
default: {},
|
|
110
110
|
options: [
|
|
111
111
|
{
|
|
112
|
-
displayName: '
|
|
113
|
-
name: '
|
|
112
|
+
displayName: 'Watch Subfolders',
|
|
113
|
+
name: 'recursive',
|
|
114
|
+
type: 'boolean',
|
|
115
|
+
default: false,
|
|
116
|
+
description: 'Whether to watch subfolders recursively',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
displayName: 'Ignore Duplicates',
|
|
120
|
+
name: 'ignoreDuplicates',
|
|
121
|
+
type: 'boolean',
|
|
122
|
+
default: true,
|
|
123
|
+
description: 'Whether to ignore duplicate notifications for the same file',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
displayName: 'Wait for File Completion (seconds)',
|
|
127
|
+
name: 'waitForCompletion',
|
|
114
128
|
type: 'number',
|
|
115
|
-
default:
|
|
116
|
-
description: '
|
|
129
|
+
default: 0,
|
|
130
|
+
description: 'Wait time after detecting change to ensure file is fully uploaded. Useful for large files.',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
displayName: 'Include File Metadata',
|
|
134
|
+
name: 'includeMetadata',
|
|
135
|
+
type: 'boolean',
|
|
136
|
+
default: true,
|
|
137
|
+
description: 'Whether to include extended metadata like version, lock status, etc.',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
displayName: 'Periodic Full Scan',
|
|
141
|
+
name: 'fullScanEnabled',
|
|
142
|
+
type: 'boolean',
|
|
143
|
+
default: false,
|
|
144
|
+
description: 'Whether to periodically perform a full scan as backup (in case delta misses changes)',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
displayName: 'Full Scan Interval (hours)',
|
|
148
|
+
name: 'fullScanInterval',
|
|
149
|
+
type: 'number',
|
|
150
|
+
default: 24,
|
|
151
|
+
displayOptions: {
|
|
152
|
+
show: {
|
|
153
|
+
fullScanEnabled: [true],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
description: 'How often to perform a full scan (in hours)',
|
|
117
157
|
},
|
|
118
158
|
],
|
|
119
159
|
},
|
|
@@ -232,6 +272,14 @@ class MicrosoftOneDriveBusinessTrigger {
|
|
|
232
272
|
else {
|
|
233
273
|
watchFolderId = watchFolder.value || 'root';
|
|
234
274
|
}
|
|
275
|
+
// Validate webhook URL accessibility (basic check)
|
|
276
|
+
if (!webhookUrl.startsWith('https://')) {
|
|
277
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Webhook URL must use HTTPS. Microsoft Graph requires secure endpoints.');
|
|
278
|
+
}
|
|
279
|
+
// Check if webhook URL contains localhost/127.0.0.1
|
|
280
|
+
if (webhookUrl.includes('localhost') || webhookUrl.includes('127.0.0.1')) {
|
|
281
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Webhook URL cannot be localhost. Microsoft Graph cannot reach local addresses. Use a public URL or tunneling service like ngrok.');
|
|
282
|
+
}
|
|
235
283
|
// Get drive ID if not provided
|
|
236
284
|
let actualDriveId = driveId;
|
|
237
285
|
if (!actualDriveId) {
|
|
@@ -242,6 +290,24 @@ class MicrosoftOneDriveBusinessTrigger {
|
|
|
242
290
|
});
|
|
243
291
|
actualDriveId = driveResponse.id;
|
|
244
292
|
}
|
|
293
|
+
// Validate drive and folder accessibility
|
|
294
|
+
try {
|
|
295
|
+
await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveBusinessOAuth2Api', {
|
|
296
|
+
method: 'GET',
|
|
297
|
+
url: `https://graph.microsoft.com/v1.0/drives/${actualDriveId}`,
|
|
298
|
+
json: true,
|
|
299
|
+
});
|
|
300
|
+
if (watchFolderId !== 'root') {
|
|
301
|
+
await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveBusinessOAuth2Api', {
|
|
302
|
+
method: 'GET',
|
|
303
|
+
url: `https://graph.microsoft.com/v1.0/drives/${actualDriveId}/items/${watchFolderId}`,
|
|
304
|
+
json: true,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (validationError) {
|
|
309
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot access drive/folder. It may have been deleted, moved, or you may not have permission. Error: ${validationError.message}`);
|
|
310
|
+
}
|
|
245
311
|
// Microsoft Graph only supports subscriptions at drive root level
|
|
246
312
|
// We'll filter by folder in the webhook handler
|
|
247
313
|
const resource = `/drives/${actualDriveId}/root`;
|
|
@@ -265,7 +331,22 @@ class MicrosoftOneDriveBusinessTrigger {
|
|
|
265
331
|
webhookData.subscriptionId = responseData.id;
|
|
266
332
|
webhookData.driveId = actualDriveId;
|
|
267
333
|
webhookData.watchFolderId = watchFolderId;
|
|
268
|
-
webhookData.
|
|
334
|
+
webhookData.subscriptionExpiry = responseData.expirationDateTime;
|
|
335
|
+
webhookData.processedItems = {};
|
|
336
|
+
// Initialize delta link to avoid getting all existing items
|
|
337
|
+
try {
|
|
338
|
+
const deltaResponse = await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveBusinessOAuth2Api', {
|
|
339
|
+
method: 'GET',
|
|
340
|
+
url: `https://graph.microsoft.com/v1.0/drives/${actualDriveId}/root/delta`,
|
|
341
|
+
json: true,
|
|
342
|
+
});
|
|
343
|
+
// Store the delta link to track only future changes
|
|
344
|
+
webhookData.deltaLink = deltaResponse['@odata.deltaLink'];
|
|
345
|
+
}
|
|
346
|
+
catch (deltaError) {
|
|
347
|
+
// If delta initialization fails, set to null
|
|
348
|
+
webhookData.deltaLink = null;
|
|
349
|
+
}
|
|
269
350
|
}
|
|
270
351
|
catch (error) {
|
|
271
352
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -305,6 +386,39 @@ class MicrosoftOneDriveBusinessTrigger {
|
|
|
305
386
|
webhookResponse: query.validationToken,
|
|
306
387
|
};
|
|
307
388
|
}
|
|
389
|
+
// Check if subscription needs renewal (renew if less than 24h remaining)
|
|
390
|
+
const subscriptionExpiry = webhookData.subscriptionExpiry;
|
|
391
|
+
const isRenewing = webhookData.isRenewing;
|
|
392
|
+
if (subscriptionExpiry && !isRenewing) {
|
|
393
|
+
const expiryTime = new Date(subscriptionExpiry).getTime();
|
|
394
|
+
const now = Date.now();
|
|
395
|
+
const hoursRemaining = (expiryTime - now) / (1000 * 60 * 60);
|
|
396
|
+
if (hoursRemaining < 24 && hoursRemaining > 0) {
|
|
397
|
+
try {
|
|
398
|
+
// Set renewal flag to prevent concurrent renewals
|
|
399
|
+
webhookData.isRenewing = true;
|
|
400
|
+
const newExpirationDateTime = new Date();
|
|
401
|
+
newExpirationDateTime.setHours(newExpirationDateTime.getHours() + 72);
|
|
402
|
+
const renewResponse = await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveBusinessOAuth2Api', {
|
|
403
|
+
method: 'PATCH',
|
|
404
|
+
url: `https://graph.microsoft.com/v1.0/subscriptions/${webhookData.subscriptionId}`,
|
|
405
|
+
body: {
|
|
406
|
+
expirationDateTime: newExpirationDateTime.toISOString(),
|
|
407
|
+
},
|
|
408
|
+
json: true,
|
|
409
|
+
});
|
|
410
|
+
webhookData.subscriptionExpiry = renewResponse.expirationDateTime;
|
|
411
|
+
}
|
|
412
|
+
catch (renewError) {
|
|
413
|
+
// If renewal fails, log but continue processing
|
|
414
|
+
console.log('Failed to renew subscription:', renewError);
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
// Clear renewal flag
|
|
418
|
+
webhookData.isRenewing = false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
308
422
|
// Verify client state
|
|
309
423
|
const notification = bodyData.value?.[0];
|
|
310
424
|
if (notification?.clientState !== 'n8n-secret-token') {
|
|
@@ -314,6 +428,13 @@ class MicrosoftOneDriveBusinessTrigger {
|
|
|
314
428
|
}
|
|
315
429
|
const event = this.getNodeParameter('event');
|
|
316
430
|
const watchFolder = this.getNodeParameter('watchFolderId', {});
|
|
431
|
+
const options = this.getNodeParameter('options', {});
|
|
432
|
+
const recursive = options.recursive || false;
|
|
433
|
+
const ignoreDuplicates = options.ignoreDuplicates !== false;
|
|
434
|
+
const waitForCompletion = options.waitForCompletion || 0;
|
|
435
|
+
const includeMetadata = options.includeMetadata !== false;
|
|
436
|
+
const fullScanEnabled = options.fullScanEnabled || false;
|
|
437
|
+
const fullScanInterval = options.fullScanInterval || 24;
|
|
317
438
|
// Extract folder ID from resource locator
|
|
318
439
|
let watchFolderId;
|
|
319
440
|
if (typeof watchFolder === 'string') {
|
|
@@ -324,20 +445,69 @@ class MicrosoftOneDriveBusinessTrigger {
|
|
|
324
445
|
}
|
|
325
446
|
const driveId = webhookData.driveId;
|
|
326
447
|
const storedWatchFolderId = webhookData.watchFolderId || 'root';
|
|
448
|
+
// Check if full scan is needed
|
|
449
|
+
if (fullScanEnabled) {
|
|
450
|
+
const lastFullScan = webhookData.lastFullScan || 0;
|
|
451
|
+
const now = Date.now();
|
|
452
|
+
const hoursSinceLastScan = (now - lastFullScan) / (1000 * 60 * 60);
|
|
453
|
+
if (hoursSinceLastScan >= fullScanInterval) {
|
|
454
|
+
// Reset delta link to force full scan
|
|
455
|
+
webhookData.deltaLink = null;
|
|
456
|
+
webhookData.lastFullScan = now;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
327
459
|
// Always use root delta query (Microsoft Graph requirement)
|
|
328
460
|
let deltaUrl = webhookData.deltaLink;
|
|
329
461
|
if (!deltaUrl) {
|
|
330
462
|
deltaUrl = `https://graph.microsoft.com/v1.0/drives/${driveId}/root/delta`;
|
|
331
463
|
}
|
|
332
464
|
try {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
465
|
+
// Wait for file completion if configured
|
|
466
|
+
if (waitForCompletion > 0) {
|
|
467
|
+
await new Promise(resolve => setTimeout(resolve, waitForCompletion * 1000));
|
|
468
|
+
}
|
|
469
|
+
// Fetch all pages of delta changes
|
|
470
|
+
let allChanges = [];
|
|
471
|
+
let currentDeltaUrl = deltaUrl;
|
|
472
|
+
let finalDeltaLink;
|
|
473
|
+
let retryCount = 0;
|
|
474
|
+
const maxRetries = 3;
|
|
475
|
+
while (currentDeltaUrl) {
|
|
476
|
+
try {
|
|
477
|
+
const deltaResponse = await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveBusinessOAuth2Api', {
|
|
478
|
+
method: 'GET',
|
|
479
|
+
url: currentDeltaUrl,
|
|
480
|
+
json: true,
|
|
481
|
+
});
|
|
482
|
+
const pageChanges = deltaResponse.value;
|
|
483
|
+
allChanges = allChanges.concat(pageChanges);
|
|
484
|
+
// Check for next page or delta link
|
|
485
|
+
if (deltaResponse['@odata.nextLink']) {
|
|
486
|
+
currentDeltaUrl = deltaResponse['@odata.nextLink'];
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
currentDeltaUrl = undefined;
|
|
490
|
+
finalDeltaLink = deltaResponse['@odata.deltaLink'];
|
|
491
|
+
}
|
|
492
|
+
retryCount = 0; // Reset retry count on success
|
|
493
|
+
}
|
|
494
|
+
catch (pageError) {
|
|
495
|
+
// Retry logic for transient failures
|
|
496
|
+
const statusCode = pageError?.statusCode || pageError?.response?.status;
|
|
497
|
+
if ((statusCode === 429 || statusCode >= 500) && retryCount < maxRetries) {
|
|
498
|
+
retryCount++;
|
|
499
|
+
const delay = Math.min(1000 * Math.pow(2, retryCount), 10000); // Exponential backoff
|
|
500
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
501
|
+
continue; // Retry same URL
|
|
502
|
+
}
|
|
503
|
+
throw pageError; // Re-throw if not retryable or max retries reached
|
|
504
|
+
}
|
|
505
|
+
}
|
|
338
506
|
// Store the new delta link
|
|
339
|
-
|
|
340
|
-
|
|
507
|
+
if (finalDeltaLink) {
|
|
508
|
+
webhookData.deltaLink = finalDeltaLink;
|
|
509
|
+
}
|
|
510
|
+
const changes = allChanges;
|
|
341
511
|
// Get folder path if watching specific folder
|
|
342
512
|
let watchFolderPath = null;
|
|
343
513
|
if (storedWatchFolderId !== 'root') {
|
|
@@ -355,45 +525,184 @@ class MicrosoftOneDriveBusinessTrigger {
|
|
|
355
525
|
// If can't get folder path, don't filter
|
|
356
526
|
}
|
|
357
527
|
}
|
|
528
|
+
// Deduplication: track processed items
|
|
529
|
+
const processedItems = webhookData.processedItems || {};
|
|
530
|
+
const now = Date.now();
|
|
531
|
+
// Clean up old processed items (older than 1 hour)
|
|
532
|
+
for (const key in processedItems) {
|
|
533
|
+
if (now - processedItems[key] > 3600000) {
|
|
534
|
+
delete processedItems[key];
|
|
535
|
+
}
|
|
536
|
+
}
|
|
358
537
|
const filteredChanges = changes.filter((change) => {
|
|
538
|
+
// Validate required fields
|
|
539
|
+
if (!change.id || !change.name) {
|
|
540
|
+
return false; // Skip incomplete items
|
|
541
|
+
}
|
|
359
542
|
const isFile = change.file !== undefined;
|
|
360
543
|
const isFolder = change.folder !== undefined;
|
|
361
544
|
const isDeleted = change.deleted !== undefined;
|
|
362
545
|
if (isDeleted)
|
|
363
546
|
return false;
|
|
547
|
+
// Skip the root folder itself
|
|
548
|
+
if (change.root !== undefined)
|
|
549
|
+
return false;
|
|
550
|
+
// Skip if missing critical metadata
|
|
551
|
+
if (!change.createdDateTime || !change.lastModifiedDateTime) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
// Deduplication check
|
|
555
|
+
if (ignoreDuplicates) {
|
|
556
|
+
const itemKey = `${change.id}_${change.lastModifiedDateTime}`;
|
|
557
|
+
if (processedItems[itemKey]) {
|
|
558
|
+
return false; // Already processed this exact change
|
|
559
|
+
}
|
|
560
|
+
processedItems[itemKey] = now;
|
|
561
|
+
}
|
|
364
562
|
// Filter by folder path if watching specific folder
|
|
365
|
-
if (
|
|
563
|
+
if (storedWatchFolderId !== 'root' && change.parentReference) {
|
|
366
564
|
const parentRef = change.parentReference;
|
|
367
|
-
const
|
|
368
|
-
if (
|
|
369
|
-
|
|
565
|
+
const parentId = parentRef.id;
|
|
566
|
+
if (recursive) {
|
|
567
|
+
// For recursive, check if item is in watched folder or any subfolder
|
|
568
|
+
const itemPath = parentRef.path || '';
|
|
569
|
+
if (watchFolderPath) {
|
|
570
|
+
if (!itemPath.includes(watchFolderPath)) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
// Fallback: check parent ID chain (would need additional API calls)
|
|
576
|
+
// For now, just check direct parent
|
|
577
|
+
if (parentId !== storedWatchFolderId) {
|
|
578
|
+
// Could be in subfolder, but we can't verify without more API calls
|
|
579
|
+
// Let it through for now
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
// Non-recursive: must be direct child
|
|
585
|
+
if (parentId !== storedWatchFolderId) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
370
588
|
}
|
|
371
589
|
}
|
|
372
590
|
if (event === 'fileCreated' && isFile) {
|
|
373
|
-
|
|
591
|
+
// Compare timestamps instead of strings for better accuracy
|
|
592
|
+
const created = new Date(change.createdDateTime).getTime();
|
|
593
|
+
const modified = new Date(change.lastModifiedDateTime).getTime();
|
|
594
|
+
// Allow small time difference (within 5 seconds) for creation detection
|
|
595
|
+
return Math.abs(created - modified) < 5000;
|
|
374
596
|
}
|
|
375
597
|
else if (event === 'fileUpdated' && isFile) {
|
|
376
|
-
|
|
598
|
+
const created = new Date(change.createdDateTime).getTime();
|
|
599
|
+
const modified = new Date(change.lastModifiedDateTime).getTime();
|
|
600
|
+
return modified > created + 5000;
|
|
377
601
|
}
|
|
378
602
|
else if (event === 'folderCreated' && isFolder) {
|
|
379
|
-
|
|
603
|
+
const created = new Date(change.createdDateTime).getTime();
|
|
604
|
+
const modified = new Date(change.lastModifiedDateTime).getTime();
|
|
605
|
+
return Math.abs(created - modified) < 5000;
|
|
380
606
|
}
|
|
381
607
|
else if (event === 'folderUpdated' && isFolder) {
|
|
382
|
-
|
|
608
|
+
const created = new Date(change.createdDateTime).getTime();
|
|
609
|
+
const modified = new Date(change.lastModifiedDateTime).getTime();
|
|
610
|
+
return modified > created + 5000;
|
|
383
611
|
}
|
|
384
612
|
return false;
|
|
385
613
|
});
|
|
614
|
+
// Save processed items
|
|
615
|
+
webhookData.processedItems = processedItems;
|
|
386
616
|
if (filteredChanges.length === 0) {
|
|
387
617
|
return {
|
|
388
618
|
webhookResponse: 'No matching changes',
|
|
389
619
|
workflowData: [],
|
|
390
620
|
};
|
|
391
621
|
}
|
|
622
|
+
// Enrich with extended metadata if requested
|
|
623
|
+
if (includeMetadata && filteredChanges.length > 0) {
|
|
624
|
+
for (const change of filteredChanges) {
|
|
625
|
+
try {
|
|
626
|
+
const itemDetails = await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveBusinessOAuth2Api', {
|
|
627
|
+
method: 'GET',
|
|
628
|
+
url: `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${change.id}`,
|
|
629
|
+
qs: {
|
|
630
|
+
$select: 'id,name,size,webUrl,file,folder,publication,createdBy,lastModifiedBy,fileSystemInfo,versions'
|
|
631
|
+
},
|
|
632
|
+
json: true,
|
|
633
|
+
});
|
|
634
|
+
// Add version information
|
|
635
|
+
if (itemDetails.file) {
|
|
636
|
+
change.versionNumber = itemDetails.file.hashes ?
|
|
637
|
+
itemDetails['@microsoft.graph.version'] : undefined;
|
|
638
|
+
}
|
|
639
|
+
// Add lock/checkout status (SharePoint feature)
|
|
640
|
+
if (itemDetails.publication) {
|
|
641
|
+
const pub = itemDetails.publication;
|
|
642
|
+
change.checkoutUser = pub.checkedOutBy;
|
|
643
|
+
change.isCheckedOut = !!pub.checkedOutBy;
|
|
644
|
+
}
|
|
645
|
+
// Merge additional details
|
|
646
|
+
Object.assign(change, {
|
|
647
|
+
webUrl: itemDetails.webUrl,
|
|
648
|
+
createdBy: itemDetails.createdBy,
|
|
649
|
+
lastModifiedBy: itemDetails.lastModifiedBy,
|
|
650
|
+
fileSystemInfo: itemDetails.fileSystemInfo,
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
catch (enrichError) {
|
|
654
|
+
// If enrichment fails, continue with basic data
|
|
655
|
+
console.log('Failed to enrich metadata:', enrichError);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
392
659
|
return {
|
|
393
660
|
workflowData: [filteredChanges.map((change) => ({ json: change }))],
|
|
394
661
|
};
|
|
395
662
|
}
|
|
396
663
|
catch (error) {
|
|
664
|
+
// Handle specific error cases
|
|
665
|
+
const statusCode = error?.statusCode || error?.response?.status;
|
|
666
|
+
const errorCode = error?.error?.code || error?.response?.data?.error?.code;
|
|
667
|
+
// Delta link expired (410 Gone) or invalid (404)
|
|
668
|
+
if (statusCode === 410 || statusCode === 404 || errorCode === 'resyncRequired') {
|
|
669
|
+
// Reset delta link and try again with fresh sync
|
|
670
|
+
webhookData.deltaLink = null;
|
|
671
|
+
return {
|
|
672
|
+
webhookResponse: 'Delta link expired, will resync on next change',
|
|
673
|
+
workflowData: [],
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
// Folder not found or access denied
|
|
677
|
+
if (statusCode === 404 && storedWatchFolderId !== 'root') {
|
|
678
|
+
const errorMessage = 'Watched folder no longer exists or is inaccessible. Please reconfigure the trigger.';
|
|
679
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage);
|
|
680
|
+
}
|
|
681
|
+
// Forbidden - permissions changed
|
|
682
|
+
if (statusCode === 403) {
|
|
683
|
+
const errorMessage = 'Access denied. Permissions may have changed or credentials need to be refreshed.';
|
|
684
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage);
|
|
685
|
+
}
|
|
686
|
+
// Unauthorized - token expired/revoked
|
|
687
|
+
if (statusCode === 401) {
|
|
688
|
+
const errorMessage = 'Authentication failed. Please re-authenticate your OneDrive connection.';
|
|
689
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage);
|
|
690
|
+
}
|
|
691
|
+
// Service unavailable or gateway timeout
|
|
692
|
+
if (statusCode === 503 || statusCode === 504) {
|
|
693
|
+
return {
|
|
694
|
+
webhookResponse: 'Microsoft Graph service temporarily unavailable, will retry on next notification',
|
|
695
|
+
workflowData: [],
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
// Rate limiting (429)
|
|
699
|
+
if (statusCode === 429) {
|
|
700
|
+
const retryAfter = error?.response?.headers?.['retry-after'] || '60';
|
|
701
|
+
return {
|
|
702
|
+
webhookResponse: `Rate limited, retry after ${retryAfter} seconds`,
|
|
703
|
+
workflowData: [],
|
|
704
|
+
};
|
|
705
|
+
}
|
|
397
706
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
398
707
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to fetch delta changes: ${errorMessage}`);
|
|
399
708
|
}
|