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.
@@ -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
+ }