n8n-nodes-onedrive-business 1.0.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/README.md +452 -0
- package/dist/credentials/OneDriveBusinessOAuth2.credentials.d.ts +25 -0
- package/dist/credentials/OneDriveBusinessOAuth2.credentials.js +114 -0
- package/dist/nodes/OneDriveBusiness.node.d.ts +35 -0
- package/dist/nodes/OneDriveBusiness.node.js +661 -0
- package/dist/nodes/OneDriveBusinessTrigger.node.d.ts +63 -0
- package/dist/nodes/OneDriveBusinessTrigger.node.js +332 -0
- package/dist/onedrive.svg +1 -0
- package/dist/types.d.ts +218 -0
- package/dist/types.js +8 -0
- package/dist/utils/DeltaProcessor.d.ts +91 -0
- package/dist/utils/DeltaProcessor.js +267 -0
- package/dist/utils/GraphClient.d.ts +67 -0
- package/dist/utils/GraphClient.js +182 -0
- package/dist/utils/StateStore.d.ts +96 -0
- package/dist/utils/StateStore.js +280 -0
- package/dist/utils/WebhookHandler.d.ts +78 -0
- package/dist/utils/WebhookHandler.js +220 -0
- package/dist/utils/helpers.d.ts +113 -0
- package/dist/utils/helpers.js +250 -0
- package/package.json +56 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseGraphError = exports.cleanupProcessedVersions = exports.buildVersionKey = exports.getMimeType = exports.isFolder = exports.isFile = exports.isValidItemId = exports.generateSecureRandomString = exports.parseRetryAfter = exports.sleep = exports.calculateBackoffDelay = exports.formatBytes = exports.getFileExtension = exports.buildItemPath = exports.sanitizePath = exports.resolveSubscriptionResource = exports.resolveDrivePath = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Helper Functions for OneDrive Business Node
|
|
6
|
+
*
|
|
7
|
+
* These utilities handle common operations like drive resolution,
|
|
8
|
+
* path sanitization, and binary data handling.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the drive path for Microsoft Graph API
|
|
12
|
+
*
|
|
13
|
+
* OneDrive Business drives are accessed through:
|
|
14
|
+
* - User context: /users/{userId}/drive
|
|
15
|
+
* - Site context: /sites/{siteId}/drive
|
|
16
|
+
*
|
|
17
|
+
* NEVER use /me/drive without tenant context
|
|
18
|
+
*/
|
|
19
|
+
function resolveDrivePath(location) {
|
|
20
|
+
if (location.type === 'user' && location.userId) {
|
|
21
|
+
return `/users/${location.userId}/drive`;
|
|
22
|
+
}
|
|
23
|
+
if (location.type === 'site' && location.siteId) {
|
|
24
|
+
return `/sites/${location.siteId}/drive`;
|
|
25
|
+
}
|
|
26
|
+
throw new Error('Invalid drive location. Must specify either userId for user drive or siteId for site drive.');
|
|
27
|
+
}
|
|
28
|
+
exports.resolveDrivePath = resolveDrivePath;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the subscription resource for webhooks
|
|
31
|
+
*
|
|
32
|
+
* For delta queries and webhooks, we use:
|
|
33
|
+
* - User context: /users/{userId}/drive/root
|
|
34
|
+
* - Site context: /sites/{siteId}/drive
|
|
35
|
+
*/
|
|
36
|
+
function resolveSubscriptionResource(location) {
|
|
37
|
+
if (location.type === 'user' && location.userId) {
|
|
38
|
+
return `/users/${location.userId}/drive/root`;
|
|
39
|
+
}
|
|
40
|
+
if (location.type === 'site' && location.siteId) {
|
|
41
|
+
return `/sites/${location.siteId}/drive`;
|
|
42
|
+
}
|
|
43
|
+
throw new Error('Invalid drive location for subscription. Must specify either userId or siteId.');
|
|
44
|
+
}
|
|
45
|
+
exports.resolveSubscriptionResource = resolveSubscriptionResource;
|
|
46
|
+
/**
|
|
47
|
+
* Sanitize file or folder path for OneDrive API
|
|
48
|
+
*
|
|
49
|
+
* - Removes leading/trailing slashes
|
|
50
|
+
* - Converts backslashes to forward slashes
|
|
51
|
+
* - Encodes special characters
|
|
52
|
+
*/
|
|
53
|
+
function sanitizePath(path) {
|
|
54
|
+
if (!path) {
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
// Convert backslashes to forward slashes
|
|
58
|
+
let sanitized = path.replace(/\\/g, '/');
|
|
59
|
+
// Remove leading and trailing slashes
|
|
60
|
+
sanitized = sanitized.replace(/^\/+|\/+$/g, '');
|
|
61
|
+
// Encode special characters but preserve forward slashes
|
|
62
|
+
const parts = sanitized.split('/');
|
|
63
|
+
const encodedParts = parts.map(part => encodeURIComponent(part));
|
|
64
|
+
return encodedParts.join('/');
|
|
65
|
+
}
|
|
66
|
+
exports.sanitizePath = sanitizePath;
|
|
67
|
+
/**
|
|
68
|
+
* Build the full item path for OneDrive API
|
|
69
|
+
*
|
|
70
|
+
* Constructs paths like:
|
|
71
|
+
* - /drive/root:/Documents/file.pdf
|
|
72
|
+
* - /drive/items/{itemId}
|
|
73
|
+
*/
|
|
74
|
+
function buildItemPath(drivePath, itemPath, itemId) {
|
|
75
|
+
if (itemId) {
|
|
76
|
+
return `${drivePath}/items/${itemId}`;
|
|
77
|
+
}
|
|
78
|
+
if (itemPath) {
|
|
79
|
+
const sanitized = sanitizePath(itemPath);
|
|
80
|
+
if (sanitized) {
|
|
81
|
+
return `${drivePath}/root:/${sanitized}`;
|
|
82
|
+
}
|
|
83
|
+
return `${drivePath}/root`;
|
|
84
|
+
}
|
|
85
|
+
return `${drivePath}/root`;
|
|
86
|
+
}
|
|
87
|
+
exports.buildItemPath = buildItemPath;
|
|
88
|
+
/**
|
|
89
|
+
* Extract file extension from filename
|
|
90
|
+
*/
|
|
91
|
+
function getFileExtension(filename) {
|
|
92
|
+
const lastDot = filename.lastIndexOf('.');
|
|
93
|
+
if (lastDot === -1 || lastDot === 0) {
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
return filename.substring(lastDot + 1).toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
exports.getFileExtension = getFileExtension;
|
|
99
|
+
/**
|
|
100
|
+
* Format bytes to human-readable size
|
|
101
|
+
*/
|
|
102
|
+
function formatBytes(bytes) {
|
|
103
|
+
if (bytes === 0)
|
|
104
|
+
return '0 Bytes';
|
|
105
|
+
const k = 1024;
|
|
106
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
107
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
108
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
109
|
+
}
|
|
110
|
+
exports.formatBytes = formatBytes;
|
|
111
|
+
/**
|
|
112
|
+
* Calculate exponential backoff delay for retries
|
|
113
|
+
*
|
|
114
|
+
* Used for throttling (429) and transient errors
|
|
115
|
+
*/
|
|
116
|
+
function calculateBackoffDelay(retryCount, initialDelayMs = 1000, maxDelayMs = 60000, backoffMultiplier = 2) {
|
|
117
|
+
const delay = initialDelayMs * Math.pow(backoffMultiplier, retryCount);
|
|
118
|
+
return Math.min(delay, maxDelayMs);
|
|
119
|
+
}
|
|
120
|
+
exports.calculateBackoffDelay = calculateBackoffDelay;
|
|
121
|
+
/**
|
|
122
|
+
* Sleep for specified milliseconds
|
|
123
|
+
* Used in retry logic
|
|
124
|
+
*/
|
|
125
|
+
function sleep(ms) {
|
|
126
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
127
|
+
}
|
|
128
|
+
exports.sleep = sleep;
|
|
129
|
+
/**
|
|
130
|
+
* Parse Retry-After header from 429 responses
|
|
131
|
+
* Returns delay in milliseconds
|
|
132
|
+
*/
|
|
133
|
+
function parseRetryAfter(retryAfterHeader) {
|
|
134
|
+
if (!retryAfterHeader) {
|
|
135
|
+
return 1000; // Default 1 second
|
|
136
|
+
}
|
|
137
|
+
// Try to parse as seconds (integer)
|
|
138
|
+
const seconds = parseInt(retryAfterHeader, 10);
|
|
139
|
+
if (!isNaN(seconds)) {
|
|
140
|
+
return seconds * 1000;
|
|
141
|
+
}
|
|
142
|
+
// Try to parse as HTTP date
|
|
143
|
+
const date = new Date(retryAfterHeader);
|
|
144
|
+
if (!isNaN(date.getTime())) {
|
|
145
|
+
const delayMs = date.getTime() - Date.now();
|
|
146
|
+
return Math.max(0, delayMs);
|
|
147
|
+
}
|
|
148
|
+
return 1000; // Default 1 second
|
|
149
|
+
}
|
|
150
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
151
|
+
/**
|
|
152
|
+
* Generate a cryptographically secure random string
|
|
153
|
+
* Used for clientState in webhook subscriptions
|
|
154
|
+
*/
|
|
155
|
+
function generateSecureRandomString(length = 32) {
|
|
156
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
157
|
+
const crypto = require('crypto');
|
|
158
|
+
const randomBytes = crypto.randomBytes(length);
|
|
159
|
+
let result = '';
|
|
160
|
+
for (let i = 0; i < length; i++) {
|
|
161
|
+
result += chars[randomBytes[i] % chars.length];
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
exports.generateSecureRandomString = generateSecureRandomString;
|
|
166
|
+
/**
|
|
167
|
+
* Validate Microsoft Graph item ID format
|
|
168
|
+
*/
|
|
169
|
+
function isValidItemId(itemId) {
|
|
170
|
+
if (!itemId || typeof itemId !== 'string') {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
// Graph item IDs are typically base64-encoded strings
|
|
174
|
+
// They should not contain spaces or special characters except !, _, -, and =
|
|
175
|
+
return /^[A-Za-z0-9!_\-=]+$/.test(itemId);
|
|
176
|
+
}
|
|
177
|
+
exports.isValidItemId = isValidItemId;
|
|
178
|
+
/**
|
|
179
|
+
* Check if an item is a file (vs folder)
|
|
180
|
+
*/
|
|
181
|
+
function isFile(item) {
|
|
182
|
+
return !!item.file && !item.folder;
|
|
183
|
+
}
|
|
184
|
+
exports.isFile = isFile;
|
|
185
|
+
/**
|
|
186
|
+
* Check if an item is a folder (vs file)
|
|
187
|
+
*/
|
|
188
|
+
function isFolder(item) {
|
|
189
|
+
return !!item.folder && !item.file;
|
|
190
|
+
}
|
|
191
|
+
exports.isFolder = isFolder;
|
|
192
|
+
/**
|
|
193
|
+
* Extract MIME type from DriveItem
|
|
194
|
+
*/
|
|
195
|
+
function getMimeType(item) {
|
|
196
|
+
var _a;
|
|
197
|
+
return ((_a = item.file) === null || _a === void 0 ? void 0 : _a.mimeType) || 'application/octet-stream';
|
|
198
|
+
}
|
|
199
|
+
exports.getMimeType = getMimeType;
|
|
200
|
+
/**
|
|
201
|
+
* Build a version key for deduplication
|
|
202
|
+
* Format: ${itemId}_${eTag}
|
|
203
|
+
*/
|
|
204
|
+
function buildVersionKey(itemId, eTag) {
|
|
205
|
+
return `${itemId}_${eTag}`;
|
|
206
|
+
}
|
|
207
|
+
exports.buildVersionKey = buildVersionKey;
|
|
208
|
+
/**
|
|
209
|
+
* Clean up old processed versions to prevent memory bloat
|
|
210
|
+
* Keeps only the most recent maxVersions per item
|
|
211
|
+
*/
|
|
212
|
+
function cleanupProcessedVersions(processedVersions, maxVersionsPerItem = 10) {
|
|
213
|
+
// Group by itemId
|
|
214
|
+
const itemVersions = {};
|
|
215
|
+
for (const versionKey of Object.keys(processedVersions)) {
|
|
216
|
+
const [itemId] = versionKey.split('_');
|
|
217
|
+
if (!itemVersions[itemId]) {
|
|
218
|
+
itemVersions[itemId] = [];
|
|
219
|
+
}
|
|
220
|
+
itemVersions[itemId].push(versionKey);
|
|
221
|
+
}
|
|
222
|
+
// Keep only the most recent versions
|
|
223
|
+
const cleaned = {};
|
|
224
|
+
for (const itemId of Object.keys(itemVersions)) {
|
|
225
|
+
const versions = itemVersions[itemId];
|
|
226
|
+
// Keep the last N versions
|
|
227
|
+
const toKeep = versions.slice(-maxVersionsPerItem);
|
|
228
|
+
for (const versionKey of toKeep) {
|
|
229
|
+
cleaned[versionKey] = true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return cleaned;
|
|
233
|
+
}
|
|
234
|
+
exports.cleanupProcessedVersions = cleanupProcessedVersions;
|
|
235
|
+
/**
|
|
236
|
+
* Parse Graph API error response
|
|
237
|
+
*/
|
|
238
|
+
function parseGraphError(error) {
|
|
239
|
+
if (error === null || error === void 0 ? void 0 : error.error) {
|
|
240
|
+
return {
|
|
241
|
+
code: error.error.code || 'UnknownError',
|
|
242
|
+
message: error.error.message || 'An unknown error occurred',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
code: 'UnknownError',
|
|
247
|
+
message: (error === null || error === void 0 ? void 0 : error.message) || 'An unknown error occurred',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
exports.parseGraphError = parseGraphError;
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-onedrive-business",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "n8n custom node for OneDrive Business with robust deduplication and webhook triggers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n",
|
|
7
|
+
"n8n-nodes",
|
|
8
|
+
"onedrive",
|
|
9
|
+
"onedrive-business",
|
|
10
|
+
"sharepoint",
|
|
11
|
+
"microsoft-graph"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"homepage": "",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "CS1ANHNT",
|
|
17
|
+
"email": "anhntuyentuan713@gmail.com"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": ""
|
|
22
|
+
},
|
|
23
|
+
"main": "index.js",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc && npm run copy-icons",
|
|
26
|
+
"copy-icons": "copyfiles -u 1 \"nodes/**/*.{png,svg}\" dist/",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"format": "prettier nodes --write",
|
|
29
|
+
"lint": "tslint -p tsconfig.json -c tslint.json",
|
|
30
|
+
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"n8n": {
|
|
37
|
+
"n8nNodesApiVersion": 1,
|
|
38
|
+
"credentials": [
|
|
39
|
+
"dist/credentials/OneDriveBusinessOAuth2.credentials.js"
|
|
40
|
+
],
|
|
41
|
+
"nodes": [
|
|
42
|
+
"dist/nodes/OneDriveBusiness.node.js",
|
|
43
|
+
"dist/nodes/OneDriveBusinessTrigger.node.js"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^18.0.0",
|
|
48
|
+
"copyfiles": "^2.4.1",
|
|
49
|
+
"prettier": "^2.7.1",
|
|
50
|
+
"tslint": "^6.1.3",
|
|
51
|
+
"typescript": "^5.3.3"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"n8n-workflow": "^1.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|