@pipedream/dropbox 0.3.3 → 0.3.5

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,484 @@
1
+ import dropbox from "dropbox";
2
+ import fetch from "isomorphic-fetch";
3
+ import get from "lodash/get.js";
4
+ import config from "./config.mjs";
5
+ import isString from "lodash/isString.js";
6
+ import isEmpty from "lodash/isEmpty.js";
7
+
8
+ const Dropbox = dropbox.Dropbox;
9
+
10
+ export default {
11
+ type: "app",
12
+ app: "dropbox",
13
+ propDefinitions: {
14
+ pathFolder: {
15
+ type: "string",
16
+ label: "Path",
17
+ description: "The folder path. (Please use a valid path to filter the values)",
18
+ useQuery: true,
19
+ withLabel: true,
20
+ async options({
21
+ prevContext,
22
+ query,
23
+ returnSimpleString,
24
+ }) {
25
+ if (prevContext?.reachedLastPage) {
26
+ return [];
27
+ }
28
+ return this.getPathOptions(
29
+ query,
30
+ prevContext?.cursor,
31
+ {
32
+ omitFiles: true,
33
+ returnSimpleString,
34
+ },
35
+ );
36
+ },
37
+ },
38
+ pathFile: {
39
+ type: "string",
40
+ label: "Path",
41
+ description: "The file path. (Please use a valid path to filter the values)",
42
+ useQuery: true,
43
+ withLabel: true,
44
+ async options({
45
+ prevContext,
46
+ query,
47
+ returnSimpleString,
48
+ }) {
49
+ if (prevContext?.reachedLastPage) {
50
+ return [];
51
+ }
52
+ return this.getPathOptions(
53
+ query,
54
+ prevContext?.cursor,
55
+ {
56
+ omitFolders: true,
57
+ returnSimpleString,
58
+ },
59
+ );
60
+ },
61
+ },
62
+ pathFileFolder: {
63
+ type: "string",
64
+ label: "Path",
65
+ description: "The file or folder path. (Please use a valid path to filter the values)",
66
+ useQuery: true,
67
+ withLabel: true,
68
+ async options({
69
+ prevContext,
70
+ query,
71
+ returnSimpleString,
72
+ }) {
73
+ if (prevContext?.reachedLastPage) {
74
+ return [];
75
+ }
76
+ return this.getPathOptions(
77
+ query,
78
+ prevContext?.cursor,
79
+ {
80
+ returnSimpleString,
81
+ },
82
+ );
83
+ },
84
+ },
85
+ fileRevision: {
86
+ type: "string",
87
+ label: "Revision",
88
+ description: "The file revision",
89
+ withLabel: true,
90
+ async options({ path }) {
91
+ return this.getFileRevisionOptions(get(path, "value", path));
92
+ },
93
+ },
94
+ recursive: {
95
+ type: "boolean",
96
+ label: "Recursive",
97
+ description: "Also watch sub-directories and their contents.",
98
+ default: false,
99
+ },
100
+ query: {
101
+ type: "string",
102
+ label: "Query",
103
+ description: "The string to search for. May match across multiple fields based on the request arguments.",
104
+ },
105
+ limit: {
106
+ type: "integer",
107
+ label: "Limit",
108
+ description: "Specify a max amount of register to be fetched. Defaults to `100` if left blank.",
109
+ optional: true,
110
+ min: 1,
111
+ },
112
+ },
113
+ methods: {
114
+ async sdk() {
115
+ const baseClientOpts = {
116
+ accessToken: this.$auth.oauth_access_token,
117
+ fetch,
118
+ };
119
+
120
+ // In order to properly set the [root
121
+ // path](https://www.dropbox.com/developers/reference/path-root-header-modes)
122
+ // to use in every API request we first need to extract some information
123
+ // from the authenticated user's account, for which we need to create a
124
+ // client and issue an API request.
125
+ const dpx = new Dropbox(baseClientOpts);
126
+ const { result } = await dpx.usersGetCurrentAccount();
127
+
128
+ const pathRoot = JSON.stringify({
129
+ ".tag": "root",
130
+ "root": result.root_info.root_namespace_id,
131
+ });
132
+ return new Dropbox({
133
+ ...baseClientOpts,
134
+ pathRoot,
135
+ });
136
+ },
137
+ normalizeError(err) {
138
+ if (!err) {
139
+ throw new Error("Unknown error");
140
+ }
141
+
142
+ if (isString(err.error)) {
143
+ throw new Error(err.error);
144
+ }
145
+
146
+ if (err.error?.error_summary) {
147
+ throw new Error(err.error.error_summary);
148
+ }
149
+
150
+ throw new Error(JSON.stringify(err));
151
+ },
152
+ async getFileRevisionOptions(path) {
153
+ try {
154
+ const dpx = await this.sdk();
155
+ const revisions = await dpx.filesListRevisions({
156
+ path,
157
+ mode: {
158
+ ".tag": "id",
159
+ },
160
+ });
161
+ return revisions.result?.entries?.map((revision) => ({
162
+ label: `${revision.name} - ${revision.server_modified}`,
163
+ value: revision.rev,
164
+ }));
165
+ } catch (err) {
166
+ this.normalizeError(err);
167
+ }
168
+ },
169
+ async getPathOptions(path, prevCursor, opts = {}) {
170
+ try {
171
+ const {
172
+ omitFolders,
173
+ omitFiles,
174
+ } = opts;
175
+
176
+ const LIMIT = config.GET_PATH_OPTIONS.DEFAULT_MAX_RESULTS;
177
+
178
+ let data = [];
179
+ let res = null;
180
+ let nextCursor = null;
181
+ let lastPage = false;
182
+ path = path === "/" || path === null
183
+ ? ""
184
+ : path;
185
+ const dpx = await this.sdk();
186
+
187
+ if (prevCursor) {
188
+ res = await dpx.filesListFolderContinue({
189
+ cursor: prevCursor,
190
+ });
191
+ } else {
192
+ res = await dpx.filesListFolder({
193
+ path,
194
+ limit: LIMIT,
195
+ recursive: true,
196
+ });
197
+ }
198
+
199
+ data = res.result.entries.map((item) => ({
200
+ path: item.path_display,
201
+ type: item[".tag"],
202
+ }));
203
+
204
+ if (res.result.has_more) {
205
+ nextCursor = res.result.cursor;
206
+ } else {
207
+ lastPage = true;
208
+ }
209
+
210
+ if (omitFiles) {
211
+ data = data.filter((item) => item.type !== "file");
212
+ }
213
+
214
+ if (omitFolders) {
215
+ data = data.filter((item) => item.type !== "folder");
216
+ }
217
+
218
+ data = data.map((item) => (item.path));
219
+
220
+ data.sort((a, b) => {
221
+ return a > b ?
222
+ 1 :
223
+ -1;
224
+ });
225
+
226
+ return {
227
+ options: data,
228
+ context: {
229
+ cursor: nextCursor,
230
+ reachedLastPage: lastPage,
231
+ },
232
+ };
233
+ } catch (err) {
234
+ console.log(err);
235
+ return [];
236
+ }
237
+ },
238
+ async initState(context) {
239
+ const {
240
+ path,
241
+ recursive,
242
+ } = context;
243
+ try {
244
+ let fixedPath = path == "/" ?
245
+ "" :
246
+ path;
247
+ fixedPath = typeof (fixedPath) === "object" ?
248
+ fixedPath.value :
249
+ fixedPath;
250
+
251
+ const dpx = await this.sdk();
252
+ let response = await dpx.filesListFolderGetLatestCursor({
253
+ path: fixedPath,
254
+ recursive,
255
+ });
256
+ if (response.result) {
257
+ response = response.result;
258
+ }
259
+ const { cursor } = response;
260
+ const state = {
261
+ path: fixedPath,
262
+ recursive,
263
+ cursor,
264
+ };
265
+ return state;
266
+ } catch (err) {
267
+ console.log(err);
268
+ throw `Error connecting to Dropbox API to get latest cursor for folder: ${path}${recursive
269
+ ? " (recursive)"
270
+ : ""
271
+ }`;
272
+ }
273
+ },
274
+ async getState(context, state) {
275
+ const {
276
+ path,
277
+ recursive,
278
+ } = context;
279
+ const fixedPath = typeof (path) === "object" ?
280
+ path.value :
281
+ path;
282
+ if (state == null || state.path != fixedPath || state.recursive != recursive) {
283
+ state = await this.initState(context);
284
+ }
285
+ return state;
286
+ },
287
+ async getUpdates(context, dbState) {
288
+ let ret = [];
289
+ const state = await this.getState(context, dbState);
290
+ if (state) {
291
+ try {
292
+ let [
293
+ cursor,
294
+ hasMore,
295
+ entries,
296
+ ] =
297
+ [
298
+ state.cursor,
299
+ true,
300
+ null,
301
+ ];
302
+ while (hasMore) {
303
+ const dpx = await this.sdk();
304
+ let response = await dpx.filesListFolderContinue({
305
+ cursor,
306
+ });
307
+ if (response.result) {
308
+ response = response.result;
309
+ }
310
+ ({
311
+ entries,
312
+ cursor,
313
+ has_more: hasMore,
314
+ } = response);
315
+ ret = ret.concat(entries);
316
+ }
317
+ state.cursor = cursor;
318
+ } catch (err) {
319
+ console.log(err);
320
+ throw `Error connecting to Dropbox API to get list of updated files/folders for cursor: ${state.cursor}`;
321
+ }
322
+ }
323
+ return {
324
+ ret,
325
+ state,
326
+ };
327
+ },
328
+ async createFolder(args) {
329
+ try {
330
+ const dpx = await this.sdk();
331
+ return await dpx.filesCreateFolderV2(args);
332
+ } catch (err) {
333
+ this.normalizeError(err);
334
+ }
335
+ },
336
+ async listFileRevisions(args) {
337
+ try {
338
+ const dpx = await this.sdk();
339
+ return await dpx.filesListRevisions(args);
340
+ } catch (err) {
341
+ this.normalizeError(err);
342
+ }
343
+ },
344
+ async listFilesFolders(params, limit) {
345
+ try {
346
+ const dpx = await this.sdk();
347
+ let data = [];
348
+ let cursor = null;
349
+
350
+ const args = {
351
+ ...params,
352
+ limit: limit <= config.LIST_FILES_IN_FOLDER.DEFAULT_MAX_RESULTS
353
+ ? limit
354
+ : config.LIST_FILES_IN_FOLDER.DEFAULT_MAX_RESULTS,
355
+ };
356
+
357
+ let res = await dpx.filesListFolder(args);
358
+
359
+ if (!res.result?.has_more || limit <= config.LIST_FILES_IN_FOLDER.DEFAULT_MAX_RESULTS) {
360
+ return res.result?.entries;
361
+ }
362
+
363
+ data = res.result?.entries;
364
+ cursor = res.result?.cursor;
365
+ do {
366
+ const res = await dpx.filesListFolderContinue({
367
+ cursor,
368
+ });
369
+ data = data.concat(res.result?.entries);
370
+ cursor = res.result?.cursor;
371
+ if (!res.result?.has_more || data.length >= limit) {
372
+ break;
373
+ }
374
+ } while (true);
375
+ return data;
376
+ } catch (err) {
377
+ this.normalizeError(err);
378
+ }
379
+ },
380
+ async filesMove(args) {
381
+ try {
382
+ const dpx = await this.sdk();
383
+ return await dpx.filesMoveV2(args);
384
+ } catch (err) {
385
+ this.normalizeError(err);
386
+ }
387
+ },
388
+ async restoreFile(args) {
389
+ try {
390
+ const dpx = await this.sdk();
391
+ return await dpx.filesRestore(args);
392
+ } catch (err) {
393
+ this.normalizeError(err);
394
+ }
395
+ },
396
+ async createSharedLink(args) {
397
+ try {
398
+ const dpx = await this.sdk();
399
+ const links = await dpx.sharingListSharedLinks({
400
+ path: args.path,
401
+ });
402
+ if (links.result?.links.length > 0) {
403
+ return await dpx.sharingModifySharedLinkSettings({
404
+ ...args,
405
+ path: undefined,
406
+ url: links.result?.links[0].url,
407
+ remove_expiration: isEmpty(args.remove_expiration)
408
+ ? false
409
+ : args.remove_expiration,
410
+ });
411
+ } else {
412
+ return await dpx.sharingCreateSharedLinkWithSettings({
413
+ ...args,
414
+ remove_expiration: undefined,
415
+ });
416
+ }
417
+ } catch (err) {
418
+ this.normalizeError(err);
419
+ }
420
+ },
421
+ async deleteFileFolder(args) {
422
+ try {
423
+ const dpx = await this.sdk();
424
+ return await dpx.filesDeleteV2(args);
425
+ } catch (err) {
426
+ this.normalizeError(err);
427
+ }
428
+ },
429
+ async uploadFile(args) {
430
+ try {
431
+ const dpx = await this.sdk();
432
+ return await dpx.filesUpload(args);
433
+ } catch (err) {
434
+ this.normalizeError(err);
435
+ }
436
+ },
437
+ async downloadFile(args) {
438
+ try {
439
+ const dpx = await this.sdk();
440
+ return await dpx.filesDownload(args);
441
+ } catch (err) {
442
+ this.normalizeError(err);
443
+ }
444
+ },
445
+ async searchFilesFolders(params, limit) {
446
+ try {
447
+ const dpx = await this.sdk();
448
+ let data = [];
449
+ let cursor = null;
450
+
451
+ const args = {
452
+ ...params,
453
+ options: {
454
+ ...params.options,
455
+ max_results: limit <= config.SEARCH_FILE_FOLDERS.DEFAULT_MAX_RESULTS
456
+ ? limit
457
+ : config.SEARCH_FILE_FOLDERS.DEFAULT_MAX_RESULTS,
458
+ },
459
+ };
460
+ let res = await dpx.filesSearchV2(args);
461
+
462
+ if (!res.result?.has_more || limit <= config.SEARCH_FILE_FOLDERS.DEFAULT_MAX_RESULTS) {
463
+ return res.result?.matches;
464
+ }
465
+
466
+ data = res.result?.matches;
467
+ cursor = res.result?.cursor;
468
+ do {
469
+ const res = await dpx.filesSearchContinueV2({
470
+ cursor,
471
+ });
472
+ data = data.concat(res.result?.matches);
473
+ cursor = res.result?.cursor;
474
+ if (!res.result?.has_more || data.length >= limit) {
475
+ break;
476
+ }
477
+ } while (true);
478
+ return data;
479
+ } catch (err) {
480
+ this.normalizeError(err);
481
+ }
482
+ },
483
+ },
484
+ };
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
- "name": "@pipedream/dropbox",
3
- "version": "0.3.3",
4
- "description": "Pipedream Dropbox Components",
5
- "main": "dropbox.app.js",
6
- "keywords": [
7
- "pipedream",
8
- "dropbox"
9
- ],
10
- "homepage": "https://pipedream.com/apps/dropbox",
11
- "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
12
- "license": "MIT",
13
- "dependencies": {
14
- "dropbox": "^8.2.0",
15
- "isomorphic-fetch": "^3.0.0"
16
- },
17
- "gitHead": "12ad36100ee95629c1b22923c79923bfff156ca7",
18
- "publishConfig": {
19
- "access": "public"
20
- }
21
- }
2
+ "name": "@pipedream/dropbox",
3
+ "version": "0.3.5",
4
+ "description": "Pipedream Dropbox Components",
5
+ "main": "dropbox.app.js",
6
+ "keywords": [
7
+ "pipedream",
8
+ "dropbox"
9
+ ],
10
+ "homepage": "https://pipedream.com/apps/dropbox",
11
+ "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "dropbox": "^8.2.0",
15
+ "isomorphic-fetch": "^3.0.0"
16
+ },
17
+ "gitHead": "e12480b94cc03bed4808ebc6b13e7fdb3a1ba535",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ }
21
+ }
@@ -0,0 +1,59 @@
1
+ import common from "../common.mjs";
2
+
3
+ export default {
4
+ ...common,
5
+ dedupe: "unique",
6
+ type: "source",
7
+ key: "dropbox-all-updates",
8
+ name: "New or Modified File or Folder",
9
+ version: "0.0.8",
10
+ description: "Emit new event when a file or folder is added or modified. Make sure the number of files/folders in the watched folder does not exceed 4000.",
11
+ props: {
12
+ ...common.props,
13
+ includeMediaInfo: {
14
+ label: "Include Media Info",
15
+ type: "boolean",
16
+ description: "Emit media info for photo and video files (incurs an additional API call)",
17
+ default: false,
18
+ },
19
+ includeLink: {
20
+ label: "Include Link",
21
+ type: "boolean",
22
+ description: "Emit temporary download link for files (incurs an additional API call)",
23
+ default: false,
24
+ },
25
+ },
26
+ hooks: {
27
+ async activate() {
28
+ await this.getHistoricalEvents([
29
+ "file",
30
+ "folder",
31
+ ]);
32
+ const state = await this.dropbox.initState(this);
33
+ this._setDropboxState(state);
34
+ },
35
+ },
36
+ async run() {
37
+ const state = this._getDropboxState();
38
+ const {
39
+ ret: updates, state: newState,
40
+ } = await this.dropbox.getUpdates(this, state);
41
+ this._setDropboxState(newState);
42
+ for (let update of updates) {
43
+ let file = {
44
+ ...update,
45
+ };
46
+ if (this.includeMediaInfo) {
47
+ file = await this.getMediaInfo(update);
48
+ }
49
+ if (this.includeLink) {
50
+ file.link = await this.getTemporaryLink(update);
51
+ }
52
+ // new unique identification from merging the file id and the last file revision
53
+ const id = update[".tag"] === "file"
54
+ ? `${file.id}-${file.rev}`
55
+ : `${file.id}-${newState.cursor}`;
56
+ this.$emit(file, this.getMeta(id, file.path_display || file.id));
57
+ }
58
+ },
59
+ };
@@ -0,0 +1,120 @@
1
+ import dropbox from "../dropbox.app.mjs";
2
+
3
+ export default {
4
+ props: {
5
+ dropbox,
6
+ db: "$.service.db",
7
+ path: {
8
+ propDefinition: [
9
+ dropbox,
10
+ "pathFolder",
11
+ ],
12
+ },
13
+ recursive: {
14
+ propDefinition: [
15
+ dropbox,
16
+ "recursive",
17
+ ],
18
+ },
19
+ dropboxApphook: {
20
+ type: "$.interface.apphook",
21
+ appProp: "dropbox",
22
+ static: [],
23
+ },
24
+ },
25
+ methods: {
26
+ _getDropboxState() {
27
+ return this.db.get("dropbox_state");
28
+ },
29
+ _setDropboxState(state) {
30
+ this.db.set("dropbox_state", state);
31
+ },
32
+ async getHistoricalEvents(fileTypes = []) {
33
+ const files = await this.dropbox.listFilesFolders({
34
+ path: this.path?.value || this.path,
35
+ recursive: this.recursive,
36
+ include_media_info: this.includeMediaInfo,
37
+ });
38
+ let count = 0;
39
+ for (const file of files) {
40
+ if (!fileTypes.includes(file[".tag"])) {
41
+ continue;
42
+ }
43
+ this.$emit(file, this.getMeta(file.id, file.path_display || file.id));
44
+ count++;
45
+ if (count >= 25) {
46
+ break;
47
+ }
48
+ }
49
+ },
50
+ getMeta(id, summary) {
51
+ return {
52
+ id,
53
+ summary,
54
+ ts: Date.now(),
55
+ };
56
+ },
57
+ async isNewFile(update, lastFileModTime) {
58
+ try {
59
+ const dpx = await this.dropbox.sdk();
60
+ let revisions = await dpx.filesListRevisions({
61
+ path: update.id,
62
+ mode: {
63
+ ".tag": "id",
64
+ },
65
+ limit: 10,
66
+ });
67
+ if (revisions.result) {
68
+ revisions = revisions.result;
69
+ }
70
+ if (revisions.entries.length > 1) {
71
+ const oldest = revisions.entries.pop();
72
+ if (lastFileModTime && lastFileModTime >= oldest.client_modified) {
73
+ return false;
74
+ }
75
+ }
76
+ return true;
77
+ } catch (err) {
78
+ console.log(err);
79
+ this.dropbox.normalizeError(err);
80
+ }
81
+ },
82
+ async getMediaInfo(update) {
83
+ try {
84
+ const dpx = await this.dropbox.sdk();
85
+ const info = await dpx.filesGetMetadata({
86
+ path: update.path_lower,
87
+ include_media_info: true,
88
+ });
89
+ if (info.result) {
90
+ return info.result;
91
+ }
92
+ return update;
93
+ } catch (err) {
94
+ console.log(err);
95
+ this.dropbox.normalizeError(err);
96
+ }
97
+ },
98
+ async getTemporaryLink(update) {
99
+ try {
100
+ const dpx = await this.dropbox.sdk();
101
+ let response = await dpx.filesGetTemporaryLink({
102
+ path: update.path_lower,
103
+ });
104
+ if (response.result) {
105
+ response = response.result;
106
+ }
107
+ const { link } = response;
108
+ return link;
109
+ } catch (err) {
110
+ // returning null because not all files supports download links,
111
+ // but the source should still emit update events to those files
112
+ if (err?.error?.error_summary?.startsWith("unsupported_file")) {
113
+ return null;
114
+ }
115
+ console.log(err);
116
+ this.dropbox.normalizeError(err);
117
+ }
118
+ },
119
+ },
120
+ };