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 +22 -0
- package/README.md +160 -2
- package/config.schema.json +92 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +105 -0
- package/dist/platform.js.map +1 -0
- package/dist/sensor.d.ts +11 -0
- package/dist/sensor.js +33 -0
- package/dist/sensor.js.map +1 -0
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +6 -0
- package/dist/settings.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook-server.d.ts +18 -0
- package/dist/webhook-server.js +105 -0
- package/dist/webhook-server.js.map +1 -0
- package/package.json +51 -4
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
|
-
#
|
|
1
|
+
# homebridge-changedetection
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Alexxanddr/homebridge-changedetection/actions/workflows/ci.yml)
|
|
4
|
+
[](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
|
+
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -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
|
+
}
|
package/dist/platform.js
ADDED
|
@@ -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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"}
|
package/dist/sensor.d.ts
ADDED
|
@@ -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;
|
package/dist/settings.js
ADDED
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
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
|
+
}
|