From cebc3f3cafa1ba759dc77b6a6cddeb932721231a Mon Sep 17 00:00:00 2001 From: Jacky Liang Date: Sat, 30 Dec 2023 20:26:16 -0500 Subject: [PATCH] Fixed unknown action detector ### FIXED - Plugin sent the context instead of the panel/sensor actions. --- package.json | 2 +- src/lib/accessory.ts | 49 +++++++-- src/lib/detect.ts | 255 ++++++++++++++++++++++++++++--------------- src/lib/utility.ts | 1 - src/types/index.d.ts | 49 ++++++--- 5 files changed, 244 insertions(+), 112 deletions(-) diff --git a/package.json b/package.json index 5945706..e524557 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "homebridge-adt-pulse", "displayName": "Homebridge ADT Pulse", - "version": "3.0.0-beta.13", + "version": "3.0.0-beta.14", "description": "Homebridge security system platform for ADT Pulse", "main": "./build/src/index.js", "exports": "./build/src/index.js", diff --git a/src/lib/accessory.ts b/src/lib/accessory.ts index 803ee69..bbb2360 100644 --- a/src/lib/accessory.ts +++ b/src/lib/accessory.ts @@ -1,6 +1,6 @@ import chalk from 'chalk'; -import { detectedNewDeviceContext } from '@/lib/detect.js'; +import { detectedUnknownPanelAction, detectedUnknownSensorAction } from '@/lib/detect.js'; import { condensedSensorTypeItems } from '@/lib/items.js'; import { condensePanelStates, generateHash } from '@/lib/utility.js'; import type { @@ -23,8 +23,11 @@ import type { ADTPulseAccessoryGetSensorStatusReturns, ADTPulseAccessoryInstance, ADTPulseAccessoryLog, - ADTPulseAccessoryNewInformationDispatcherContext, + ADTPulseAccessoryNewInformationDispatcherData, + ADTPulseAccessoryNewInformationDispatcherPanel, ADTPulseAccessoryNewInformationDispatcherReturns, + ADTPulseAccessoryNewInformationDispatcherSensor, + ADTPulseAccessoryNewInformationDispatcherType, ADTPulseAccessoryReportedHashes, ADTPulseAccessoryServices, ADTPulseAccessorySetPanelStatusArm, @@ -302,11 +305,11 @@ export class ADTPulseAccessory { zone, } = context; - const sensor = this.#state.data.sensorsStatus.find((sensorStatus) => zone !== null && sensorStatus.name === name && sensorStatus.zone === zone); + const matchedSensorStatus = this.#state.data.sensorsStatus.find((sensorStatus) => zone !== null && sensorStatus.name === name && sensorStatus.zone === zone); // If sensor is not found or sensor type is not supported. if ( - sensor === undefined + matchedSensorStatus === undefined || type === 'gateway' || type === 'panel' || !condensedSensorTypeItems.includes(type) @@ -316,7 +319,7 @@ export class ADTPulseAccessory { throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.RESOURCE_DOES_NOT_EXIST); } - const { statuses } = sensor; + const { statuses } = matchedSensorStatus; // If sensor is currently "Offline" or "Unknown". if (statuses.includes('Offline') || statuses.includes('Unknown')) { @@ -387,7 +390,7 @@ export class ADTPulseAccessory { this.#log.warn(`Attempted to get sensor status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but actions have not been implemented yet.`); - await this.newInformationDispatcher(context); + await this.newInformationDispatcher(type, matchedSensorStatus); throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.INVALID_VALUE_IN_REQUEST); } @@ -460,7 +463,7 @@ export class ADTPulseAccessory { this.#log.warn(`Attempted to get panel status on ${chalk.underline(name)} (id: ${id}, uuid: ${uuid}) accessory but actions have not been implemented yet.`); - await this.newInformationDispatcher(context); + await this.newInformationDispatcher(type, this.#state.data.panelStatus); throw new this.#api.hap.HapStatusError(this.#api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); } @@ -552,7 +555,8 @@ export class ADTPulseAccessory { /** * ADT Pulse Accessory - New information dispatcher. * - * @param {ADTPulseAccessoryNewInformationDispatcherContext} context - Context. + * @param {ADTPulseAccessoryNewInformationDispatcherType} type - Type. + * @param {ADTPulseAccessoryNewInformationDispatcherData} data - Data. * * @private * @@ -560,12 +564,35 @@ export class ADTPulseAccessory { * * @since 1.0.0 */ - private async newInformationDispatcher(context: ADTPulseAccessoryNewInformationDispatcherContext): ADTPulseAccessoryNewInformationDispatcherReturns { - const dataHash = generateHash(JSON.stringify(context)); + private async newInformationDispatcher(type: ADTPulseAccessoryNewInformationDispatcherType, data: ADTPulseAccessoryNewInformationDispatcherData): ADTPulseAccessoryNewInformationDispatcherReturns { + const dataHash = generateHash(`${type}: ${JSON.stringify(data)}`); // If the detector has not reported this event before. if (this.#reportedHashes.find((reportedHash) => dataHash === reportedHash) === undefined) { - const detectedNew = await detectedNewDeviceContext(context, this.#log, this.#debugMode); + let detectedNew = false; + + // Determine what information needs to be checked. + switch (type) { + case 'panel': + detectedNew = await detectedUnknownPanelAction(data as ADTPulseAccessoryNewInformationDispatcherPanel, this.#log, this.#debugMode); + break; + case 'co': + case 'doorWindow': + case 'fire': + case 'flood': + case 'glass': + case 'keypad': + case 'motion': + case 'panic': + case 'shock': + case 'supervisory': + case 'temperature': + case 'unknown': + detectedNew = await detectedUnknownSensorAction(data as ADTPulseAccessoryNewInformationDispatcherSensor, this.#log, this.#debugMode); + break; + default: + break; + } // Save this hash so the detector does not detect the same thing multiple times. if (detectedNew) { diff --git a/src/lib/detect.ts b/src/lib/detect.ts index 9df3ca1..e859238 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -27,10 +27,6 @@ import { stackTracer, } from '@/lib/utility.js'; import type { - DetectedNewDeviceContextContext, - DetectedNewDeviceContextDebugMode, - DetectedNewDeviceContextLogger, - DetectedNewDeviceContextReturns, DetectedNewDoSubmitHandlersDebugMode, DetectedNewDoSubmitHandlersHandlers, DetectedNewDoSubmitHandlersLogger, @@ -63,89 +59,16 @@ import type { DetectedNewSensorsStatusLogger, DetectedNewSensorsStatusReturns, DetectedNewSensorsStatusSensors, + DetectedUnknownPanelActionDebugMode, + DetectedUnknownPanelActionLogger, + DetectedUnknownPanelActionReturns, + DetectedUnknownPanelActionStatus, + DetectedUnknownSensorActionDebugMode, + DetectedUnknownSensorActionLogger, + DetectedUnknownSensorActionReturns, + DetectedUnknownSensorActionStatus, } from '@/types/index.d.ts'; -/** - * Detected new device context. - * - * @param {DetectedNewDeviceContextContext} context - Context. - * @param {DetectedNewDeviceContextLogger} logger - Logger. - * @param {DetectedNewDeviceContextDebugMode} debugMode - Debug mode. - * - * @returns {DetectedNewDeviceContextReturns} - * - * @since 1.0.0 - */ -export async function detectedNewDeviceContext(context: DetectedNewDeviceContextContext, logger: DetectedNewDeviceContextLogger, debugMode: DetectedNewDeviceContextDebugMode): DetectedNewDeviceContextReturns { - const cleanedData = removePersonalIdentifiableInformation(context); - - // If outdated, it means plugin may already have support. - try { - const outdated = await isPluginOutdated(); - - if (outdated) { - if (logger !== null) { - logger.warn('Plugin has detected new device context. You are running an older plugin version, please update soon.'); - } - - // This is intentionally duplicated if using Homebridge debug mode. - if (debugMode) { - debugLog(logger, 'detect.ts / detectedNewDeviceContext()', 'warn', 'Plugin has detected new device context. You are running an older plugin version, please update soon'); - } - - // Do not send analytics for users running outdated plugin versions. - return false; - } - } catch (error) { - if (debugMode === true) { - debugLog(logger, 'detect.ts / detectedNewDeviceContext()', 'error', 'Failed to check if plugin is outdated'); - stackTracer('serialize-error', serializeError(error)); - } - - // Try to check if plugin is outdated later on. - return false; - } - - if (logger !== null) { - logger.warn('Plugin has detected new device context. Notifying plugin author about this discovery ...'); - } - - // This is intentionally duplicated if using Homebridge debug mode. - if (debugMode) { - debugLog(logger, 'detect.ts / detectedNewDeviceContext()', 'warn', 'Plugin has detected new device context. Notifying plugin author about this discovery'); - } - - // Show content being sent to author. - stackTracer('detect-content', cleanedData); - - try { - await axios.post( - 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', - [ - 'Please update the plugin as soon as possible.', - JSON.stringify(cleanedData, null, 2), - ].join('\n\n'), - { - family: 4, - headers: { - 'User-Agent': 'homebridge-adt-pulse', - 'X-Title': 'Detected new device context', - }, - }, - ); - - return true; - } catch (error) { - if (debugMode === true) { - debugLog(logger, 'detect.ts / detectedNewDeviceContext()', 'error', 'Failed to notify plugin author about the new device context'); - stackTracer('serialize-error', serializeError(error)); - } - - // Try to send information to author later. - return false; - } -} - /** * Detected new do submit handlers. * @@ -877,3 +800,165 @@ export async function detectedNewSensorsStatus(sensors: DetectedNewSensorsStatus return false; } + +/** + * Detected unknown panel action. + * + * @param {DetectedUnknownPanelActionStatus} status - Status. + * @param {DetectedUnknownPanelActionLogger} logger - Logger. + * @param {DetectedUnknownPanelActionDebugMode} debugMode - Debug mode. + * + * @returns {DetectedUnknownPanelActionReturns} + * + * @since 1.0.0 + */ +export async function detectedUnknownPanelAction(status: DetectedUnknownPanelActionStatus, logger: DetectedUnknownPanelActionLogger, debugMode: DetectedUnknownPanelActionDebugMode): DetectedUnknownPanelActionReturns { + const cleanedData = removePersonalIdentifiableInformation(status); + + // If outdated, it means plugin may already have support. + try { + const outdated = await isPluginOutdated(); + + if (outdated) { + if (logger !== null) { + logger.warn('Plugin has detected unknown panel action. You are running an older plugin version, please update soon.'); + } + + // This is intentionally duplicated if using Homebridge debug mode. + if (debugMode) { + debugLog(logger, 'detect.ts / detectedUnknownPanelAction()', 'warn', 'Plugin has detected unknown panel action. You are running an older plugin version, please update soon'); + } + + // Do not send analytics for users running outdated plugin versions. + return false; + } + } catch (error) { + if (debugMode === true) { + debugLog(logger, 'detect.ts / detectedUnknownPanelAction()', 'error', 'Failed to check if plugin is outdated'); + stackTracer('serialize-error', serializeError(error)); + } + + // Try to check if plugin is outdated later on. + return false; + } + + if (logger !== null) { + logger.warn('Plugin has detected unknown panel action. Notifying plugin author about this discovery ...'); + } + + // This is intentionally duplicated if using Homebridge debug mode. + if (debugMode) { + debugLog(logger, 'detect.ts / detectedUnknownPanelAction()', 'warn', 'Plugin has detected unknown panel action. Notifying plugin author about this discovery'); + } + + // Show content being sent to author. + stackTracer('detect-content', cleanedData); + + try { + await axios.post( + 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', + [ + 'Please update the plugin as soon as possible.', + JSON.stringify(cleanedData, null, 2), + ].join('\n\n'), + { + family: 4, + headers: { + 'User-Agent': 'homebridge-adt-pulse', + 'X-Title': 'Detected unknown panel action', + }, + }, + ); + + return true; + } catch (error) { + if (debugMode === true) { + debugLog(logger, 'detect.ts / detectedUnknownPanelAction()', 'error', 'Failed to notify plugin author about the unknown panel action'); + stackTracer('serialize-error', serializeError(error)); + } + + // Try to send information to author later. + return false; + } +} + +/** + * Detected unknown sensor action. + * + * @param {DetectedUnknownSensorActionStatus} status - Status. + * @param {DetectedUnknownSensorActionLogger} logger - Logger. + * @param {DetectedUnknownSensorActionDebugMode} debugMode - Debug mode. + * + * @returns {DetectedUnknownSensorActionReturns} + * + * @since 1.0.0 + */ +export async function detectedUnknownSensorAction(status: DetectedUnknownSensorActionStatus, logger: DetectedUnknownSensorActionLogger, debugMode: DetectedUnknownSensorActionDebugMode): DetectedUnknownSensorActionReturns { + const cleanedData = removePersonalIdentifiableInformation(status); + + // If outdated, it means plugin may already have support. + try { + const outdated = await isPluginOutdated(); + + if (outdated) { + if (logger !== null) { + logger.warn('Plugin has detected unknown sensor action. You are running an older plugin version, please update soon.'); + } + + // This is intentionally duplicated if using Homebridge debug mode. + if (debugMode) { + debugLog(logger, 'detect.ts / detectedUnknownSensorAction()', 'warn', 'Plugin has detected unknown sensor action. You are running an older plugin version, please update soon'); + } + + // Do not send analytics for users running outdated plugin versions. + return false; + } + } catch (error) { + if (debugMode === true) { + debugLog(logger, 'detect.ts / detectedUnknownSensorAction()', 'error', 'Failed to check if plugin is outdated'); + stackTracer('serialize-error', serializeError(error)); + } + + // Try to check if plugin is outdated later on. + return false; + } + + if (logger !== null) { + logger.warn('Plugin has detected unknown sensor action. Notifying plugin author about this discovery ...'); + } + + // This is intentionally duplicated if using Homebridge debug mode. + if (debugMode) { + debugLog(logger, 'detect.ts / detectedUnknownSensorAction()', 'warn', 'Plugin has detected unknown sensor action. Notifying plugin author about this discovery'); + } + + // Show content being sent to author. + stackTracer('detect-content', cleanedData); + + try { + await axios.post( + 'https://fs65kt4c5xf8.ntfy.mrjackyliang.com', + [ + 'Please update the plugin as soon as possible.', + JSON.stringify(cleanedData, null, 2), + ].join('\n\n'), + { + family: 4, + headers: { + 'User-Agent': 'homebridge-adt-pulse', + 'X-Title': 'Detected unknown sensor action', + }, + }, + ); + + return true; + } catch (error) { + if (debugMode === true) { + debugLog(logger, 'detect.ts / detectedUnknownSensorAction()', 'error', 'Failed to notify plugin author about the unknown sensor action'); + stackTracer('serialize-error', serializeError(error)); + } + + // Try to send information to author later. + return false; + } +} diff --git a/src/lib/utility.ts b/src/lib/utility.ts index 9b3ccc4..8c1dfb1 100644 --- a/src/lib/utility.ts +++ b/src/lib/utility.ts @@ -952,7 +952,6 @@ export function removePersonalIdentifiableInformation(data: RemovePersonalIdenti 'mac', 'masterCode', 'sat', - 'serial', 'serialNumber', 'wanIp', ]; diff --git a/src/types/index.d.ts b/src/types/index.d.ts index aa75265..897ccca 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -20,6 +20,7 @@ import type { ADTPulse } from '@/lib/api.js'; import type { platformConfig } from '@/lib/schema.js'; import type { PluginDeviceCategory, + PluginDevicePanelType, PluginDeviceSensorType, PluginLogLevel, PortalDeviceGatewayStatus, @@ -83,6 +84,7 @@ import type { SensorInformationStatus, SensorsInformation, SensorsStatus, + SensorStatus, SensorStatusIcon, SensorStatusStatus, Sessions, @@ -565,10 +567,16 @@ export type ADTPulseAccessoryGetPanelStatusReturns = Promise; +export type ADTPulseAccessoryNewInformationDispatcherPanel = PanelStatus; + +export type ADTPulseAccessoryNewInformationDispatcherSensor = SensorStatus; + /** * ADT Pulse Accessory - Set panel status. * @@ -1091,19 +1099,6 @@ export type DebugLogMessage = string; export type DebugLogReturns = void; -/** - * Detected new device context. - * - * @since 1.0.0 - */ -export type DetectedNewDeviceContextContext = Device; - -export type DetectedNewDeviceContextLogger = Logger | null; - -export type DetectedNewDeviceContextDebugMode = boolean | null; - -export type DetectedNewDeviceContextReturns = Promise; - /** * Detected new do submit handlers. * @@ -1208,6 +1203,32 @@ export type DetectedNewSensorsStatusDebugMode = boolean | null; export type DetectedNewSensorsStatusReturns = Promise; +/** + * Detected unknown panel action. + * + * @since 1.0.0 + */ +export type DetectedUnknownPanelActionStatus = PanelStatus; + +export type DetectedUnknownPanelActionLogger = Logger | null; + +export type DetectedUnknownPanelActionDebugMode = boolean | null; + +export type DetectedUnknownPanelActionReturns = Promise; + +/** + * Detected unknown sensor action. + * + * @since 1.0.0 + */ +export type DetectedUnknownSensorActionStatus = SensorStatus; + +export type DetectedUnknownSensorActionLogger = Logger | null; + +export type DetectedUnknownSensorActionDebugMode = boolean | null; + +export type DetectedUnknownSensorActionReturns = Promise; + /** * Do submit handler relative url items. *