homebridge-securitysystem 11.0.0-beta.3 → 11.0.0

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.
@@ -7,13 +7,14 @@ on:
7
7
  paths-ignore:
8
8
  - "**.md"
9
9
  pull_request:
10
- types: [opened, reopened, synchronize, ready_for_review]
10
+ types: [opened, reopened, synchronize, ready_for_review, labeled, unlabeled]
11
11
  paths-ignore:
12
12
  - "**.md"
13
13
 
14
14
  jobs:
15
15
  build-package:
16
16
  name: Build package
17
+ if: ${{ !contains(github.event.pull_request.labels.*.name, 'ignore-for-release') }}
17
18
  runs-on: ubuntu-latest
18
19
  permissions:
19
20
  contents: read
@@ -35,6 +36,7 @@ jobs:
35
36
  - name: Build package
36
37
  run: |
37
38
  npm ci
39
+ npm run lint
38
40
  npm pack
39
41
 
40
42
  - name: Attest build provenance
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## v11.0.0-beta.5 - 2026-04-19
4
+
5
+ <!-- Release notes generated using configuration in .github/release.yml at v11.0.0-beta.5 -->
6
+
7
+ ## What's Changed
8
+ ### 🛠️ Other Changes
9
+ * Add lint step to build workflow and improve PR trigger conditions by @MiguelRipoll23 in https://github.com/MiguelRipoll23/homebridge-securitysystem/pull/768
10
+
11
+
12
+ **Full Changelog**: https://github.com/MiguelRipoll23/homebridge-securitysystem/compare/v11.0.0-beta.4...v11.0.0-beta.5
13
+
14
+
15
+ ## v11.0.0-beta.4 - 2026-04-19
16
+
17
+ <!-- Release notes generated using configuration in .github/release.yml at v11.0.0-beta.4 -->
18
+
19
+ ## What's Changed
20
+ ### 🛠️ Other Changes
21
+ * Separate trip endpoint from mode update endpoint by @MiguelRipoll23 in https://github.com/MiguelRipoll23/homebridge-securitysystem/pull/766
22
+
23
+
24
+ **Full Changelog**: https://github.com/MiguelRipoll23/homebridge-securitysystem/compare/v11.0.0-beta.3...v11.0.0-beta.4
25
+
26
+
27
+ ## v11.0.0-beta.3 - 2026-04-19
28
+
29
+ <!-- Release notes generated using configuration in .github/release.yml at v11.0.0-beta.3 -->
30
+
31
+ ## What's Changed
32
+ ### ✨ Enhancements
33
+ * refactor: replace boolean returns with ServiceResult objects and add condition failure reasons by @MiguelRipoll23 in https://github.com/MiguelRipoll23/homebridge-securitysystem/pull/764
34
+ ### 🛠️ Other Changes
35
+ * fix: update server configuration to use API key instead of server code by @MiguelRipoll23 in https://github.com/MiguelRipoll23/homebridge-securitysystem/pull/762
36
+ * Improve API authentication logging and naming clarity by @MiguelRipoll23 in https://github.com/MiguelRipoll23/homebridge-securitysystem/pull/763
37
+
38
+
39
+ **Full Changelog**: https://github.com/MiguelRipoll23/homebridge-securitysystem/compare/v11.0.0-beta.2...v11.0.0-beta.3
40
+
41
+
3
42
  ## v11.0.0-beta.2 - 2026-04-18
4
43
 
5
44
  <!-- Release notes generated using configuration in .github/release.yml at v11.0.0-beta.2 -->
@@ -5,7 +5,6 @@ export declare const ModeRequestSchema: z.ZodObject<{
5
5
  home: "home";
6
6
  away: "away";
7
7
  night: "night";
8
- triggered: "triggered";
9
8
  }>;
10
9
  delay: z.ZodOptional<z.ZodNumber>;
11
10
  }, z.core.$strip>;
@@ -2,8 +2,8 @@ import { z } from '@hono/zod-openapi';
2
2
  export const ModeRequestSchema = z
3
3
  .object({
4
4
  mode: z
5
- .enum(['home', 'away', 'night', 'off', 'triggered'])
6
- .openapi({ example: 'home', description: 'Target security mode to activate' }),
5
+ .enum(['home', 'away', 'night', 'off'])
6
+ .openapi({ example: 'home', description: 'Target mode to activate (home, away, night, or off)' }),
7
7
  delay: z
8
8
  .number()
9
9
  .int()
@@ -1 +1 @@
1
- {"version":3,"file":"mode-request-schema.js","sourceRoot":"","sources":["../../src/schemas/mode-request-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAEtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;SACnD,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC;IAChF,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,WAAW,EAAE;SACb,QAAQ,EAAE;SACV,OAAO,CAAC;QACP,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,2DAA2D;KACzE,CAAC;CACL,CAAC;KACD,OAAO,CAAC,aAAa,CAAC,CAAC"}
1
+ {"version":3,"file":"mode-request-schema.js","sourceRoot":"","sources":["../../src/schemas/mode-request-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAEtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;SACtC,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC;IACnG,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,WAAW,EAAE;SACb,QAAQ,EAAE;SACV,OAAO,CAAC;QACP,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,2DAA2D;KACzE,CAAC;CACL,CAAC;KACD,OAAO,CAAC,aAAa,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { z } from '@hono/zod-openapi';
2
+ export declare const TripModeRequestSchema: z.ZodObject<{
3
+ mode: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
4
+ home: "home";
5
+ away: "away";
6
+ night: "night";
7
+ }>>>;
8
+ delay: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
9
+ }, z.core.$strip>;
@@ -0,0 +1,24 @@
1
+ import { z } from '@hono/zod-openapi';
2
+ export const TripModeRequestSchema = z
3
+ .object({
4
+ mode: z
5
+ .enum(['home', 'away', 'night'])
6
+ .nullable()
7
+ .optional()
8
+ .openapi({
9
+ example: 'home',
10
+ description: 'Mode to trip. If omitted or null, the action applies to all modes.',
11
+ }),
12
+ delay: z
13
+ .number()
14
+ .int()
15
+ .nonnegative()
16
+ .nullable()
17
+ .optional()
18
+ .openapi({
19
+ example: 5,
20
+ description: 'Delay in seconds before activating the alarm.',
21
+ }),
22
+ })
23
+ .openapi('TripModeRequest');
24
+ //# sourceMappingURL=trip-mode-request-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trip-mode-request-schema.js","sourceRoot":"","sources":["../../src/schemas/trip-mode-request-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAEtC,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC;KACnC,MAAM,CAAC;IACN,IAAI,EAAE,CAAC;SACJ,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;SAC/B,QAAQ,EAAE;SACV,QAAQ,EAAE;SACV,OAAO,CAAC;QACP,OAAO,EAAE,MAAM;QACf,WAAW,EAAE,oEAAoE;KAClF,CAAC;IACJ,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,WAAW,EAAE;SACb,QAAQ,EAAE;SACV,QAAQ,EAAE;SACV,OAAO,CAAC;QACP,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,+CAA+C;KAC7D,CAAC;CACL,CAAC;KACD,OAAO,CAAC,iBAAiB,CAAC,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { OpenAPIHono } from '@hono/zod-openapi';
1
2
  import type { Logging } from 'homebridge';
2
3
  import type { SecuritySystemOptions } from '../interfaces/options-interface.js';
3
4
  import type { SystemState } from '../interfaces/system-state-interface.js';
@@ -14,6 +15,8 @@ export declare class ServerService {
14
15
  private readonly switchHandler;
15
16
  private readonly application;
16
17
  constructor(log: Logging, options: SecuritySystemOptions, state: SystemState, stateHandler: StateHandler, tripHandler: TripHandler, switchHandler: SwitchHandler);
18
+ /** Exposed for testing — returns the underlying Hono app. */
19
+ get app(): OpenAPIHono;
17
20
  start(): void;
18
21
  private createAuthMiddleware;
19
22
  private registerRoutes;
@@ -7,6 +7,7 @@ import { stateToMode } from '../utils/state-util.js';
7
7
  import { ErrorSchema } from '../schemas/error-schema.js';
8
8
  import { StatusResponseSchema } from '../schemas/status-response-schema.js';
9
9
  import { ModeRequestSchema } from '../schemas/mode-request-schema.js';
10
+ import { TripModeRequestSchema } from '../schemas/trip-mode-request-schema.js';
10
11
  import { ArmingLockRequestSchema } from '../schemas/arming-lock-schema.js';
11
12
  const MODE_TO_STATE = {
12
13
  home: SecurityState.HOME,
@@ -40,10 +41,11 @@ const statusRoute = createRoute({
40
41
  });
41
42
  const modeRoute = createRoute({
42
43
  method: 'put',
43
- path: '/mode',
44
- summary: 'Change security mode',
45
- description: 'Sets the target security mode. Supported modes: home, away, night, off, triggered. ' +
46
- 'Use "triggered" to activate the alarm. An optional delay (seconds) defers the transition.',
44
+ path: '/mode/update',
45
+ summary: 'Change mode',
46
+ description: 'Sets the target security mode. Supported modes: home, away, night, off. ' +
47
+ 'An optional delay (seconds) defers the transition. ' +
48
+ 'To trigger the alarm use the POST /mode/trip endpoint instead.',
47
49
  security: [{ BearerAuth: [] }],
48
50
  request: {
49
51
  body: {
@@ -65,10 +67,38 @@ const modeRoute = createRoute({
65
67
  },
66
68
  },
67
69
  });
70
+ const tripModeRoute = createRoute({
71
+ method: 'post',
72
+ path: '/mode/trip',
73
+ summary: 'Trip mode',
74
+ description: 'Activates the alarm ("triggered" state). ' +
75
+ 'If mode is specified the alarm only fires when the system is currently in that mode. ' +
76
+ 'An optional delay (seconds) defers the activation.',
77
+ security: [{ BearerAuth: [] }],
78
+ request: {
79
+ body: {
80
+ content: {
81
+ 'application/json': {
82
+ schema: TripModeRequestSchema,
83
+ example: { mode: 'home', delay: 30 },
84
+ },
85
+ },
86
+ required: true,
87
+ },
88
+ },
89
+ responses: {
90
+ 204: { description: 'Trip accepted' },
91
+ ...AUTH_RESPONSES,
92
+ 409: {
93
+ content: { 'application/json': { schema: ErrorSchema } },
94
+ description: 'Trip rejected by the system',
95
+ },
96
+ },
97
+ });
68
98
  const armingLockRoute = createRoute({
69
99
  method: 'put',
70
100
  path: '/switches/arming-lock',
71
- summary: 'Update arming lock switch',
101
+ summary: 'Update arming lock',
72
102
  description: 'Enables or disables the arming lock for a specific mode or globally.',
73
103
  security: [{ BearerAuth: [] }],
74
104
  request: {
@@ -107,9 +137,13 @@ export class ServerService {
107
137
  this.stateHandler = stateHandler;
108
138
  this.tripHandler = tripHandler;
109
139
  this.switchHandler = switchHandler;
140
+ this.registerRoutes();
141
+ }
142
+ /** Exposed for testing — returns the underlying Hono app. */
143
+ get app() {
144
+ return this.application;
110
145
  }
111
146
  start() {
112
- this.registerRoutes();
113
147
  const server = serve({ fetch: this.application.fetch, port: this.options.serverPort }, () => this.log.info(`Server (${this.options.serverPort})`));
114
148
  server.on('error', (error) => {
115
149
  this.log.error('Server error.');
@@ -158,27 +192,45 @@ export class ServerService {
158
192
  tripped: this.stateHandler.isTripping(),
159
193
  });
160
194
  });
161
- // PUT /mode
162
- this.application.use('/mode', auth);
195
+ // PUT /mode/update
196
+ this.application.use('/mode/update', auth);
163
197
  this.application.openapi(modeRoute, (c) => {
164
198
  const { mode, delay = 0 } = c.req.valid('json');
165
- let result;
166
- if (mode === 'triggered') {
167
- if (delay > 0) {
168
- result = this.tripHandler.updateTripSwitch(true, OriginType.EXTERNAL, false);
199
+ const result = this.stateHandler.updateTargetState(MODE_TO_STATE[mode], OriginType.EXTERNAL, delay);
200
+ if (!result.success) {
201
+ return c.json({ reason: result.reason ?? 'Mode change rejected by the system' }, 409);
202
+ }
203
+ return c.body(null, 204);
204
+ });
205
+ // POST /mode/trip
206
+ this.application.use('/mode/trip', auth);
207
+ this.application.openapi(tripModeRoute, (c) => {
208
+ const { mode, delay } = c.req.valid('json');
209
+ const effectiveDelay = delay ?? 0;
210
+ if (effectiveDelay > 0) {
211
+ const precheck = this.tripHandler.checkTripConditions(true, OriginType.EXTERNAL);
212
+ if (!precheck.success) {
213
+ return c.json({ reason: precheck.reason ?? 'Trip rejected by the system' }, 409);
169
214
  }
170
- else {
171
- result = this.tripHandler.checkTripConditions(true, OriginType.EXTERNAL);
172
- if (result.success) {
173
- this.stateHandler.setCurrentState(SecurityState.TRIGGERED, OriginType.EXTERNAL);
215
+ setTimeout(() => {
216
+ if (mode != null) {
217
+ this.tripHandler.triggerIfModeSet(MODE_TO_STATE[mode], true);
174
218
  }
175
- }
219
+ else {
220
+ this.tripHandler.updateTripSwitch(true, OriginType.EXTERNAL, false);
221
+ }
222
+ }, effectiveDelay * 1000);
223
+ return c.body(null, 204);
224
+ }
225
+ let result;
226
+ if (mode != null) {
227
+ result = this.tripHandler.triggerIfModeSet(MODE_TO_STATE[mode], true);
176
228
  }
177
229
  else {
178
- result = this.stateHandler.updateTargetState(MODE_TO_STATE[mode], OriginType.EXTERNAL, delay);
230
+ result = this.tripHandler.updateTripSwitch(true, OriginType.EXTERNAL, false);
179
231
  }
180
232
  if (!result.success) {
181
- return c.json({ reason: result.reason ?? 'Mode change rejected by the system' }, 409);
233
+ return c.json({ reason: result.reason ?? 'Trip rejected by the system' }, 409);
182
234
  }
183
235
  return c.body(null, 204);
184
236
  });
@@ -1 +1 @@
1
- {"version":3,"file":"server-service.js","sourceRoot":"","sources":["../../src/services/server-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server-service.js","sourceRoot":"","sources":["../../src/services/server-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAMrD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAG3E,MAAM,aAAa,GAAkC;IACnD,IAAI,EAAE,aAAa,CAAC,IAAI;IACxB,IAAI,EAAE,aAAa,CAAC,IAAI;IACxB,KAAK,EAAE,aAAa,CAAC,KAAK;IAC1B,GAAG,EAAE,aAAa,CAAC,GAAG;CACvB,CAAC;AAEF,MAAM,cAAc,GAAG;IACrB,GAAG,EAAE;QACH,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE;QACxD,WAAW,EAAE,kBAAkB;KAChC;IACD,GAAG,EAAE;QACH,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE;QACxD,WAAW,EAAE,4BAA4B;KAC1C;CACO,CAAC;AAEX,MAAM,WAAW,GAAG,WAAW,CAAC;IAC9B,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,WAAW;IACpB,WAAW,EAAE,8EAA8E;IAC3F,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC9B,SAAS,EAAE;QACT,GAAG,EAAE;YACH,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE;YACjE,WAAW,EAAE,sBAAsB;SACpC;QACD,GAAG,cAAc;KAClB;CACF,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,WAAW,CAAC;IAC5B,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,cAAc;IACpB,OAAO,EAAE,aAAa;IACtB,WAAW,EACT,0EAA0E;QAC1E,qDAAqD;QACrD,gEAAgE;IAClE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC9B,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,kBAAkB,EAAE;oBAClB,MAAM,EAAE,iBAAiB;oBACzB,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE;iBACpC;aACF;YACD,QAAQ,EAAE,IAAI;SACf;KACF;IACD,SAAS,EAAE;QACT,GAAG,EAAE,EAAE,WAAW,EAAE,sBAAsB,EAAE;QAC5C,GAAG,cAAc;QACjB,GAAG,EAAE;YACH,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE;YACxD,WAAW,EAAE,oCAAoC;SAClD;KACF;CACF,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,WAAW,CAAC;IAChC,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,WAAW;IACpB,WAAW,EACT,2CAA2C;QAC3C,uFAAuF;QACvF,oDAAoD;IACtD,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC9B,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,kBAAkB,EAAE;oBAClB,MAAM,EAAE,qBAAqB;oBAC7B,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE;iBACrC;aACF;YACD,QAAQ,EAAE,IAAI;SACf;KACF;IACD,SAAS,EAAE;QACT,GAAG,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE;QACrC,GAAG,cAAc;QACjB,GAAG,EAAE;YACH,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE;YACxD,WAAW,EAAE,6BAA6B;SAC3C;KACF;CACF,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,WAAW,CAAC;IAClC,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,oBAAoB;IAC7B,WAAW,EAAE,sEAAsE;IACnF,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC9B,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,kBAAkB,EAAE;oBAClB,MAAM,EAAE,uBAAuB;oBAC/B,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE;iBACvC;aACF;YACD,QAAQ,EAAE,IAAI;SACf;KACF;IACD,SAAS,EAAE;QACT,GAAG,EAAE,EAAE,WAAW,EAAE,qBAAqB,EAAE;QAC3C,GAAG,cAAc;QACjB,GAAG,EAAE;YACH,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE;YACxD,WAAW,EAAE,2CAA2C;SACzD;KACF;CACF,CAAC,CAAC;AAEH,wEAAwE;AACxE,MAAM,OAAO,aAAa;IAIL;IACA;IACA;IACA;IACA;IACA;IARF,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;IAEjD,YACmB,GAAY,EACZ,OAA8B,EAC9B,KAAkB,EAClB,YAA0B,EAC1B,WAAwB,EACxB,aAA4B;QAL5B,QAAG,GAAH,GAAG,CAAS;QACZ,YAAO,GAAP,OAAO,CAAuB;QAC9B,UAAK,GAAL,KAAK,CAAa;QAClB,iBAAY,GAAZ,YAAY,CAAc;QAC1B,gBAAW,GAAX,WAAW,CAAa;QACxB,kBAAa,GAAb,aAAa,CAAe;QAE7C,IAAI,CAAC,cAAc,EAAE,CAAC;IACxB,CAAC;IAED,6DAA6D;IAC7D,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,KAAK;QACH,MAAM,MAAM,GAAG,KAAK,CAClB,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,UAAW,EAAE,EACjE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,UAAU,GAAG,CAAC,CAC3D,CAAC;QAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;YAClC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;YAChC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,oBAAoB;QAC1B,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;YACvB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACvC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,MAAM,IAAI,EAAE,CAAC;QACf,CAAC,CAAC;IACJ,CAAC;IAEO,cAAc;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAEzC,YAAY;QACZ,IAAI,CAAC,WAAW,CAAC,GAAG,CAClB,GAAG,EACH,MAAM,CAAC;YACL,GAAG,EAAE,eAAe;YACpB,SAAS,EAAE,gCAAgC;YAC3C,kBAAkB,EAAE,IAAI;SACzB,CAAC,CACH,CAAC;QAEF,iEAAiE;QACjE,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,iBAAiB,CAAC,iBAAiB,EAAE,YAAY,EAAE;YAClF,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,QAAQ;YAChB,WAAW,EAAE,0DAA0D;SACxE,CAAC,CAAC;QAEH,eAAe;QACf,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,eAAe,EAAE;YACpC,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE;gBACJ,KAAK,EAAE,gCAAgC;gBACvC,OAAO,EAAE,OAAO;gBAChB,WAAW,EAAE,+DAA+D;aAC7E;SACF,CAAC,CAAC;QAEH,aAAa;QACb,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;YAC1C,OAAO,CAAC,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ;gBAC3B,YAAY,EAAE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;gBAClD,WAAW,EAAE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;gBAChD,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE;aACxC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,mBAAmB;QACnB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YACxC,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAEpG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,oCAAoC,EAAE,EAAE,GAAG,CAAC,CAAC;YACxF,CAAC;YAED,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,kBAAkB;QAClB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE;YAC5C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC5C,MAAM,cAAc,GAAG,KAAK,IAAI,CAAC,CAAC;YAElC,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;gBACjF,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;oBACtB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,6BAA6B,EAAE,EAAE,GAAG,CAAC,CAAC;gBACnF,CAAC;gBAED,UAAU,CAAC,GAAG,EAAE;oBACd,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;wBACjB,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;oBAC/D,CAAC;yBAAM,CAAC;wBACN,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;oBACtE,CAAC;gBACH,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC,CAAC;gBAE1B,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC3B,CAAC;YAED,IAAI,MAAqB,CAAC;YAE1B,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;gBACjB,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YACxE,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC/E,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,6BAA6B,EAAE,EAAE,GAAG,CAAC,CAAC;YACjF,CAAC;YAED,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,EAAE,EAAE;YAC9C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAEhE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,2CAA2C,EAAE,EAAE,GAAG,CAAC,CAAC;YAC/F,CAAC;YAED,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,OAAgB;QACnC,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YACvC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QAEvD,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAClE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;YAC1E,OAAO,OAAO,CAAC,IAAI,CACjB,EAAE,MAAM,EAAE,+DAA+D,EAAE,EAC3E,GAAG,CACQ,CAAC;QAChB,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,CAAC,4BAA4B,IAAI,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;YACtE,OAAO,OAAO,CAAC,IAAI,CACjB,EAAE,MAAM,EAAE,kDAAkD,EAAE,EAC9D,GAAG,CACQ,CAAC;QAChB,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAEzD,IAAI,MAAM,KAAK,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YACzC,IAAI,CAAC,KAAK,CAAC,4BAA4B,EAAE,CAAC;YAC1C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,6BAA6B,IAAI,CAAC,KAAK,CAAC,4BAA4B,aAAa,CAAC,CAAC;YACjG,OAAO,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE,GAAG,CAAa,CAAC;QACtE,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,4BAA4B,GAAG,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { SecurityState } from '../types/security-state-type.js';
3
+ import { OriginType } from '../types/origin-type.js';
4
+ // ── Helpers ───────────────────────────────────────────────────────────────────
5
+ function makeLog() {
6
+ return { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
7
+ }
8
+ function makeState(overrides = {}) {
9
+ return {
10
+ currentState: SecurityState.OFF,
11
+ targetState: SecurityState.OFF,
12
+ defaultState: SecurityState.OFF,
13
+ availableTargetStates: [SecurityState.HOME, SecurityState.AWAY, SecurityState.NIGHT, SecurityState.OFF],
14
+ isArming: false,
15
+ isTripping: false,
16
+ isKnocked: false,
17
+ serverAuthenticationAttempts: 0,
18
+ pausedCurrentState: null,
19
+ audioProcess: null,
20
+ ...overrides,
21
+ };
22
+ }
23
+ function makeOptions(overrides = {}) {
24
+ return {
25
+ serverPort: 8080,
26
+ serverApiKey: null,
27
+ ...overrides,
28
+ };
29
+ }
30
+ function makeStateHandler(result = { success: true }) {
31
+ return {
32
+ updateTargetState: vi.fn().mockReturnValue(result),
33
+ isTripping: vi.fn().mockReturnValue(false),
34
+ setCurrentState: vi.fn(),
35
+ };
36
+ }
37
+ function makeTripHandler(result = { success: true }) {
38
+ return {
39
+ updateTripSwitch: vi.fn().mockReturnValue(result),
40
+ triggerIfModeSet: vi.fn().mockReturnValue(result),
41
+ checkTripConditions: vi.fn().mockReturnValue(result),
42
+ };
43
+ }
44
+ function makeSwitchHandler(result = { success: true }) {
45
+ return {
46
+ updateArmingLock: vi.fn().mockReturnValue(result),
47
+ };
48
+ }
49
+ async function makeService(stateHandlerResult, tripHandlerResult, switchHandlerResult, stateOverrides, optionsOverrides) {
50
+ const { ServerService } = await import('../services/server-service.js');
51
+ const log = makeLog();
52
+ const state = makeState(stateOverrides);
53
+ const options = makeOptions(optionsOverrides);
54
+ const stateHandler = makeStateHandler(stateHandlerResult);
55
+ const tripHandler = makeTripHandler(tripHandlerResult);
56
+ const switchHandler = makeSwitchHandler(switchHandlerResult);
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ const service = new ServerService(log, options, state, stateHandler, tripHandler, switchHandler);
59
+ return { service, stateHandler, tripHandler, switchHandler, state };
60
+ }
61
+ function json(body, path, method = 'PUT') {
62
+ return new Request(`http://localhost${path}`, {
63
+ method,
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify(body),
66
+ });
67
+ }
68
+ // ── PUT /mode/update ──────────────────────────────────────────────────────────
69
+ describe('PUT /mode/update', () => {
70
+ it('returns 204 and calls updateTargetState with no delay', async () => {
71
+ const { service, stateHandler } = await makeService();
72
+ const res = await service.app.request(json({ mode: 'home' }, '/mode/update'));
73
+ expect(res.status).toBe(204);
74
+ expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.HOME, OriginType.EXTERNAL, 0);
75
+ });
76
+ it('returns 204 and passes delay to updateTargetState', async () => {
77
+ const { service, stateHandler } = await makeService();
78
+ const res = await service.app.request(json({ mode: 'away', delay: 10 }, '/mode/update'));
79
+ expect(res.status).toBe(204);
80
+ expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.AWAY, OriginType.EXTERNAL, 10);
81
+ });
82
+ it('returns 204 for off mode without delay', async () => {
83
+ const { service, stateHandler } = await makeService();
84
+ const res = await service.app.request(json({ mode: 'off' }, '/mode/update'));
85
+ expect(res.status).toBe(204);
86
+ expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.OFF, OriginType.EXTERNAL, 0);
87
+ });
88
+ it('returns 409 when stateHandler rejects the mode change', async () => {
89
+ const { service } = await makeService({ success: false, reason: 'target mode is already set' });
90
+ const res = await service.app.request(json({ mode: 'night' }, '/mode/update'));
91
+ expect(res.status).toBe(409);
92
+ const body = await res.json();
93
+ expect(body.reason).toBe('target mode is already set');
94
+ });
95
+ it('rejects "triggered" — returns 400 (Zod validation failure)', async () => {
96
+ const { service } = await makeService();
97
+ const res = await service.app.request(json({ mode: 'triggered' }, '/mode/update'));
98
+ expect(res.status).toBe(400);
99
+ });
100
+ });
101
+ // ── POST /mode/trip ───────────────────────────────────────────────────────────
102
+ describe('POST /mode/trip', () => {
103
+ afterEach(() => {
104
+ vi.useRealTimers();
105
+ });
106
+ it('returns 204 and calls updateTripSwitch when mode and delay are omitted', async () => {
107
+ const { service, tripHandler } = await makeService();
108
+ const res = await service.app.request(json({}, '/mode/trip', 'POST'));
109
+ expect(res.status).toBe(204);
110
+ expect(tripHandler.updateTripSwitch).toHaveBeenCalledWith(true, OriginType.EXTERNAL, false);
111
+ expect(tripHandler.triggerIfModeSet).not.toHaveBeenCalled();
112
+ });
113
+ it('returns 204 and calls updateTripSwitch when mode is null', async () => {
114
+ const { service, tripHandler } = await makeService();
115
+ const res = await service.app.request(json({ mode: null }, '/mode/trip', 'POST'));
116
+ expect(res.status).toBe(204);
117
+ expect(tripHandler.updateTripSwitch).toHaveBeenCalledWith(true, OriginType.EXTERNAL, false);
118
+ });
119
+ it('returns 204 and calls triggerIfModeSet when mode is specified', async () => {
120
+ const { service, tripHandler } = await makeService();
121
+ const res = await service.app.request(json({ mode: 'away' }, '/mode/trip', 'POST'));
122
+ expect(res.status).toBe(204);
123
+ expect(tripHandler.triggerIfModeSet).toHaveBeenCalledWith(SecurityState.AWAY, true);
124
+ expect(tripHandler.updateTripSwitch).not.toHaveBeenCalled();
125
+ });
126
+ it('returns 409 when trip handler rejects without delay', async () => {
127
+ const { service } = await makeService(undefined, { success: false, reason: 'not armed' });
128
+ const res = await service.app.request(json({}, '/mode/trip', 'POST'));
129
+ expect(res.status).toBe(409);
130
+ const body = await res.json();
131
+ expect(body.reason).toBe('not armed');
132
+ });
133
+ it('returns 409 when mode does not match (triggerIfModeSet rejects)', async () => {
134
+ const { service } = await makeService(undefined, { success: false, reason: 'mode not set' });
135
+ const res = await service.app.request(json({ mode: 'home' }, '/mode/trip', 'POST'));
136
+ expect(res.status).toBe(409);
137
+ const body = await res.json();
138
+ expect(body.reason).toBe('mode not set');
139
+ });
140
+ it('returns 204 immediately and calls updateTripSwitch after delay when mode is omitted', async () => {
141
+ vi.useFakeTimers();
142
+ const { service, tripHandler } = await makeService();
143
+ const res = await service.app.request(json({ delay: 5 }, '/mode/trip', 'POST'));
144
+ expect(res.status).toBe(204);
145
+ expect(tripHandler.checkTripConditions).toHaveBeenCalledWith(true, OriginType.EXTERNAL);
146
+ expect(tripHandler.updateTripSwitch).not.toHaveBeenCalled();
147
+ await vi.advanceTimersByTimeAsync(5000);
148
+ expect(tripHandler.updateTripSwitch).toHaveBeenCalledWith(true, OriginType.EXTERNAL, false);
149
+ });
150
+ it('returns 204 immediately and calls triggerIfModeSet after delay when mode is specified', async () => {
151
+ vi.useFakeTimers();
152
+ const { service, tripHandler } = await makeService();
153
+ const res = await service.app.request(json({ mode: 'night', delay: 3 }, '/mode/trip', 'POST'));
154
+ expect(res.status).toBe(204);
155
+ expect(tripHandler.triggerIfModeSet).not.toHaveBeenCalled();
156
+ await vi.advanceTimersByTimeAsync(3000);
157
+ expect(tripHandler.triggerIfModeSet).toHaveBeenCalledWith(SecurityState.NIGHT, true);
158
+ });
159
+ it('returns 409 on pre-check failure even when delay is specified', async () => {
160
+ const { service, tripHandler } = await makeService(undefined, { success: false, reason: 'arming in progress' });
161
+ const res = await service.app.request(json({ delay: 10 }, '/mode/trip', 'POST'));
162
+ expect(res.status).toBe(409);
163
+ expect(tripHandler.updateTripSwitch).not.toHaveBeenCalled();
164
+ });
165
+ it('treats delay: null the same as no delay', async () => {
166
+ const { service, tripHandler } = await makeService();
167
+ const res = await service.app.request(json({ delay: null }, '/mode/trip', 'POST'));
168
+ expect(res.status).toBe(204);
169
+ expect(tripHandler.updateTripSwitch).toHaveBeenCalledWith(true, OriginType.EXTERNAL, false);
170
+ });
171
+ });
172
+ // ── PUT /switches/arming-lock ─────────────────────────────────────────────────
173
+ describe('PUT /switches/arming-lock', () => {
174
+ it('returns 204 and calls updateArmingLock', async () => {
175
+ const { service, switchHandler } = await makeService();
176
+ const res = await service.app.request(json({ mode: 'home', value: true }, '/switches/arming-lock'));
177
+ expect(res.status).toBe(204);
178
+ expect(switchHandler.updateArmingLock).toHaveBeenCalledWith('home', true);
179
+ });
180
+ it('returns 409 when switchHandler rejects', async () => {
181
+ const { service } = await makeService(undefined, undefined, { success: false, reason: 'unknown arming lock mode: invalid' });
182
+ const res = await service.app.request(json({ mode: 'global', value: false }, '/switches/arming-lock'));
183
+ expect(res.status).toBe(409);
184
+ const body = await res.json();
185
+ expect(body.reason).toBe('unknown arming lock mode: invalid');
186
+ });
187
+ });
188
+ //# sourceMappingURL=server-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-service.test.js","sourceRoot":"","sources":["../../src/tests/server-service.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAKrD,iFAAiF;AAEjF,SAAS,OAAO;IACd,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;AAC1E,CAAC;AAED,SAAS,SAAS,CAAC,YAAkC,EAAE;IACrD,OAAO;QACL,YAAY,EAAE,aAAa,CAAC,GAAG;QAC/B,WAAW,EAAE,aAAa,CAAC,GAAG;QAC9B,YAAY,EAAE,aAAa,CAAC,GAAG;QAC/B,qBAAqB,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC;QACvG,QAAQ,EAAE,KAAK;QACf,UAAU,EAAE,KAAK;QACjB,SAAS,EAAE,KAAK;QAChB,4BAA4B,EAAE,CAAC;QAC/B,kBAAkB,EAAE,IAAI;QACxB,YAAY,EAAE,IAAI;QAClB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,YAA4C,EAAE;IACjE,OAAO;QACL,UAAU,EAAE,IAAI;QAChB,YAAY,EAAE,IAAI;QAClB,GAAG,SAAS;KACuB,CAAC;AACxC,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAwB,EAAE,OAAO,EAAE,IAAI,EAAE;IACjE,OAAO;QACL,iBAAiB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC;QAClD,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC;QAC1C,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE;KACzB,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,SAAwB,EAAE,OAAO,EAAE,IAAI,EAAE;IAChE,OAAO;QACL,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC;QACjD,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC;QACjD,mBAAmB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC;KACrD,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,SAAwB,EAAE,OAAO,EAAE,IAAI,EAAE;IAClE,OAAO;QACL,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC;KAClD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,kBAAkC,EAClC,iBAAiC,EACjC,mBAAmC,EACnC,cAAqC,EACrC,gBAAiD;IAEjD,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;IACxE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,MAAM,KAAK,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,eAAe,CAAC,iBAAiB,CAAC,CAAC;IACvD,MAAM,aAAa,GAAG,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;IAE7D,8DAA8D;IAC9D,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,GAAU,EAAE,OAAO,EAAE,KAAK,EAAE,YAAmB,EAAE,WAAkB,EAAE,aAAoB,CAAC,CAAC;IAE7H,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AACtE,CAAC;AAED,SAAS,IAAI,CAAC,IAAa,EAAE,IAAY,EAAE,MAAM,GAAG,KAAK;IACvD,OAAO,IAAI,OAAO,CAAC,mBAAmB,IAAI,EAAE,EAAE;QAC5C,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;AACL,CAAC;AAED,iFAAiF;AAEjF,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;QAC9E,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC1G,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;QACzF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC3G,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;QAC7E,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,GAAG,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACzG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,4BAA4B,EAAE,CAAC,CAAC;QAChG,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;QAC/E,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;QACnF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,KAAK,IAAI,EAAE;QACtF,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC5F,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QAClF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACpF,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;QAC1F,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;QAC7F,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qFAAqF,EAAE,KAAK,IAAI,EAAE;QACnG,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QAErD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QAChF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;QACxF,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAE5D,MAAM,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uFAAuF,EAAE,KAAK,IAAI,EAAE;QACrG,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QAErD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/F,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAE5D,MAAM,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,WAAW,CAChD,SAAS,EACT,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CACjD,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QACjF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QACnF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,WAAW,EAAE,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;QACpG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CACnC,SAAS,EACT,SAAS,EACT,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,mCAAmC,EAAE,CAChE,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;QACvG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "homebridge-securitysystem",
3
3
  "displayName": "Homebridge Security System",
4
4
  "type": "module",
5
- "version": "11.0.0-beta.3",
5
+ "version": "11.0.0",
6
6
  "description": "Homebridge plugin that creates a security system accessory that can be triggered by HomeKit sensors.",
7
7
  "author": "MiguelRipoll23",
8
8
  "license": "MIT",
@@ -3,8 +3,8 @@ import { z } from '@hono/zod-openapi';
3
3
  export const ModeRequestSchema = z
4
4
  .object({
5
5
  mode: z
6
- .enum(['home', 'away', 'night', 'off', 'triggered'])
7
- .openapi({ example: 'home', description: 'Target security mode to activate' }),
6
+ .enum(['home', 'away', 'night', 'off'])
7
+ .openapi({ example: 'home', description: 'Target mode to activate (home, away, night, or off)' }),
8
8
  delay: z
9
9
  .number()
10
10
  .int()
@@ -0,0 +1,24 @@
1
+ import { z } from '@hono/zod-openapi';
2
+
3
+ export const TripModeRequestSchema = z
4
+ .object({
5
+ mode: z
6
+ .enum(['home', 'away', 'night'])
7
+ .nullable()
8
+ .optional()
9
+ .openapi({
10
+ example: 'home',
11
+ description: 'Mode to trip. If omitted or null, the action applies to all modes.',
12
+ }),
13
+ delay: z
14
+ .number()
15
+ .int()
16
+ .nonnegative()
17
+ .nullable()
18
+ .optional()
19
+ .openapi({
20
+ example: 5,
21
+ description: 'Delay in seconds before activating the alarm.',
22
+ }),
23
+ })
24
+ .openapi('TripModeRequest');
@@ -14,6 +14,7 @@ import type { SwitchHandler } from '../handlers/switch-handler.js';
14
14
  import { ErrorSchema } from '../schemas/error-schema.js';
15
15
  import { StatusResponseSchema } from '../schemas/status-response-schema.js';
16
16
  import { ModeRequestSchema } from '../schemas/mode-request-schema.js';
17
+ import { TripModeRequestSchema } from '../schemas/trip-mode-request-schema.js';
17
18
  import { ArmingLockRequestSchema } from '../schemas/arming-lock-schema.js';
18
19
  import type { ServiceResult } from '../types/service-result-type.js';
19
20
 
@@ -52,11 +53,12 @@ const statusRoute = createRoute({
52
53
 
53
54
  const modeRoute = createRoute({
54
55
  method: 'put',
55
- path: '/mode',
56
- summary: 'Change security mode',
56
+ path: '/mode/update',
57
+ summary: 'Change mode',
57
58
  description:
58
- 'Sets the target security mode. Supported modes: home, away, night, off, triggered. ' +
59
- 'Use "triggered" to activate the alarm. An optional delay (seconds) defers the transition.',
59
+ 'Sets the target security mode. Supported modes: home, away, night, off. ' +
60
+ 'An optional delay (seconds) defers the transition. ' +
61
+ 'To trigger the alarm use the POST /mode/trip endpoint instead.',
60
62
  security: [{ BearerAuth: [] }],
61
63
  request: {
62
64
  body: {
@@ -79,10 +81,40 @@ const modeRoute = createRoute({
79
81
  },
80
82
  });
81
83
 
84
+ const tripModeRoute = createRoute({
85
+ method: 'post',
86
+ path: '/mode/trip',
87
+ summary: 'Trip mode',
88
+ description:
89
+ 'Activates the alarm ("triggered" state). ' +
90
+ 'If mode is specified the alarm only fires when the system is currently in that mode. ' +
91
+ 'An optional delay (seconds) defers the activation.',
92
+ security: [{ BearerAuth: [] }],
93
+ request: {
94
+ body: {
95
+ content: {
96
+ 'application/json': {
97
+ schema: TripModeRequestSchema,
98
+ example: { mode: 'home', delay: 30 },
99
+ },
100
+ },
101
+ required: true,
102
+ },
103
+ },
104
+ responses: {
105
+ 204: { description: 'Trip accepted' },
106
+ ...AUTH_RESPONSES,
107
+ 409: {
108
+ content: { 'application/json': { schema: ErrorSchema } },
109
+ description: 'Trip rejected by the system',
110
+ },
111
+ },
112
+ });
113
+
82
114
  const armingLockRoute = createRoute({
83
115
  method: 'put',
84
116
  path: '/switches/arming-lock',
85
- summary: 'Update arming lock switch',
117
+ summary: 'Update arming lock',
86
118
  description: 'Enables or disables the arming lock for a specific mode or globally.',
87
119
  security: [{ BearerAuth: [] }],
88
120
  request: {
@@ -117,11 +149,16 @@ export class ServerService {
117
149
  private readonly stateHandler: StateHandler,
118
150
  private readonly tripHandler: TripHandler,
119
151
  private readonly switchHandler: SwitchHandler,
120
- ) {}
121
-
122
- start(): void {
152
+ ) {
123
153
  this.registerRoutes();
154
+ }
124
155
 
156
+ /** Exposed for testing — returns the underlying Hono app. */
157
+ get app(): OpenAPIHono {
158
+ return this.application;
159
+ }
160
+
161
+ start(): void {
125
162
  const server = serve(
126
163
  { fetch: this.application.fetch, port: this.options.serverPort! },
127
164
  () => this.log.info(`Server (${this.options.serverPort})`),
@@ -184,27 +221,52 @@ export class ServerService {
184
221
  });
185
222
  });
186
223
 
187
- // PUT /mode
188
- this.application.use('/mode', auth);
224
+ // PUT /mode/update
225
+ this.application.use('/mode/update', auth);
189
226
  this.application.openapi(modeRoute, (c) => {
190
227
  const { mode, delay = 0 } = c.req.valid('json');
191
- let result: ServiceResult;
228
+ const result = this.stateHandler.updateTargetState(MODE_TO_STATE[mode], OriginType.EXTERNAL, delay);
192
229
 
193
- if (mode === 'triggered') {
194
- if (delay > 0) {
195
- result = this.tripHandler.updateTripSwitch(true, OriginType.EXTERNAL, false);
196
- } else {
197
- result = this.tripHandler.checkTripConditions(true, OriginType.EXTERNAL);
198
- if (result.success) {
199
- this.stateHandler.setCurrentState(SecurityState.TRIGGERED, OriginType.EXTERNAL);
200
- }
230
+ if (!result.success) {
231
+ return c.json({ reason: result.reason ?? 'Mode change rejected by the system' }, 409);
232
+ }
233
+
234
+ return c.body(null, 204);
235
+ });
236
+
237
+ // POST /mode/trip
238
+ this.application.use('/mode/trip', auth);
239
+ this.application.openapi(tripModeRoute, (c) => {
240
+ const { mode, delay } = c.req.valid('json');
241
+ const effectiveDelay = delay ?? 0;
242
+
243
+ if (effectiveDelay > 0) {
244
+ const precheck = this.tripHandler.checkTripConditions(true, OriginType.EXTERNAL);
245
+ if (!precheck.success) {
246
+ return c.json({ reason: precheck.reason ?? 'Trip rejected by the system' }, 409);
201
247
  }
248
+
249
+ setTimeout(() => {
250
+ if (mode != null) {
251
+ this.tripHandler.triggerIfModeSet(MODE_TO_STATE[mode], true);
252
+ } else {
253
+ this.tripHandler.updateTripSwitch(true, OriginType.EXTERNAL, false);
254
+ }
255
+ }, effectiveDelay * 1000);
256
+
257
+ return c.body(null, 204);
258
+ }
259
+
260
+ let result: ServiceResult;
261
+
262
+ if (mode != null) {
263
+ result = this.tripHandler.triggerIfModeSet(MODE_TO_STATE[mode], true);
202
264
  } else {
203
- result = this.stateHandler.updateTargetState(MODE_TO_STATE[mode], OriginType.EXTERNAL, delay);
265
+ result = this.tripHandler.updateTripSwitch(true, OriginType.EXTERNAL, false);
204
266
  }
205
267
 
206
268
  if (!result.success) {
207
- return c.json({ reason: result.reason ?? 'Mode change rejected by the system' }, 409);
269
+ return c.json({ reason: result.reason ?? 'Trip rejected by the system' }, 409);
208
270
  }
209
271
 
210
272
  return c.body(null, 204);
@@ -0,0 +1,238 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { SecurityState } from '../types/security-state-type.js';
3
+ import { OriginType } from '../types/origin-type.js';
4
+ import type { SystemState } from '../interfaces/system-state-interface.js';
5
+ import type { SecuritySystemOptions } from '../interfaces/options-interface.js';
6
+ import type { ServiceResult } from '../types/service-result-type.js';
7
+
8
+ // ── Helpers ───────────────────────────────────────────────────────────────────
9
+
10
+ function makeLog() {
11
+ return { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
12
+ }
13
+
14
+ function makeState(overrides: Partial<SystemState> = {}): SystemState {
15
+ return {
16
+ currentState: SecurityState.OFF,
17
+ targetState: SecurityState.OFF,
18
+ defaultState: SecurityState.OFF,
19
+ availableTargetStates: [SecurityState.HOME, SecurityState.AWAY, SecurityState.NIGHT, SecurityState.OFF],
20
+ isArming: false,
21
+ isTripping: false,
22
+ isKnocked: false,
23
+ serverAuthenticationAttempts: 0,
24
+ pausedCurrentState: null,
25
+ audioProcess: null,
26
+ ...overrides,
27
+ };
28
+ }
29
+
30
+ function makeOptions(overrides: Partial<SecuritySystemOptions> = {}): SecuritySystemOptions {
31
+ return {
32
+ serverPort: 8080,
33
+ serverApiKey: null,
34
+ ...overrides,
35
+ } as unknown as SecuritySystemOptions;
36
+ }
37
+
38
+ function makeStateHandler(result: ServiceResult = { success: true }) {
39
+ return {
40
+ updateTargetState: vi.fn().mockReturnValue(result),
41
+ isTripping: vi.fn().mockReturnValue(false),
42
+ setCurrentState: vi.fn(),
43
+ };
44
+ }
45
+
46
+ function makeTripHandler(result: ServiceResult = { success: true }) {
47
+ return {
48
+ updateTripSwitch: vi.fn().mockReturnValue(result),
49
+ triggerIfModeSet: vi.fn().mockReturnValue(result),
50
+ checkTripConditions: vi.fn().mockReturnValue(result),
51
+ };
52
+ }
53
+
54
+ function makeSwitchHandler(result: ServiceResult = { success: true }) {
55
+ return {
56
+ updateArmingLock: vi.fn().mockReturnValue(result),
57
+ };
58
+ }
59
+
60
+ async function makeService(
61
+ stateHandlerResult?: ServiceResult,
62
+ tripHandlerResult?: ServiceResult,
63
+ switchHandlerResult?: ServiceResult,
64
+ stateOverrides?: Partial<SystemState>,
65
+ optionsOverrides?: Partial<SecuritySystemOptions>,
66
+ ) {
67
+ const { ServerService } = await import('../services/server-service.js');
68
+ const log = makeLog();
69
+ const state = makeState(stateOverrides);
70
+ const options = makeOptions(optionsOverrides);
71
+ const stateHandler = makeStateHandler(stateHandlerResult);
72
+ const tripHandler = makeTripHandler(tripHandlerResult);
73
+ const switchHandler = makeSwitchHandler(switchHandlerResult);
74
+
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ const service = new ServerService(log as any, options, state, stateHandler as any, tripHandler as any, switchHandler as any);
77
+
78
+ return { service, stateHandler, tripHandler, switchHandler, state };
79
+ }
80
+
81
+ function json(body: unknown, path: string, method = 'PUT') {
82
+ return new Request(`http://localhost${path}`, {
83
+ method,
84
+ headers: { 'Content-Type': 'application/json' },
85
+ body: JSON.stringify(body),
86
+ });
87
+ }
88
+
89
+ // ── PUT /mode/update ──────────────────────────────────────────────────────────
90
+
91
+ describe('PUT /mode/update', () => {
92
+ it('returns 204 and calls updateTargetState with no delay', async () => {
93
+ const { service, stateHandler } = await makeService();
94
+ const res = await service.app.request(json({ mode: 'home' }, '/mode/update'));
95
+ expect(res.status).toBe(204);
96
+ expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.HOME, OriginType.EXTERNAL, 0);
97
+ });
98
+
99
+ it('returns 204 and passes delay to updateTargetState', async () => {
100
+ const { service, stateHandler } = await makeService();
101
+ const res = await service.app.request(json({ mode: 'away', delay: 10 }, '/mode/update'));
102
+ expect(res.status).toBe(204);
103
+ expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.AWAY, OriginType.EXTERNAL, 10);
104
+ });
105
+
106
+ it('returns 204 for off mode without delay', async () => {
107
+ const { service, stateHandler } = await makeService();
108
+ const res = await service.app.request(json({ mode: 'off' }, '/mode/update'));
109
+ expect(res.status).toBe(204);
110
+ expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.OFF, OriginType.EXTERNAL, 0);
111
+ });
112
+
113
+ it('returns 409 when stateHandler rejects the mode change', async () => {
114
+ const { service } = await makeService({ success: false, reason: 'target mode is already set' });
115
+ const res = await service.app.request(json({ mode: 'night' }, '/mode/update'));
116
+ expect(res.status).toBe(409);
117
+ const body = await res.json();
118
+ expect(body.reason).toBe('target mode is already set');
119
+ });
120
+
121
+ it('rejects "triggered" — returns 400 (Zod validation failure)', async () => {
122
+ const { service } = await makeService();
123
+ const res = await service.app.request(json({ mode: 'triggered' }, '/mode/update'));
124
+ expect(res.status).toBe(400);
125
+ });
126
+ });
127
+
128
+ // ── POST /mode/trip ───────────────────────────────────────────────────────────
129
+
130
+ describe('POST /mode/trip', () => {
131
+ afterEach(() => {
132
+ vi.useRealTimers();
133
+ });
134
+
135
+ it('returns 204 and calls updateTripSwitch when mode and delay are omitted', async () => {
136
+ const { service, tripHandler } = await makeService();
137
+ const res = await service.app.request(json({}, '/mode/trip', 'POST'));
138
+ expect(res.status).toBe(204);
139
+ expect(tripHandler.updateTripSwitch).toHaveBeenCalledWith(true, OriginType.EXTERNAL, false);
140
+ expect(tripHandler.triggerIfModeSet).not.toHaveBeenCalled();
141
+ });
142
+
143
+ it('returns 204 and calls updateTripSwitch when mode is null', async () => {
144
+ const { service, tripHandler } = await makeService();
145
+ const res = await service.app.request(json({ mode: null }, '/mode/trip', 'POST'));
146
+ expect(res.status).toBe(204);
147
+ expect(tripHandler.updateTripSwitch).toHaveBeenCalledWith(true, OriginType.EXTERNAL, false);
148
+ });
149
+
150
+ it('returns 204 and calls triggerIfModeSet when mode is specified', async () => {
151
+ const { service, tripHandler } = await makeService();
152
+ const res = await service.app.request(json({ mode: 'away' }, '/mode/trip', 'POST'));
153
+ expect(res.status).toBe(204);
154
+ expect(tripHandler.triggerIfModeSet).toHaveBeenCalledWith(SecurityState.AWAY, true);
155
+ expect(tripHandler.updateTripSwitch).not.toHaveBeenCalled();
156
+ });
157
+
158
+ it('returns 409 when trip handler rejects without delay', async () => {
159
+ const { service } = await makeService(undefined, { success: false, reason: 'not armed' });
160
+ const res = await service.app.request(json({}, '/mode/trip', 'POST'));
161
+ expect(res.status).toBe(409);
162
+ const body = await res.json();
163
+ expect(body.reason).toBe('not armed');
164
+ });
165
+
166
+ it('returns 409 when mode does not match (triggerIfModeSet rejects)', async () => {
167
+ const { service } = await makeService(undefined, { success: false, reason: 'mode not set' });
168
+ const res = await service.app.request(json({ mode: 'home' }, '/mode/trip', 'POST'));
169
+ expect(res.status).toBe(409);
170
+ const body = await res.json();
171
+ expect(body.reason).toBe('mode not set');
172
+ });
173
+
174
+ it('returns 204 immediately and calls updateTripSwitch after delay when mode is omitted', async () => {
175
+ vi.useFakeTimers();
176
+ const { service, tripHandler } = await makeService();
177
+
178
+ const res = await service.app.request(json({ delay: 5 }, '/mode/trip', 'POST'));
179
+ expect(res.status).toBe(204);
180
+ expect(tripHandler.checkTripConditions).toHaveBeenCalledWith(true, OriginType.EXTERNAL);
181
+ expect(tripHandler.updateTripSwitch).not.toHaveBeenCalled();
182
+
183
+ await vi.advanceTimersByTimeAsync(5000);
184
+ expect(tripHandler.updateTripSwitch).toHaveBeenCalledWith(true, OriginType.EXTERNAL, false);
185
+ });
186
+
187
+ it('returns 204 immediately and calls triggerIfModeSet after delay when mode is specified', async () => {
188
+ vi.useFakeTimers();
189
+ const { service, tripHandler } = await makeService();
190
+
191
+ const res = await service.app.request(json({ mode: 'night', delay: 3 }, '/mode/trip', 'POST'));
192
+ expect(res.status).toBe(204);
193
+ expect(tripHandler.triggerIfModeSet).not.toHaveBeenCalled();
194
+
195
+ await vi.advanceTimersByTimeAsync(3000);
196
+ expect(tripHandler.triggerIfModeSet).toHaveBeenCalledWith(SecurityState.NIGHT, true);
197
+ });
198
+
199
+ it('returns 409 on pre-check failure even when delay is specified', async () => {
200
+ const { service, tripHandler } = await makeService(
201
+ undefined,
202
+ { success: false, reason: 'arming in progress' },
203
+ );
204
+ const res = await service.app.request(json({ delay: 10 }, '/mode/trip', 'POST'));
205
+ expect(res.status).toBe(409);
206
+ expect(tripHandler.updateTripSwitch).not.toHaveBeenCalled();
207
+ });
208
+
209
+ it('treats delay: null the same as no delay', async () => {
210
+ const { service, tripHandler } = await makeService();
211
+ const res = await service.app.request(json({ delay: null }, '/mode/trip', 'POST'));
212
+ expect(res.status).toBe(204);
213
+ expect(tripHandler.updateTripSwitch).toHaveBeenCalledWith(true, OriginType.EXTERNAL, false);
214
+ });
215
+ });
216
+
217
+ // ── PUT /switches/arming-lock ─────────────────────────────────────────────────
218
+
219
+ describe('PUT /switches/arming-lock', () => {
220
+ it('returns 204 and calls updateArmingLock', async () => {
221
+ const { service, switchHandler } = await makeService();
222
+ const res = await service.app.request(json({ mode: 'home', value: true }, '/switches/arming-lock'));
223
+ expect(res.status).toBe(204);
224
+ expect(switchHandler.updateArmingLock).toHaveBeenCalledWith('home', true);
225
+ });
226
+
227
+ it('returns 409 when switchHandler rejects', async () => {
228
+ const { service } = await makeService(
229
+ undefined,
230
+ undefined,
231
+ { success: false, reason: 'unknown arming lock mode: invalid' },
232
+ );
233
+ const res = await service.app.request(json({ mode: 'global', value: false }, '/switches/arming-lock'));
234
+ expect(res.status).toBe(409);
235
+ const body = await res.json();
236
+ expect(body.reason).toBe('unknown arming lock mode: invalid');
237
+ });
238
+ });