@nubisco/openbridge 0.38.6 → 0.39.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth-gate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-gate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth-gate.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { createHmac } from 'crypto';
3
+ import { request } from 'http';
4
+ import Fastify from 'fastify';
5
+ import websocket from '@fastify/websocket';
6
+ import { normalizeRequestPath, registerAuthRoutes, requiresSession } from '../auth.js';
7
+ const SECRET = Buffer.from('auth-gate-test-secret-0123456789', 'utf8');
8
+ function sessionCookie() {
9
+ const data = Buffer.from(JSON.stringify({ userId: 'u1', email: 'u1@example.test', exp: Math.floor(Date.now() / 1000) + 600 }), 'utf8').toString('base64url');
10
+ const sig = createHmac('sha256', SECRET).update(data).digest('base64url');
11
+ return `openbridge_session=${data}.${sig}`;
12
+ }
13
+ /**
14
+ * Every spelling here reached a protected handler, or would have if the
15
+ * router learned to fold it, while the gate compared the raw URL against
16
+ * "/api/" and "/ws/".
17
+ */
18
+ const BYPASS_ATTEMPTS = [
19
+ '/%61pi/plugins',
20
+ '/%61%70%69/plugins',
21
+ '/%2561pi/plugins',
22
+ '/%41PI/plugins',
23
+ '/API/plugins',
24
+ '/Api/Plugins',
25
+ '/api%2fplugins',
26
+ '/api%2Fplugins',
27
+ '/%2fapi/plugins',
28
+ '//api/plugins',
29
+ '/api//plugins',
30
+ '/api/plugins.',
31
+ '/api./plugins',
32
+ '/api/./plugins',
33
+ '/%2e/api/plugins',
34
+ '/assets/../api/plugins',
35
+ '/assets/..%2fapi/plugins',
36
+ '/assets/%2e%2e/api/plugins',
37
+ '/static/..%5capi/plugins',
38
+ '/api/plugins?x=1',
39
+ ];
40
+ describe('normalizeRequestPath', () => {
41
+ it('folds every spelling of a protected path to the same form', () => {
42
+ for (const url of BYPASS_ATTEMPTS) {
43
+ expect(normalizeRequestPath(url), url).toBe('/api/plugins');
44
+ }
45
+ });
46
+ it('leaves dashboard routes alone', () => {
47
+ expect(normalizeRequestPath('/devices/abc')).toBe('/devices/abc');
48
+ expect(normalizeRequestPath('/')).toBe('/');
49
+ });
50
+ it('refuses a path that cannot be decoded', () => {
51
+ expect(normalizeRequestPath('/%E0%A4%A')).toBeNull();
52
+ });
53
+ });
54
+ describe('requiresSession', () => {
55
+ it('gates by the matched route even when the raw URL looks unrelated', () => {
56
+ expect(requiresSession('/api/plugins', '/%61pi/plugins')).toBe(true);
57
+ expect(requiresSession('/ws/shell', '/%77s/shell')).toBe(true);
58
+ });
59
+ it('gates unmatched spellings of protected paths', () => {
60
+ for (const url of BYPASS_ATTEMPTS)
61
+ expect(requiresSession(undefined, url), url).toBe(true);
62
+ expect(requiresSession(undefined, '/WS/logs')).toBe(true);
63
+ });
64
+ it('keeps health, auth and the dashboard public', () => {
65
+ expect(requiresSession('/api/health', '/api/health')).toBe(false);
66
+ expect(requiresSession('/auth/me', '/auth/me')).toBe(false);
67
+ expect(requiresSession('/auth/platform/config', '/auth/platform/config')).toBe(false);
68
+ expect(requiresSession(undefined, '/')).toBe(false);
69
+ expect(requiresSession(undefined, '/devices/abc')).toBe(false);
70
+ expect(requiresSession(undefined, '/apiary')).toBe(false);
71
+ });
72
+ });
73
+ describe('platform auth gate on a running server', () => {
74
+ let app;
75
+ let port = 0;
76
+ beforeAll(async () => {
77
+ app = Fastify({ logger: false });
78
+ await app.register(websocket);
79
+ const config = { enabled: true, issuer: 'https://issuer.invalid', appId: null, secret: SECRET };
80
+ await registerAuthRoutes(app, config);
81
+ app.get('/api/health', async () => ({ status: 'ok' }));
82
+ app.get('/api/plugins', async () => ({ plugins: ['secret'] }));
83
+ app.get('/ws/shell', { websocket: true }, (ws) => ws.send('shell-open'));
84
+ // Mirrors the dashboard fallback in server.ts.
85
+ app.setNotFoundHandler(async (_req, reply) => reply.type('text/html').send('<html></html>'));
86
+ await app.listen({ port: 0, host: '127.0.0.1' });
87
+ port = app.server.address().port;
88
+ });
89
+ afterAll(async () => {
90
+ await app.close();
91
+ });
92
+ it.each(BYPASS_ATTEMPTS)('refuses %s without a session', async (url) => {
93
+ const res = await app.inject({ method: 'GET', url });
94
+ expect(res.statusCode).toBe(401);
95
+ expect(res.json()).toEqual({ error: 'unauthenticated' });
96
+ });
97
+ it('serves protected routes and health as before', async () => {
98
+ expect((await app.inject({ method: 'GET', url: '/api/health' })).statusCode).toBe(200);
99
+ expect((await app.inject({ method: 'GET', url: '/api/plugins' })).statusCode).toBe(401);
100
+ const authed = await app.inject({ method: 'GET', url: '/%61pi/plugins', headers: { cookie: sessionCookie() } });
101
+ expect(authed.statusCode).toBe(200);
102
+ expect(authed.json()).toEqual({ plugins: ['secret'] });
103
+ expect((await app.inject({ method: 'GET', url: '/devices/abc' })).statusCode).toBe(200);
104
+ });
105
+ /** Send a WebSocket upgrade and report the status the server answered with. */
106
+ function upgrade(path, cookie) {
107
+ return new Promise((resolve, reject) => {
108
+ // A fresh connection per attempt: the server closes one it refused an upgrade on.
109
+ const req = request({
110
+ agent: false,
111
+ host: '127.0.0.1',
112
+ port,
113
+ path,
114
+ headers: {
115
+ Connection: 'Upgrade',
116
+ Upgrade: 'websocket',
117
+ 'Sec-WebSocket-Version': '13',
118
+ 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
119
+ ...(cookie ? { Cookie: cookie } : {}),
120
+ },
121
+ });
122
+ req.on('upgrade', (res, socket) => {
123
+ socket.destroy();
124
+ resolve(res.statusCode ?? 0);
125
+ });
126
+ req.on('response', (res) => {
127
+ res.resume();
128
+ resolve(res.statusCode ?? 0);
129
+ });
130
+ req.on('error', reject);
131
+ req.end();
132
+ });
133
+ }
134
+ it.each(['/ws/shell', '/%77s/shell', '/%77%73/shell', '/WS/shell', '/ws//shell', '/assets/..%2fws/shell'])('refuses a WebSocket upgrade to %s without a session', async (path) => {
135
+ expect(await upgrade(path)).toBe(401);
136
+ });
137
+ it('upgrades a WebSocket with a session', async () => {
138
+ expect(await upgrade('/ws/shell', sessionCookie())).toBe(101);
139
+ });
140
+ });
141
+ //# sourceMappingURL=auth-gate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-gate.test.js","sourceRoot":"","sources":["../../src/__tests__/auth-gate.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=observations.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observations.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/observations.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync, readFileSync, readdirSync } from 'fs';
3
+ import { tmpdir } from 'os';
4
+ import { join } from 'path';
5
+ import { ObservationStore, validateObservation } from '../observations.js';
6
+ let home;
7
+ beforeEach(() => {
8
+ home = mkdtempSync(join(tmpdir(), 'ob-obs-'));
9
+ });
10
+ afterEach(() => {
11
+ rmSync(home, { recursive: true, force: true });
12
+ });
13
+ function obs(over = {}) {
14
+ return { at: new Date().toISOString(), roomTemperature: 21, comfort: 'ok', ...over };
15
+ }
16
+ describe('ObservationStore', () => {
17
+ it('returns nothing for a device that has never been reported on', () => {
18
+ expect(new ObservationStore(home).list('heating')).toEqual([]);
19
+ expect(new ObservationStore(home).count('heating')).toBe(0);
20
+ });
21
+ it('stores and returns an observation with its context', () => {
22
+ const store = new ObservationStore(home);
23
+ store.append('heating', obs({ roomTemperature: 19, comfort: 'cold', context: { outside: 8, flow: 40 } }));
24
+ const [first] = store.list('heating');
25
+ expect(first.roomTemperature).toBe(19);
26
+ expect(first.comfort).toBe('cold');
27
+ expect(first.context).toEqual({ outside: 8, flow: 40 });
28
+ });
29
+ it('returns newest first', () => {
30
+ const store = new ObservationStore(home);
31
+ store.append('heating', obs({ roomTemperature: 1 }));
32
+ store.append('heating', obs({ roomTemperature: 2 }));
33
+ store.append('heating', obs({ roomTemperature: 3 }));
34
+ expect(store.list('heating').map((o) => o.roomTemperature)).toEqual([3, 2, 1]);
35
+ });
36
+ it('keeps devices apart', () => {
37
+ const store = new ObservationStore(home);
38
+ store.append('heating', obs({ roomTemperature: 19 }));
39
+ store.append('pool', obs({ roomTemperature: 28 }));
40
+ expect(store.list('heating')).toHaveLength(1);
41
+ expect(store.list('pool')[0].roomTemperature).toBe(28);
42
+ });
43
+ it('survives a torn line rather than losing the whole history', () => {
44
+ // A power cut mid-append leaves half a line. One bad record must not take
45
+ // a season of calibration data with it.
46
+ const store = new ObservationStore(home);
47
+ store.append('heating', obs({ roomTemperature: 19 }));
48
+ mkdirSync(join(home, 'observations'), { recursive: true });
49
+ const file = join(home, 'observations', 'heating.jsonl');
50
+ writeFileSync(file, readFileSync(file, 'utf8') + '{"at":"2026-01-01","roomTem\n', 'utf8');
51
+ store.append('heating', obs({ roomTemperature: 21 }));
52
+ expect(store.list('heating').map((o) => o.roomTemperature)).toEqual([21, 19]);
53
+ });
54
+ it('caps how many it returns', () => {
55
+ const store = new ObservationStore(home);
56
+ for (let i = 0; i < 20; i++)
57
+ store.append('heating', obs({ roomTemperature: i }));
58
+ expect(store.list('heating', 5)).toHaveLength(5);
59
+ });
60
+ it('cannot be made to write outside its directory by a hostile device id', () => {
61
+ // Device ids come from plugins and reach the filesystem here. Separators are
62
+ // replaced, so a traversal becomes one flat filename inside observations/
63
+ // rather than a write up the tree.
64
+ const store = new ObservationStore(home);
65
+ store.append('../../escape', obs());
66
+ const written = readdirSync(join(home, 'observations'));
67
+ expect(written).toHaveLength(1);
68
+ expect(written[0]).not.toContain('/');
69
+ expect(store.count('../../escape')).toBe(1);
70
+ // Nothing landed beside the home directory, which is where a traversal
71
+ // would have put it.
72
+ expect(readdirSync(home)).toEqual(['observations']);
73
+ });
74
+ });
75
+ describe('validateObservation', () => {
76
+ it('accepts a well-formed report and stamps its own time', () => {
77
+ const r = validateObservation({ roomTemperature: 21.5, comfort: 'ok' });
78
+ expect(r.ok).toBe(true);
79
+ if (!r.ok)
80
+ return;
81
+ expect(r.value.roomTemperature).toBe(21.5);
82
+ // Stamped here so a client's clock cannot disagree with the rest of the data.
83
+ expect(Date.parse(r.value.at)).toBeGreaterThan(0);
84
+ });
85
+ it('rejects a missing or non-numeric temperature', () => {
86
+ expect(validateObservation({ comfort: 'ok' }).ok).toBe(false);
87
+ expect(validateObservation({ roomTemperature: 'warm', comfort: 'ok' }).ok).toBe(false);
88
+ });
89
+ it('rejects a temperature outside any plausible room', () => {
90
+ // A sanity bound against a typo or a Fahrenheit reading, not an opinion
91
+ // about comfort: one bad sample skews a curve fitted from very few.
92
+ expect(validateObservation({ roomTemperature: 72, comfort: 'ok' }).ok).toBe(false);
93
+ expect(validateObservation({ roomTemperature: -40, comfort: 'ok' }).ok).toBe(false);
94
+ });
95
+ it('requires a comfort verdict', () => {
96
+ expect(validateObservation({ roomTemperature: 21 }).ok).toBe(false);
97
+ expect(validateObservation({ roomTemperature: 21, comfort: 'chilly' }).ok).toBe(false);
98
+ });
99
+ it('keeps a note but bounds its length', () => {
100
+ const r = validateObservation({ roomTemperature: 21, comfort: 'ok', note: 'x'.repeat(900) });
101
+ expect(r.ok).toBe(true);
102
+ if (!r.ok)
103
+ return;
104
+ expect(r.value.note).toHaveLength(500);
105
+ });
106
+ it('omits an absent note rather than storing an empty one', () => {
107
+ const r = validateObservation({ roomTemperature: 21, comfort: 'ok' });
108
+ expect(r.ok && 'note' in r.value).toBe(false);
109
+ });
110
+ it('rejects a non-object body', () => {
111
+ expect(validateObservation(null).ok).toBe(false);
112
+ expect(validateObservation('21').ok).toBe(false);
113
+ });
114
+ });
115
+ //# sourceMappingURL=observations.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observations.test.js","sourceRoot":"","sources":["../../src/__tests__/observations.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAC7F,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAA;AAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAA0B,MAAM,oBAAoB,CAAA;AAElG,IAAI,IAAY,CAAA;AAChB,UAAU,CAAC,GAAG,EAAE;IACd,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC,CAAA;AAC/C,CAAC,CAAC,CAAA;AACF,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AAChD,CAAC,CAAC,CAAA;AAEF,SAAS,GAAG,CAAC,OAAmC,EAAE;IAChD,OAAO,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAA;AACtF,CAAC;AAED,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC9D,MAAM,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QAEzG,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACrC,MAAM,CAAC,KAAM,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACvC,MAAM,CAAC,KAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACnC,MAAM,CAAC,KAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QACpD,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QACpD,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QAEpD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAChF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QACrD,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QAElD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QAC7C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,0EAA0E;QAC1E,wCAAwC;QACxC,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QACrD,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,eAAe,CAAC,CAAA;QACxD,aAAa,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,+BAA+B,EAAE,MAAM,CAAC,CAAA;QACzF,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QAErD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC/E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE;YAAE,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QACjF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,6EAA6E;QAC7E,0EAA0E;QAC1E,mCAAmC;QACnC,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxC,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,CAAA;QAEnC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAA;QACvD,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QAC/B,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;QACrC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAC3C,uEAAuE;QACvE,qBAAqB;QACrB,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,GAAG,mBAAmB,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QACvE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,OAAM;QACjB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC1C,8EAA8E;QAC9E,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,mBAAmB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC7D,MAAM,CAAC,mBAAmB,CAAC,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,wEAAwE;QACxE,oEAAoE;QACpE,MAAM,CAAC,mBAAmB,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClF,MAAM,CAAC,mBAAmB,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,mBAAmB,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnE,MAAM,CAAC,mBAAmB,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,GAAG,mBAAmB,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC5F,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,OAAM;QACjB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,GAAG,mBAAmB,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QACrE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChD,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
package/dist/auth.d.ts CHANGED
@@ -7,9 +7,11 @@
7
7
  * - Verifies platform-issued JWTs via JWKS (no extra deps).
8
8
  * - Issues a short-lived, HMAC-signed local session cookie so subsequent
9
9
  * /api/* requests don't have to re-validate against the platform.
10
- * - Adds a Fastify pre-handler that requires a valid session for any
11
- * /api/* or /ws/* path, except /api/health (so Docker healthcheck
12
- * keeps working) and /auth/* (the auth endpoints themselves).
10
+ * - Adds a Fastify onRequest hook that requires a valid session for any
11
+ * /api/* or /ws/* route, except /api/health (so Docker healthcheck
12
+ * keeps working). /auth/* (the auth endpoints themselves) stays public.
13
+ * The check uses the route the router matched and a normalized path,
14
+ * never the raw URL, see requiresSession().
13
15
  */
14
16
  import type { FastifyInstance } from 'fastify';
15
17
  export interface TAuthConfig {
@@ -36,4 +38,26 @@ export interface TAuthConfig {
36
38
  export declare function acceptPictureClaim(issuer: string | null, picture: unknown): string | undefined;
37
39
  export declare function loadAuthConfig(): TAuthConfig;
38
40
  export declare function registerAuthRoutes(app: FastifyInstance, config: TAuthConfig): Promise<void>;
41
+ /**
42
+ * Reduce a request path to the form the gate compares against, so that no
43
+ * spelling of a protected path can slip past it: percent-encoding (applied any
44
+ * number of times, including encoded slashes and dots), backslashes, repeated
45
+ * slashes, dot segments, trailing dots on a segment and letter case all fold
46
+ * away. Returns null when the path cannot be decoded.
47
+ */
48
+ export declare function normalizeRequestPath(rawUrl: string): string | null;
49
+ /**
50
+ * Whether a request must carry a valid session.
51
+ *
52
+ * The decision rests on what the router matched, never on the raw URL alone.
53
+ * The router decodes the path before matching, so a check on the raw string
54
+ * let `/%61pi/plugins` reach `/api/plugins` (and `/%77s/shell` open a shell)
55
+ * without a session. The matched route pattern is authoritative for anything
56
+ * that reaches a handler. The normalized path is checked as well, so an
57
+ * unmatched spelling of a protected path is refused rather than falling
58
+ * through, and a future router option (case-insensitive matching, trailing
59
+ * slash folding) cannot reopen the hole. A path that cannot be decoded is
60
+ * refused.
61
+ */
62
+ export declare function requiresSession(routeUrl: string | undefined, rawUrl: string): boolean;
39
63
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAA;AAS9D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;CACf;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAc9F;AA2BD,wBAAgB,cAAc,IAAI,WAAW,CAS5C;AAkCD,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAyFjG"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAA;AAS9D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;CACf;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAc9F;AA2BD,wBAAgB,cAAc,IAAI,WAAW,CAS5C;AAkCD,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqFjG;AASD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0BlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAKrF"}
package/dist/auth.js CHANGED
@@ -7,9 +7,11 @@
7
7
  * - Verifies platform-issued JWTs via JWKS (no extra deps).
8
8
  * - Issues a short-lived, HMAC-signed local session cookie so subsequent
9
9
  * /api/* requests don't have to re-validate against the platform.
10
- * - Adds a Fastify pre-handler that requires a valid session for any
11
- * /api/* or /ws/* path, except /api/health (so Docker healthcheck
12
- * keeps working) and /auth/* (the auth endpoints themselves).
10
+ * - Adds a Fastify onRequest hook that requires a valid session for any
11
+ * /api/* or /ws/* route, except /api/health (so Docker healthcheck
12
+ * keeps working). /auth/* (the auth endpoints themselves) stays public.
13
+ * The check uses the route the router matched and a normalized path,
14
+ * never the raw URL, see requiresSession().
13
15
  */
14
16
  import { createHmac, randomBytes } from 'crypto';
15
17
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
@@ -192,19 +194,77 @@ export async function registerAuthRoutes(app, config) {
192
194
  // Gate /api/* and /ws/* when auth is enabled. Health and auth routes
193
195
  // are public so the Docker healthcheck and login flow keep working.
194
196
  app.addHook('onRequest', async (req, reply) => {
195
- const url = req.url.split('?')[0] ?? '';
196
- const gated = url.startsWith('/api/') || url.startsWith('/ws/');
197
- if (!gated)
198
- return;
199
- if (url === '/api/health')
200
- return;
201
- if (url.startsWith('/auth/'))
197
+ if (!requiresSession(req.routeOptions?.url, req.url))
202
198
  return;
203
199
  const cookie = getCookie(req, COOKIE_NAME);
204
200
  const session = cookie ? verifySession(config.secret, cookie) : null;
205
201
  if (!session) {
206
- reply.code(401).send({ error: 'unauthenticated' });
202
+ return reply.code(401).send({ error: 'unauthenticated' });
207
203
  }
208
204
  });
209
205
  }
206
+ const PUBLIC_PATHS = new Set(['/api/health']);
207
+ function isGatedPath(path) {
208
+ if (PUBLIC_PATHS.has(path))
209
+ return false;
210
+ return path === '/api' || path.startsWith('/api/') || path === '/ws' || path.startsWith('/ws/');
211
+ }
212
+ /**
213
+ * Reduce a request path to the form the gate compares against, so that no
214
+ * spelling of a protected path can slip past it: percent-encoding (applied any
215
+ * number of times, including encoded slashes and dots), backslashes, repeated
216
+ * slashes, dot segments, trailing dots on a segment and letter case all fold
217
+ * away. Returns null when the path cannot be decoded.
218
+ */
219
+ export function normalizeRequestPath(rawUrl) {
220
+ let path = rawUrl.split('?')[0]?.split('#')[0] ?? '';
221
+ for (let i = 0; i < 5; i++) {
222
+ let decoded;
223
+ try {
224
+ decoded = decodeURIComponent(path);
225
+ }
226
+ catch {
227
+ return null;
228
+ }
229
+ if (decoded === path)
230
+ break;
231
+ path = decoded;
232
+ }
233
+ if (/%[0-9a-f]{2}/i.test(path))
234
+ return null;
235
+ const out = [];
236
+ for (const raw of path.replace(/\\/g, '/').toLowerCase().split('/')) {
237
+ const segment = raw.trim();
238
+ if (segment === '' || segment === '.')
239
+ continue;
240
+ if (segment === '..') {
241
+ out.pop();
242
+ continue;
243
+ }
244
+ // Some layers treat "api." and "api" as the same name, so the gate does too.
245
+ out.push(segment.replace(/[.\s]+$/, '') || segment);
246
+ }
247
+ return '/' + out.join('/');
248
+ }
249
+ /**
250
+ * Whether a request must carry a valid session.
251
+ *
252
+ * The decision rests on what the router matched, never on the raw URL alone.
253
+ * The router decodes the path before matching, so a check on the raw string
254
+ * let `/%61pi/plugins` reach `/api/plugins` (and `/%77s/shell` open a shell)
255
+ * without a session. The matched route pattern is authoritative for anything
256
+ * that reaches a handler. The normalized path is checked as well, so an
257
+ * unmatched spelling of a protected path is refused rather than falling
258
+ * through, and a future router option (case-insensitive matching, trailing
259
+ * slash folding) cannot reopen the hole. A path that cannot be decoded is
260
+ * refused.
261
+ */
262
+ export function requiresSession(routeUrl, rawUrl) {
263
+ if (routeUrl !== undefined && isGatedPath(routeUrl))
264
+ return true;
265
+ const path = normalizeRequestPath(rawUrl);
266
+ if (path === null)
267
+ return true;
268
+ return isGatedPath(path);
269
+ }
210
270
  //# sourceMappingURL=auth.js.map
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/daemon.d.ts CHANGED
@@ -22,6 +22,15 @@ export declare class Daemon {
22
22
  private metricSeries;
23
23
  /** Per-device event timeline, written by plugins through ctx.recordEvent */
24
24
  private deviceEvents;
25
+ private observations;
26
+ /**
27
+ * Per-device providers of machine state to attach to an observation.
28
+ *
29
+ * Registered by plugins, because only the plugin knows which of its readings
30
+ * matter. The daemon asks at submit time rather than letting a client supply
31
+ * them, so a report cannot claim conditions that were not happening.
32
+ */
33
+ private observationContexts;
25
34
  /** Per-service HomeKit type overrides, applied at the bridge */
26
35
  private homekitServiceTypes;
27
36
  /** Per-accessory HomeKit visibility, enforced at the bridge */
@@ -1 +1 @@
1
- {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,eAAe,QAAuC,CAAA;AACnE,eAAO,MAAM,cAAc,QAAiD,CAAA;AAC5E,eAAO,MAAM,cAAc,QAAiD,CAAA;AAC5E,eAAO,MAAM,WAAW,QAAmC,CAAA;AAiB3D,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAID,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,aAAa,CAAe;IACpC,8FAA8F;IAC9F,OAAO,CAAC,mBAAmB,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,wEAAwE;IACxE,OAAO,CAAC,YAAY,CAAiD;IACrE,gFAAgF;IAChF,OAAO,CAAC,aAAa,CAAsB;IAC3C,qEAAqE;IACrE,OAAO,CAAC,YAAY,CAAkC;IACtD,4EAA4E;IAC5E,OAAO,CAAC,YAAY,CAA6D;IACjF,gEAAgE;IAChE,OAAO,CAAC,mBAAmB,CAA+E;IAC1G,+DAA+D;IAC/D,OAAO,CAAC,iBAAiB,CAIxB;IAED;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,sBAAsB;IAK9B,0EAA0E;IAC1E,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,mBAAmB;IAMrB,KAAK,CAAC,OAAO,GAAE,aAAkB;YAgYzB,0BAA0B;IA0LxC,OAAO,CAAC,WAAW;IA2CnB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IASjB,+EAA+E;IAC/E,OAAO,CAAC,aAAa;IAmCrB,yEAAyE;IACzE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,mBAAmB;CA8B5B"}
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AA0BA,eAAO,MAAM,eAAe,QAAuC,CAAA;AACnE,eAAO,MAAM,cAAc,QAAiD,CAAA;AAC5E,eAAO,MAAM,cAAc,QAAiD,CAAA;AAC5E,eAAO,MAAM,WAAW,QAAmC,CAAA;AAiB3D,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAID,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,aAAa,CAAe;IACpC,8FAA8F;IAC9F,OAAO,CAAC,mBAAmB,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,wEAAwE;IACxE,OAAO,CAAC,YAAY,CAAiD;IACrE,gFAAgF;IAChF,OAAO,CAAC,aAAa,CAAsB;IAC3C,qEAAqE;IACrE,OAAO,CAAC,YAAY,CAAkC;IACtD,4EAA4E;IAC5E,OAAO,CAAC,YAAY,CAA6D;IACjF,OAAO,CAAC,YAAY,CAAwC;IAC5D;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB,CAAmD;IAC9E,gEAAgE;IAChE,OAAO,CAAC,mBAAmB,CAA+E;IAC1G,+DAA+D;IAC/D,OAAO,CAAC,iBAAiB,CAIxB;IAED;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,sBAAsB;IAK9B,0EAA0E;IAC1E,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,mBAAmB;IAMrB,KAAK,CAAC,OAAO,GAAE,aAAkB;YAkYzB,0BAA0B;IA0LxC,OAAO,CAAC,WAAW;IA8CnB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IASjB,+EAA+E;IAC/E,OAAO,CAAC,aAAa;IAmCrB,yEAAyE;IACzE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,mBAAmB;CA8B5B"}
package/dist/daemon.js CHANGED
@@ -9,6 +9,7 @@ import { Logger } from '@nubisco/openbridge-logger';
9
9
  import { waitForFreePort } from './port.js';
10
10
  import { rotateIfLarge } from './log-rotation.js';
11
11
  import { DeviceEventLog } from './device-events.js';
12
+ import { ObservationStore } from './observations.js';
12
13
  import { loadConfig, defaultConfigPath } from '@nubisco/openbridge-config';
13
14
  import { createServer } from './server.js';
14
15
  import { DeviceSeries, DEFAULT_TIERS } from './timeseries.js';
@@ -52,6 +53,15 @@ export class Daemon {
52
53
  metricSeries = new Map();
53
54
  /** Per-device event timeline, written by plugins through ctx.recordEvent */
54
55
  deviceEvents = new DeviceEventLog(join(OPENBRIDGE_HOME, 'device-events'));
56
+ observations = new ObservationStore(OPENBRIDGE_HOME);
57
+ /**
58
+ * Per-device providers of machine state to attach to an observation.
59
+ *
60
+ * Registered by plugins, because only the plugin knows which of its readings
61
+ * matter. The daemon asks at submit time rather than letting a client supply
62
+ * them, so a report cannot claim conditions that were not happening.
63
+ */
64
+ observationContexts = new Map();
55
65
  /** Per-service HomeKit type overrides, applied at the bridge */
56
66
  homekitServiceTypes = new HomeKitServiceTypes(join(OPENBRIDGE_HOME, 'homekit-service-types.json'));
57
67
  /** Per-accessory HomeKit visibility, enforced at the bridge */
@@ -348,7 +358,7 @@ export class Daemon {
348
358
  }
349
359
  // ── HTTP server ───────────────────────────────────────────────────────────
350
360
  const localPluginSources = config.localPluginSources ?? [];
351
- const server = await createServer(this.registry, homebridgeAPI, hapInfo, localPluginSources, this.knownHbPackageNames, this.controls, this.restrictedControls, this.homekitVisibility, this.homekitServiceTypes, (deviceId) => this.resolveNativeAccessory(deviceId), (deviceId) => this.nativeAccessoryUuid(deviceId), this.deviceEvents);
361
+ const server = await createServer(this.registry, homebridgeAPI, hapInfo, localPluginSources, this.knownHbPackageNames, this.controls, this.restrictedControls, this.homekitVisibility, this.homekitServiceTypes, (deviceId) => this.resolveNativeAccessory(deviceId), (deviceId) => this.nativeAccessoryUuid(deviceId), this.deviceEvents, this.observations, (deviceId) => this.observationContexts.get(deviceId)?.());
352
362
  await server.listen({ port, host: '0.0.0.0' });
353
363
  // ── Energy history sampling ───────────────────────────────────────────────
354
364
  setInterval(() => {
@@ -604,6 +614,9 @@ export class Daemon {
604
614
  recordEvent: (deviceId, event) => {
605
615
  this.deviceEvents.record(deviceId, event);
606
616
  },
617
+ registerObservationContext: (deviceId, provider) => {
618
+ this.observationContexts.set(deviceId, provider);
619
+ },
607
620
  registerHapBridge(info) {
608
621
  const entry = registry.get(plugin.manifest.name);
609
622
  if (!entry) {