Skip to content

Commit

Permalink
Implement warning messages for invalid characters in names (#1009)
Browse files Browse the repository at this point in the history
  • Loading branch information
NorthernMan54 authored Jun 23, 2024
1 parent 5122684 commit 63ef5c2
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 0 deletions.
223 changes: 223 additions & 0 deletions src/lib/Accessory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,229 @@ describe("Accessory", () => {
});
});

describe("Accessory and Service naming checks", () => {
let consoleLogSpy: jest.SpyInstance;

beforeEach(() => {
consoleLogSpy = jest.spyOn(console, "warn");
});

afterEach(() => {
consoleLogSpy.mockRestore();
});

test("Accessory Name ending with !", async () => {
const accessoryBadName = new Accessory("Bad Name!",uuid.generate("Bad Name"));

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
// eslint-disable-next-line max-len
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!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Accessory Name containing !", async () => {
const accessoryBadName = new Accessory("Bad ! Name",uuid.generate("Bad Name"));

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
// eslint-disable-next-line max-len
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!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Accessory Name containing '", async () => {
const accessoryBadName = new Accessory("Bad ' Name",uuid.generate("Bad ' Name"));

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(0);

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Accessory Name starting with '", async () => {
const accessoryBadName = new Accessory("'Bad Name",uuid.generate("Bad Name'"));

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(accessoryBadName.displayName.startsWith(TEST_DISPLAY_NAME));
expect(consoleLogSpy).toBeCalledTimes(2);
// eslint-disable-next-line max-len
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!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name containing !", async () => {
const switchService = new Service.Switch("My Bad ! Switch");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
// eslint-disable-next-line max-len
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!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name ending with !", async () => {
const switchService = new Service.Switch("My Bad Switch!");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
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!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name containing '", async () => {
const switchService = new Service.Switch("My Bad ' Switch");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(0);

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name ending with '", async () => {
const switchService = new Service.Switch("My Bad Switch'");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
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!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name beginning with '", async () => {
const switchService = new Service.Switch("'My Bad Switch");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
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!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service ConfiguredName beginning with '", async () => {
const switchService = new Service.Switch("My Bad Switch");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
switchService.addCharacteristic(Characteristic.ConfiguredName);
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);

switchService.getCharacteristic(Characteristic.ConfiguredName).updateValue("'Bad Name");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
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!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

});

describe("pairing", () => {
let defaultPairingInfo: PairingInformation;

Expand Down
5 changes: 5 additions & 0 deletions src/lib/Accessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import { EventName, HAPConnection, HAPUsername } from "./util/eventedhttp";
import { formatOutgoingCharacteristicValue } from "./util/request-util";
import * as uuid from "./util/uuid";
import { toShortForm } from "./util/uuid";
import { checkName } from "./util/checkName";

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

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

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

checkValue("Model", model);
checkValue("SerialNumber", serialNumber);
checkValue("FirmwareRevision", firmwareRevision);
checkValue("Name", name);
checkName(this.displayName, "Name", name);
checkName(this.displayName, "Manufacturer", manufacturer);
}

if (mainAccessory) {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/Characteristic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ import {
numericUpperBound,
} from "./util/request-util";
import { BASE_UUID, toShortForm } from "./util/uuid";
import { checkName } from "./util/checkName";

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

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

if (this.UUID === "000000E3-0000-1000-8000-0026BB765291") {
checkName("unknown", this.displayName, value);
}

return value;
}
case Formats.DATA:
Expand Down
2 changes: 2 additions & 0 deletions src/lib/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import { IdentifierCache } from "./model/IdentifierCache";
import { HAPConnection } from "./util/eventedhttp";
import { HapStatusError } from "./util/hapStatusError";
import { toShortForm } from "./util/uuid";
import { checkName } from "./util/checkName";

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

Expand Down Expand Up @@ -553,6 +554,7 @@ export class Service extends EventEmitter {
// if you don't provide a display name, some HomeKit apps may choose to hide the device.
if (displayName) {
// create the characteristic if necessary
checkName(this.displayName, "Name", displayName);
const nameCharacteristic =
this.getCharacteristic(Characteristic.Name) ||
this.addCharacteristic(Characteristic.Name);
Expand Down
54 changes: 54 additions & 0 deletions src/lib/util/checkName.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { checkName } from "./checkName";

describe("#checkName()", () => {
let consoleLogSpy: jest.SpyInstance;

beforeEach(() => {
consoleLogSpy = jest.spyOn(console, "warn");
});

afterEach(() => {
consoleLogSpy.mockRestore();
});

test("Accessory Name ending with !", async () => {
checkName("displayName", "name", "bad name!");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
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!");
});

test("Accessory Name begining with !", async () => {
checkName("displayName", "name", "!bad name");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
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!");
});

test("Accessory Name containing !", async () => {
checkName("displayName", "name", "bad ! name");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
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!");
});

test("Accessory Name begining with '", async () => {
checkName("displayName", "name", "'bad name");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
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!");
});

test("Accessory Name containing '", async () => {
checkName("displayName", "name", "bad ' name");

expect(consoleLogSpy).toBeCalledTimes(0);
// eslint-disable-next-line max-len
// 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!");
});

});
19 changes: 19 additions & 0 deletions src/lib/util/checkName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Checks that supplied field meets Apple HomeKit naming rules
* https://developer.apple.com/design/human-interface-guidelines/homekit#Help-people-choose-useful-names
* @private Private API
*/

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export function checkName(displayName: string, name: string, value: any): void {
const validHK = /^[a-zA-Z0-9\s'-.]+$/; // Ensure only letter, numbers, apostrophe, or dash
const startWith = /^[a-zA-Z0-9]/; // Ensure only letters or numbers are at the beginning of string
const endWith = /[a-zA-Z0-9]$/; // Ensure only letters or numbers are at the end of string

if (!validHK.test(value) || !startWith.test(value) || !endWith.test(value)) {
console.warn("HAP-NodeJS WARNING: The accessory '" + displayName + "' is getting published with the characteristic '" +
name + "'" + " not following HomeKit naming rules ('" + value + "'). " +
"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!");
}
}

0 comments on commit 63ef5c2

Please sign in to comment.