@adobe/helix-google-support 1.0.0 → 1.0.1
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/CHANGELOG.md +7 -0
- package/package.json +3 -2
- package/src/GoogleClient.js +136 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.0.1](https://github.com/adobe/helix-google-support/compare/v1.0.0...v1.0.1) (2022-04-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* minor update ([#6](https://github.com/adobe/helix-google-support/issues/6)) ([8d7cd50](https://github.com/adobe/helix-google-support/commit/8d7cd50c8162da12c4d4430833416fac0f2eda93))
|
|
7
|
+
|
|
1
8
|
# 1.0.0 (2022-04-13)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-google-support",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Helix Google Support",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -47,9 +47,10 @@
|
|
|
47
47
|
"eslint-plugin-import": "2.26.0",
|
|
48
48
|
"husky": "7.0.4",
|
|
49
49
|
"junit-report-builder": "3.0.0",
|
|
50
|
-
"lint-staged": "12.3.
|
|
50
|
+
"lint-staged": "12.3.8",
|
|
51
51
|
"mocha": "9.2.2",
|
|
52
52
|
"mocha-multi-reporters": "1.5.1",
|
|
53
|
+
"nock": "13.2.4",
|
|
53
54
|
"semantic-release": "19.0.2"
|
|
54
55
|
},
|
|
55
56
|
"lint-staged": {
|
package/src/GoogleClient.js
CHANGED
|
@@ -10,14 +10,49 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
import { google } from 'googleapis';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
editDistance, sanitizeName, splitByExtension,
|
|
15
|
+
} from '@adobe/helix-onedrive-support/utils';
|
|
16
|
+
import { StatusCodeError } from '@adobe/helix-onedrive-support';
|
|
14
17
|
import { GoogleTokenCache } from './GoogleTokenCache.js';
|
|
15
18
|
import cache from './cache.js';
|
|
16
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @typedef DriveItemInfo {
|
|
22
|
+
* @property {string} id
|
|
23
|
+
* @property {string} name
|
|
24
|
+
* @property {string} url
|
|
25
|
+
* @property {string} path
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Adds the last modified property if defined in item
|
|
30
|
+
* @param {DriveItemInfo} itemInfo
|
|
31
|
+
* @param item
|
|
32
|
+
* @returns {DriveItemInfo}
|
|
33
|
+
*/
|
|
34
|
+
function addLastModified(itemInfo, item) {
|
|
35
|
+
if (item.modifiedTime) {
|
|
36
|
+
// eslint-disable-next-line no-param-reassign
|
|
37
|
+
itemInfo.lastModified = Date.parse(item.modifiedTime);
|
|
38
|
+
}
|
|
39
|
+
return itemInfo;
|
|
40
|
+
}
|
|
41
|
+
|
|
17
42
|
/**
|
|
18
43
|
* Google auth client
|
|
19
44
|
*/
|
|
20
45
|
export class GoogleClient {
|
|
46
|
+
/**
|
|
47
|
+
* Sets the global item cache options. It internally creates a new LRU
|
|
48
|
+
* with the given options
|
|
49
|
+
*
|
|
50
|
+
* @param opts
|
|
51
|
+
*/
|
|
52
|
+
static setItemCacheOptions(opts) {
|
|
53
|
+
cache.options(opts);
|
|
54
|
+
}
|
|
55
|
+
|
|
21
56
|
/**
|
|
22
57
|
* Returns a url for a google drive id.
|
|
23
58
|
* @param {string} id
|
|
@@ -35,22 +70,26 @@ export class GoogleClient {
|
|
|
35
70
|
* @param {ICachePlugin} plugin
|
|
36
71
|
*/
|
|
37
72
|
constructor(opts) {
|
|
38
|
-
this
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
73
|
+
Object.assign(this, {
|
|
74
|
+
log: opts.log,
|
|
75
|
+
auth: new google.auth.OAuth2(
|
|
76
|
+
opts.clientId,
|
|
77
|
+
opts.clientSecret,
|
|
78
|
+
opts.redirectUri,
|
|
79
|
+
),
|
|
80
|
+
});
|
|
46
81
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
82
|
+
if (opts.cachePlugin) {
|
|
83
|
+
this.cachePlugin = opts.cachePlugin;
|
|
84
|
+
this.cache = new GoogleTokenCache(opts.cachePlugin).withLog(opts.log);
|
|
85
|
+
/// hack to capture tokens, since the emit handler is not awaited in the google client
|
|
86
|
+
const originalRefreshTokenNoCache = this.auth.refreshTokenNoCache.bind(this.auth);
|
|
87
|
+
this.auth.refreshTokenNoCache = async (...args) => {
|
|
88
|
+
const ret = await originalRefreshTokenNoCache(...args);
|
|
89
|
+
await this.cache.store(ret.tokens);
|
|
90
|
+
return ret;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
54
93
|
|
|
55
94
|
this.drive = google.drive({
|
|
56
95
|
version: 'v3',
|
|
@@ -58,16 +97,18 @@ export class GoogleClient {
|
|
|
58
97
|
});
|
|
59
98
|
|
|
60
99
|
/**
|
|
61
|
-
* Cached version of `
|
|
100
|
+
* Cached version of `getUncachedItemsFromSegments`
|
|
62
101
|
*/
|
|
63
|
-
this.
|
|
64
|
-
hash: (fn,
|
|
102
|
+
this.getDriveItemsFromSegments = cache(this.getUncachedItemsFromSegments.bind(this), {
|
|
103
|
+
hash: (fn, segs, parentId) => `${parentId}:${segs.join('/')}`,
|
|
65
104
|
});
|
|
66
105
|
}
|
|
67
106
|
|
|
68
107
|
async init() {
|
|
69
|
-
|
|
70
|
-
|
|
108
|
+
if (this.cache) {
|
|
109
|
+
await this.cache.load();
|
|
110
|
+
this.auth.setCredentials(this.cache.tokens);
|
|
111
|
+
}
|
|
71
112
|
return this;
|
|
72
113
|
}
|
|
73
114
|
|
|
@@ -75,28 +116,40 @@ export class GoogleClient {
|
|
|
75
116
|
return this.auth.generateAuthUrl(...args);
|
|
76
117
|
}
|
|
77
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Sets the credentials
|
|
121
|
+
* @param tokens
|
|
122
|
+
* @returns {Promise<void>}
|
|
123
|
+
*/
|
|
78
124
|
async setCredentials(tokens) {
|
|
79
|
-
|
|
125
|
+
if (this.cache) {
|
|
126
|
+
await this.cache.store(tokens);
|
|
127
|
+
}
|
|
80
128
|
this.auth.setCredentials(tokens);
|
|
81
129
|
}
|
|
82
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Returns the token for the given code
|
|
133
|
+
* @param {string} code
|
|
134
|
+
* @returns {Promise<*>}
|
|
135
|
+
*/
|
|
83
136
|
async getToken(code) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
137
|
+
const { tokens } = await this.auth.getToken(code);
|
|
138
|
+
if (this.cache) {
|
|
139
|
+
await this.cache.store(tokens);
|
|
140
|
+
}
|
|
141
|
+
return tokens;
|
|
87
142
|
}
|
|
88
143
|
|
|
89
144
|
/**
|
|
90
|
-
* @param {Drive} drive
|
|
91
|
-
* @param {AdminContext} context
|
|
92
145
|
* @param {string} path
|
|
93
146
|
* @param {string} parentId
|
|
94
147
|
* @param {string} parentPath
|
|
95
|
-
* @returns {Promise<
|
|
148
|
+
* @returns {Promise<DriveItemInfo[]>}
|
|
96
149
|
*/
|
|
97
|
-
async
|
|
150
|
+
async getUncachedItemsFromSegments(pathSegments, parentId, parentPath) {
|
|
98
151
|
const { log, drive } = this;
|
|
99
|
-
const
|
|
152
|
+
const name = pathSegments.shift();
|
|
100
153
|
const [baseName, ext] = splitByExtension(name);
|
|
101
154
|
const sanitizedName = sanitizeName(baseName);
|
|
102
155
|
|
|
@@ -106,7 +159,7 @@ export class GoogleClient {
|
|
|
106
159
|
`'${parentId}' in parents`,
|
|
107
160
|
'and trashed=false',
|
|
108
161
|
// folder if path continues, sheet otherwise
|
|
109
|
-
`and mimeType ${
|
|
162
|
+
`and mimeType ${pathSegments.length ? '=' : '!='} 'application/vnd.google-apps.folder'`,
|
|
110
163
|
].join(' '),
|
|
111
164
|
fields: 'nextPageToken, files(id, name, modifiedTime)',
|
|
112
165
|
includeItemsFromAllDrives: true,
|
|
@@ -163,35 +216,39 @@ export class GoogleClient {
|
|
|
163
216
|
}
|
|
164
217
|
|
|
165
218
|
const itemPath = `${parentPath}/${sanitizedName}`;
|
|
166
|
-
const children =
|
|
219
|
+
const children = pathSegments.length
|
|
167
220
|
// eslint-disable-next-line no-use-before-define
|
|
168
|
-
? await this.
|
|
221
|
+
? await this.getDriveItemsFromSegments(pathSegments, item.id, itemPath)
|
|
169
222
|
: [];
|
|
170
223
|
|
|
171
224
|
if (!children) {
|
|
172
225
|
return null;
|
|
173
226
|
}
|
|
174
227
|
|
|
175
|
-
const pathItem = {
|
|
228
|
+
const pathItem = addLastModified({
|
|
176
229
|
name,
|
|
177
230
|
path: itemPath,
|
|
178
231
|
id: item.id,
|
|
179
|
-
|
|
180
|
-
};
|
|
232
|
+
}, item);
|
|
181
233
|
|
|
182
234
|
return [...children, pathItem];
|
|
183
235
|
}
|
|
184
236
|
|
|
185
237
|
/**
|
|
186
238
|
* returns the items hierarchy for the given path and root id, starting with the given path.
|
|
187
|
-
* @param context
|
|
188
239
|
* @param path
|
|
189
240
|
* @param rootId
|
|
241
|
+
* @return {DriveItemInfo[]}
|
|
190
242
|
*/
|
|
191
243
|
async getItemsFromPath(path, rootId) {
|
|
192
|
-
const
|
|
244
|
+
const segs = path.split('/');
|
|
245
|
+
if (!segs[0]) {
|
|
246
|
+
// if path starts with '/' the first segment is empty
|
|
247
|
+
segs.shift();
|
|
248
|
+
}
|
|
249
|
+
const result = await this.getDriveItemsFromSegments(segs, rootId, '');
|
|
193
250
|
if (!result) {
|
|
194
|
-
return
|
|
251
|
+
return [];
|
|
195
252
|
}
|
|
196
253
|
return [...result, {
|
|
197
254
|
name: '',
|
|
@@ -202,11 +259,9 @@ export class GoogleClient {
|
|
|
202
259
|
|
|
203
260
|
/**
|
|
204
261
|
* returns the items hierarchy for the given item, starting with the given id
|
|
205
|
-
* @param {AdminContext} context
|
|
206
|
-
* @param {Drive} drive
|
|
207
262
|
* @param {string} fileId
|
|
208
263
|
* @param {object} roots
|
|
209
|
-
* @returns {Promise<
|
|
264
|
+
* @returns {Promise<DriveItemInfo[]>}
|
|
210
265
|
*/
|
|
211
266
|
async getItems(fileId, roots) {
|
|
212
267
|
const { log } = this;
|
|
@@ -223,62 +278,84 @@ export class GoogleClient {
|
|
|
223
278
|
const root = roots[fileId];
|
|
224
279
|
if (root) {
|
|
225
280
|
// stop at mount root
|
|
226
|
-
return [{
|
|
281
|
+
return [addLastModified({
|
|
227
282
|
id: fileId,
|
|
228
283
|
name: '',
|
|
229
284
|
path: root,
|
|
230
|
-
|
|
231
|
-
}];
|
|
285
|
+
}, data)];
|
|
232
286
|
}
|
|
233
287
|
|
|
234
288
|
const parentId = data.parents ? data.parents[0] : '';
|
|
235
289
|
if (!parentId) {
|
|
236
290
|
// outside mountpoint
|
|
237
|
-
return [{
|
|
291
|
+
return [addLastModified({
|
|
238
292
|
id: fileId,
|
|
239
293
|
name: data.name,
|
|
240
294
|
path: `/root:/${data.name}`,
|
|
241
|
-
|
|
242
|
-
}];
|
|
295
|
+
}, data)];
|
|
243
296
|
}
|
|
244
297
|
|
|
245
298
|
const ancestors = await this.getItems(data.parents[0], roots);
|
|
246
299
|
const parentPath = ancestors[0].path.replace(/\/+$/, '');
|
|
247
|
-
ancestors.unshift({
|
|
300
|
+
ancestors.unshift(addLastModified({
|
|
248
301
|
id: fileId,
|
|
249
302
|
name: data.name,
|
|
250
303
|
path: `${parentPath}/${data.name}`,
|
|
251
|
-
|
|
252
|
-
});
|
|
304
|
+
}, data));
|
|
253
305
|
return ancestors;
|
|
254
306
|
}
|
|
255
307
|
|
|
256
308
|
/**
|
|
257
|
-
* @param {AdminContext} context
|
|
258
309
|
* @param {string} fileId
|
|
259
310
|
* @param {object} roots
|
|
260
|
-
* @returns {Promise<
|
|
311
|
+
* @returns {Promise<DriveItemInfo[]>}
|
|
261
312
|
*/
|
|
262
313
|
async getItemsFromId(fileId, roots) {
|
|
263
314
|
const { log } = this;
|
|
264
315
|
try {
|
|
265
316
|
return await this.getItems(fileId, roots);
|
|
266
317
|
} catch (e) {
|
|
318
|
+
if (e.response && e.response.status === 404) {
|
|
319
|
+
log.warn(`unable to get items for ${fileId}. Not found`);
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
267
322
|
log.warn(`unable to get items for ${fileId}. ${e}`);
|
|
268
|
-
|
|
323
|
+
throw e;
|
|
269
324
|
}
|
|
270
325
|
}
|
|
271
326
|
|
|
272
327
|
/**
|
|
273
|
-
* @param {AdminContext} context
|
|
274
328
|
* @param {string} fileId
|
|
275
329
|
* @returns {string} file data
|
|
276
330
|
*/
|
|
277
331
|
async getFile(fileId) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
332
|
+
try {
|
|
333
|
+
const res = await this.drive.files.get({
|
|
334
|
+
fileId,
|
|
335
|
+
alt: 'media',
|
|
336
|
+
});
|
|
337
|
+
return res.data;
|
|
338
|
+
} catch (e) {
|
|
339
|
+
if (e.response && e.response.status === 404) {
|
|
340
|
+
throw new StatusCodeError(`Not Found: ${fileId}`, 404);
|
|
341
|
+
}
|
|
342
|
+
throw e;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async getDocument(documentId) {
|
|
347
|
+
const docs = google.docs({
|
|
348
|
+
version: 'v1',
|
|
349
|
+
auth: this.auth,
|
|
281
350
|
});
|
|
282
|
-
|
|
351
|
+
try {
|
|
352
|
+
const res = await docs.documents.get({ documentId });
|
|
353
|
+
return res.data;
|
|
354
|
+
} catch (e) {
|
|
355
|
+
if (e.response && e.response.status === 404) {
|
|
356
|
+
throw new StatusCodeError(`Not Found: ${documentId}`, 404);
|
|
357
|
+
}
|
|
358
|
+
throw e;
|
|
359
|
+
}
|
|
283
360
|
}
|
|
284
361
|
}
|