homebridge-changedetection 0.0.0-stage → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexxanddr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -1,3 +1,161 @@
1
- # Temporary Holding Version
1
+ # homebridge-changedetection
2
+
3
+ [![CI](https://github.com/Alexxanddr/homebridge-changedetection/actions/workflows/ci.yml/badge.svg)](https://github.com/Alexxanddr/homebridge-changedetection/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/homebridge-changedetection)](https://www.npmjs.com/package/homebridge-changedetection)
5
+
6
+ A Homebridge dynamic platform plugin that turns
7
+ [changedetection.io](https://github.com/dgtlmoon/changedetection.io) webhook notifications into
8
+ HomeKit motion sensor events.
9
+
10
+ Each configured watch appears as a separate motion sensor in Apple Home. Use that sensor as the
11
+ trigger for automations that control lights, scenes, switches, notifications, and other accessories.
12
+
13
+ ## Features
14
+
15
+ - Push-based: events arrive immediately through a webhook; no polling or API key is required.
16
+ - One independent HomeKit motion sensor per changedetection.io watch.
17
+ - Works with self-hosted and hosted changedetection.io instances.
18
+ - Shared-secret authentication with constant-time comparison.
19
+ - Built-in Homebridge settings UI schema.
20
+ - No cloud service, analytics, or product-specific infrastructure.
21
+
22
+ ## Requirements
23
+
24
+ - Homebridge 1.8 or 2.x
25
+ - Node.js 22.10 or 24.x
26
+ - Network access from changedetection.io to the Homebridge webhook port
27
+
28
+ ## Installation
29
+
30
+ Search for **ChangeDetection** in Homebridge UI, or install it from a terminal:
31
+
32
+ ```shell
33
+ npm install --global homebridge-changedetection
34
+ ```
35
+
36
+ The package will appear in Homebridge's plugin search after it has been published to npm. A GitHub
37
+ repository by itself is not enough for registry discovery.
38
+
39
+ ## Homebridge configuration
40
+
41
+ The recommended method is Homebridge UI. Add the platform, generate a random token of at least 16
42
+ characters, and create one sensor for each watch.
43
+
44
+ Equivalent `config.json` example:
45
+
46
+ ```json
47
+ {
48
+ "platform": "ChangeDetection",
49
+ "name": "ChangeDetection",
50
+ "port": 3210,
51
+ "bindAddress": "0.0.0.0",
52
+ "token": "replace-with-a-long-random-secret",
53
+ "resetAfterSeconds": 10,
54
+ "sensors": [
55
+ {
56
+ "id": "product-stock",
57
+ "name": "Product stock changed"
58
+ },
59
+ {
60
+ "id": "release-page",
61
+ "name": "New software release"
62
+ }
63
+ ]
64
+ }
65
+ ```
66
+
67
+ Sensor IDs are user-defined stable identifiers. They do not need to match changedetection.io watch
68
+ UUIDs, although using a watch UUID is supported.
69
+
70
+ ## Configure changedetection.io
71
+
72
+ For each watch, open **Edit > Notifications** and add a POST notification URL that targets the
73
+ matching sensor ID:
74
+
75
+ ```text
76
+ post://HOMEbridge_HOST:3210/webhook/product-stock?+x-changedetection-token=YOUR_TOKEN
77
+ ```
78
+
79
+ Use `posts://` when the webhook endpoint is behind HTTPS. The `+` prefix tells Apprise to send the
80
+ query parameter as an HTTP header rather than in the request URL.
81
+
82
+ Set the notification body to a small valid JSON document:
83
+
84
+ ```json
85
+ {
86
+ "title": {{ watch_title | tojson }},
87
+ "url": {{ watch_url | tojson }},
88
+ "watch_uuid": {{ watch_uuid | tojson }}
89
+ }
90
+ ```
91
+
92
+ The plugin currently ignores the body; it is included for useful logging and future compatibility.
93
+ The sensor is selected exclusively by the URL path.
94
+
95
+ Use changedetection.io's **Send test notification** action, then check the Homebridge log and Apple
96
+ Home. The sensor should become active and return to inactive after `resetAfterSeconds`.
97
+
98
+ ### Test with curl
99
+
100
+ ```shell
101
+ curl --request POST \
102
+ --header 'X-ChangeDetection-Token: YOUR_TOKEN' \
103
+ --header 'Content-Type: application/json' \
104
+ --data '{}' \
105
+ http://HOMEbridge_HOST:3210/webhook/product-stock
106
+ ```
107
+
108
+ Health checks are available without authentication:
109
+
110
+ ```shell
111
+ curl http://HOMEbridge_HOST:3210/health
112
+ ```
113
+
114
+ ## Containers and reverse proxies
115
+
116
+ Expose TCP port `3210` from the Homebridge container, or route it only on a trusted internal network.
117
+ The webhook does not need to be publicly accessible when changedetection.io and Homebridge can
118
+ communicate locally.
119
+
120
+ If a reverse proxy terminates TLS, forward POST requests to the configured webhook port and retain
121
+ the `X-ChangeDetection-Token` header. Do not publish the endpoint without authentication.
122
+
123
+ ## Apple Home automations
124
+
125
+ Create an automation using the sensor event **Detects Motion**. For example:
126
+
127
+ 1. When `Product stock changed` detects motion;
128
+ 2. turn on a light or activate a scene;
129
+ 3. optionally turn it off after a delay.
130
+
131
+ The plugin resets the sensor automatically so a later website change can trigger the automation
132
+ again.
133
+
134
+ ## Troubleshooting
135
+
136
+ - **Plugin is not configured**: add at least one sensor and a token containing 16 or more characters.
137
+ - **401 Unauthorized**: the `X-ChangeDetection-Token` header does not match the configured token.
138
+ - **404 Unknown sensor**: the final URL segment must exactly match a configured sensor ID.
139
+ - **Connection refused**: expose the configured port and verify routing between the two services.
140
+ - **No Apple Home automation**: confirm the sensor changes state in the Homebridge log first.
141
+
142
+ ## Development
143
+
144
+ ```shell
145
+ npm install
146
+ npm run check
147
+ npm link
148
+ homebridge -D
149
+ ```
150
+
151
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow and
152
+ [docs/PUBLISHING.md](docs/PUBLISHING.md) for the release process.
153
+
154
+ ## Security
155
+
156
+ See [SECURITY.md](SECURITY.md). Never include a real webhook token in issues, screenshots, or logs.
157
+
158
+ ## License
159
+
160
+ [MIT](LICENSE)
2
161
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
@@ -0,0 +1,92 @@
1
+ {
2
+ "pluginAlias": "ChangeDetection",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "strictValidation": true,
6
+ "schema": {
7
+ "type": "object",
8
+ "required": ["name", "token", "sensors"],
9
+ "additionalProperties": false,
10
+ "properties": {
11
+ "name": {
12
+ "title": "Name",
13
+ "type": "string",
14
+ "default": "ChangeDetection",
15
+ "minLength": 1
16
+ },
17
+ "port": {
18
+ "title": "Webhook port",
19
+ "type": "integer",
20
+ "default": 3210,
21
+ "minimum": 1,
22
+ "maximum": 65535
23
+ },
24
+ "bindAddress": {
25
+ "title": "Bind address",
26
+ "type": "string",
27
+ "default": "0.0.0.0",
28
+ "description": "Use 0.0.0.0 when changedetection.io runs on another host or in another container."
29
+ },
30
+ "token": {
31
+ "title": "Webhook token",
32
+ "type": "string",
33
+ "minLength": 16,
34
+ "format": "password",
35
+ "description": "A random shared secret sent in the X-ChangeDetection-Token header."
36
+ },
37
+ "resetAfterSeconds": {
38
+ "title": "Reset after",
39
+ "type": "integer",
40
+ "default": 10,
41
+ "minimum": 1,
42
+ "maximum": 3600,
43
+ "description": "Seconds before a triggered motion sensor returns to inactive."
44
+ },
45
+ "sensors": {
46
+ "title": "Watch sensors",
47
+ "type": "array",
48
+ "minItems": 1,
49
+ "uniqueItems": true,
50
+ "items": {
51
+ "type": "object",
52
+ "required": ["id", "name"],
53
+ "additionalProperties": false,
54
+ "properties": {
55
+ "id": {
56
+ "title": "Webhook ID",
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "maxLength": 128,
60
+ "pattern": "^[A-Za-z0-9._~-]+$",
61
+ "description": "A stable ID used in /webhook/<id>, for example product-stock."
62
+ },
63
+ "name": {
64
+ "title": "HomeKit name",
65
+ "type": "string",
66
+ "minLength": 1,
67
+ "maxLength": 64
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ },
74
+ "layout": [
75
+ "name",
76
+ {
77
+ "key": "sensors",
78
+ "type": "array",
79
+ "name": "Watch sensors",
80
+ "orderable": true,
81
+ "buttonText": "Add sensor"
82
+ },
83
+ {
84
+ "type": "fieldset",
85
+ "title": "Webhook server",
86
+ "expandable": true,
87
+ "expanded": false,
88
+ "items": ["port", "bindAddress", "token", "resetAfterSeconds"]
89
+ }
90
+ ]
91
+ }
92
+
@@ -0,0 +1,3 @@
1
+ import type { API } from 'homebridge';
2
+ declare const _default: (api: API) => void;
3
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import { ChangeDetectionPlatform } from './platform.js';
2
+ import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
3
+ export default (api) => {
4
+ api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, ChangeDetectionPlatform);
5
+ };
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE3D,eAAe,CAAC,GAAQ,EAAQ,EAAE;IAChC,GAAG,CAAC,gBAAgB,CAAC,WAAW,EAAE,aAAa,EAAE,uBAAuB,CAAC,CAAC;AAC5E,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { type API, type DynamicPlatformPlugin, type Logging, type PlatformAccessory } from 'homebridge';
2
+ import type { ChangeDetectionConfig } from './types.js';
3
+ export declare class ChangeDetectionPlatform implements DynamicPlatformPlugin {
4
+ private readonly log;
5
+ private readonly config;
6
+ private readonly api;
7
+ private readonly cachedAccessories;
8
+ private readonly sensors;
9
+ private server?;
10
+ constructor(log: Logging, config: ChangeDetectionConfig, api: API);
11
+ configureAccessory(accessory: PlatformAccessory): void;
12
+ private start;
13
+ private stop;
14
+ private validSensors;
15
+ private integerInRange;
16
+ }
@@ -0,0 +1,105 @@
1
+ import { ChangeSensor } from './sensor.js';
2
+ import { DEFAULT_BIND_ADDRESS, DEFAULT_PORT, DEFAULT_RESET_SECONDS, PLATFORM_NAME, PLUGIN_NAME, } from './settings.js';
3
+ import { WebhookServer } from './webhook-server.js';
4
+ export class ChangeDetectionPlatform {
5
+ log;
6
+ config;
7
+ api;
8
+ cachedAccessories = new Map();
9
+ sensors = new Map();
10
+ server;
11
+ constructor(log, config, api) {
12
+ this.log = log;
13
+ this.config = config;
14
+ this.api = api;
15
+ if (!config) {
16
+ return;
17
+ }
18
+ api.on("didFinishLaunching" /* APIEvent.DID_FINISH_LAUNCHING */, () => this.start());
19
+ api.on("shutdown" /* APIEvent.SHUTDOWN */, () => this.stop());
20
+ }
21
+ configureAccessory(accessory) {
22
+ this.cachedAccessories.set(accessory.UUID, accessory);
23
+ }
24
+ start() {
25
+ const token = this.config.token?.trim();
26
+ const sensorConfigs = this.validSensors(this.config.sensors);
27
+ if (!token || token.length < 16 || sensorConfigs.length === 0) {
28
+ this.log.error('Plugin is not configured: add a token of at least 16 characters and one sensor.');
29
+ return;
30
+ }
31
+ const resetAfterSeconds = this.integerInRange(this.config.resetAfterSeconds, 1, 3600, DEFAULT_RESET_SECONDS);
32
+ const activeUuids = new Set();
33
+ for (const sensorConfig of sensorConfigs) {
34
+ const uuid = this.api.hap.uuid.generate(sensorConfig.id);
35
+ activeUuids.add(uuid);
36
+ const accessory = this.cachedAccessories.get(uuid)
37
+ ?? new this.api.platformAccessory(sensorConfig.name, uuid);
38
+ accessory.displayName = sensorConfig.name;
39
+ accessory.context.sensorId = sensorConfig.id;
40
+ accessory.getService(this.api.hap.Service.AccessoryInformation)
41
+ ?.setCharacteristic(this.api.hap.Characteristic.Manufacturer, 'ChangeDetection')
42
+ .setCharacteristic(this.api.hap.Characteristic.Model, 'Webhook Motion Sensor')
43
+ .setCharacteristic(this.api.hap.Characteristic.SerialNumber, sensorConfig.id);
44
+ const motionService = accessory.getService(this.api.hap.Service.MotionSensor)
45
+ ?? accessory.addService(this.api.hap.Service.MotionSensor, 'Change detected');
46
+ motionService.setCharacteristic(this.api.hap.Characteristic.Name, sensorConfig.name);
47
+ if (!this.cachedAccessories.has(uuid)) {
48
+ this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
49
+ this.cachedAccessories.set(uuid, accessory);
50
+ }
51
+ this.sensors.set(sensorConfig.id, new ChangeSensor(accessory, motionService, this.log, resetAfterSeconds));
52
+ }
53
+ const staleAccessories = [...this.cachedAccessories.values()]
54
+ .filter(accessory => !activeUuids.has(accessory.UUID));
55
+ if (staleAccessories.length > 0) {
56
+ this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, staleAccessories);
57
+ }
58
+ const port = this.integerInRange(this.config.port, 1, 65535, DEFAULT_PORT);
59
+ const bindAddress = this.config.bindAddress?.trim() || DEFAULT_BIND_ADDRESS;
60
+ this.server = new WebhookServer({
61
+ bindAddress,
62
+ port,
63
+ token,
64
+ onTrigger: id => {
65
+ const sensor = this.sensors.get(id);
66
+ sensor?.trigger();
67
+ return Boolean(sensor);
68
+ },
69
+ });
70
+ void this.server.start()
71
+ .then(() => this.log.info('Webhook server listening on %s:%d', bindAddress, port))
72
+ .catch(error => this.log.error('Could not start webhook server: %s', String(error)));
73
+ }
74
+ stop() {
75
+ for (const sensor of this.sensors.values()) {
76
+ sensor.stop();
77
+ }
78
+ void this.server?.stop()
79
+ .catch(error => this.log.error('Could not stop webhook server: %s', String(error)));
80
+ }
81
+ validSensors(value) {
82
+ if (!Array.isArray(value)) {
83
+ return [];
84
+ }
85
+ const result = [];
86
+ const ids = new Set();
87
+ for (const sensor of value) {
88
+ const id = sensor?.id?.trim();
89
+ const name = sensor?.name?.trim();
90
+ if (!id || !name || !/^[A-Za-z0-9._~-]{1,128}$/.test(id) || ids.has(id)) {
91
+ this.log.warn('Ignoring invalid or duplicate sensor configuration.');
92
+ continue;
93
+ }
94
+ ids.add(id);
95
+ result.push({ id, name });
96
+ }
97
+ return result;
98
+ }
99
+ integerInRange(value, min, max, fallback) {
100
+ return Number.isInteger(value) && value !== undefined && value >= min && value <= max
101
+ ? value
102
+ : fallback;
103
+ }
104
+ }
105
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Logging, PlatformAccessory, Service } from 'homebridge';
2
+ export declare class ChangeSensor {
3
+ private readonly accessory;
4
+ private readonly motionService;
5
+ private readonly log;
6
+ private readonly resetAfterSeconds;
7
+ private resetTimer?;
8
+ constructor(accessory: PlatformAccessory, motionService: Service, log: Logging, resetAfterSeconds: number);
9
+ trigger(): void;
10
+ stop(): void;
11
+ }
package/dist/sensor.js ADDED
@@ -0,0 +1,33 @@
1
+ export class ChangeSensor {
2
+ accessory;
3
+ motionService;
4
+ log;
5
+ resetAfterSeconds;
6
+ resetTimer;
7
+ constructor(accessory, motionService, log, resetAfterSeconds) {
8
+ this.accessory = accessory;
9
+ this.motionService = motionService;
10
+ this.log = log;
11
+ this.resetAfterSeconds = resetAfterSeconds;
12
+ }
13
+ trigger() {
14
+ if (this.resetTimer) {
15
+ clearTimeout(this.resetTimer);
16
+ }
17
+ this.motionService.updateCharacteristic('MotionDetected', true);
18
+ this.log.info('Change detected for %s', this.accessory.displayName);
19
+ this.resetTimer = setTimeout(() => {
20
+ this.motionService.updateCharacteristic('MotionDetected', false);
21
+ this.resetTimer = undefined;
22
+ this.log.debug('Reset sensor %s', this.accessory.displayName);
23
+ }, this.resetAfterSeconds * 1000);
24
+ this.resetTimer.unref();
25
+ }
26
+ stop() {
27
+ if (this.resetTimer) {
28
+ clearTimeout(this.resetTimer);
29
+ this.resetTimer = undefined;
30
+ }
31
+ }
32
+ }
33
+ //# sourceMappingURL=sensor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sensor.js","sourceRoot":"","sources":["../src/sensor.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,YAAY;IAIJ;IACA;IACA;IACA;IANX,UAAU,CAAkB;IAEpC,YACmB,SAA4B,EAC5B,aAAsB,EACtB,GAAY,EACZ,iBAAyB;QAHzB,cAAS,GAAT,SAAS,CAAmB;QAC5B,kBAAa,GAAb,aAAa,CAAS;QACtB,QAAG,GAAH,GAAG,CAAS;QACZ,sBAAiB,GAAjB,iBAAiB,CAAQ;IACzC,CAAC;IAEJ,OAAO;QACL,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;QAChE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAEpE,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;YACjE,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAChE,CAAC,EAAE,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;QAElC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export declare const PLATFORM_NAME = "ChangeDetection";
2
+ export declare const PLUGIN_NAME = "homebridge-changedetection";
3
+ export declare const DEFAULT_PORT = 3210;
4
+ export declare const DEFAULT_BIND_ADDRESS = "0.0.0.0";
5
+ export declare const DEFAULT_RESET_SECONDS = 10;
@@ -0,0 +1,6 @@
1
+ export const PLATFORM_NAME = 'ChangeDetection';
2
+ export const PLUGIN_NAME = 'homebridge-changedetection';
3
+ export const DEFAULT_PORT = 3210;
4
+ export const DEFAULT_BIND_ADDRESS = '0.0.0.0';
5
+ export const DEFAULT_RESET_SECONDS = 10;
6
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAC/C,MAAM,CAAC,MAAM,WAAW,GAAG,4BAA4B,CAAC;AACxD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC;AACjC,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAC9C,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { PlatformConfig } from 'homebridge';
2
+ export interface SensorConfig {
3
+ id: string;
4
+ name: string;
5
+ }
6
+ export interface ChangeDetectionConfig extends PlatformConfig {
7
+ bindAddress?: string;
8
+ port?: number;
9
+ resetAfterSeconds?: number;
10
+ sensors?: SensorConfig[];
11
+ token?: string;
12
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ export interface WebhookServerOptions {
2
+ bindAddress: string;
3
+ onTrigger: (id: string) => boolean;
4
+ port: number;
5
+ token: string;
6
+ }
7
+ export declare class WebhookServer {
8
+ private readonly options;
9
+ private server?;
10
+ constructor(options: WebhookServerOptions);
11
+ start(): Promise<void>;
12
+ stop(): Promise<void>;
13
+ address(): string | null;
14
+ private handle;
15
+ private authorized;
16
+ private consumeBody;
17
+ private send;
18
+ }
@@ -0,0 +1,105 @@
1
+ import { timingSafeEqual } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ const MAX_BODY_BYTES = 64 * 1024;
4
+ export class WebhookServer {
5
+ options;
6
+ server;
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ async start() {
11
+ if (this.server) {
12
+ return;
13
+ }
14
+ const server = createServer((request, response) => {
15
+ void this.handle(request, response);
16
+ });
17
+ this.server = server;
18
+ await new Promise((resolve, reject) => {
19
+ const onError = (error) => {
20
+ this.server = undefined;
21
+ reject(error);
22
+ };
23
+ server.once('error', onError);
24
+ server.listen(this.options.port, this.options.bindAddress, () => {
25
+ server.removeListener('error', onError);
26
+ resolve();
27
+ });
28
+ });
29
+ }
30
+ async stop() {
31
+ const server = this.server;
32
+ this.server = undefined;
33
+ if (!server || !server.listening) {
34
+ return;
35
+ }
36
+ await new Promise((resolve, reject) => {
37
+ server.close(error => error ? reject(error) : resolve());
38
+ });
39
+ }
40
+ address() {
41
+ const address = this.server?.address();
42
+ if (!address || typeof address === 'string') {
43
+ return address ?? null;
44
+ }
45
+ return `${address.address}:${address.port}`;
46
+ }
47
+ async handle(request, response) {
48
+ const url = new URL(request.url ?? '/', 'http://localhost');
49
+ if (request.method === 'GET' && url.pathname === '/health') {
50
+ this.send(response, 200, { status: 'ok' });
51
+ return;
52
+ }
53
+ if (request.method !== 'POST') {
54
+ this.send(response, 404, { error: 'not_found' });
55
+ return;
56
+ }
57
+ const match = /^\/webhook\/([^/]+)$/.exec(url.pathname);
58
+ if (!match) {
59
+ this.send(response, 404, { error: 'not_found' });
60
+ return;
61
+ }
62
+ if (!this.authorized(request)) {
63
+ this.send(response, 401, { error: 'unauthorized' });
64
+ return;
65
+ }
66
+ try {
67
+ await this.consumeBody(request);
68
+ }
69
+ catch {
70
+ this.send(response, 413, { error: 'payload_too_large' });
71
+ return;
72
+ }
73
+ const id = decodeURIComponent(match[1]);
74
+ if (!this.options.onTrigger(id)) {
75
+ this.send(response, 404, { error: 'unknown_sensor' });
76
+ return;
77
+ }
78
+ this.send(response, 202, { accepted: true, id });
79
+ }
80
+ authorized(request) {
81
+ const header = request.headers['x-changedetection-token'];
82
+ const supplied = Array.isArray(header) ? header[0] : header;
83
+ if (!supplied) {
84
+ return false;
85
+ }
86
+ const expectedBuffer = Buffer.from(this.options.token);
87
+ const suppliedBuffer = Buffer.from(supplied);
88
+ return expectedBuffer.length === suppliedBuffer.length
89
+ && timingSafeEqual(expectedBuffer, suppliedBuffer);
90
+ }
91
+ async consumeBody(request) {
92
+ let size = 0;
93
+ for await (const chunk of request) {
94
+ size += Buffer.byteLength(chunk);
95
+ if (size > MAX_BODY_BYTES) {
96
+ throw new Error('Payload too large');
97
+ }
98
+ }
99
+ }
100
+ send(response, status, body) {
101
+ response.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
102
+ response.end(JSON.stringify(body));
103
+ }
104
+ }
105
+ //# sourceMappingURL=webhook-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-server.js","sourceRoot":"","sources":["../src/webhook-server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,53 @@
1
1
  {
2
2
  "name": "homebridge-changedetection",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "displayName": "ChangeDetection",
4
+ "version": "0.1.4",
5
+ "description": "Expose changedetection.io webhook events as HomeKit motion sensors.",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "config.schema.json",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": "^22.10.0 || ^24.0.0",
17
+ "homebridge": "^1.8.0 || ^2.0.0"
18
+ },
19
+ "scripts": {
20
+ "build": "rimraf dist && tsc",
21
+ "check": "npm run lint && npm run build && npm test",
22
+ "lint": "eslint . --max-warnings=0",
23
+ "prepublishOnly": "npm run check",
24
+ "test": "node --test test/*.test.mjs"
25
+ },
26
+ "keywords": [
27
+ "homebridge-plugin",
28
+ "supports-hap",
29
+ "changedetection.io",
30
+ "changedetection",
31
+ "homekit",
32
+ "webhook"
33
+ ],
34
+ "author": "Alexxanddr",
35
+ "license": "MIT",
36
+ "homepage": "https://github.com/Alexxanddr/homebridge-changedetection#readme",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/Alexxanddr/homebridge-changedetection.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/Alexxanddr/homebridge-changedetection/issues"
43
+ },
44
+ "devDependencies": {
45
+ "@eslint/js": "^10.0.0",
46
+ "@types/node": "^24.0.0",
47
+ "eslint": "^10.0.0",
48
+ "homebridge": "^2.0.0",
49
+ "rimraf": "^6.0.0",
50
+ "typescript": "^5.9.0",
51
+ "typescript-eslint": "^8.0.0"
52
+ }
53
+ }