@coffeerunhobby/mcp-ha-connect 2.1.1 → 2.2.1
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/README.md +23 -5
- package/dist/config.d.ts +4 -0
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/haClient/automations.d.ts +18 -2
- package/dist/haClient/automations.js +25 -7
- package/dist/haClient/automations.js.map +1 -1
- package/dist/haClient/events.d.ts +60 -7
- package/dist/haClient/events.js +252 -47
- package/dist/haClient/events.js.map +1 -1
- package/dist/haClient/index.d.ts +2 -1
- package/dist/haClient/index.js +4 -3
- package/dist/haClient/index.js.map +1 -1
- package/dist/haClient/wsCommand.d.ts +16 -0
- package/dist/haClient/wsCommand.js +36 -0
- package/dist/haClient/wsCommand.js.map +1 -0
- package/dist/omadaClient/client.d.ts +33 -1
- package/dist/omadaClient/client.js +61 -2
- package/dist/omadaClient/client.js.map +1 -1
- package/dist/omadaClient/index.d.ts +7 -0
- package/dist/omadaClient/index.js +6 -0
- package/dist/omadaClient/index.js.map +1 -1
- package/dist/omadaClient/request.d.ts +5 -0
- package/dist/omadaClient/request.js +11 -2
- package/dist/omadaClient/request.js.map +1 -1
- package/dist/ownerQuestions/service.d.ts +167 -0
- package/dist/ownerQuestions/service.js +439 -0
- package/dist/ownerQuestions/service.js.map +1 -0
- package/dist/server/common.d.ts +3 -0
- package/dist/server/common.js +2 -1
- package/dist/server/common.js.map +1 -1
- package/dist/server/http.js +30 -6
- package/dist/server/http.js.map +1 -1
- package/dist/server/stream.d.ts +2 -0
- package/dist/server/stream.js +2 -1
- package/dist/server/stream.js.map +1 -1
- package/dist/tools/common.d.ts +1 -1
- package/dist/tools/common.js +9 -2
- package/dist/tools/common.js.map +1 -1
- package/dist/tools/homeassistant/askOwner.d.ts +29 -0
- package/dist/tools/homeassistant/askOwner.js +85 -0
- package/dist/tools/homeassistant/askOwner.js.map +1 -0
- package/dist/tools/homeassistant/getAutomationTrace.d.ts +5 -0
- package/dist/tools/homeassistant/getAutomationTrace.js +10 -5
- package/dist/tools/homeassistant/getAutomationTrace.js.map +1 -1
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/omada/blockClient.js +3 -1
- package/dist/tools/omada/blockClient.js.map +1 -1
- package/dist/tools/omada/deleteClient.d.ts +3 -0
- package/dist/tools/omada/deleteClient.js +15 -0
- package/dist/tools/omada/deleteClient.js.map +1 -0
- package/dist/tools/omada/index.js +7 -2
- package/dist/tools/omada/index.js.map +1 -1
- package/dist/tools/omada/listBlockedClients.d.ts +3 -0
- package/dist/tools/omada/listBlockedClients.js +9 -0
- package/dist/tools/omada/listBlockedClients.js.map +1 -0
- package/dist/tools/omada/namespace.js +17 -0
- package/dist/tools/omada/namespace.js.map +1 -1
- package/dist/utils/plainText.d.ts +22 -0
- package/dist/utils/plainText.js +39 -0
- package/dist/utils/plainText.js.map +1 -0
- package/dist/utils/redact.d.ts +21 -0
- package/dist/utils/redact.js +53 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/CHANGELOG.md +24 -0
- package/openapi.json +1 -1
- package/package.json +1 -1
- package/src/config.ts +10 -0
- package/src/haClient/automations.ts +39 -7
- package/src/haClient/events.ts +289 -56
- package/src/haClient/index.ts +10 -4
- package/src/haClient/wsCommand.ts +44 -0
- package/src/omadaClient/client.ts +82 -4
- package/src/omadaClient/index.ts +9 -1
- package/src/omadaClient/request.ts +13 -2
- package/src/ownerQuestions/service.ts +536 -0
- package/src/server/common.ts +5 -1
- package/src/server/http.ts +30 -6
- package/src/server/stream.ts +4 -1
- package/src/tools/common.ts +9 -2
- package/src/tools/homeassistant/askOwner.ts +133 -0
- package/src/tools/homeassistant/getAutomationTrace.ts +13 -7
- package/src/tools/index.ts +8 -1
- package/src/tools/omada/blockClient.ts +3 -1
- package/src/tools/omada/deleteClient.ts +27 -0
- package/src/tools/omada/index.ts +7 -2
- package/src/tools/omada/listBlockedClients.ts +17 -0
- package/src/tools/omada/namespace.ts +19 -0
- package/src/utils/plainText.ts +48 -0
- package/src/utils/redact.ts +59 -0
- package/src/version.ts +1 -1
- package/tests/config.test.ts +12 -0
- package/tests/haClient/automationTrace.test.ts +59 -0
- package/tests/haClient/automations.test.ts +16 -0
- package/tests/haClient/events.test.ts +455 -0
- package/tests/haClient/eventsTls.test.ts +52 -0
- package/tests/omadaClient/clientBlocks.test.ts +170 -0
- package/tests/ownerQuestions/service.test.ts +499 -0
- package/tests/ownerQuestions/tools.test.ts +186 -0
- package/tests/tools/homeassistant/handlers.test.ts +10 -1
- package/tests/tools/omada/graph.test.ts +13 -2
- package/tests/utils/redact.test.ts +105 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner questions: ask the owner a yes/no-style question as an actionable phone
|
|
3
|
+
* notification and collect the button they tap.
|
|
4
|
+
*
|
|
5
|
+
* Stateless by design: everything needed to recognise a question travels in
|
|
6
|
+
* signed identifiers, so a server restart does not lose open questions.
|
|
7
|
+
* - The requestId given to the caller carries the question, title, buttons,
|
|
8
|
+
* target, expiry, the asking token's `sub` and a random nonce, signed with a
|
|
9
|
+
* key derived from the server secret.
|
|
10
|
+
* - Each button's action id carries the nonce, the button index, the expiry,
|
|
11
|
+
* the target and its own signature, so a tap that arrives after a restart is
|
|
12
|
+
* still verified and attributed. The secret never leaves the server, and the
|
|
13
|
+
* requestId is not enough to forge a button id.
|
|
14
|
+
* Only answers (and cancellations) are kept in memory until they are read. A tap
|
|
15
|
+
* that lands while the server is down is lost; the question then expires as NO.
|
|
16
|
+
*
|
|
17
|
+
* A tap counts only if Home Assistant reports it as coming from a mobile app
|
|
18
|
+
* webhook (origin REMOTE, with a user), so an automation that fires a fake
|
|
19
|
+
* `mobile_app_notification_action` event (origin LOCAL) cannot approve anything.
|
|
20
|
+
* With `allowedUserIds` (MCP_OWNER_QUESTIONS_HA_USERS) only those HA users can answer.
|
|
21
|
+
*
|
|
22
|
+
* Known limit, accepted: cancellations and answers are remembered in memory
|
|
23
|
+
* only, while a button's signed id stays valid until the question expires. After
|
|
24
|
+
* a restart, re-sending an old button id through Home Assistant's own API (which
|
|
25
|
+
* needs an HA login, and the id from HA's history) could answer a question that
|
|
26
|
+
* had been cancelled or denied. The phone itself no longer shows the buttons.
|
|
27
|
+
* Anyone with that access can already control the house directly.
|
|
28
|
+
*/
|
|
29
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
30
|
+
import { logger } from '../utils/logger.js';
|
|
31
|
+
export const ACTION_EVENT = 'mobile_app_notification_action';
|
|
32
|
+
export const LIMITS = {
|
|
33
|
+
questionChars: 256,
|
|
34
|
+
titleChars: 60,
|
|
35
|
+
buttonChars: 20,
|
|
36
|
+
maxButtons: 3,
|
|
37
|
+
minTimeoutSeconds: 60,
|
|
38
|
+
maxTimeoutSeconds: 7 * 24 * 60 * 60,
|
|
39
|
+
defaultTimeoutSeconds: 600,
|
|
40
|
+
maxWaitSeconds: 50,
|
|
41
|
+
maxOpenPerCaller: 3,
|
|
42
|
+
maxPerCallerPerHour: 30,
|
|
43
|
+
};
|
|
44
|
+
const REQUEST_PREFIX = 'q1';
|
|
45
|
+
const ACTION_PREFIX = 'MCPASK';
|
|
46
|
+
/** How long the confirmation that replaces an answered question stays on the phone. */
|
|
47
|
+
const CONFIRMATION_SECONDS = 300;
|
|
48
|
+
/** Outcomes stay readable this long after their question expired. */
|
|
49
|
+
const OUTCOME_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
50
|
+
const SWEEP_INTERVAL_MS = 10 * 60 * 1000;
|
|
51
|
+
const b64url = (buffer) => buffer.toString('base64url');
|
|
52
|
+
export class OwnerQuestionService {
|
|
53
|
+
deps;
|
|
54
|
+
key;
|
|
55
|
+
now;
|
|
56
|
+
/** Final outcomes by nonce, until the question's expiry passes. */
|
|
57
|
+
outcomes = new Map();
|
|
58
|
+
/** Open questions per caller sub: nonce -> expiry (ms). */
|
|
59
|
+
open = new Map();
|
|
60
|
+
/** Ask timestamps per caller sub, for the hourly cap. */
|
|
61
|
+
asked = new Map();
|
|
62
|
+
/** Pollers waiting for an outcome, by nonce. */
|
|
63
|
+
waiters = new Map();
|
|
64
|
+
/** Expiry timers by nonce (best effort: lost on restart; the phone also times out on its own). */
|
|
65
|
+
expiryTimers = new Map();
|
|
66
|
+
/** Labels and target of open questions, for nicer confirmations while the server stays up. */
|
|
67
|
+
known = new Map();
|
|
68
|
+
sweepTimer;
|
|
69
|
+
/** Why questions cannot be answered right now (Home Assistant refused the tap subscription). */
|
|
70
|
+
unavailable;
|
|
71
|
+
constructor(deps) {
|
|
72
|
+
this.deps = deps;
|
|
73
|
+
this.now = deps.now ?? Date.now;
|
|
74
|
+
if (deps.secret) {
|
|
75
|
+
this.key = createHmac('sha256', deps.secret).update('mcp-ha-connect/owner-questions/v1').digest();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.key = randomBytes(32);
|
|
79
|
+
logger.warn('Owner questions: no server secret configured; open questions will not survive a restart');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async start() {
|
|
83
|
+
await this.deps.subscribe(ACTION_EVENT, (event) => this.handleActionEvent(event), () => {
|
|
84
|
+
this.unavailable =
|
|
85
|
+
'Home Assistant refused the subscription to notification taps (the server needs an admin Home Assistant token)';
|
|
86
|
+
logger.error(`Owner questions unavailable: ${this.unavailable}`);
|
|
87
|
+
});
|
|
88
|
+
this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
|
|
89
|
+
this.sweepTimer.unref?.();
|
|
90
|
+
logger.info('Owner questions: listening for notification taps');
|
|
91
|
+
if (!this.deps.allowedUserIds?.length) {
|
|
92
|
+
logger.warn('Owner questions: any Home Assistant user can answer; set MCP_OWNER_QUESTIONS_HA_USERS to restrict it');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
stop() {
|
|
96
|
+
clearInterval(this.sweepTimer);
|
|
97
|
+
for (const timer of this.expiryTimers.values())
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
this.expiryTimers.clear();
|
|
100
|
+
}
|
|
101
|
+
async ask(input, caller) {
|
|
102
|
+
if (this.unavailable) {
|
|
103
|
+
throw new Error(`Owner questions are unavailable: ${this.unavailable}`);
|
|
104
|
+
}
|
|
105
|
+
const now = this.now();
|
|
106
|
+
this.enforceRateLimits(caller, now);
|
|
107
|
+
const nonce = b64url(randomBytes(16));
|
|
108
|
+
const expiresAtSec = Math.floor(now / 1000) + input.timeoutSeconds;
|
|
109
|
+
const payload = {
|
|
110
|
+
v: 1,
|
|
111
|
+
n: nonce,
|
|
112
|
+
s: caller,
|
|
113
|
+
t: input.target,
|
|
114
|
+
e: expiresAtSec,
|
|
115
|
+
b: input.buttons,
|
|
116
|
+
q: input.question,
|
|
117
|
+
ti: input.title,
|
|
118
|
+
};
|
|
119
|
+
const requestId = this.signRequest(payload);
|
|
120
|
+
const data = {
|
|
121
|
+
tag: this.tagFor(nonce),
|
|
122
|
+
actions: input.buttons.map((title, index) => ({
|
|
123
|
+
action: this.actionId(nonce, index, expiresAtSec, input.target),
|
|
124
|
+
title,
|
|
125
|
+
})),
|
|
126
|
+
// Stays until answered: not dismissed by tapping the body (sticky) or by
|
|
127
|
+
// swiping (persistent, needs the tag); the phone removes it at expiry.
|
|
128
|
+
sticky: true,
|
|
129
|
+
persistent: true,
|
|
130
|
+
priority: 'high',
|
|
131
|
+
ttl: 0,
|
|
132
|
+
timeout: input.timeoutSeconds,
|
|
133
|
+
};
|
|
134
|
+
if (input.channel)
|
|
135
|
+
data.channel = input.channel;
|
|
136
|
+
if (input.importance)
|
|
137
|
+
data.importance = input.importance;
|
|
138
|
+
// Reserve the slot before sending, so concurrent calls cannot all pass the limits.
|
|
139
|
+
this.reserve(caller, nonce, expiresAtSec * 1000, now);
|
|
140
|
+
try {
|
|
141
|
+
await this.deps.notify(input.target, { title: input.title, message: input.question, data });
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// The underlying error can carry request details; neither the caller
|
|
145
|
+
// nor this log gets its text.
|
|
146
|
+
this.release(caller, nonce, now);
|
|
147
|
+
logger.warn('Owner questions: sending the notification failed', { target: input.target });
|
|
148
|
+
throw new Error(`Could not send the notification through Home Assistant (is '${input.target}' a valid notify target?)`);
|
|
149
|
+
}
|
|
150
|
+
this.known.set(nonce, { target: input.target, title: input.title, buttons: input.buttons, expiresAt: expiresAtSec * 1000 });
|
|
151
|
+
// Count from now, after delivery: the signed expiry is what matters.
|
|
152
|
+
this.scheduleExpiry(nonce, Math.max(expiresAtSec * 1000 - this.now(), 0));
|
|
153
|
+
logger.info('Owner question sent', { caller, target: input.target, buttons: input.buttons.length, expiresAt: expiresAtSec });
|
|
154
|
+
return { requestId, expiresAt: new Date(expiresAtSec * 1000).toISOString() };
|
|
155
|
+
}
|
|
156
|
+
async getAnswer(requestId, caller, waitSeconds) {
|
|
157
|
+
const payload = this.verifyRequest(requestId);
|
|
158
|
+
if (!payload) {
|
|
159
|
+
return { status: 'unknown', reason: 'Not a valid question id from this server (or the server secret changed)' };
|
|
160
|
+
}
|
|
161
|
+
this.assertOwner(payload, caller);
|
|
162
|
+
const settled = this.settledAnswer(payload);
|
|
163
|
+
if (settled || waitSeconds <= 0) {
|
|
164
|
+
return settled ?? this.pendingAnswer(payload);
|
|
165
|
+
}
|
|
166
|
+
const untilExpiry = payload.e * 1000 - this.now();
|
|
167
|
+
await this.waitForOutcome(payload.n, Math.min(waitSeconds * 1000, Math.max(untilExpiry, 0)));
|
|
168
|
+
return this.settledAnswer(payload) ?? this.pendingAnswer(payload);
|
|
169
|
+
}
|
|
170
|
+
async cancel(requestId, caller) {
|
|
171
|
+
const payload = this.verifyRequest(requestId);
|
|
172
|
+
if (!payload) {
|
|
173
|
+
return { status: 'unknown', reason: 'Not a valid question id from this server (or the server secret changed)' };
|
|
174
|
+
}
|
|
175
|
+
this.assertOwner(payload, caller);
|
|
176
|
+
const settled = this.settledAnswer(payload);
|
|
177
|
+
if (settled) {
|
|
178
|
+
return settled;
|
|
179
|
+
}
|
|
180
|
+
this.settle(payload.n, { status: 'cancelled', cancelledAt: this.now() }, payload.e * 1000);
|
|
181
|
+
await this.replaceNotification(payload.n, payload.t, payload.ti, 'Cancelled - no answer needed');
|
|
182
|
+
return this.settledAnswer(payload);
|
|
183
|
+
}
|
|
184
|
+
/** Handle a `mobile_app_notification_action` event. Exposed for tests. */
|
|
185
|
+
handleActionEvent(event) {
|
|
186
|
+
const parsed = this.parseActionId(event.data.action);
|
|
187
|
+
if (!parsed) {
|
|
188
|
+
return; // not one of ours
|
|
189
|
+
}
|
|
190
|
+
if (event.origin !== 'REMOTE' || !event.context.user_id) {
|
|
191
|
+
logger.warn('Owner questions: ignored a tap not coming from a mobile app', { origin: event.origin });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (this.deps.allowedUserIds?.length && !this.deps.allowedUserIds.includes(event.context.user_id)) {
|
|
195
|
+
logger.warn('Owner questions: ignored a tap from a Home Assistant user that may not answer');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const { nonce, index, expiresAtSec, target } = parsed;
|
|
199
|
+
const known = this.known.get(nonce);
|
|
200
|
+
if (this.now() >= expiresAtSec * 1000) {
|
|
201
|
+
logger.info('Owner questions: ignored a tap after expiry');
|
|
202
|
+
void this.replaceNotification(nonce, target, known?.title ?? 'Question', 'Expired - the answer was not recorded');
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (this.outcomes.has(nonce)) {
|
|
206
|
+
return; // first tap wins
|
|
207
|
+
}
|
|
208
|
+
this.settle(nonce, { status: 'answered', index, answeredAt: this.now(), answeredBy: event.context.user_id }, expiresAtSec * 1000);
|
|
209
|
+
logger.info('Owner question answered', { index });
|
|
210
|
+
const label = known?.buttons[index];
|
|
211
|
+
void this.replaceNotification(nonce, target, known?.title ?? 'Question', label ? `Answered: ${label}` : 'Answer recorded');
|
|
212
|
+
}
|
|
213
|
+
// ---- internals -------------------------------------------------------
|
|
214
|
+
signRequest(payload) {
|
|
215
|
+
const body = `${REQUEST_PREFIX}.${b64url(Buffer.from(JSON.stringify(payload), 'utf8'))}`;
|
|
216
|
+
return `${body}.${b64url(this.hmac(`request|${body}`))}`;
|
|
217
|
+
}
|
|
218
|
+
verifyRequest(requestId) {
|
|
219
|
+
const parts = typeof requestId === 'string' ? requestId.split('.') : [];
|
|
220
|
+
if (parts.length !== 3 || parts[0] !== REQUEST_PREFIX) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const body = `${parts[0]}.${parts[1]}`;
|
|
224
|
+
if (!this.safeEqual(parts[2], b64url(this.hmac(`request|${body}`)))) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
229
|
+
return payload.v === 1 ? payload : null;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
actionId(nonce, index, expiresAtSec, target) {
|
|
236
|
+
const sig = b64url(this.hmac(`action|${nonce}|${index}|${expiresAtSec}|${target}`)).slice(0, 22);
|
|
237
|
+
return `${ACTION_PREFIX}.${nonce}.${index}.${expiresAtSec}.${target}.${sig}`;
|
|
238
|
+
}
|
|
239
|
+
parseActionId(action) {
|
|
240
|
+
if (typeof action !== 'string' || !action.startsWith(`${ACTION_PREFIX}.`)) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const match = /^MCPASK\.([A-Za-z0-9_-]{22})\.([0-2])\.(\d{1,12})\.(mobile_app_[a-z0-9_]{1,64})\.([A-Za-z0-9_-]{22})$/.exec(action);
|
|
244
|
+
if (!match) {
|
|
245
|
+
logger.warn('Owner questions: ignored a malformed action id');
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const [, nonce, index, expires, target, sig] = match;
|
|
249
|
+
const expected = b64url(this.hmac(`action|${nonce}|${index}|${expires}|${target}`)).slice(0, 22);
|
|
250
|
+
if (!this.safeEqual(sig, expected)) {
|
|
251
|
+
logger.warn('Owner questions: ignored a tap with an invalid signature');
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return { nonce, index: Number(index), expiresAtSec: Number(expires), target };
|
|
255
|
+
}
|
|
256
|
+
hmac(message) {
|
|
257
|
+
return createHmac('sha256', this.key).update(message).digest();
|
|
258
|
+
}
|
|
259
|
+
safeEqual(a, b) {
|
|
260
|
+
const left = Buffer.from(a);
|
|
261
|
+
const right = Buffer.from(b);
|
|
262
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
263
|
+
}
|
|
264
|
+
tagFor(nonce) {
|
|
265
|
+
return `mcpask-${nonce}`;
|
|
266
|
+
}
|
|
267
|
+
assertOwner(payload, caller) {
|
|
268
|
+
if (payload.s !== caller) {
|
|
269
|
+
throw new Error('This question was asked by a different client');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
settledAnswer(payload) {
|
|
273
|
+
const base = { question: payload.q, buttons: payload.b };
|
|
274
|
+
const outcome = this.outcomes.get(payload.n);
|
|
275
|
+
if (outcome?.status === 'answered') {
|
|
276
|
+
return {
|
|
277
|
+
status: 'answered',
|
|
278
|
+
index: outcome.index,
|
|
279
|
+
button: payload.b[outcome.index] ?? `#${outcome.index}`,
|
|
280
|
+
answeredAt: new Date(outcome.answeredAt).toISOString(),
|
|
281
|
+
answeredBy: outcome.answeredBy,
|
|
282
|
+
...base,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
if (outcome?.status === 'cancelled') {
|
|
286
|
+
return { status: 'cancelled', cancelledAt: new Date(outcome.cancelledAt).toISOString(), ...base };
|
|
287
|
+
}
|
|
288
|
+
if (this.now() >= payload.e * 1000) {
|
|
289
|
+
return { status: 'timeout', expiredAt: new Date(payload.e * 1000).toISOString(), ...base };
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
pendingAnswer(payload) {
|
|
294
|
+
return { status: 'pending', expiresAt: new Date(payload.e * 1000).toISOString(), question: payload.q, buttons: payload.b };
|
|
295
|
+
}
|
|
296
|
+
settle(nonce, outcome, expiresAtMs) {
|
|
297
|
+
this.outcomes.set(nonce, { ...outcome, expiresAt: expiresAtMs });
|
|
298
|
+
this.known.delete(nonce);
|
|
299
|
+
for (const questions of this.open.values()) {
|
|
300
|
+
questions.delete(nonce);
|
|
301
|
+
}
|
|
302
|
+
const timer = this.expiryTimers.get(nonce);
|
|
303
|
+
if (timer) {
|
|
304
|
+
clearTimeout(timer);
|
|
305
|
+
this.expiryTimers.delete(nonce);
|
|
306
|
+
}
|
|
307
|
+
this.wake(nonce);
|
|
308
|
+
}
|
|
309
|
+
wake(nonce) {
|
|
310
|
+
for (const resolve of this.waiters.get(nonce) ?? []) {
|
|
311
|
+
resolve();
|
|
312
|
+
}
|
|
313
|
+
this.waiters.delete(nonce);
|
|
314
|
+
}
|
|
315
|
+
waitForOutcome(nonce, ms) {
|
|
316
|
+
return new Promise((resolve) => {
|
|
317
|
+
const waiters = this.waiters.get(nonce) ?? new Set();
|
|
318
|
+
this.waiters.set(nonce, waiters);
|
|
319
|
+
const done = () => {
|
|
320
|
+
clearTimeout(timer);
|
|
321
|
+
waiters.delete(done);
|
|
322
|
+
if (waiters.size === 0 && this.waiters.get(nonce) === waiters) {
|
|
323
|
+
this.waiters.delete(nonce);
|
|
324
|
+
}
|
|
325
|
+
resolve();
|
|
326
|
+
};
|
|
327
|
+
const timer = setTimeout(done, ms);
|
|
328
|
+
waiters.add(done);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
scheduleExpiry(nonce, ms) {
|
|
332
|
+
// Node timers overflow above ~24.8 days; questions last at most 7 days.
|
|
333
|
+
const timer = setTimeout(() => {
|
|
334
|
+
this.expiryTimers.delete(nonce);
|
|
335
|
+
if (this.outcomes.has(nonce)) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
for (const questions of this.open.values()) {
|
|
339
|
+
questions.delete(nonce);
|
|
340
|
+
}
|
|
341
|
+
this.wake(nonce);
|
|
342
|
+
const known = this.known.get(nonce);
|
|
343
|
+
this.known.delete(nonce);
|
|
344
|
+
if (known) {
|
|
345
|
+
void this.replaceNotification(nonce, known.target, known.title, 'Expired - treated as no');
|
|
346
|
+
}
|
|
347
|
+
}, ms);
|
|
348
|
+
timer.unref?.();
|
|
349
|
+
this.expiryTimers.set(nonce, timer);
|
|
350
|
+
}
|
|
351
|
+
async replaceNotification(nonce, target, title, message) {
|
|
352
|
+
try {
|
|
353
|
+
// Same tag replaces the question; no actions, dismissible, gone after a while.
|
|
354
|
+
await this.deps.notify(target, {
|
|
355
|
+
title,
|
|
356
|
+
message,
|
|
357
|
+
data: { tag: this.tagFor(nonce), sticky: false, persistent: false, timeout: CONFIRMATION_SECONDS },
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
logger.warn('Owner questions: could not update the notification', { target });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
enforceRateLimits(caller, now) {
|
|
365
|
+
const open = this.open.get(caller);
|
|
366
|
+
if (open) {
|
|
367
|
+
for (const [nonce, expiresAt] of open) {
|
|
368
|
+
if (expiresAt <= now)
|
|
369
|
+
open.delete(nonce);
|
|
370
|
+
}
|
|
371
|
+
if (open.size >= LIMITS.maxOpenPerCaller) {
|
|
372
|
+
throw new Error(`Too many open questions (${open.size}); answer, cancel or wait for one to expire first`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const recent = (this.asked.get(caller) ?? []).filter((t) => t > now - 60 * 60 * 1000);
|
|
376
|
+
if (recent.length >= LIMITS.maxPerCallerPerHour) {
|
|
377
|
+
throw new Error(`Too many questions in the last hour (limit ${LIMITS.maxPerCallerPerHour})`);
|
|
378
|
+
}
|
|
379
|
+
this.asked.set(caller, recent);
|
|
380
|
+
}
|
|
381
|
+
reserve(caller, nonce, expiresAtMs, now) {
|
|
382
|
+
const open = this.open.get(caller) ?? new Map();
|
|
383
|
+
open.set(nonce, expiresAtMs);
|
|
384
|
+
this.open.set(caller, open);
|
|
385
|
+
const asked = this.asked.get(caller) ?? [];
|
|
386
|
+
asked.push(now);
|
|
387
|
+
this.asked.set(caller, asked);
|
|
388
|
+
}
|
|
389
|
+
release(caller, nonce, now) {
|
|
390
|
+
this.open.get(caller)?.delete(nonce);
|
|
391
|
+
const asked = this.asked.get(caller);
|
|
392
|
+
const index = asked?.lastIndexOf(now) ?? -1;
|
|
393
|
+
if (asked && index >= 0)
|
|
394
|
+
asked.splice(index, 1);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Periodic cleanup, independent of new activity: outcomes a day after their
|
|
398
|
+
* question expired (a caller polling that late sees "timeout"), labels of
|
|
399
|
+
* expired questions, and rate-limit entries that no longer count.
|
|
400
|
+
* Exposed for tests.
|
|
401
|
+
*/
|
|
402
|
+
sweep() {
|
|
403
|
+
const now = this.now();
|
|
404
|
+
for (const [nonce, outcome] of this.outcomes) {
|
|
405
|
+
if (outcome.expiresAt + OUTCOME_RETENTION_MS < now)
|
|
406
|
+
this.outcomes.delete(nonce);
|
|
407
|
+
}
|
|
408
|
+
for (const [nonce, known] of this.known) {
|
|
409
|
+
if (known.expiresAt < now)
|
|
410
|
+
this.known.delete(nonce);
|
|
411
|
+
}
|
|
412
|
+
for (const [caller, open] of this.open) {
|
|
413
|
+
for (const [nonce, expiresAt] of open) {
|
|
414
|
+
if (expiresAt <= now)
|
|
415
|
+
open.delete(nonce);
|
|
416
|
+
}
|
|
417
|
+
if (open.size === 0)
|
|
418
|
+
this.open.delete(caller);
|
|
419
|
+
}
|
|
420
|
+
for (const [caller, asked] of this.asked) {
|
|
421
|
+
const recent = asked.filter((t) => t > now - 60 * 60 * 1000);
|
|
422
|
+
if (recent.length === 0)
|
|
423
|
+
this.asked.delete(caller);
|
|
424
|
+
else
|
|
425
|
+
this.asked.set(caller, recent);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/** Sizes of the in-memory state, for tests and diagnostics. */
|
|
429
|
+
stateSize() {
|
|
430
|
+
return {
|
|
431
|
+
outcomes: this.outcomes.size,
|
|
432
|
+
known: this.known.size,
|
|
433
|
+
open: this.open.size,
|
|
434
|
+
asked: this.asked.size,
|
|
435
|
+
waiters: this.waiters.size,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/ownerQuestions/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,CAAC,MAAM,YAAY,GAAG,gCAAgC,CAAC;AAE7D,MAAM,CAAC,MAAM,MAAM,GAAG;IAClB,aAAa,EAAE,GAAG;IAClB,UAAU,EAAE,EAAE;IACd,WAAW,EAAE,EAAE;IACf,UAAU,EAAE,CAAC;IACb,iBAAiB,EAAE,EAAE;IACrB,iBAAiB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;IACnC,qBAAqB,EAAE,GAAG;IAC1B,cAAc,EAAE,EAAE;IAClB,gBAAgB,EAAE,CAAC;IACnB,mBAAmB,EAAE,EAAE;CACjB,CAAC;AAEX,MAAM,cAAc,GAAG,IAAI,CAAC;AAC5B,MAAM,aAAa,GAAG,QAAQ,CAAC;AAC/B,uFAAuF;AACvF,MAAM,oBAAoB,GAAG,GAAG,CAAC;AACjC,qEAAqE;AACrE,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACjD,MAAM,iBAAiB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAsEzC,MAAM,MAAM,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAExE,MAAM,OAAO,oBAAoB;IAmBA;IAlBZ,GAAG,CAAS;IACZ,GAAG,CAAe;IACnC,mEAAmE;IAClD,QAAQ,GAAG,IAAI,GAAG,EAA2C,CAAC;IAC/E,2DAA2D;IAC1C,IAAI,GAAG,IAAI,GAAG,EAA+B,CAAC;IAC/D,yDAAyD;IACxC,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IACrD,gDAAgD;IAC/B,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC9D,kGAAkG;IACjF,YAAY,GAAG,IAAI,GAAG,EAAyC,CAAC;IACjF,8FAA8F;IAC7E,KAAK,GAAG,IAAI,GAAG,EAAmF,CAAC;IAC5G,UAAU,CAA6C;IAC/D,gGAAgG;IACxF,WAAW,CAAqB;IAExC,YAA6B,IAAuB;QAAvB,SAAI,GAAJ,IAAI,CAAmB;QAChD,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAChC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,mCAAmC,CAAC,CAAC,MAAM,EAAE,CAAC;QACtG,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,GAAG,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,yFAAyF,CAAC,CAAC;QAC3G,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK;QACP,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CACrB,YAAY,EACZ,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EACxC,GAAG,EAAE;YACD,IAAI,CAAC,WAAW;gBACZ,+GAA+G,CAAC;YACpH,MAAM,CAAC,KAAK,CAAC,gCAAgC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACrE,CAAC,CACJ,CAAC;QACF,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;QACrE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,sGAAsG,CAAC,CAAC;QACxH,CAAC;IACL,CAAC;IAED,IAAI;QACA,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACpE,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAoB,EAAE,MAAc;QAC1C,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,oCAAoC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAEpC,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;QACtC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,cAAc,CAAC;QACnE,MAAM,OAAO,GAAoB;YAC7B,CAAC,EAAE,CAAC;YACJ,CAAC,EAAE,KAAK;YACR,CAAC,EAAE,MAAM;YACT,CAAC,EAAE,KAAK,CAAC,MAAM;YACf,CAAC,EAAE,YAAY;YACf,CAAC,EAAE,KAAK,CAAC,OAAO;YAChB,CAAC,EAAE,KAAK,CAAC,QAAQ;YACjB,EAAE,EAAE,KAAK,CAAC,KAAK;SAClB,CAAC;QACF,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAE5C,MAAM,IAAI,GAA4B;YAClC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YACvB,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC1C,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC;gBAC/D,KAAK;aACR,CAAC,CAAC;YACH,yEAAyE;YACzE,uEAAuE;YACvE,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,MAAM;YAChB,GAAG,EAAE,CAAC;YACN,OAAO,EAAE,KAAK,CAAC,cAAc;SAChC,CAAC;QACF,IAAI,KAAK,CAAC,OAAO;YAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAChD,IAAI,KAAK,CAAC,UAAU;YAAE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;QAEzD,mFAAmF;QACnF,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC;QACtD,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAChG,CAAC;QAAC,MAAM,CAAC;YACL,qEAAqE;YACrE,8BAA8B;YAC9B,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,kDAAkD,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YAC1F,MAAM,IAAI,KAAK,CAAC,+DAA+D,KAAK,CAAC,MAAM,2BAA2B,CAAC,CAAC;QAC5H,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,GAAG,IAAI,EAAE,CAAC,CAAC;QAC5H,qEAAqE;QACrE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1E,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC;QAE7H,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,SAAiB,EAAE,MAAc,EAAE,WAAmB;QAClE,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,yEAAyE,EAAE,CAAC;QACpH,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAElC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO,OAAO,IAAI,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAClD,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7F,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB,EAAE,MAAc;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,yEAAyE,EAAE,CAAC;QACpH,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAElC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,EAAE,CAAC;YACV,OAAO,OAAO,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3F,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,8BAA8B,CAAC,CAAC;QACjG,OAAO,IAAI,CAAC,aAAa,CAAC,OAAO,CAAE,CAAC;IACxC,CAAC;IAED,0EAA0E;IAC1E,iBAAiB,CAAC,KAAkB;QAChC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,CAAC,kBAAkB;QAC9B,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACtD,MAAM,CAAC,IAAI,CAAC,6DAA6D,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YACrG,OAAO;QACX,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAChG,MAAM,CAAC,IAAI,CAAC,+EAA+E,CAAC,CAAC;YAC7F,OAAO;QACX,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,YAAY,GAAG,IAAI,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;YAC3D,KAAK,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,UAAU,EAAE,uCAAuC,CAAC,CAAC;YAClH,OAAO;QACX,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,iBAAiB;QAC7B,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,YAAY,GAAG,IAAI,CAAC,CAAC;QAClI,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACpC,KAAK,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa,KAAK,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC/H,CAAC;IAED,yEAAyE;IAEjE,WAAW,CAAC,OAAwB;QACxC,MAAM,IAAI,GAAG,GAAG,cAAc,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACzF,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;IAC7D,CAAC;IAEO,aAAa,CAAC,SAAiB;QACnC,MAAM,KAAK,GAAG,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,cAAc,EAAE,CAAC;YACpD,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAoB,CAAC;YACnG,OAAO,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAEO,QAAQ,CAAC,KAAa,EAAE,KAAa,EAAE,YAAoB,EAAE,MAAc;QAC/E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,IAAI,KAAK,IAAI,YAAY,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACjG,OAAO,GAAG,aAAa,IAAI,KAAK,IAAI,KAAK,IAAI,YAAY,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;IACjF,CAAC;IAEO,aAAa,CAAC,MAAe;QACjC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,aAAa,GAAG,CAAC,EAAE,CAAC;YACxE,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,KAAK,GAAG,uGAAuG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnI,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;QACrD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,IAAI,KAAK,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACjG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;YACxE,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAClF,CAAC;IAEO,IAAI,CAAC,OAAe;QACxB,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;IACnE,CAAC;IAEO,SAAS,CAAC,CAAS,EAAE,CAAS;QAClC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACxE,CAAC;IAEO,MAAM,CAAC,KAAa;QACxB,OAAO,UAAU,KAAK,EAAE,CAAC;IAC7B,CAAC;IAEO,WAAW,CAAC,OAAwB,EAAE,MAAc;QACxD,IAAI,OAAO,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACrE,CAAC;IACL,CAAC;IAEO,aAAa,CAAC,OAAwB;QAC1C,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7C,IAAI,OAAO,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;YACjC,OAAO;gBACH,MAAM,EAAE,UAAU;gBAClB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE;gBACvD,UAAU,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE;gBACtD,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,GAAG,IAAI;aACV,CAAC;QACN,CAAC;QACD,IAAI,OAAO,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC;QACtG,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;YACjC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC;QAC/F,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAEO,aAAa,CAAC,OAAwB;QAC1C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;IAC/H,CAAC;IAEO,MAAM,CAAC,KAAa,EAAE,OAAgB,EAAE,WAAmB;QAC/D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzB,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACzC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,KAAK,EAAE,CAAC;YACR,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAEO,IAAI,CAAC,KAAa;QACtB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAClD,OAAO,EAAE,CAAC;QACd,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAEO,cAAc,CAAC,KAAa,EAAE,EAAU;QAC5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,EAAc,CAAC;YACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,GAAG,EAAE;gBACd,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACrB,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,OAAO,EAAE,CAAC;oBAC5D,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC/B,CAAC;gBACD,OAAO,EAAE,CAAC;YACd,CAAC,CAAC;YACF,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACnC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,cAAc,CAAC,KAAa,EAAE,EAAU;QAC5C,wEAAwE;QACxE,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,OAAO;YACX,CAAC;YACD,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;gBACzC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACpC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACzB,IAAI,KAAK,EAAE,CAAC;gBACR,KAAK,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,yBAAyB,CAAC,CAAC;YAC/F,CAAC;QACL,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACxC,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAAC,KAAa,EAAE,MAAc,EAAE,KAAa,EAAE,OAAe;QAC3F,IAAI,CAAC;YACD,+EAA+E;YAC/E,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;gBAC3B,KAAK;gBACL,OAAO;gBACP,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE;aACrG,CAAC,CAAC;QACP,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,CAAC,IAAI,CAAC,oDAAoD,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAClF,CAAC;IACL,CAAC;IAEO,iBAAiB,CAAC,MAAc,EAAE,GAAW;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,IAAI,EAAE,CAAC;YACP,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC;gBACpC,IAAI,SAAS,IAAI,GAAG;oBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC7C,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,IAAI,mDAAmD,CAAC,CAAC;YAC9G,CAAC;QACL,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACtF,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,8CAA8C,MAAM,CAAC,mBAAmB,GAAG,CAAC,CAAC;QACjG,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAEO,OAAO,CAAC,MAAc,EAAE,KAAa,EAAE,WAAmB,EAAE,GAAW;QAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,EAAkB,CAAC;QAChE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAEO,OAAO,CAAC,MAAc,EAAE,KAAa,EAAE,GAAW;QACtD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5C,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC;YAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC;IAED;;;;;OAKG;IACH,KAAK;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3C,IAAI,OAAO,CAAC,SAAS,GAAG,oBAAoB,GAAG,GAAG;gBAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACpF,CAAC;QACD,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC;QACD,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACrC,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC;gBACpC,IAAI,SAAS,IAAI,GAAG;oBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC7C,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClD,CAAC;QACD,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC7D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;;gBAC9C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;IACL,CAAC;IAED,+DAA+D;IAC/D,SAAS;QACL,OAAO;YACH,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;YAC5B,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;YACtB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;YACpB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;YACtB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI;SAC7B,CAAC;IACN,CAAC;CACJ"}
|
package/dist/server/common.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { LocalAIClient } from '../localAI/index.js';
|
|
|
9
9
|
import type { OmadaClient } from '../omadaClient/index.js';
|
|
10
10
|
import type { OmadaRegistrationMode } from '../tools/omada/index.js';
|
|
11
11
|
import type { RestAction } from '../tools/infra/index.js';
|
|
12
|
+
import type { OwnerQuestionService } from '../ownerQuestions/service.js';
|
|
12
13
|
export interface CreateServerOptions {
|
|
13
14
|
haClient?: HaClient;
|
|
14
15
|
omadaClient?: OmadaClient;
|
|
@@ -24,6 +25,8 @@ export interface CreateServerOptions {
|
|
|
24
25
|
* Execution-time RBAC in wrapToolHandler still applies either way.
|
|
25
26
|
*/
|
|
26
27
|
callerPermissions?: number;
|
|
28
|
+
/** Owner questions service (askOwner & co.), when running. */
|
|
29
|
+
ownerQuestions?: OwnerQuestionService;
|
|
27
30
|
}
|
|
28
31
|
export declare function createServer(options: CreateServerOptions): McpServer;
|
|
29
32
|
/**
|
package/dist/server/common.js
CHANGED
|
@@ -10,7 +10,7 @@ import { generateInstructions } from './instructions.js';
|
|
|
10
10
|
import { logger } from '../utils/logger.js';
|
|
11
11
|
import { VERSION } from '../version.js';
|
|
12
12
|
export function createServer(options) {
|
|
13
|
-
const { haClient, omadaClient, aiClient, restActions, toolRegistrationMode, callerPermissions } = options;
|
|
13
|
+
const { haClient, omadaClient, aiClient, restActions, toolRegistrationMode, callerPermissions, ownerQuestions } = options;
|
|
14
14
|
logger.debug('Creating MCP server instance');
|
|
15
15
|
// Generate instructions based on enabled plugins
|
|
16
16
|
const instructions = generateInstructions({
|
|
@@ -45,6 +45,7 @@ export function createServer(options) {
|
|
|
45
45
|
aiClient,
|
|
46
46
|
restActions,
|
|
47
47
|
toolRegistrationMode,
|
|
48
|
+
ownerQuestions,
|
|
48
49
|
});
|
|
49
50
|
}
|
|
50
51
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/server/common.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAKzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/server/common.ts"],"names":[],"mappings":"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"}
|
package/dist/server/http.js
CHANGED
|
@@ -10,6 +10,7 @@ import { handleEventSubscription } from './eventSubscription.js';
|
|
|
10
10
|
import { RateLimiter } from './rateLimiter.js';
|
|
11
11
|
import { createAuthMiddleware } from './auth.js';
|
|
12
12
|
import { sanitizeError } from '../utils/sanitizeError.js';
|
|
13
|
+
import { OwnerQuestionService } from '../ownerQuestions/service.js';
|
|
13
14
|
import { LEGACY_BINDINGS, resolveChatBindings, buildOpenApiSpec, dispatchChatRequest, requiredPermissionFor, } from './chatFace.js';
|
|
14
15
|
// Session storage for stateful mode
|
|
15
16
|
const sessions = new Map();
|
|
@@ -278,23 +279,45 @@ export async function startHttpServer(options) {
|
|
|
278
279
|
issuer: config.authIssuer,
|
|
279
280
|
audience: config.authAudience,
|
|
280
281
|
});
|
|
281
|
-
// Initialize event subscriber
|
|
282
|
+
// Initialize the HA event subscriber when SSE events or owner questions need it
|
|
282
283
|
let eventSubscriber = null;
|
|
283
|
-
if (config.sseEventsEnabled && config.baseUrl && config.token) {
|
|
284
|
+
if ((config.sseEventsEnabled || config.ownerQuestionsEnabled) && config.baseUrl && config.token) {
|
|
284
285
|
eventSubscriber = new EventSubscriber({
|
|
285
286
|
baseUrl: config.baseUrl,
|
|
286
287
|
token: config.token,
|
|
288
|
+
strictSsl: config.strictSsl,
|
|
287
289
|
});
|
|
288
|
-
// Connect to Home Assistant WebSocket
|
|
290
|
+
// Connect to Home Assistant WebSocket. On failure the subscriber keeps
|
|
291
|
+
// retrying in the background; it is kept (not dropped) so events and owner
|
|
292
|
+
// questions start working as soon as Home Assistant is reachable.
|
|
289
293
|
try {
|
|
290
294
|
await eventSubscriber.connect();
|
|
291
295
|
logger.info('Event subscriber connected to Home Assistant');
|
|
292
296
|
}
|
|
293
297
|
catch (error) {
|
|
294
|
-
logger.warn('
|
|
298
|
+
logger.warn('Home Assistant WebSocket not reachable yet; retrying in the background', {
|
|
299
|
+
error: error instanceof Error ? error.message : String(error),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Owner questions (askOwner & co.) listen for notification taps on the subscriber.
|
|
304
|
+
let ownerQuestions;
|
|
305
|
+
if (config.ownerQuestionsEnabled && eventSubscriber && client) {
|
|
306
|
+
const subscriber = eventSubscriber;
|
|
307
|
+
const service = new OwnerQuestionService({
|
|
308
|
+
notify: (target, serviceData) => client.callService({ domain: 'notify', service: target, service_data: serviceData }),
|
|
309
|
+
subscribe: (eventType, callback, onRejected) => subscriber.subscribeEventType(eventType, callback, onRejected),
|
|
310
|
+
secret: config.authSecret,
|
|
311
|
+
allowedUserIds: config.ownerQuestionsHaUsers,
|
|
312
|
+
});
|
|
313
|
+
try {
|
|
314
|
+
await service.start();
|
|
315
|
+
ownerQuestions = service;
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
logger.warn('Owner questions unavailable: could not subscribe to notification taps', {
|
|
295
319
|
error: error instanceof Error ? error.message : String(error),
|
|
296
320
|
});
|
|
297
|
-
eventSubscriber = null;
|
|
298
321
|
}
|
|
299
322
|
}
|
|
300
323
|
logger.info('Starting HTTP server', {
|
|
@@ -402,7 +425,7 @@ export async function startHttpServer(options) {
|
|
|
402
425
|
const sessionId = req.headers['mcp-session-id'];
|
|
403
426
|
const existingState = sessionId ? sessions.get(sessionId) : undefined;
|
|
404
427
|
const body = req.method !== 'GET' ? await parseBody(req) : undefined;
|
|
405
|
-
const streamOptions = { haClient: client, omadaClient, aiClient, config };
|
|
428
|
+
const streamOptions = { haClient: client, omadaClient, aiClient, config, ownerQuestions };
|
|
406
429
|
const state = await handleStreamRequest(streamOptions, req, res, body, existingState);
|
|
407
430
|
if (state && config.stateful && sessionId) {
|
|
408
431
|
sessions.set(sessionId, state);
|
|
@@ -474,6 +497,7 @@ export async function startHttpServer(options) {
|
|
|
474
497
|
}
|
|
475
498
|
sessions.clear();
|
|
476
499
|
// Close event subscriber
|
|
500
|
+
ownerQuestions?.stop();
|
|
477
501
|
if (eventSubscriber) {
|
|
478
502
|
eventSubscriber.disconnect();
|
|
479
503
|
}
|