Skip to content

Commit 56bdff7

Browse files
Add public function to normalize device-info field values (#129)
* Normalize device-info field values * fix broken tests * restore working package-lock
1 parent c522ed1 commit 56bdff7

4 files changed

Lines changed: 78 additions & 20 deletions

File tree

src/RokuDeploy.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,29 @@ describe('index', () => {
536536
});
537537
});
538538

539+
describe('normalizeDeviceInfoFieldValue', () => {
540+
it('converts normal values', () => {
541+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('true')).to.eql(true);
542+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('false')).to.eql(false);
543+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('1')).to.eql(1);
544+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('1.2')).to.eql(1.2);
545+
//it'll trim whitespace too
546+
expect(rokuDeploy.normalizeDeviceInfoFieldValue(' 1.2')).to.eql(1.2);
547+
expect(rokuDeploy.normalizeDeviceInfoFieldValue(' 1.2 ')).to.eql(1.2);
548+
});
549+
550+
it('leaves invalid numbers as strings', () => {
551+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('v1.2.3')).to.eql('v1.2.3');
552+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('1.2.3-alpha.1')).to.eql('1.2.3-alpha.1');
553+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('123Four')).to.eql('123Four');
554+
});
555+
556+
it('decodes HTML entities', () => {
557+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('3&4')).to.eql('3&4');
558+
expect(rokuDeploy.normalizeDeviceInfoFieldValue('3&4')).to.eql('3&4');
559+
});
560+
});
561+
539562

540563
describe('getDevId', () => {
541564
it('should return the current Dev ID if successful', async () => {

src/RokuDeploy.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,27 +1011,10 @@ export class RokuDeploy {
10111011
} as Record<string, any>;
10121012

10131013
if (options.enhance) {
1014-
// convert 'true' and 'false' string values to boolean
1015-
for (let key in deviceInfo) {
1016-
if (deviceInfo[key] === 'true') {
1017-
deviceInfo[key] = true;
1018-
} else if (deviceInfo[key] === 'false') {
1019-
deviceInfo[key] = false;
1020-
}
1021-
}
1022-
1023-
// convert the following string values into numbers
1024-
const numberFields = ['software-build', 'uptime', 'trc-version', 'av-sync-calibration-enabled', 'time-zone-offset'];
1025-
for (const field of numberFields) {
1026-
if (deviceInfo.hasOwnProperty(field)) {
1027-
deviceInfo[field] = parseInt(deviceInfo[field]);
1028-
}
1029-
}
1030-
1031-
//convert the property names to camel case
10321014
const result = {};
1033-
for (const key in deviceInfo) {
1034-
result[lodash.camelCase(key)] = deviceInfo[key];
1015+
// sanitize/normalize values to their native formats, and also convert property names to camelCase
1016+
for (let key in deviceInfo) {
1017+
result[lodash.camelCase(key)] = this.normalizeDeviceInfoFieldValue(deviceInfo[key]);
10351018
}
10361019
deviceInfo = result;
10371020
}
@@ -1041,6 +1024,25 @@ export class RokuDeploy {
10411024
}
10421025
}
10431026

1027+
/**
1028+
* Normalize a deviceInfo field value. This includes things like converting boolean strings to booleans, number strings to numbers,
1029+
* decoding HtmlEntities, etc.
1030+
* @param deviceInfo
1031+
*/
1032+
public normalizeDeviceInfoFieldValue(value: any) {
1033+
let num: number;
1034+
// convert 'true' and 'false' string values to boolean
1035+
if (value === 'true') {
1036+
return true;
1037+
} else if (value === 'false') {
1038+
return false;
1039+
} else if (value.trim() !== '' && !isNaN(num = Number(value))) {
1040+
return num;
1041+
} else {
1042+
return util.decodeHtmlEntities(value);
1043+
}
1044+
}
1045+
10441046
public async getDevId(options?: RokuDeployOptions) {
10451047
const deviceInfo = await this.getDeviceInfo(options as any);
10461048
return deviceInfo['keyed-developer-id'];

src/util.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,4 +274,15 @@ describe('util', () => {
274274
).to.eql('some-host');
275275
});
276276
});
277+
278+
describe('decodeHtmlEntities', () => {
279+
it('decodes values properly', () => {
280+
expect(util.decodeHtmlEntities('&nbsp;')).to.eql(' ');
281+
expect(util.decodeHtmlEntities('&amp;')).to.eql('&');
282+
expect(util.decodeHtmlEntities('&quot;')).to.eql('"');
283+
expect(util.decodeHtmlEntities('&lt;')).to.eql('<');
284+
expect(util.decodeHtmlEntities('&gt;')).to.eql('>');
285+
expect(util.decodeHtmlEntities('&#39;')).to.eql(`'`);
286+
});
287+
});
277288
});

src/util.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,28 @@ export class Util {
206206
}
207207

208208
private dnsCache = new Map<string, string>();
209+
210+
/**
211+
* Decode HTML entities like &nbsp; &#39; to its original character
212+
*/
213+
public decodeHtmlEntities(encodedString: string) {
214+
let translateRegex = /&(nbsp|amp|quot|lt|gt);/g;
215+
let translate = {
216+
'nbsp': ' ',
217+
'amp': '&',
218+
'quot': '"',
219+
'lt': '<',
220+
'gt': '>'
221+
};
222+
223+
return encodedString.replace(translateRegex, (match, entity) => {
224+
return translate[entity];
225+
}).replace(/&#(\d+);/gi, (match, numStr) => {
226+
let num = parseInt(numStr, 10);
227+
return String.fromCharCode(num);
228+
});
229+
}
230+
209231
}
210232

211233
export let util = new Util();

0 commit comments

Comments
 (0)