Skip to content

Commit 63ef5c2

Browse files
Implement warning messages for invalid characters in names (#1009)
1 parent 5122684 commit 63ef5c2

File tree

6 files changed

+308
-0
lines changed

6 files changed

+308
-0
lines changed

src/lib/Accessory.spec.ts

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,229 @@ describe("Accessory", () => {
408408
});
409409
});
410410

411+
describe("Accessory and Service naming checks", () => {
412+
let consoleLogSpy: jest.SpyInstance;
413+
414+
beforeEach(() => {
415+
consoleLogSpy = jest.spyOn(console, "warn");
416+
});
417+
418+
afterEach(() => {
419+
consoleLogSpy.mockRestore();
420+
});
421+
422+
test("Accessory Name ending with !", async () => {
423+
const accessoryBadName = new Accessory("Bad Name!",uuid.generate("Bad Name"));
424+
425+
const publishInfo: PublishInfo = {
426+
username: serverUsername,
427+
pincode: "000-00-000",
428+
category: Categories.SWITCH,
429+
advertiser: undefined,
430+
};
431+
432+
await accessoryBadName.publish(publishInfo);
433+
// eslint-disable-next-line max-len
434+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'Bad Name! 7430' is getting published with the characteristic 'Name' not following HomeKit naming rules ('Bad Name! 7430'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
435+
436+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
437+
await accessoryBadName?.unpublish();
438+
await accessoryBadName?.destroy();
439+
});
440+
441+
test("Accessory Name containing !", async () => {
442+
const accessoryBadName = new Accessory("Bad ! Name",uuid.generate("Bad Name"));
443+
444+
const publishInfo: PublishInfo = {
445+
username: serverUsername,
446+
pincode: "000-00-000",
447+
category: Categories.SWITCH,
448+
advertiser: undefined,
449+
};
450+
451+
await accessoryBadName.publish(publishInfo);
452+
// eslint-disable-next-line max-len
453+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'Bad ! Name 7430' is getting published with the characteristic 'Name' not following HomeKit naming rules ('Bad ! Name 7430'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
454+
455+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
456+
await accessoryBadName?.unpublish();
457+
await accessoryBadName?.destroy();
458+
});
459+
460+
test("Accessory Name containing '", async () => {
461+
const accessoryBadName = new Accessory("Bad ' Name",uuid.generate("Bad ' Name"));
462+
463+
const publishInfo: PublishInfo = {
464+
username: serverUsername,
465+
pincode: "000-00-000",
466+
category: Categories.SWITCH,
467+
advertiser: undefined,
468+
};
469+
470+
await accessoryBadName.publish(publishInfo);
471+
expect(consoleLogSpy).toBeCalledTimes(0);
472+
473+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
474+
await accessoryBadName?.unpublish();
475+
await accessoryBadName?.destroy();
476+
});
477+
478+
test("Accessory Name starting with '", async () => {
479+
const accessoryBadName = new Accessory("'Bad Name",uuid.generate("Bad Name'"));
480+
481+
const publishInfo: PublishInfo = {
482+
username: serverUsername,
483+
pincode: "000-00-000",
484+
category: Categories.SWITCH,
485+
advertiser: undefined,
486+
};
487+
488+
await accessoryBadName.publish(publishInfo);
489+
expect(accessoryBadName.displayName.startsWith(TEST_DISPLAY_NAME));
490+
expect(consoleLogSpy).toBeCalledTimes(2);
491+
// eslint-disable-next-line max-len
492+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory ''Bad Name 7430' is getting published with the characteristic 'Name' not following HomeKit naming rules (''Bad Name 7430'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
493+
494+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
495+
await accessoryBadName?.unpublish();
496+
await accessoryBadName?.destroy();
497+
});
498+
499+
test("Service Name containing !", async () => {
500+
const switchService = new Service.Switch("My Bad ! Switch");
501+
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
502+
accessoryBadName.addService(switchService);
503+
504+
const publishInfo: PublishInfo = {
505+
username: serverUsername,
506+
pincode: "000-00-000",
507+
category: Categories.SWITCH,
508+
advertiser: undefined,
509+
};
510+
511+
await accessoryBadName.publish(publishInfo);
512+
// eslint-disable-next-line max-len
513+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad ! Switch' is getting published with the characteristic 'Name' not following HomeKit naming rules ('My Bad ! Switch'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
514+
515+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
516+
await accessoryBadName?.unpublish();
517+
await accessoryBadName?.destroy();
518+
});
519+
520+
test("Service Name ending with !", async () => {
521+
const switchService = new Service.Switch("My Bad Switch!");
522+
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
523+
accessoryBadName.addService(switchService);
524+
525+
const publishInfo: PublishInfo = {
526+
username: serverUsername,
527+
pincode: "000-00-000",
528+
category: Categories.SWITCH,
529+
advertiser: undefined,
530+
};
531+
532+
await accessoryBadName.publish(publishInfo);
533+
expect(consoleLogSpy).toBeCalledTimes(1);
534+
// eslint-disable-next-line max-len
535+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad Switch!' is getting published with the characteristic 'Name' not following HomeKit naming rules ('My Bad Switch!'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
536+
537+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
538+
await accessoryBadName?.unpublish();
539+
await accessoryBadName?.destroy();
540+
});
541+
542+
test("Service Name containing '", async () => {
543+
const switchService = new Service.Switch("My Bad ' Switch");
544+
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
545+
accessoryBadName.addService(switchService);
546+
547+
const publishInfo: PublishInfo = {
548+
username: serverUsername,
549+
pincode: "000-00-000",
550+
category: Categories.SWITCH,
551+
advertiser: undefined,
552+
};
553+
554+
await accessoryBadName.publish(publishInfo);
555+
expect(consoleLogSpy).toBeCalledTimes(0);
556+
557+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
558+
await accessoryBadName?.unpublish();
559+
await accessoryBadName?.destroy();
560+
});
561+
562+
test("Service Name ending with '", async () => {
563+
const switchService = new Service.Switch("My Bad Switch'");
564+
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
565+
accessoryBadName.addService(switchService);
566+
567+
const publishInfo: PublishInfo = {
568+
username: serverUsername,
569+
pincode: "000-00-000",
570+
category: Categories.SWITCH,
571+
advertiser: undefined,
572+
};
573+
574+
await accessoryBadName.publish(publishInfo);
575+
expect(consoleLogSpy).toBeCalledTimes(1);
576+
// eslint-disable-next-line max-len
577+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad Switch'' is getting published with the characteristic 'Name' not following HomeKit naming rules ('My Bad Switch''). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
578+
579+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
580+
await accessoryBadName?.unpublish();
581+
await accessoryBadName?.destroy();
582+
});
583+
584+
test("Service Name beginning with '", async () => {
585+
const switchService = new Service.Switch("'My Bad Switch");
586+
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
587+
accessoryBadName.addService(switchService);
588+
589+
const publishInfo: PublishInfo = {
590+
username: serverUsername,
591+
pincode: "000-00-000",
592+
category: Categories.SWITCH,
593+
advertiser: undefined,
594+
};
595+
596+
await accessoryBadName.publish(publishInfo);
597+
expect(consoleLogSpy).toBeCalledTimes(1);
598+
// eslint-disable-next-line max-len
599+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory ''My Bad Switch' is getting published with the characteristic 'Name' not following HomeKit naming rules (''My Bad Switch'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
600+
601+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
602+
await accessoryBadName?.unpublish();
603+
await accessoryBadName?.destroy();
604+
});
605+
606+
test("Service ConfiguredName beginning with '", async () => {
607+
const switchService = new Service.Switch("My Bad Switch");
608+
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
609+
switchService.addCharacteristic(Characteristic.ConfiguredName);
610+
accessoryBadName.addService(switchService);
611+
612+
const publishInfo: PublishInfo = {
613+
username: serverUsername,
614+
pincode: "000-00-000",
615+
category: Categories.SWITCH,
616+
advertiser: undefined,
617+
};
618+
619+
await accessoryBadName.publish(publishInfo);
620+
621+
switchService.getCharacteristic(Characteristic.ConfiguredName).updateValue("'Bad Name");
622+
623+
expect(consoleLogSpy).toBeCalledTimes(1);
624+
// eslint-disable-next-line max-len
625+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'unknown' is getting published with the characteristic 'Configured Name' not following HomeKit naming rules (''Bad Name'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
626+
627+
await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
628+
await accessoryBadName?.unpublish();
629+
await accessoryBadName?.destroy();
630+
});
631+
632+
});
633+
411634
describe("pairing", () => {
412635
let defaultPairingInfo: PairingInformation;
413636

src/lib/Accessory.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import { EventName, HAPConnection, HAPUsername } from "./util/eventedhttp";
7474
import { formatOutgoingCharacteristicValue } from "./util/request-util";
7575
import * as uuid from "./util/uuid";
7676
import { toShortForm } from "./util/uuid";
77+
import { checkName } from "./util/checkName";
7778

7879
const debug = createDebug("HAP-NodeJS:Accessory");
7980
const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge.
@@ -498,6 +499,7 @@ export class Accessory extends EventEmitter {
498499
"valid UUID from any arbitrary string, like a serial number.");
499500

500501
// create our initial "Accessory Information" Service that all Accessories are expected to have
502+
checkName(this.displayName, "name", displayName);
501503
this.addService(Service.AccessoryInformation)
502504
.setCharacteristic(Characteristic.Name, displayName);
503505

@@ -1053,11 +1055,14 @@ export class Accessory extends EventEmitter {
10531055
const serialNumber = service.getCharacteristic(Characteristic.SerialNumber).value;
10541056
const firmwareRevision = service.getCharacteristic(Characteristic.FirmwareRevision).value;
10551057
const name = service.getCharacteristic(Characteristic.Name).value;
1058+
const manufacturer = service.getCharacteristic(Characteristic.Manufacturer).value;
10561059

10571060
checkValue("Model", model);
10581061
checkValue("SerialNumber", serialNumber);
10591062
checkValue("FirmwareRevision", firmwareRevision);
10601063
checkValue("Name", name);
1064+
checkName(this.displayName, "Name", name);
1065+
checkName(this.displayName, "Manufacturer", manufacturer);
10611066
}
10621067

10631068
if (mainAccessory) {

src/lib/Characteristic.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ import {
276276
numericUpperBound,
277277
} from "./util/request-util";
278278
import { BASE_UUID, toShortForm } from "./util/uuid";
279+
import { checkName } from "./util/checkName";
279280

280281
const debug = createDebug("HAP-NodeJS:Characteristic");
281282

@@ -2976,6 +2977,10 @@ export class Characteristic extends EventEmitter {
29762977
value = value.substring(0, maxLength);
29772978
}
29782979

2980+
if (this.UUID === "000000E3-0000-1000-8000-0026BB765291") {
2981+
checkName("unknown", this.displayName, value);
2982+
}
2983+
29792984
return value;
29802985
}
29812986
case Formats.DATA:

src/lib/Service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ import { IdentifierCache } from "./model/IdentifierCache";
8787
import { HAPConnection } from "./util/eventedhttp";
8888
import { HapStatusError } from "./util/hapStatusError";
8989
import { toShortForm } from "./util/uuid";
90+
import { checkName } from "./util/checkName";
9091

9192
const debug = createDebug("HAP-NodeJS:Service");
9293

@@ -553,6 +554,7 @@ export class Service extends EventEmitter {
553554
// if you don't provide a display name, some HomeKit apps may choose to hide the device.
554555
if (displayName) {
555556
// create the characteristic if necessary
557+
checkName(this.displayName, "Name", displayName);
556558
const nameCharacteristic =
557559
this.getCharacteristic(Characteristic.Name) ||
558560
this.addCharacteristic(Characteristic.Name);

src/lib/util/checkName.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { checkName } from "./checkName";
2+
3+
describe("#checkName()", () => {
4+
let consoleLogSpy: jest.SpyInstance;
5+
6+
beforeEach(() => {
7+
consoleLogSpy = jest.spyOn(console, "warn");
8+
});
9+
10+
afterEach(() => {
11+
consoleLogSpy.mockRestore();
12+
});
13+
14+
test("Accessory Name ending with !", async () => {
15+
checkName("displayName", "name", "bad name!");
16+
17+
expect(consoleLogSpy).toBeCalledTimes(1);
18+
// eslint-disable-next-line max-len
19+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules ('bad name!'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
20+
});
21+
22+
test("Accessory Name begining with !", async () => {
23+
checkName("displayName", "name", "!bad name");
24+
25+
expect(consoleLogSpy).toBeCalledTimes(1);
26+
// eslint-disable-next-line max-len
27+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules ('!bad name'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
28+
});
29+
30+
test("Accessory Name containing !", async () => {
31+
checkName("displayName", "name", "bad ! name");
32+
33+
expect(consoleLogSpy).toBeCalledTimes(1);
34+
// eslint-disable-next-line max-len
35+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules ('bad ! name'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
36+
});
37+
38+
test("Accessory Name begining with '", async () => {
39+
checkName("displayName", "name", "'bad name");
40+
41+
expect(consoleLogSpy).toBeCalledTimes(1);
42+
// eslint-disable-next-line max-len
43+
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules (''bad name'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
44+
});
45+
46+
test("Accessory Name containing '", async () => {
47+
checkName("displayName", "name", "bad ' name");
48+
49+
expect(consoleLogSpy).toBeCalledTimes(0);
50+
// eslint-disable-next-line max-len
51+
// expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules ('bad name!'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
52+
});
53+
54+
});

src/lib/util/checkName.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Checks that supplied field meets Apple HomeKit naming rules
3+
* https://developer.apple.com/design/human-interface-guidelines/homekit#Help-people-choose-useful-names
4+
* @private Private API
5+
*/
6+
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
8+
export function checkName(displayName: string, name: string, value: any): void {
9+
const validHK = /^[a-zA-Z0-9\s'-.]+$/; // Ensure only letter, numbers, apostrophe, or dash
10+
const startWith = /^[a-zA-Z0-9]/; // Ensure only letters or numbers are at the beginning of string
11+
const endWith = /[a-zA-Z0-9]$/; // Ensure only letters or numbers are at the end of string
12+
13+
if (!validHK.test(value) || !startWith.test(value) || !endWith.test(value)) {
14+
console.warn("HAP-NodeJS WARNING: The accessory '" + displayName + "' is getting published with the characteristic '" +
15+
name + "'" + " not following HomeKit naming rules ('" + value + "'). " +
16+
"Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. " +
17+
"This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
18+
}
19+
}

0 commit comments

Comments
 (0)