Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix isDeviceDefinition and improve check in createOrUpdateAccessory #797

Merged
merged 3 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .release-it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ github:
git:
requireCommits: true
requireCleanWorkingDir: true
requireBranch: master
requireBranch:
- master
- main
- release-*
- hotfix-*

hooks:
"before:init":
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ Since version 1.0.0, we try to follow the [Semantic Versioning](https://semver.o

## [Unreleased]

### Fixed

- Type checks on Z2M models now explicitly check that the input is not null or undefined, to prevent crashes when we get unexpected data. (see [#794](https://github.com/itavero/homebridge-z2m/issues/794))
- When creating or updating an accessory, previously it was only checked if the device definition was not undefined. Now we check if it seems to be a valid device definition. (see [#794](https://github.com/itavero/homebridge-z2m/issues/794))

## [1.9.2] - 2022-10-01

### Fixed
Expand Down
5 changes: 3 additions & 2 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
import * as mqtt from 'mqtt';
import * as fs from 'fs';
import {
DeviceListEntry, DeviceListEntryForGroup, ExposesEntry, exposesGetOverlap, GroupListEntry, isDeviceListEntry, isDeviceListEntryForGroup,
DeviceListEntry, DeviceListEntryForGroup, ExposesEntry, exposesGetOverlap, GroupListEntry, isDeviceDefinition, isDeviceListEntry,
isDeviceListEntryForGroup,
} from './z2mModels';
import * as semver from 'semver';
import { errorToString } from './helpers';
Expand Down Expand Up @@ -422,7 +423,7 @@ export class Zigbee2mqttPlatform implements DynamicPlatformPlugin {
}

private createOrUpdateAccessory(device: DeviceListEntry) {
if (!device.supported || device.definition === undefined || this.isDeviceExcluded(device)) {
if (!device.supported || !isDeviceDefinition(device.definition) || this.isDeviceExcluded(device)) {
return;
}
const uuid_input = isDeviceListEntryForGroup(device) ? `group-${device.group_id}` : device.ieee_address;
Expand Down
19 changes: 12 additions & 7 deletions src/z2mModels.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export declare type MqttValue = string | boolean | number;

const isNullOrUndefined = (x: unknown): x is null | undefined =>
x === null || x === undefined;

export interface ExposesEntry {
type: string;
name?: string;
Expand Down Expand Up @@ -38,7 +41,7 @@ export enum ExposesKnownTypes {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isExposesEntry = (x: any): x is ExposesEntry => {
if (x === undefined || x.type === undefined) {
if (isNullOrUndefined(x) || isNullOrUndefined(x.type)) {
return false;
}

Expand Down Expand Up @@ -291,7 +294,8 @@ export interface DeviceDefinition {
exposes: ExposesEntry[];
}

export const isDeviceDefinition = (x: any): x is DeviceDefinition => (x.vendor && x.model && Array.isArray(x.exposes));
export const isDeviceDefinition = (x: any): x is DeviceDefinition =>
!isNullOrUndefined(x) && x.vendor && x.model && Array.isArray(x.exposes);

export interface DeviceListEntry {
definition?: DeviceDefinition | null;
Expand All @@ -306,9 +310,8 @@ export interface DeviceListEntryForGroup extends DeviceListEntry {
group_id: number;
}

const isNullOrUndefined = (x: unknown): x is null | undefined => (x === null || x === undefined);

export const isDeviceListEntry = (x: any): x is DeviceListEntry => (x.ieee_address && x.friendly_name && x.supported);
export const isDeviceListEntry = (x: any): x is DeviceListEntry =>
!isNullOrUndefined(x) && x.ieee_address && x.friendly_name && x.supported;
export const isDeviceListEntryForGroup = (x: any): x is DeviceListEntryForGroup => {
return (isDeviceListEntry(x) && 'group_id' in x && typeof x['group_id'] === 'number');
};
Expand Down Expand Up @@ -339,11 +342,13 @@ export interface GroupMember {
endpoint: number;
}

export const isGroupMember = (x: any): x is GroupMember => (x.ieee_address && x.endpoint);
export const isGroupMember = (x: any): x is GroupMember =>
!isNullOrUndefined(x) && x.ieee_address && x.endpoint;
export interface GroupListEntry {
friendly_name: string;
id: number;
members: GroupMember[];
}

export const isGroupListEntry = (x: any): x is GroupListEntry => (x.id && x.friendly_name && x.members);
export const isGroupListEntry = (x: any): x is GroupListEntry =>
!isNullOrUndefined(x) && x.id && x.friendly_name && x.members;
52 changes: 52 additions & 0 deletions test/z2mModels.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'jest-chain';
import 'jest';
import {
isDeviceDefinition,
isDeviceListEntry,
isExposesEntry,
isGroupListEntry,
isGroupMember,
} from '../src/z2mModels';

describe('z2mModels', () => {
describe('isDeviceDefinition', () => {
it('does check on null', () => {
expect(isDeviceDefinition(null)).toBe(false);
});
it('does check on undefined', () => {
expect(isDeviceDefinition(undefined)).toBe(false);
});
});
describe('isDeviceListEntry', () => {
it('does check on null', () => {
expect(isDeviceListEntry(null)).toBe(false);
});
it('does check on undefined', () => {
expect(isDeviceListEntry(undefined)).toBe(false);
});
});
describe('isExposesEntry', () => {
it('does check on null', () => {
expect(isExposesEntry(null)).toBe(false);
});
it('does check on undefined', () => {
expect(isExposesEntry(undefined)).toBe(false);
});
});
describe('isGroupListEntry', () => {
it('does check on null', () => {
expect(isGroupListEntry(null)).toBe(false);
});
it('does check on undefined', () => {
expect(isGroupListEntry(undefined)).toBe(false);
});
});
describe('isGroupMember', () => {
it('does check on null', () => {
expect(isGroupMember(null)).toBe(false);
});
it('does check on undefined', () => {
expect(isGroupMember(undefined)).toBe(false);
});
});
});