@pipedream/sharepoint 0.7.1 → 0.8.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/actions/create-folder/create-folder.mjs +1 -1
- package/actions/create-item/create-item.mjs +1 -1
- package/actions/create-link/create-link.mjs +1 -1
- package/actions/create-list/create-list.mjs +1 -1
- package/actions/download-file/download-file.mjs +93 -7
- package/actions/download-files/download-files.mjs +88 -0
- package/actions/find-file-by-name/find-file-by-name.mjs +1 -1
- package/actions/find-files-with-metadata/find-files-with-metadata.mjs +1 -1
- package/actions/get-excel-table/get-excel-table.mjs +1 -1
- package/actions/get-file-by-id/get-file-by-id.mjs +1 -1
- package/actions/get-site/get-site.mjs +1 -1
- package/actions/list-files-in-folder/list-files-in-folder.mjs +1 -1
- package/actions/list-sites/list-sites.mjs +1 -1
- package/actions/retrieve-file-metadata/retrieve-file-metadata.mjs +55 -0
- package/actions/search-and-filter-files/search-and-filter-files.mjs +1 -1
- package/actions/search-files/search-files.mjs +1 -1
- package/actions/search-sites/search-sites.mjs +1 -1
- package/actions/update-item/update-item.mjs +1 -1
- package/actions/upload-file/upload-file.mjs +1 -1
- package/common/constants.mjs +70 -0
- package/common/file-picker-base.mjs +308 -0
- package/common/utils.mjs +78 -0
- package/package.json +5 -2
- package/sharepoint.app.mjs +400 -3
- package/sources/new-file-created/new-file-created.mjs +1 -1
- package/sources/new-folder-created/new-folder-created.mjs +1 -1
- package/sources/new-list-item/new-list-item.mjs +1 -1
- package/sources/updated-file-instant/updated-file-instant.mjs +361 -0
- package/sources/updated-list-item/updated-list-item.mjs +1 -1
- package/actions/select-files/select-files.mjs +0 -198
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import sharepoint from "../../sharepoint.app.mjs";
|
|
3
|
+
import { WEBHOOK_SUBSCRIPTION_RENEWAL_SECONDS } from "../../common/constants.mjs";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
key: "sharepoint-updated-file-instant",
|
|
7
|
+
name: "New File Updated (Instant)",
|
|
8
|
+
description: "Emit new event when specific files are updated in a SharePoint document library",
|
|
9
|
+
version: "0.0.1",
|
|
10
|
+
type: "source",
|
|
11
|
+
dedupe: "unique",
|
|
12
|
+
props: {
|
|
13
|
+
sharepoint,
|
|
14
|
+
db: "$.service.db",
|
|
15
|
+
http: {
|
|
16
|
+
type: "$.interface.http",
|
|
17
|
+
customResponse: true,
|
|
18
|
+
},
|
|
19
|
+
timer: {
|
|
20
|
+
label: "Subscription renewal schedule",
|
|
21
|
+
description:
|
|
22
|
+
"Microsoft Graph subscriptions expire after 30 days. " +
|
|
23
|
+
"This timer automatically renews the subscription. " +
|
|
24
|
+
"**You should not need to modify this schedule.**",
|
|
25
|
+
type: "$.interface.timer",
|
|
26
|
+
static: {
|
|
27
|
+
intervalSeconds: WEBHOOK_SUBSCRIPTION_RENEWAL_SECONDS,
|
|
28
|
+
},
|
|
29
|
+
hidden: true,
|
|
30
|
+
},
|
|
31
|
+
siteId: {
|
|
32
|
+
propDefinition: [
|
|
33
|
+
sharepoint,
|
|
34
|
+
"siteId",
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
driveId: {
|
|
38
|
+
propDefinition: [
|
|
39
|
+
sharepoint,
|
|
40
|
+
"driveId",
|
|
41
|
+
(c) => ({
|
|
42
|
+
siteId: c.siteId,
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
folderId: {
|
|
47
|
+
propDefinition: [
|
|
48
|
+
sharepoint,
|
|
49
|
+
"folderId",
|
|
50
|
+
(c) => ({
|
|
51
|
+
siteId: c.siteId,
|
|
52
|
+
driveId: c.driveId,
|
|
53
|
+
}),
|
|
54
|
+
],
|
|
55
|
+
description: "Optional: Select a folder to browse files from. Leave empty to browse from the drive root. " +
|
|
56
|
+
"This helps you navigate to the files you want to monitor.",
|
|
57
|
+
},
|
|
58
|
+
fileIds: {
|
|
59
|
+
propDefinition: [
|
|
60
|
+
sharepoint,
|
|
61
|
+
"fileIds",
|
|
62
|
+
(c) => ({
|
|
63
|
+
siteId: c.siteId,
|
|
64
|
+
driveId: c.driveId,
|
|
65
|
+
folderId: c.folderId,
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
label: "Files to Monitor",
|
|
69
|
+
description:
|
|
70
|
+
"Select one or more files to monitor for updates. " +
|
|
71
|
+
"You'll receive a real-time event whenever any of these files are modified.\n\n" +
|
|
72
|
+
"**Important:** Only the selected files will trigger events. Changes to other files in the drive will be ignored. " +
|
|
73
|
+
"This ensures you only receive notifications for the documents you care about.",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
hooks: {
|
|
77
|
+
async activate() {
|
|
78
|
+
// Generate a unique client state for validating incoming webhooks
|
|
79
|
+
const clientState = randomUUID();
|
|
80
|
+
|
|
81
|
+
// Resolve wrapped prop values
|
|
82
|
+
const driveId = this.sharepoint.resolveWrappedValue(this.driveId);
|
|
83
|
+
|
|
84
|
+
// Create subscription on the drive root
|
|
85
|
+
// We'll filter to specific files when notifications arrive
|
|
86
|
+
const subscription = await this.sharepoint.createSubscription({
|
|
87
|
+
resource: `drives/${driveId}/root`,
|
|
88
|
+
notificationUrl: this.http.endpoint,
|
|
89
|
+
changeType: "updated",
|
|
90
|
+
clientState,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
console.log(
|
|
94
|
+
`Created subscription ${subscription.id}, expires: ${subscription.expirationDateTime}`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Store subscription metadata
|
|
98
|
+
this._setSubscription({
|
|
99
|
+
id: subscription.id,
|
|
100
|
+
expirationDateTime: subscription.expirationDateTime,
|
|
101
|
+
clientState,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Store the file IDs we're monitoring (unwrap labeled values)
|
|
105
|
+
const fileIds = this.sharepoint.resolveWrappedArrayValues(this.fileIds);
|
|
106
|
+
this._setMonitoredFileIds(fileIds);
|
|
107
|
+
|
|
108
|
+
// Initialize delta tracking - get current state so we only see future changes
|
|
109
|
+
const deltaResponse = await this.sharepoint.getDriveDelta({
|
|
110
|
+
driveId,
|
|
111
|
+
});
|
|
112
|
+
// Follow pagination to get the final deltaLink
|
|
113
|
+
let nextLink = deltaResponse["@odata.nextLink"];
|
|
114
|
+
let deltaLink = deltaResponse["@odata.deltaLink"];
|
|
115
|
+
while (nextLink && !deltaLink) {
|
|
116
|
+
const nextResponse = await this.sharepoint.getDriveDelta({
|
|
117
|
+
driveId,
|
|
118
|
+
deltaLink: nextLink,
|
|
119
|
+
});
|
|
120
|
+
nextLink = nextResponse["@odata.nextLink"];
|
|
121
|
+
deltaLink = nextResponse["@odata.deltaLink"];
|
|
122
|
+
}
|
|
123
|
+
this._setDeltaLink(deltaLink);
|
|
124
|
+
console.log("Initialized delta tracking");
|
|
125
|
+
},
|
|
126
|
+
async deactivate() {
|
|
127
|
+
const subscription = this._getSubscription();
|
|
128
|
+
if (subscription?.id) {
|
|
129
|
+
try {
|
|
130
|
+
await this.sharepoint.deleteSubscription({
|
|
131
|
+
subscriptionId: subscription.id,
|
|
132
|
+
});
|
|
133
|
+
console.log(`Deleted subscription ${subscription.id}`);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.log(`Error deleting subscription: ${err.message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Clear stored state
|
|
140
|
+
this._setSubscription(null);
|
|
141
|
+
this._setMonitoredFileIds(null);
|
|
142
|
+
this._setDeltaLink(null);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
methods: {
|
|
146
|
+
_getSubscription() {
|
|
147
|
+
return this.db.get("subscription");
|
|
148
|
+
},
|
|
149
|
+
_setSubscription(subscription) {
|
|
150
|
+
this.db.set("subscription", subscription);
|
|
151
|
+
},
|
|
152
|
+
_getMonitoredFileIds() {
|
|
153
|
+
return this.db.get("monitoredFileIds") || [];
|
|
154
|
+
},
|
|
155
|
+
_setMonitoredFileIds(fileIds) {
|
|
156
|
+
this.db.set("monitoredFileIds", fileIds);
|
|
157
|
+
},
|
|
158
|
+
_getDeltaLink() {
|
|
159
|
+
return this.db.get("deltaLink");
|
|
160
|
+
},
|
|
161
|
+
_setDeltaLink(deltaLink) {
|
|
162
|
+
this.db.set("deltaLink", deltaLink);
|
|
163
|
+
},
|
|
164
|
+
async renewSubscription() {
|
|
165
|
+
const subscription = this._getSubscription();
|
|
166
|
+
if (!subscription?.id) {
|
|
167
|
+
console.log("No subscription to renew");
|
|
168
|
+
return {
|
|
169
|
+
success: true,
|
|
170
|
+
skipped: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const updated = await this.sharepoint.updateSubscription({
|
|
176
|
+
subscriptionId: subscription.id,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
this._setSubscription({
|
|
180
|
+
...subscription,
|
|
181
|
+
expirationDateTime: updated.expirationDateTime,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
console.log(
|
|
185
|
+
`Renewed subscription ${subscription.id}, expires: ${updated.expirationDateTime}`,
|
|
186
|
+
);
|
|
187
|
+
return {
|
|
188
|
+
success: true,
|
|
189
|
+
};
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const status = error.response?.status;
|
|
192
|
+
|
|
193
|
+
// Subscription not found - needs recreation
|
|
194
|
+
if (status === 404) {
|
|
195
|
+
console.log("Subscription not found, will recreate...");
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
shouldRecreate: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Auth errors - don't retry
|
|
203
|
+
if ([
|
|
204
|
+
401,
|
|
205
|
+
403,
|
|
206
|
+
].includes(status)) {
|
|
207
|
+
console.error(`Auth error renewing subscription: ${error.message}`);
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
shouldRecreate: false,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Other errors - log but don't recreate (will retry on next timer tick)
|
|
215
|
+
console.error(`Error renewing subscription: ${error.message}`);
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
shouldRecreate: false,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
generateMeta(file) {
|
|
223
|
+
const ts = Date.parse(file.lastModifiedDateTime);
|
|
224
|
+
return {
|
|
225
|
+
id: `${file.id}-${ts}`,
|
|
226
|
+
summary: `File updated: ${file.name}`,
|
|
227
|
+
ts,
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
async run(event) {
|
|
232
|
+
// Handle subscription validation request from Microsoft
|
|
233
|
+
// https://learn.microsoft.com/en-us/graph/change-notifications-delivery-webhooks#notificationurl-validation
|
|
234
|
+
if (event.query?.validationToken) {
|
|
235
|
+
console.log("Responding to validation request");
|
|
236
|
+
this.http.respond({
|
|
237
|
+
status: 200,
|
|
238
|
+
headers: {
|
|
239
|
+
"Content-Type": "text/plain",
|
|
240
|
+
},
|
|
241
|
+
body: event.query.validationToken,
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Handle timer event - renew subscription
|
|
247
|
+
if (event.timestamp) {
|
|
248
|
+
const result = await this.renewSubscription();
|
|
249
|
+
|
|
250
|
+
if (!result.success && result.shouldRecreate) {
|
|
251
|
+
console.log("Recreating subscription...");
|
|
252
|
+
await this.hooks.activate.call(this);
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Handle webhook notification
|
|
258
|
+
const { body } = event;
|
|
259
|
+
|
|
260
|
+
if (!body?.value?.length) {
|
|
261
|
+
console.log("No notifications in webhook payload");
|
|
262
|
+
this.http.respond({
|
|
263
|
+
status: 202,
|
|
264
|
+
body: "",
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Filter to only valid notifications for this subscription
|
|
270
|
+
const subscription = this._getSubscription();
|
|
271
|
+
const clientState = subscription?.clientState;
|
|
272
|
+
|
|
273
|
+
const validNotifications = body.value.filter((notification) => {
|
|
274
|
+
if (notification.clientState !== clientState) {
|
|
275
|
+
console.warn(
|
|
276
|
+
`Ignoring notification with unexpected clientState: ${notification.clientState}`,
|
|
277
|
+
);
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (validNotifications.length === 0) {
|
|
284
|
+
console.log("No valid notifications after clientState filtering");
|
|
285
|
+
this.http.respond({
|
|
286
|
+
status: 202,
|
|
287
|
+
body: "",
|
|
288
|
+
});
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Acknowledge receipt after validation
|
|
293
|
+
this.http.respond({
|
|
294
|
+
status: 202,
|
|
295
|
+
body: "",
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Use delta API to find what actually changed
|
|
299
|
+
const driveId = this.sharepoint.resolveWrappedValue(this.driveId);
|
|
300
|
+
const monitoredFileIds = this._getMonitoredFileIds();
|
|
301
|
+
let deltaLink = this._getDeltaLink();
|
|
302
|
+
|
|
303
|
+
console.log("Monitored file IDs:", JSON.stringify(monitoredFileIds));
|
|
304
|
+
console.log("Fetching delta changes...");
|
|
305
|
+
|
|
306
|
+
const changedFiles = [];
|
|
307
|
+
let hasMore = true;
|
|
308
|
+
|
|
309
|
+
while (hasMore) {
|
|
310
|
+
const deltaResponse = await this.sharepoint.getDriveDelta({
|
|
311
|
+
driveId,
|
|
312
|
+
deltaLink,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Find files that changed and are in our monitored list
|
|
316
|
+
for (const item of deltaResponse.value || []) {
|
|
317
|
+
if (item.file && monitoredFileIds.includes(item.id)) {
|
|
318
|
+
changedFiles.push(item);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Update for next iteration or final storage
|
|
323
|
+
if (deltaResponse["@odata.nextLink"]) {
|
|
324
|
+
deltaLink = deltaResponse["@odata.nextLink"];
|
|
325
|
+
} else {
|
|
326
|
+
deltaLink = deltaResponse["@odata.deltaLink"];
|
|
327
|
+
hasMore = false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Store the new deltaLink for next time
|
|
332
|
+
this._setDeltaLink(deltaLink);
|
|
333
|
+
|
|
334
|
+
console.log(`Found ${changedFiles.length} changed monitored files`);
|
|
335
|
+
|
|
336
|
+
// Emit events for each changed file
|
|
337
|
+
for (const file of changedFiles) {
|
|
338
|
+
// Delta response may not include downloadUrl - fetch fresh if needed
|
|
339
|
+
let downloadUrl = file["@microsoft.graph.downloadUrl"];
|
|
340
|
+
if (!downloadUrl) {
|
|
341
|
+
try {
|
|
342
|
+
const freshFile = await this.sharepoint.getDriveItem({
|
|
343
|
+
driveId,
|
|
344
|
+
fileId: file.id,
|
|
345
|
+
});
|
|
346
|
+
downloadUrl = freshFile["@microsoft.graph.downloadUrl"];
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.log(`Could not fetch download URL for ${file.name}: ${err.message}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
this.$emit(
|
|
353
|
+
{
|
|
354
|
+
file,
|
|
355
|
+
downloadUrl,
|
|
356
|
+
},
|
|
357
|
+
this.generateMeta(file),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
};
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import sharepoint from "../../sharepoint.app.mjs";
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
key: "sharepoint-select-files",
|
|
5
|
-
name: "Select Files",
|
|
6
|
-
description: "A file picker action that allows browsing and selecting one or more files from SharePoint. Returns the selected files' metadata including pre-authenticated download URLs. [See the documentation](https://learn.microsoft.com/en-us/graph/api/driveitem-get)",
|
|
7
|
-
version: "0.0.3",
|
|
8
|
-
type: "action",
|
|
9
|
-
annotations: {
|
|
10
|
-
destructiveHint: false,
|
|
11
|
-
openWorldHint: true,
|
|
12
|
-
readOnlyHint: true,
|
|
13
|
-
},
|
|
14
|
-
props: {
|
|
15
|
-
sharepoint,
|
|
16
|
-
siteId: {
|
|
17
|
-
propDefinition: [
|
|
18
|
-
sharepoint,
|
|
19
|
-
"siteId",
|
|
20
|
-
],
|
|
21
|
-
withLabel: true,
|
|
22
|
-
},
|
|
23
|
-
driveId: {
|
|
24
|
-
propDefinition: [
|
|
25
|
-
sharepoint,
|
|
26
|
-
"driveId",
|
|
27
|
-
(c) => ({
|
|
28
|
-
siteId: c.siteId?.value || c.siteId,
|
|
29
|
-
}),
|
|
30
|
-
],
|
|
31
|
-
withLabel: true,
|
|
32
|
-
},
|
|
33
|
-
folderId: {
|
|
34
|
-
propDefinition: [
|
|
35
|
-
sharepoint,
|
|
36
|
-
"folderId",
|
|
37
|
-
(c) => ({
|
|
38
|
-
siteId: c.siteId?.value || c.siteId,
|
|
39
|
-
driveId: c.driveId?.value || c.driveId,
|
|
40
|
-
}),
|
|
41
|
-
],
|
|
42
|
-
label: "Folder",
|
|
43
|
-
description: "The folder to browse. Leave empty to browse the root of the drive.",
|
|
44
|
-
optional: true,
|
|
45
|
-
withLabel: true,
|
|
46
|
-
},
|
|
47
|
-
fileOrFolderIds: {
|
|
48
|
-
propDefinition: [
|
|
49
|
-
sharepoint,
|
|
50
|
-
"fileOrFolderId",
|
|
51
|
-
(c) => ({
|
|
52
|
-
siteId: c.siteId?.value || c.siteId,
|
|
53
|
-
driveId: c.driveId?.value || c.driveId,
|
|
54
|
-
folderId: c.folderId?.value || c.folderId,
|
|
55
|
-
}),
|
|
56
|
-
],
|
|
57
|
-
type: "string[]",
|
|
58
|
-
label: "Files or Folders",
|
|
59
|
-
description: "Select one or more files, or select a folder and click 'Refresh Fields' to browse into it",
|
|
60
|
-
withLabel: true,
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
methods: {
|
|
64
|
-
resolveValue(prop) {
|
|
65
|
-
if (!prop) return null;
|
|
66
|
-
if (typeof prop === "object" && prop?.value) {
|
|
67
|
-
return prop.value;
|
|
68
|
-
}
|
|
69
|
-
return prop;
|
|
70
|
-
},
|
|
71
|
-
parseFileOrFolder(value) {
|
|
72
|
-
if (!value) return null;
|
|
73
|
-
const resolved = this.resolveValue(value);
|
|
74
|
-
try {
|
|
75
|
-
return JSON.parse(resolved);
|
|
76
|
-
} catch {
|
|
77
|
-
return {
|
|
78
|
-
id: resolved,
|
|
79
|
-
isFolder: false,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
parseFileOrFolderList(values) {
|
|
84
|
-
if (!values) return [];
|
|
85
|
-
const list = Array.isArray(values)
|
|
86
|
-
? values
|
|
87
|
-
: [
|
|
88
|
-
values,
|
|
89
|
-
];
|
|
90
|
-
return list.map((v) => this.parseFileOrFolder(v)).filter(Boolean);
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
async run({ $ }) {
|
|
94
|
-
const selections = this.parseFileOrFolderList(this.fileOrFolderIds);
|
|
95
|
-
|
|
96
|
-
if (selections.length === 0) {
|
|
97
|
-
throw new Error("Please select at least one file or folder");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const siteId = this.resolveValue(this.siteId);
|
|
101
|
-
const driveId = this.resolveValue(this.driveId);
|
|
102
|
-
|
|
103
|
-
// Separate files and folders
|
|
104
|
-
const folders = selections.filter((s) => s.isFolder);
|
|
105
|
-
const files = selections.filter((s) => !s.isFolder);
|
|
106
|
-
|
|
107
|
-
// If only folders selected, return folder info
|
|
108
|
-
if (files.length === 0 && folders.length > 0) {
|
|
109
|
-
const folderNames = folders.map((f) => f.name).join(", ");
|
|
110
|
-
$.export("$summary", `Selected ${folders.length} folder(s): ${folderNames}. Set one as the Folder ID and refresh to browse its contents.`);
|
|
111
|
-
return {
|
|
112
|
-
type: "folders",
|
|
113
|
-
folders: folders.map((f) => ({
|
|
114
|
-
id: f.id,
|
|
115
|
-
name: f.name,
|
|
116
|
-
})),
|
|
117
|
-
message: "To browse a folder, set it as the folderId and reload props",
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Fetch metadata for all selected files in parallel, handling individual failures
|
|
122
|
-
const settledResults = await Promise.allSettled(
|
|
123
|
-
files.map(async (selected) => {
|
|
124
|
-
const file = await this.sharepoint.getDriveItem({
|
|
125
|
-
$,
|
|
126
|
-
siteId,
|
|
127
|
-
driveId,
|
|
128
|
-
fileId: selected.id,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const downloadUrl = file["@microsoft.graph.downloadUrl"];
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
...file,
|
|
135
|
-
downloadUrl,
|
|
136
|
-
_meta: {
|
|
137
|
-
siteId,
|
|
138
|
-
driveId,
|
|
139
|
-
fileId: selected.id,
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}),
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
// Separate successful and failed results
|
|
146
|
-
const fileResults = [];
|
|
147
|
-
const errors = [];
|
|
148
|
-
|
|
149
|
-
settledResults.forEach((result, index) => {
|
|
150
|
-
if (result.status === "fulfilled") {
|
|
151
|
-
fileResults.push(result.value);
|
|
152
|
-
} else {
|
|
153
|
-
const selected = files[index];
|
|
154
|
-
const errorMessage = result.reason?.message || String(result.reason);
|
|
155
|
-
console.error(`Failed to fetch file ${selected.id} (${selected.name}): ${errorMessage}`);
|
|
156
|
-
errors.push({
|
|
157
|
-
fileId: selected.id,
|
|
158
|
-
fileName: selected.name,
|
|
159
|
-
error: errorMessage,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// If all files failed, throw an error
|
|
165
|
-
if (fileResults.length === 0 && errors.length > 0) {
|
|
166
|
-
throw new Error(`Failed to fetch all selected files: ${errors.map((e) => e.fileName).join(", ")}`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// If single file, return it directly for backwards compatibility
|
|
170
|
-
if (fileResults.length === 1 && folders.length === 0 && errors.length === 0) {
|
|
171
|
-
$.export("$summary", `Selected file: ${fileResults[0].name}`);
|
|
172
|
-
return fileResults[0];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Multiple files: return as array
|
|
176
|
-
const fileNames = fileResults.map((f) => f.name).join(", ");
|
|
177
|
-
const summaryParts = [
|
|
178
|
-
`Selected ${fileResults.length} file(s): ${fileNames}`,
|
|
179
|
-
];
|
|
180
|
-
if (errors.length > 0) {
|
|
181
|
-
summaryParts.push(`Failed to fetch ${errors.length} file(s): ${errors.map((e) => e.fileName).join(", ")}`);
|
|
182
|
-
}
|
|
183
|
-
$.export("$summary", summaryParts.join(". "));
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
files: fileResults,
|
|
187
|
-
...(errors.length > 0 && {
|
|
188
|
-
errors,
|
|
189
|
-
}),
|
|
190
|
-
...(folders.length > 0 && {
|
|
191
|
-
folders: folders.map((f) => ({
|
|
192
|
-
id: f.id,
|
|
193
|
-
name: f.name,
|
|
194
|
-
})),
|
|
195
|
-
}),
|
|
196
|
-
};
|
|
197
|
-
},
|
|
198
|
-
};
|