@adobe/helix-onedrive-support 5.2.1 → 6.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.
@@ -0,0 +1,6 @@
1
+ {
2
+ "reporterEnabled": "spec,xunit",
3
+ "xunitReporterOptions": {
4
+ "output": "junit/test-results.xml"
5
+ }
6
+ }
package/.nycrc.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "reporter": [
3
+ "lcov",
4
+ "text"
5
+ ],
6
+ "check-coverage": true,
7
+ "lines": 15,
8
+ "branches": 19,
9
+ "statements": 15
10
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,36 @@
1
+ # [6.1.0](https://github.com/adobe/helix-onedrive-support/compare/v6.0.1...v6.1.0) (2022-01-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * add API for SP API ([#227](https://github.com/adobe/helix-onedrive-support/issues/227)) ([6ce5938](https://github.com/adobe/helix-onedrive-support/commit/6ce59387a777d4549964a8fb90b2e0ca2a4fd86e))
7
+
8
+ ## [6.0.1](https://github.com/adobe/helix-onedrive-support/compare/v6.0.0...v6.0.1) (2021-10-18)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update dependency @adobe/helix-fetch to v3 ([#202](https://github.com/adobe/helix-onedrive-support/issues/202)) ([1b0b586](https://github.com/adobe/helix-onedrive-support/commit/1b0b58661458550a3d6b782ada364f2f48d69fb0))
14
+
15
+ # [6.0.0](https://github.com/adobe/helix-onedrive-support/compare/v5.2.2...v6.0.0) (2021-10-08)
16
+
17
+
18
+ * Support conflict behaviour (#197) ([b4a5c72](https://github.com/adobe/helix-onedrive-support/commit/b4a5c72ed76062c558ea175aaedff3475e892c34)), closes [#197](https://github.com/adobe/helix-onedrive-support/issues/197)
19
+
20
+
21
+ ### BREAKING CHANGES
22
+
23
+ * uploadDriveItem return value changed
24
+
25
+ * fix: test behaviour values
26
+
27
+ ## [5.2.2](https://github.com/adobe/helix-onedrive-support/compare/v5.2.1...v5.2.2) (2021-10-08)
28
+
29
+
30
+ ### Bug Fixes
31
+
32
+ * trim object names and values ([#196](https://github.com/adobe/helix-onedrive-support/issues/196)) ([f117175](https://github.com/adobe/helix-onedrive-support/commit/f117175f63316ed8a3b60f4e74870114134b9ef7))
33
+
1
34
  ## [5.2.1](https://github.com/adobe/helix-onedrive-support/compare/v5.2.0...v5.2.1) (2021-10-07)
2
35
 
3
36
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@adobe/helix-onedrive-support",
3
- "version": "5.2.1",
3
+ "version": "6.1.0",
4
4
  "description": "Helix OneDrive Support",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
7
7
  "scripts": {
8
- "test": " nyc --reporter=text --reporter=lcov --check-coverage --branches 19 --statements 15 --lines 15 mocha",
9
- "test-ci": "nyc --reporter=text --reporter=lcov --check-coverage --branches 19 --statements 15 --lines 15 mocha --reporter xunit --reporter-options output=./junit/test-results.xml && codecov",
8
+ "test": "nyc mocha",
9
+ "test-ci": "nyc mocha && codecov",
10
10
  "lint": "./node_modules/.bin/eslint .",
11
11
  "semantic-release": "semantic-release",
12
12
  "docs": "npx jsdoc2md -c .jsdoc.json --files 'src/*.js' > docs/API.md",
@@ -23,28 +23,29 @@
23
23
  },
24
24
  "homepage": "https://github.com/adobe/helix-onedrive-support#readme",
25
25
  "dependencies": {
26
- "@adobe/helix-fetch": "2.4.2",
26
+ "@adobe/helix-fetch": "3.0.0",
27
27
  "adal-node": "https://github.com/adobe-rnd/azure-activedirectory-library-for-nodejs.git#adobe"
28
28
  },
29
29
  "devDependencies": {
30
- "@adobe/eslint-config-helix": "1.2.0",
31
- "@semantic-release/changelog": "6.0.0",
32
- "@semantic-release/git": "10.0.0",
33
- "ajv": "8.6.3",
30
+ "@adobe/eslint-config-helix": "1.3.2",
31
+ "@semantic-release/changelog": "6.0.1",
32
+ "@semantic-release/git": "10.0.1",
33
+ "ajv": "8.9.0",
34
34
  "codecov": "3.8.3",
35
35
  "commitizen": "4.2.4",
36
36
  "cz-conventional-changelog": "3.3.0",
37
- "dotenv": "10.0.0",
38
- "eslint": "7.32.0",
37
+ "dotenv": "14.2.0",
38
+ "eslint": "8.7.0",
39
39
  "eslint-plugin-header": "3.1.1",
40
- "eslint-plugin-import": "2.24.2",
41
- "jsdoc-to-markdown": "7.0.1",
40
+ "eslint-plugin-import": "2.25.4",
41
+ "jsdoc-to-markdown": "7.1.0",
42
42
  "junit-report-builder": "3.0.0",
43
- "lint-staged": "11.1.2",
44
- "mocha": "9.1.2",
45
- "nock": "13.1.3",
43
+ "lint-staged": "12.3.1",
44
+ "mocha": "9.1.4",
45
+ "mocha-multi-reporters": "1.5.1",
46
+ "nock": "13.2.2",
46
47
  "nyc": "15.1.0",
47
- "semantic-release": "18.0.0"
48
+ "semantic-release": "19.0.2"
48
49
  },
49
50
  "lint-staged": {
50
51
  "*.js": "eslint"
@@ -56,5 +57,11 @@
56
57
  "ghooks": {
57
58
  "pre-commit": "npx lint-staged"
58
59
  }
60
+ },
61
+ "mocha": {
62
+ "spec": "test/**/*.test.js",
63
+ "require": "test/setup-env.js",
64
+ "reporter": "mocha-multi-reporters",
65
+ "reporter-options": "configFile=.mocha-multi.json"
59
66
  }
60
67
  }
package/src/OneDrive.d.ts CHANGED
@@ -60,6 +60,36 @@ export declare interface SubscriptionOptions {
60
60
  expiresIn?: number;
61
61
  }
62
62
 
63
+ export declare interface SharePointSite {
64
+ /**
65
+ * Return a file's properties.
66
+ * @param file file name
67
+ * @returns file properties
68
+ */
69
+ getFile(file: string): Promise<GraphResult>;
70
+
71
+ /**
72
+ * Return a folder's properties.
73
+ * @param folder folder name
74
+ * @returns folder properties
75
+ */
76
+ getFolder(folder: string): Promise<GraphResult>;
77
+
78
+ /**
79
+ * Return a file's contents, as a binary buffer.
80
+ * @param file file name
81
+ * @returns file contents
82
+ */
83
+ getFileContents(file: string): Promise<Buffer>;
84
+
85
+ /**
86
+ * Returns a list of children items in a folder
87
+ * @param folder folder name
88
+ * @returns list of files and folders
89
+ */
90
+ getFilesAndFolders(folder: string): Promise<GraphResult>;
91
+ }
92
+
63
93
  /**
64
94
  * Helper class that facilitates accessing one drive.
65
95
  */
@@ -203,7 +233,15 @@ export declare class OneDrive extends EventEmitter {
203
233
 
204
234
  deleteSubscription(id: string): Promise<GraphResult>;
205
235
 
206
- uploadDriveItem(buffer: Buffer, driveItem: string, relPath?: string);
236
+ /**
237
+ * Uploads a drive item.
238
+ *
239
+ * @param buffer contents of file
240
+ * @param driveItem parent item in combination with relPath or item itself
241
+ * @param relPath relative path
242
+ * @param conflictBehaviour replace, rename or fail, default is replace
243
+ */
244
+ uploadDriveItem(buffer: Buffer, driveItem: string, relPath?: string, conflictBehaviour?: string);
207
245
 
208
246
  /**
209
247
  * Returns the root item for a drive given its id.
@@ -220,4 +258,11 @@ export declare class OneDrive extends EventEmitter {
220
258
  * @returns {Promise<Array>} A return object with the values and a `@odata.deltaLink`.
221
259
  */
222
260
  fetchChanges(resource: string, token?: string);
261
+
262
+ /**
263
+ * Returns a site object exposing the SharePoint API (now called Graph API V1).
264
+ * @param siteURL site URL, in the format https://<tenant>.sharepoint.com/sites/<site>
265
+ * @return {Promise<SharePointSite} site object
266
+ */
267
+ getSite(siteURL: string): Promise<SharePointSite>;
223
268
  }
package/src/OneDrive.js CHANGED
@@ -20,6 +20,7 @@ const Workbook = require('./Workbook.js');
20
20
  const StatusCodeError = require('./StatusCodeError.js');
21
21
  const { driveItemFromURL, driveItemToURL } = require('./utils.js');
22
22
  const { splitByExtension, sanitize, editDistance } = require('./fuzzy-helper.js');
23
+ const SharePointSite = require('./SharePointSite.js');
23
24
 
24
25
  const { fetch, reset } = process.env.HELIX_FETCH_FORCE_HTTP1
25
26
  ? fetchAPI.context({
@@ -204,18 +205,26 @@ class OneDrive extends EventEmitter {
204
205
  if (this.refreshToken) {
205
206
  log.debug('acquire token with refresh token.');
206
207
  const resp = await context.acquireTokenWithRefreshToken(
207
- this.refreshToken, this.clientId, this.clientSecret, AZ_RESOURCE,
208
+ this.refreshToken,
209
+ this.clientId,
210
+ this.clientSecret,
211
+ AZ_RESOURCE,
208
212
  );
209
213
  return await this.augmentAndCacheResponse(resp);
210
214
  } else if (this.username && this.password) {
211
215
  log.debug('acquire token with ROPC.');
212
216
  return await context.acquireTokenWithUsernamePassword(
213
- AZ_RESOURCE, this.username, this.password, this.clientId,
217
+ AZ_RESOURCE,
218
+ this.username,
219
+ this.password,
220
+ this.clientId,
214
221
  );
215
222
  } else if (this.clientSecret) {
216
223
  log.debug('acquire token with client credentials.');
217
224
  return await context.acquireTokenWithClientCredentials(
218
- AZ_RESOURCE, this.clientId, this.clientSecret,
225
+ AZ_RESOURCE,
226
+ this.clientId,
227
+ this.clientSecret,
219
228
  );
220
229
  } else {
221
230
  const err = new StatusCodeError('No valid authentication credentials supplied.');
@@ -259,7 +268,11 @@ class OneDrive extends EventEmitter {
259
268
  const { log, authContext: context } = this;
260
269
  try {
261
270
  const resp = await context.acquireTokenWithAuthorizationCode(
262
- code, redirectUri, AZ_RESOURCE, this.clientId, this.clientSecret,
271
+ code,
272
+ redirectUri,
273
+ AZ_RESOURCE,
274
+ this.clientId,
275
+ this.clientSecret,
263
276
  );
264
277
  return await this.augmentAndCacheResponse(resp);
265
278
  } catch (e) {
@@ -489,7 +502,15 @@ class OneDrive extends EventEmitter {
489
502
  /**
490
503
  * @see https://docs.microsoft.com/en-us/graph/api/driveitem-put-content?view=graph-rest-1.0&tabs=http
491
504
  */
492
- async uploadDriveItem(buffer, driveItem, relPath = '') {
505
+ async uploadDriveItem(buffer, driveItem, relPath = '', conflictBehaviour = 'replace') {
506
+ const validConflictBehaviours = [
507
+ 'replace',
508
+ 'rename',
509
+ 'fail',
510
+ ];
511
+ if (!validConflictBehaviours.includes(conflictBehaviour)) {
512
+ throw new Error(`Bad confict behaviour: ${conflictBehaviour}, must be one of: ${validConflictBehaviours.join('/')}`);
513
+ }
493
514
  // eslint-disable-next-line no-param-reassign
494
515
  relPath = relPath.replace(/\/+$/, '');
495
516
  if (relPath) {
@@ -498,7 +519,7 @@ class OneDrive extends EventEmitter {
498
519
  }
499
520
 
500
521
  // PUT /drives/{drive-id}/items/{parent-id}:/{filename}:/content
501
- const uri = `/drives/${driveItem.parentReference.driveId}/items/${driveItem.id}${relPath}/content`;
522
+ const uri = `/drives/${driveItem.parentReference.driveId}/items/${driveItem.id}${relPath}/content?@microsoft.graph.conflictBehavior=${conflictBehaviour}`;
502
523
  const opts = {
503
524
  method: 'PUT',
504
525
  body: buffer,
@@ -506,7 +527,7 @@ class OneDrive extends EventEmitter {
506
527
  'Content-Type': 'application/octet-stream',
507
528
  },
508
529
  };
509
- return this.doFetch(uri, true, opts);
530
+ return this.doFetch(uri, false, opts);
510
531
  }
511
532
 
512
533
  /**
@@ -603,6 +624,35 @@ class OneDrive extends EventEmitter {
603
624
  }
604
625
  }
605
626
  }
627
+
628
+ async getSite(siteURL) {
629
+ this.log.debug(`getting site: (${siteURL})`);
630
+
631
+ const match = siteURL.match(/^https:\/\/(\S+).sharepoint.com\/sites\/([^/]+)\/(\S+)$/);
632
+ if (!match) {
633
+ throw new Error(`Site URL does not match (*.sharepoint.com/sites/.*): ${match}`);
634
+ }
635
+ const [, owner, site, root] = match;
636
+
637
+ try {
638
+ const accessToken = await this.getAccessToken();
639
+ return new SharePointSite({
640
+ owner,
641
+ site,
642
+ root,
643
+ clientId: this.clientId,
644
+ tenantId: accessToken.tenantId,
645
+ refreshToken: accessToken.refreshToken,
646
+ log: this.log,
647
+ });
648
+ } catch (e) {
649
+ if (e.statusCode === 401) {
650
+ // an inexistant share returns 401, we prefer to just say it wasn't found
651
+ throw new StatusCodeError(e.message, 404, e.details);
652
+ }
653
+ throw e;
654
+ }
655
+ }
606
656
  }
607
657
 
608
658
  module.exports = Object.assign(OneDrive, {
package/src/Range.d.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { GraphResult, Logger } from './OneDrive'
14
+ import { FormatOptions } from "./Table";
14
15
 
15
16
  /**
16
17
  * Excel Range
@@ -45,7 +46,7 @@ export declare interface Range {
45
46
  * Returns the rows as a list of objects. the rows have the columns names as property names
46
47
  * and the row values as value.
47
48
  */
48
- getRowsAsObjects(): Promise<Array<object>>;
49
+ getRowsAsObjects(opts?:FormatOptions): Promise<Array<object>>;
49
50
 
50
51
  /**
51
52
  * Returns the values of the range.
package/src/Range.js CHANGED
@@ -9,6 +9,8 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
+ const { superTrim } = require('./utils.js');
13
+
12
14
  class Range {
13
15
  constructor(oneDrive, uri, log) {
14
16
  this._oneDrive = oneDrive;
@@ -44,13 +46,18 @@ class Range {
44
46
  return (await this.getData()).values[0];
45
47
  }
46
48
 
47
- async getRowsAsObjects() {
49
+ async getRowsAsObjects({ trim = false } = {}) {
48
50
  const values = await this.getValues();
49
51
 
50
52
  const columnNames = values[0];
51
53
  const rows = values.map((row) => columnNames.reduce((obj, name, index) => {
52
- // eslint-disable-next-line no-param-reassign
53
- obj[name] = row[index];
54
+ if (trim) {
55
+ // eslint-disable-next-line no-param-reassign
56
+ obj[superTrim(name)] = superTrim(row[index]);
57
+ } else {
58
+ // eslint-disable-next-line no-param-reassign
59
+ obj[name] = row[index];
60
+ }
54
61
  return obj;
55
62
  }, {}));
56
63
  // discard first row
@@ -0,0 +1,139 @@
1
+ /*
2
+ * Copyright 2021 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ const fetchAPI = require('@adobe/helix-fetch');
14
+ const StatusCodeError = require('./StatusCodeError.js');
15
+
16
+ /* istanbul ignore next */
17
+ const { fetch } = process.env.HELIX_FETCH_FORCE_HTTP1
18
+ ? fetchAPI.context({
19
+ alpnProtocols: [fetchAPI.ALPN_HTTP1_1],
20
+ userAgent: 'helix-fetch', // static user agent for test recordings
21
+ })
22
+ /* istanbul ignore next */
23
+ : fetchAPI;
24
+
25
+ /**
26
+ * Helper class accessing folders and files using the SharePoint V1 API.
27
+ */
28
+ class SharePointSite {
29
+ constructor(opts) {
30
+ this._owner = opts.owner;
31
+ this._site = opts.site;
32
+ this._clientId = opts.clientId;
33
+ this._tenantId = opts.tenantId;
34
+ this._refreshToken = opts.refreshToken;
35
+ this._root = opts.root || '';
36
+ this._log = opts.log || console;
37
+ }
38
+
39
+ async getAccessToken() {
40
+ const { log } = this;
41
+ if (!this._accessToken || Date.now() >= this._expires) {
42
+ const url = `https://login.microsoftonline.com/${this._tenantId}/oauth2/v2.0/token`;
43
+ const resp = await fetch(url, {
44
+ method: 'POST',
45
+ body: new URLSearchParams({
46
+ client_id: this._clientId,
47
+ refresh_token: this._refreshToken,
48
+ grant_type: 'refresh_token',
49
+ scope: `https://${this._owner}.sharepoint.com/Sites.ReadWrite.All`,
50
+ }),
51
+ });
52
+ if (!resp.ok) {
53
+ const text = await resp.text();
54
+ log.error(`Error while getting a SharePoint API token: ${text}}`);
55
+ throw new StatusCodeError(text, resp.status);
56
+ }
57
+ const json = await resp.json();
58
+ this._accessToken = json.access_token;
59
+ this._expires = Date.now() + json.expires_in * 1000;
60
+ }
61
+ return this._accessToken;
62
+ }
63
+
64
+ _splitDirAndBase(file) {
65
+ const idx = file.lastIndexOf('/');
66
+ const [dir, base] = (idx < 0)
67
+ ? ['', file]
68
+ : [file.substring(0, idx), file.substring(idx + 1)];
69
+ return dir ? [`${this._root}/${dir}`, base] : [this._root, base];
70
+ }
71
+
72
+ _getRelativePath(folder) {
73
+ return folder ? `${this._root}/${folder}` : this._root;
74
+ }
75
+
76
+ async getFile(file) {
77
+ const [dir, base] = this._splitDirAndBase(file);
78
+ return this.doFetch(`/GetFolderByServerRelativeUrl('${dir}')/Files('${base}')?$expand=ModifiedBy`);
79
+ }
80
+
81
+ async getFolder(folder) {
82
+ const dir = this._getRelativePath(folder);
83
+ return this.doFetch(`/GetFolderByServerRelativeUrl('${dir}')`);
84
+ }
85
+
86
+ async getFileContents(file) {
87
+ const [dir, base] = this._splitDirAndBase(file);
88
+ return this.doFetch(`/GetFolderByServerRelativeUrl('${dir}')/Files('${base}')/$value`, true);
89
+ }
90
+
91
+ async getFilesAndFolders(folder) {
92
+ const dir = this._getRelativePath(folder);
93
+ return this.doFetch(`/GetFolderByServerRelativeUrl('${dir}')?$expand=Files/ModifiedBy,Folders`);
94
+ }
95
+
96
+ async doFetch(relUrl, rawResponseBody = false) {
97
+ const opts = { headers: {} };
98
+ const accessToken = await this.getAccessToken();
99
+ opts.headers.authorization = `Bearer ${accessToken}`;
100
+ if (!rawResponseBody) {
101
+ opts.headers.accept = 'application/json;odata=verbose';
102
+ }
103
+
104
+ const url = `https://${this._owner}.sharepoint.com/sites/${this._site}/_api/web${relUrl}`;
105
+ try {
106
+ const resp = await fetch(url, opts);
107
+ if (!resp.ok) {
108
+ const text = await resp.text();
109
+ let err;
110
+ try {
111
+ // try to parse json
112
+ err = StatusCodeError.fromErrorResponse(JSON.parse(text), resp.status);
113
+ } catch {
114
+ err = new StatusCodeError(text, resp.status);
115
+ }
116
+ throw err;
117
+ }
118
+ // check content type before trying to parse a response body as JSON
119
+ const contentType = resp.headers.get('content-type');
120
+ const json = contentType && contentType.startsWith('application/json');
121
+
122
+ // await result in order to be able to catch any error
123
+ return await (rawResponseBody || !json ? resp.buffer() : resp.json());
124
+ } catch (e) {
125
+ /* istanbul ignore else */
126
+ if (e instanceof StatusCodeError) {
127
+ throw e;
128
+ }
129
+ /* istanbul ignore next */
130
+ throw StatusCodeError.fromError(e);
131
+ }
132
+ }
133
+
134
+ get log() {
135
+ return this._log;
136
+ }
137
+ }
138
+
139
+ module.exports = SharePointSite;
@@ -58,7 +58,7 @@ class StatusCodeError extends Error {
58
58
  * @param {object} details underlying error
59
59
  */
60
60
  constructor(msg, statusCode, details) {
61
- super(msg);
61
+ super(msg?.value ?? msg);
62
62
  this.statusCode = statusCode;
63
63
  this.details = details;
64
64
  }
package/src/Table.d.ts CHANGED
@@ -9,7 +9,14 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- import { GraphResult } from './OneDrive';
12
+ import {DriveItem, GraphResult} from './OneDrive';
13
+
14
+ export declare interface FormatOptions {
15
+ /**
16
+ * Trims the object names and values and strips zero-width unicode characters.
17
+ */
18
+ trim?:boolean;
19
+ }
13
20
 
14
21
  /**
15
22
  * Excel Table
@@ -37,7 +44,7 @@ export declare interface Table {
37
44
  * Returns the rows as a list of objects. the rows have the columns names as property names
38
45
  * and the row values as value.
39
46
  */
40
- getRowsAsObjects(): Promise<Array<object>>;
47
+ getRowsAsObjects(opts?:FormatOptions): Promise<Array<object>>;
41
48
 
42
49
  /**
43
50
  * Return a row given its index
@@ -85,5 +92,5 @@ export declare interface Table {
85
92
  * @param index zero-based index or missing to add at end
86
93
  * @returns new column
87
94
  */
88
- addColumn(name, index?): Promise<DriveItem>;
95
+ addColumn(name:string, index?:number): Promise<DriveItem>;
89
96
  }
package/src/Table.js CHANGED
@@ -9,6 +9,8 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
+ const { superTrim } = require('./utils.js');
13
+
12
14
  class Table {
13
15
  constructor(oneDrive, prefix, name, log) {
14
16
  this._oneDrive = oneDrive;
@@ -39,7 +41,7 @@ class Table {
39
41
  return result.value.map((v) => v.values[0]);
40
42
  }
41
43
 
42
- async getRowsAsObjects() {
44
+ async getRowsAsObjects({ trim = false } = {}) {
43
45
  const { log } = this;
44
46
  this.log.debug(`get columns from ${this.uri}/columns`);
45
47
  const result = await this._oneDrive.doFetch(`${this.uri}/columns`);
@@ -49,8 +51,13 @@ class Table {
49
51
  const rowValues = result.value[0].values
50
52
  .map((_, rownum) => columnNames.reduce((row, name, column) => {
51
53
  const [value] = result.value[column].values[rownum];
52
- // eslint-disable-next-line no-param-reassign
53
- row[name] = value;
54
+ if (trim) {
55
+ // eslint-disable-next-line no-param-reassign
56
+ row[superTrim(name)] = superTrim(value);
57
+ } else {
58
+ // eslint-disable-next-line no-param-reassign
59
+ row[name] = value;
60
+ }
54
61
  return row;
55
62
  }, {}));
56
63
 
package/src/utils.js CHANGED
@@ -10,6 +10,23 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ /**
14
+ * Trims the string at both ends and removes the zero width unicode chars:
15
+ *
16
+ * - U+200B zero width space
17
+ * - U+200C zero width non-joiner Unicode code point
18
+ * - U+200D zero width joiner Unicode code point
19
+ * - U+FEFF zero width no-break space Unicode code point
20
+ *
21
+ * @param {string} str input string
22
+ * @return {string} trimmed and stripped string
23
+ */
24
+ function superTrim(str) {
25
+ return String(str)
26
+ .replace(/[\u200B-\u200D\uFEFF]/g, '')
27
+ .trim();
28
+ }
29
+
13
30
  /**
14
31
  * Returns a onedrive uri for the given drive item. the uri has the format:
15
32
  * `onedrive:/drives/<driveId>/items/<itemId>`
@@ -55,4 +72,5 @@ function driveItemFromURL(url) {
55
72
  module.exports = {
56
73
  driveItemFromURL,
57
74
  driveItemToURL,
75
+ superTrim,
58
76
  };