@adobe/helix-onedrive-support 6.2.2 → 7.1.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/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ # [7.1.0](https://github.com/adobe/helix-onedrive-support/compare/v7.0.1...v7.1.0) (2022-04-01)
2
+
3
+
4
+ ### Features
5
+
6
+ * speed up fuzzyItem lookup ([#259](https://github.com/adobe/helix-onedrive-support/issues/259)) ([5963fa6](https://github.com/adobe/helix-onedrive-support/commit/5963fa6389329d0cf737f64724688593b9291923)), closes [#258](https://github.com/adobe/helix-onedrive-support/issues/258)
7
+
8
+ ## [7.0.1](https://github.com/adobe/helix-onedrive-support/compare/v7.0.0...v7.0.1) (2022-03-23)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * detect *-my.sharepoint.com links ([#256](https://github.com/adobe/helix-onedrive-support/issues/256)) ([001f57d](https://github.com/adobe/helix-onedrive-support/commit/001f57d7d73fd760df86c5a18ac5f27bcf9f0cf5))
14
+ * trigger release ([c8826a3](https://github.com/adobe/helix-onedrive-support/commit/c8826a38c69279593338b34918b2c305912225b1))
15
+
16
+ # [7.0.0](https://github.com/adobe/helix-onedrive-support/compare/v6.2.2...v7.0.0) (2022-03-23)
17
+
18
+
19
+ ### Features
20
+
21
+ * add transparent tenant resolution ([dc59dbf](https://github.com/adobe/helix-onedrive-support/commit/dc59dbfc53d767593b82c845c753da3885560852))
22
+
23
+
24
+ ### BREAKING CHANGES
25
+
26
+ * API slightly refactored
27
+ - authorityUrl is now method: `getAuthorityIUrl`
28
+ - new method: `setAccessToken`
29
+
1
30
  ## [6.2.2](https://github.com/adobe/helix-onedrive-support/compare/v6.2.1...v6.2.2) (2022-03-20)
2
31
 
3
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-onedrive-support",
3
- "version": "6.2.2",
3
+ "version": "7.1.0",
4
4
  "description": "Helix OneDrive Support",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -24,18 +24,19 @@
24
24
  "homepage": "https://github.com/adobe/helix-onedrive-support#readme",
25
25
  "dependencies": {
26
26
  "@adobe/helix-fetch": "3.0.7",
27
- "adal-node": "https://github.com/adobe-rnd/azure-activedirectory-library-for-nodejs.git#adobe"
27
+ "adal-node": "https://github.com/adobe-rnd/azure-activedirectory-library-for-nodejs.git#adobe",
28
+ "jose": "4.6.0"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@adobe/eslint-config-helix": "1.3.2",
31
32
  "@semantic-release/changelog": "6.0.1",
32
33
  "@semantic-release/git": "10.0.1",
33
- "ajv": "8.10.0",
34
+ "ajv": "8.11.0",
34
35
  "codecov": "3.8.3",
35
36
  "commitizen": "4.2.4",
36
37
  "cz-conventional-changelog": "3.3.0",
37
38
  "dotenv": "16.0.0",
38
- "eslint": "8.11.0",
39
+ "eslint": "8.12.0",
39
40
  "eslint-plugin-header": "3.1.1",
40
41
  "eslint-plugin-import": "2.25.4",
41
42
  "jsdoc-to-markdown": "7.1.1",
package/src/OneDrive.d.ts CHANGED
@@ -25,6 +25,7 @@ export declare interface OneDriveOptions {
25
25
  refreshToken?: string;
26
26
  log?: Logger;
27
27
  tenant?: string;
28
+ resource?: string;
28
29
  username?: string;
29
30
  password?: string;
30
31
 
@@ -44,6 +45,18 @@ export declare interface OneDriveOptions {
44
45
  * Note that the cache is only used, if the `noShareLinkCache` flag is `falsy`
45
46
  */
46
47
  shareLinkCache?: Map<string, DriveItem>,
48
+
49
+ /**
50
+ * Disables the cache for the tenant lookup.
51
+ * @default process.env.HELIX_ONEDRIVE_NO_TENANT_CACHE
52
+ */
53
+ noTenantCache?: boolean;
54
+
55
+ /**
56
+ * Map to use for the tenant lookup cache. If empty, a module-global cache will be used.
57
+ * Note that the cache is only used, if the `noTenantCache` flag is `falsy`
58
+ */
59
+ tenantCache?: Map<string, DriveItem>,
47
60
  }
48
61
 
49
62
  export declare interface GraphResult {
@@ -145,7 +158,7 @@ export declare class OneDrive extends EventEmitter {
145
158
  /**
146
159
  * the authority url for login.
147
160
  */
148
- authorityUrl: string;
161
+ getAuthorityUrl(): string;
149
162
 
150
163
  /**
151
164
  * Adds entries to the token cache
@@ -161,6 +174,14 @@ export declare class OneDrive extends EventEmitter {
161
174
  */
162
175
  login(onCode: Function): Promise<TokenResponse>;
163
176
 
177
+ /**
178
+ * Sets the access token to use for all requests. if the token is a valid JWT token,
179
+ * its `tid` claim is used a tenant (if no tenant is already set).
180
+ *
181
+ * @param {string} bearerToken
182
+ */
183
+ setAccessToken(bearerToken);
184
+
164
185
  getAccessToken(autoRefresh: boolean): Promise<TokenResponse>;
165
186
 
166
187
  createLoginUrl(): string;
@@ -173,6 +194,8 @@ export declare class OneDrive extends EventEmitter {
173
194
 
174
195
  me(): Promise<GraphResult>;
175
196
 
197
+ initTenantFromShareLink(sharingUrl: string|URL): Promise<void>;
198
+
176
199
  resolveShareLink(sharingUrl: string|URL): Promise<GraphResult>;
177
200
 
178
201
  /**
@@ -204,16 +227,17 @@ export declare class OneDrive extends EventEmitter {
204
227
  * - replace all non-alphanumeric characters with a dash
205
228
  * - remove all consecutive dashes
206
229
  * - remove all leading and trailing dashes
207
- * - extensions are ignored, if the given path doesn't have one
230
+ * - extensions are ignored, if the given path doesn't have one or if ignoreExtension is true
208
231
  *
209
232
  * The result is an array of drive items that match the given path. They are ordered by the edit
210
233
  * distance to the original name and then alphanumerically.
211
234
  *
212
235
  * @param folderItem
213
236
  * @param relPath
237
+ * @param ignoreExtension
214
238
  * @returns {Promise<DriveItem[]>}
215
239
  */
216
- fuzzyGetDriveItem(folderItem: DriveItem, relPath?: string): Promise<DriveItem[]>;
240
+ fuzzyGetDriveItem(folderItem: DriveItem, relPath?: string, ignoreExtension?: boolean): Promise<DriveItem[]>;
217
241
 
218
242
  downloadDriveItem(driveItem: DriveItem): Promise<GraphResult>;
219
243
 
package/src/OneDrive.js CHANGED
@@ -13,6 +13,7 @@
13
13
  // eslint-disable-next-line max-classes-per-file
14
14
  const EventEmitter = require('events');
15
15
  const { promisify } = require('util');
16
+ const jose = require('jose');
16
17
  const { AuthenticationContext, MemoryCache } = require('adal-node');
17
18
  const { fetch, reset } = require('@adobe/helix-fetch').keepAliveNoCache({ userAgent: 'helix-fetch' });
18
19
 
@@ -24,7 +25,7 @@ const SharePointSite = require('./SharePointSite.js');
24
25
 
25
26
  const AZ_AUTHORITY_HOST_URL = 'https://login.windows.net';
26
27
  const AZ_DEFAULT_RESOURCE = 'https://graph.microsoft.com'; // '00000002-0000-0000-c000-000000000000'; ??
27
- const AZ_DEFAULT_TENANT = 'common';
28
+ const AZ_COMMON_TENANT = 'common';
28
29
 
29
30
  /**
30
31
  * the maximum subscription time in milliseconds
@@ -37,27 +38,23 @@ const MAX_SUBSCRIPTION_EXPIRATION_TIME = 4230 * 60 * 1000;
37
38
 
38
39
  /**
39
40
  * map that caches share item data. key is a sharing url, the value a drive item.
40
- * @type {Map<string, *>}
41
+ * @type {Map<string, string>}
41
42
  * @private
42
43
  */
43
44
  const globalShareLinkCache = new Map();
44
45
 
46
+ /**
47
+ * map that caches the tenant ids
48
+ * @type {Map<string, string>}
49
+ */
50
+ const globalTenantCache = new Map();
51
+
45
52
  /**
46
53
  * Helper class that facilitates accessing one drive.
47
54
  */
48
55
  class OneDrive extends EventEmitter {
49
56
  /**
50
57
  * @param {OneDriveOptions} opts Options
51
- * @param {string} opts.clientId The client id of the app
52
- * @param {string} [opts.clientSecret] The client secret of the app
53
- * @param {string} [opts.refreshToken] The refresh token.
54
- * @param {string} [opts.accessToken] The access token.
55
- * @param {string} [opts.username] Username for username/password authentication.
56
- * @param {string} [opts.password] Password for username/password authentication.
57
- * @param {number} [opts.expiresOn] Expiration time.
58
- * @param {Logger} [opts.log] A logger.
59
- * @param {boolean} [opts.localAuthCache] Whether to use local auth cache
60
- * @param {string} [opts.resource] Azure resource to authenticate against. defaults to MS Graph.
61
58
  */
62
59
  constructor(opts) {
63
60
  super(opts);
@@ -67,53 +64,116 @@ class OneDrive extends EventEmitter {
67
64
  this.username = opts.username || '';
68
65
  this.password = opts.password || '';
69
66
  this._log = opts.log || console;
70
- this.tenant = opts.tenant || AZ_DEFAULT_TENANT;
67
+ this.tenant = opts.tenant;
71
68
  this.resource = opts.resource || AZ_DEFAULT_RESOURCE;
69
+ this.localAuthCache = opts.localAuthCache;
72
70
 
73
71
  if (!opts.noShareLinkCache && !process.env.HELIX_ONEDRIVE_NO_SHARE_LINK_CACHE) {
72
+ /** @type {Map<string, string>} */
74
73
  this.shareLinkCache = opts.shareLinkCache || globalShareLinkCache;
75
74
  }
75
+ if (!opts.noTenantCache && !process.env.HELIX_ONEDRIVE_NO_TENANT_CACHE) {
76
+ /** @type {Map<string, string>} */
77
+ this.tenantCache = opts.tenantCache || globalTenantCache;
78
+ }
76
79
 
77
80
  if (!this.clientId) {
78
81
  throw new Error('Missing clientId.');
79
82
  }
80
- this.authContext = new AuthenticationContext(
81
- this.authorityUrl,
82
- undefined,
83
- opts.localAuthCache ? new MemoryCache() : undefined,
84
- );
85
- [
86
- 'acquireUserCode',
87
- 'acquireToken',
88
- 'acquireTokenWithDeviceCode',
89
- 'acquireTokenWithRefreshToken',
90
- 'acquireTokenWithUsernamePassword',
91
- 'acquireTokenWithClientCredentials',
92
- ].forEach((m) => {
93
- this.authContext[m] = promisify(this.authContext[m].bind(this.authContext));
94
- });
95
- const { cache } = this.authContext;
96
- if (opts.localAuthCache) {
97
- const originalAdd = cache.add;
98
- cache.add = (entries, cb) => {
99
- originalAdd.call(cache, entries, (...args) => {
100
- // eslint-disable-next-line no-underscore-dangle
101
- this.emit('tokens', cache._entries);
102
- cb(...args);
103
- });
104
- };
105
- const originalRemove = cache.remove;
106
- cache.remove = (entries, cb) => {
107
- originalRemove.call(cache, entries, (...args) => {
108
- // eslint-disable-next-line no-underscore-dangle
109
- this.emit('tokens', cache._entries);
110
- cb(...args);
111
- });
112
- };
113
- }
114
- cache.add.promise = promisify(cache.add.bind(cache));
115
- cache.remove.promise = promisify(cache.remove.bind(cache));
116
- cache.find.promise = promisify(cache.find.bind(cache));
83
+ }
84
+
85
+ /**
86
+ * Return the auth context
87
+ * @returns {AuthenticationContext}
88
+ */
89
+ async getAuthContext() {
90
+ if (!this.authContext) {
91
+ this.authContext = new AuthenticationContext(
92
+ this.getAuthorityUrl(),
93
+ undefined,
94
+ this.localAuthCache ? new MemoryCache() : undefined,
95
+ );
96
+ [
97
+ 'acquireUserCode',
98
+ 'acquireToken',
99
+ 'acquireTokenWithDeviceCode',
100
+ 'acquireTokenWithRefreshToken',
101
+ 'acquireTokenWithUsernamePassword',
102
+ 'acquireTokenWithClientCredentials',
103
+ ].forEach((m) => {
104
+ this.authContext[m] = promisify(this.authContext[m].bind(this.authContext));
105
+ });
106
+ const { cache } = this.authContext;
107
+ if (this.localAuthCache) {
108
+ const originalAdd = cache.add;
109
+ cache.add = (entries, cb) => {
110
+ originalAdd.call(cache, entries, (...args) => {
111
+ // eslint-disable-next-line no-underscore-dangle
112
+ this.emit('tokens', cache._entries);
113
+ cb(...args);
114
+ });
115
+ };
116
+ const originalRemove = cache.remove;
117
+ cache.remove = (entries, cb) => {
118
+ originalRemove.call(cache, entries, (...args) => {
119
+ // eslint-disable-next-line no-underscore-dangle
120
+ this.emit('tokens', cache._entries);
121
+ cb(...args);
122
+ });
123
+ };
124
+ }
125
+ cache.add.promise = promisify(cache.add.bind(cache));
126
+ cache.remove.promise = promisify(cache.remove.bind(cache));
127
+ cache.find.promise = promisify(cache.find.bind(cache));
128
+ }
129
+ return this.authContext;
130
+ }
131
+
132
+ async resolveTenant(tenantHost) {
133
+ const { log } = this;
134
+ const configUrl = `https://login.windows.net/${tenantHost}.onmicrosoft.com/.well-known/openid-configuration`;
135
+ const res = await fetch(configUrl);
136
+ if (!res.ok) {
137
+ log.info(`error fetching openid-configuration for ${tenantHost}: ${res.status}. Fallback to 'common'`);
138
+ return AZ_COMMON_TENANT;
139
+ }
140
+
141
+ const { issuer } = await res.json();
142
+ if (!issuer) {
143
+ log.info(`unable to extract tenant from openid-configuration for ${tenantHost}: no 'issuer'. Fallback to 'common'`);
144
+ return AZ_COMMON_TENANT;
145
+ }
146
+
147
+ // eslint-disable-next-line prefer-destructuring
148
+ const tenant = new URL(issuer).pathname.split('/')[1];
149
+ log.info(`fetched tenant information from for ${tenantHost}: ${tenant}`);
150
+ return tenant;
151
+ }
152
+
153
+ async initTenantFromShareLink(sharingUrl) {
154
+ if (this.tenant) {
155
+ return;
156
+ }
157
+ const { log } = this;
158
+ const url = sharingUrl instanceof URL
159
+ ? sharingUrl
160
+ : new URL(sharingUrl);
161
+ let [tenantHost] = url.hostname.split('.');
162
+ // special case: `xxxx-my.sharepoint.com`
163
+ if (url.hostname.endsWith('-my.sharepoint.com')) {
164
+ tenantHost = tenantHost.substring(0, tenantHost.length - 3);
165
+ }
166
+
167
+ if (this.tenantCache) {
168
+ this.tenant = this.tenantCache.get(tenantHost);
169
+ }
170
+ if (!this.tenant) {
171
+ this.tenant = await this.resolveTenant(tenantHost);
172
+ if (this.tenantCache) {
173
+ this.tenantCache.set(tenantHost, this.tenant);
174
+ }
175
+ }
176
+ log.info(`using tenant ${this.tenant} for ${tenantHost} from ${sharingUrl}`);
117
177
  }
118
178
 
119
179
  /**
@@ -130,7 +190,10 @@ class OneDrive extends EventEmitter {
130
190
  return this._log;
131
191
  }
132
192
 
133
- get authorityUrl() {
193
+ getAuthorityUrl() {
194
+ if (!this.tenant) {
195
+ throw new Error('unable to compute authority url. no tenant.');
196
+ }
134
197
  return `${AZ_AUTHORITY_HOST_URL}/${this.tenant}`;
135
198
  }
136
199
 
@@ -139,7 +202,7 @@ class OneDrive extends EventEmitter {
139
202
  */
140
203
  get authenticated() {
141
204
  // eslint-disable-next-line no-underscore-dangle
142
- return this.authContext.cache._entries.length > 0;
205
+ return this.authContext?.cache._entries.length > 0;
143
206
  }
144
207
 
145
208
  /**
@@ -148,7 +211,7 @@ class OneDrive extends EventEmitter {
148
211
  * @return this;
149
212
  */
150
213
  async loadTokenCache(entries) {
151
- return this.authContext.cache.add.promise(entries);
214
+ return (await this.getAuthContext()).cache.add.promise(entries);
152
215
  }
153
216
 
154
217
  /**
@@ -158,7 +221,8 @@ class OneDrive extends EventEmitter {
158
221
  * @returns {Promise<TokenResponse>}
159
222
  */
160
223
  async login(onCode) {
161
- const { log, authContext: context } = this;
224
+ const { log } = this;
225
+ const context = await this.getAuthContext();
162
226
 
163
227
  let code;
164
228
  try {
@@ -182,9 +246,35 @@ class OneDrive extends EventEmitter {
182
246
  }
183
247
 
184
248
  /**
249
+ * Sets the access token to use for all requests. if the token is a valid JWT token,
250
+ * its `tid` claim is used a tenant (if no tenant is already set).
251
+ *
252
+ * @param {string} bearerToken
185
253
  */
186
- async getAccessToken() {
187
- const { log, authContext: context } = this;
254
+ setAccessToken(bearerToken) {
255
+ const { log } = this;
256
+ this.accessToken = {
257
+ accessToken: bearerToken,
258
+ };
259
+ if (!this.tenant) {
260
+ try {
261
+ const { tid } = jose.decodeJwt(bearerToken);
262
+ if (tid) {
263
+ log.info(`using tenant from access token: ${tid}`);
264
+ this.tenant = tid;
265
+ }
266
+ } catch (e) {
267
+ log.warn(`unable to decode access token: ${e.message}`);
268
+ }
269
+ }
270
+ this.accessToken.tenantId = this.tenant;
271
+ }
272
+
273
+ /**
274
+ */
275
+ async fetchAccessToken() {
276
+ const { log } = this;
277
+ const context = await this.getAuthContext();
188
278
  try {
189
279
  return await context.acquireToken(this.resource, this.username, this.clientId);
190
280
  } catch (e) {
@@ -231,10 +321,17 @@ class OneDrive extends EventEmitter {
231
321
  }
232
322
  }
233
323
 
324
+ async getAccessToken() {
325
+ if (!this.accessToken) {
326
+ this.accessToken = await this.fetchAccessToken();
327
+ }
328
+ return this.accessToken;
329
+ }
330
+
234
331
  /**
235
332
  */
236
333
  createLoginUrl(redirectUri, state) {
237
- return `${this.authorityUrl}/oauth2/authorize?response_type=code&scope=/.default&client_id=${this.clientId}&redirect_uri=${redirectUri}&state=${state}&resource=${this.resource}`;
334
+ return `${this.getAuthorityUrl()}/oauth2/authorize?response_type=code&scope=/.default&client_id=${this.clientId}&redirect_uri=${redirectUri}&state=${state}&resource=${this.resource}`;
238
335
  }
239
336
 
240
337
  async augmentAndCacheResponse(response) {
@@ -259,7 +356,8 @@ class OneDrive extends EventEmitter {
259
356
  /**
260
357
  */
261
358
  async acquireToken(redirectUri, code) {
262
- const { log, authContext: context } = this;
359
+ const { log } = this;
360
+ const context = await this.getAuthContext();
263
361
  try {
264
362
  const resp = await context.acquireTokenWithAuthorizationCode(
265
363
  code,
@@ -342,6 +440,7 @@ class OneDrive extends EventEmitter {
342
440
  /**
343
441
  */
344
442
  async resolveShareLink(sharingUrl) {
443
+ await this.initTenantFromShareLink(sharingUrl);
345
444
  const link = OneDrive.encodeSharingUrl(sharingUrl);
346
445
  this.log.debug(`resolving sharelink ${sharingUrl} (${link})`);
347
446
  try {
@@ -368,6 +467,7 @@ class OneDrive extends EventEmitter {
368
467
  if (driveItem) {
369
468
  return driveItem;
370
469
  }
470
+ await this.initTenantFromShareLink(sharingUrl);
371
471
  if (this.shareLinkCache) {
372
472
  driveItem = this.shareLinkCache.get(sharingUrl);
373
473
  }
@@ -402,22 +502,39 @@ class OneDrive extends EventEmitter {
402
502
  * - convert to lower case
403
503
  * - replace all non-alphanumeric characters with a dash
404
504
  * - remove all consecutive dashes
405
- * - extensions are ignored, if the given path doesn't have one
505
+ * - extensions are ignored, if the given path doesn't have one or if ignoreExtension is true
406
506
  *
407
507
  * The result is an array of drive items that match the given path. They are ordered by the edit
408
508
  * distance to the original name and then alphanumerically.
409
509
  *
410
510
  * @param {DriveItem} folderItem
411
- * @param {string} relPath
511
+ * @param {string} [relPath = '']
512
+ * @param {boolean} [ignoreExtension = false]
412
513
  * @returns {Promise<DriveItem[]>}
413
514
  */
414
- async fuzzyGetDriveItem(folderItem, relPath = '') {
415
- const idx = relPath.lastIndexOf('/');
416
- if (idx < 0) {
417
- const ret = await this.getDriveItem(folderItem, '', false);
418
- // todo: add extra extension
419
- return [ret.value];
515
+ async fuzzyGetDriveItem(folderItem, relPath = '', ignoreExtension = false) {
516
+ if (relPath && !relPath.startsWith('/')) {
517
+ throw new Error('relPath must be empty or start with /');
420
518
  }
519
+
520
+ // first try to get item directly
521
+ try {
522
+ const ret = await this.getDriveItem(folderItem, relPath, false);
523
+ if (relPath) {
524
+ // eslint-disable-next-line prefer-destructuring
525
+ ret.extension = splitByExtension(relPath)[1];
526
+ }
527
+ this.log.info(`fetched drive item directly: /drives/${folderItem.parentReference.driveId}/items/${folderItem.id}:${relPath}`);
528
+ return [ret];
529
+ } catch (e) {
530
+ this.log.info(`fetched drive item directly failed: /drives/${folderItem.parentReference.driveId}/items/${folderItem.id}:${relPath} (${e.statusCode})`);
531
+ // if no 404 or no relPath, propagate error
532
+ if (e.statusCode !== 404 || !relPath) {
533
+ throw e;
534
+ }
535
+ }
536
+
537
+ const idx = relPath.lastIndexOf('/');
421
538
  const folderRelPath = relPath.substring(0, idx);
422
539
  const name = relPath.substring(idx + 1);
423
540
  const [baseName, ext] = splitByExtension(name);
@@ -441,7 +558,7 @@ class OneDrive extends EventEmitter {
441
558
  }
442
559
  } while (query.$skiptoken);
443
560
 
444
- this.log.debug(`loaded ${fileList.length} children from ${relPath}`);
561
+ this.log.info(`loaded ${fileList.length} children from /drives/${folderItem.parentReference.driveId}/items/${folderItem.id}:${relPath}`);
445
562
  const items = fileList.filter((item) => {
446
563
  if (!item.file) {
447
564
  return false;
@@ -450,7 +567,7 @@ class OneDrive extends EventEmitter {
450
567
  // remember extension
451
568
  // eslint-disable-next-line no-param-reassign
452
569
  item.extension = itemExt;
453
- if (ext && ext !== itemExt) {
570
+ if (ext && ext !== itemExt && !ignoreExtension) {
454
571
  // only match extension if given via relPath
455
572
  return false;
456
573
  }
@@ -239,6 +239,9 @@ class OneDriveMock extends OneDrive {
239
239
  const url = new URL(`https://dummy.org${uri}`);
240
240
  if (url.pathname in this.driveItems) {
241
241
  const result = this.driveItems[url.pathname];
242
+ if (result instanceof Error) {
243
+ throw result;
244
+ }
242
245
  if (!Array.isArray(result.value)) {
243
246
  return result;
244
247
  }