homematic-manager 3.0.0-beta.4 → 3.0.0-beta.6
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/dist/cli.js +10 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +1 -0
- package/node_modules/@homematic-manager/backend/dist/api/backend.js +40 -2
- package/node_modules/@homematic-manager/backend/dist/meta/index.d.ts +1 -0
- package/node_modules/@homematic-manager/backend/dist/meta/index.js +1 -0
- package/node_modules/@homematic-manager/backend/dist/meta/provider.d.ts +6 -5
- package/node_modules/@homematic-manager/backend/dist/meta/provider.js +5 -4
- package/node_modules/@homematic-manager/backend/dist/meta/regaProvider.d.ts +84 -0
- package/node_modules/@homematic-manager/backend/dist/meta/regaProvider.js +403 -0
- package/node_modules/@homematic-manager/backend/dist/meta/service.d.ts +18 -1
- package/node_modules/@homematic-manager/backend/dist/meta/service.js +53 -0
- package/node_modules/@homematic-manager/backend/dist/rega/client.d.ts +11 -0
- package/node_modules/@homematic-manager/backend/dist/rega/client.js +13 -0
- package/node_modules/@homematic-manager/backend/dist/rega/scripts.d.ts +51 -0
- package/node_modules/@homematic-manager/backend/dist/rega/scripts.js +171 -0
- package/node_modules/@homematic-manager/backend/dist/util/net.d.ts +7 -1
- package/node_modules/@homematic-manager/backend/dist/util/net.js +8 -1
- package/node_modules/@homematic-manager/backend/package.json +1 -1
- package/node_modules/@homematic-manager/core/dist/api/types.d.ts +27 -8
- package/node_modules/@homematic-manager/core/dist/api/types.js +2 -2
- package/node_modules/@homematic-manager/core/dist/meta/paths.js +3 -2
- package/node_modules/@homematic-manager/core/dist/rpc/methods.d.ts +10 -0
- package/node_modules/@homematic-manager/core/dist/rpc/methods.js +51 -1
- package/node_modules/@homematic-manager/core/package.json +1 -1
- package/package.json +3 -3
- package/ui/assets/index-C99NI3-M.css +1 -0
- package/ui/assets/index-Mfj6d7fY.js +14 -0
- package/ui/index.html +2 -2
- package/ui/assets/index-0KwZUy-l.js +0 -13
- package/ui/assets/index-qbhgN2pA.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { realpathSync } from 'node:fs';
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { isLoopbackHost } from './auth.js';
|
|
13
14
|
import { createWebHost } from './server.js';
|
|
14
15
|
import { installService, uninstallService } from './install.js';
|
|
15
16
|
import { createLogger } from './log.js';
|
|
@@ -93,6 +94,15 @@ export async function runCli(options = {}) {
|
|
|
93
94
|
log.debug(`token ${host.token}`);
|
|
94
95
|
}
|
|
95
96
|
log.debug(`a client without a cookie can use ${host.url}?token=${host.token}`);
|
|
97
|
+
if (host.token !== undefined && host.issueCookie && !isLoopbackHost(parsed.host)) {
|
|
98
|
+
// D-41: the Docker image binds 0.0.0.0 and issues the cookie so that the UI works out of
|
|
99
|
+
// the box - which means whoever reaches the port is in. Said once, at start, with the ways
|
|
100
|
+
// out; the host cannot see a proxy or TLS in front of it, so it says this whenever it is
|
|
101
|
+
// in that position
|
|
102
|
+
log.warn(`the token cookie is handed to every browser that loads the page on ${parsed.host}: whoever reaches this port is in;` +
|
|
103
|
+
' put a proxy with authentication and TLS in front, publish the port on the loopback only,' +
|
|
104
|
+
' or set HMM_ISSUE_COOKIE=false and open the UI once with ?token=');
|
|
105
|
+
}
|
|
96
106
|
if (parsed.authMode === 'rega') {
|
|
97
107
|
// D-32: the token still works and `settings.cgi` still hands it out - the login is a second
|
|
98
108
|
// door, not a replacement, and saying so keeps the addon's two paths straight in the log
|
package/dist/server.d.ts
CHANGED
|
@@ -142,6 +142,8 @@ export interface WebHost {
|
|
|
142
142
|
readonly base: string;
|
|
143
143
|
/** `undefined` when auth is off. */
|
|
144
144
|
readonly token: string | undefined;
|
|
145
|
+
/** Whether the page load hands the token to the browser as a cookie (D-41 says when to warn). */
|
|
146
|
+
readonly issueCookie: boolean;
|
|
145
147
|
/** D-32: how a browser is let in. */
|
|
146
148
|
readonly authMode: AuthMode;
|
|
147
149
|
/** D-32/D-40: the login sessions; `undefined` in `token` mode, where there are none. */
|
package/dist/server.js
CHANGED
|
@@ -342,6 +342,11 @@ export class Backend {
|
|
|
342
342
|
case 'meta.node.delete':
|
|
343
343
|
await (await this.#requireMeta()).deleteNode(p[0], p[1] === true);
|
|
344
344
|
return null;
|
|
345
|
+
case 'meta.refresh': {
|
|
346
|
+
const meta = await this.#requireMeta();
|
|
347
|
+
await meta.refresh();
|
|
348
|
+
return meta.state();
|
|
349
|
+
}
|
|
345
350
|
case 'meta.export':
|
|
346
351
|
return (await this.#requireMeta()).document();
|
|
347
352
|
case 'meta.import':
|
|
@@ -540,6 +545,14 @@ export class Backend {
|
|
|
540
545
|
cacheDir: this.#config.cacheDir,
|
|
541
546
|
names: this.#caches.names,
|
|
542
547
|
interfaceOf: (address) => this.#interfaceOf(address),
|
|
548
|
+
// task 27: ReGa as the store of rooms and functions on a CCU. Read through the
|
|
549
|
+
// service that is current at call time - a reconnect replaces it.
|
|
550
|
+
rega: this.#rega === undefined || !connection.rega
|
|
551
|
+
? undefined
|
|
552
|
+
: {
|
|
553
|
+
available: this.#rega.available,
|
|
554
|
+
exec: (script) => this.#requireRega().exec(script),
|
|
555
|
+
},
|
|
543
556
|
onChanged: () => {
|
|
544
557
|
this.#onMetaChanged();
|
|
545
558
|
},
|
|
@@ -567,6 +580,12 @@ export class Backend {
|
|
|
567
580
|
this.#notice('warn', `the metadata store could not be opened: ${errorMessage(error)}`);
|
|
568
581
|
}
|
|
569
582
|
}
|
|
583
|
+
#requireRega() {
|
|
584
|
+
if (!this.#rega) {
|
|
585
|
+
throw configError('ReGa is not connected');
|
|
586
|
+
}
|
|
587
|
+
return this.#rega;
|
|
588
|
+
}
|
|
570
589
|
/** The store changed - locally, or on the box because somebody else edited it. */
|
|
571
590
|
#onMetaChanged() {
|
|
572
591
|
const meta = this.#meta;
|
|
@@ -869,7 +888,22 @@ export class Backend {
|
|
|
869
888
|
async #setNames(entries) {
|
|
870
889
|
const written = this.#caches.names.set(entries);
|
|
871
890
|
this.#caches.saveNames();
|
|
872
|
-
|
|
891
|
+
// task 27: with ReGa as the metadata store the provider writes the rename itself, and a
|
|
892
|
+
// second `Name()` through the name service would be the same script twice - but only for
|
|
893
|
+
// the objects the provider holds, and only while ReGa answers it; anything else still goes
|
|
894
|
+
// the way it always did (found by the e2e suite: a rename lost for good is worse than one
|
|
895
|
+
// script twice)
|
|
896
|
+
const meta = this.#meta;
|
|
897
|
+
const held = meta?.kind === 'rega' && meta.state().reachable ? meta.document().objects : undefined;
|
|
898
|
+
const throughNames = held === undefined
|
|
899
|
+
? written
|
|
900
|
+
: written.filter((entry) => {
|
|
901
|
+
const ref = meta?.refFor(entry.address);
|
|
902
|
+
return ref === undefined || !(ref in held);
|
|
903
|
+
});
|
|
904
|
+
if (throughNames.length > 0) {
|
|
905
|
+
await this.#rega?.rename(throughNames);
|
|
906
|
+
}
|
|
873
907
|
// D-40: and into the metadata store, which on an openccu-lite box is the box's own. The
|
|
874
908
|
// local cache is written first either way, so a store that refuses the write still leaves
|
|
875
909
|
// the name where the user typed it - and the refusal is reported rather than swallowed.
|
|
@@ -884,7 +918,10 @@ export class Backend {
|
|
|
884
918
|
/** The state, even before a connection exists - the settings dialog asks for it either way. */
|
|
885
919
|
#metaState() {
|
|
886
920
|
return (this.#meta?.state() ?? {
|
|
887
|
-
provider: this.#config.connection.metaProvider === 'occulite'
|
|
921
|
+
provider: this.#config.connection.metaProvider === 'occulite' ||
|
|
922
|
+
this.#config.connection.metaProvider === 'rega'
|
|
923
|
+
? this.#config.connection.metaProvider
|
|
924
|
+
: 'local',
|
|
888
925
|
reachable: false,
|
|
889
926
|
writable: false,
|
|
890
927
|
revision: 0,
|
|
@@ -1325,6 +1362,7 @@ export const API_METHOD_NAMES = [
|
|
|
1325
1362
|
'meta.node.create',
|
|
1326
1363
|
'meta.node.update',
|
|
1327
1364
|
'meta.node.delete',
|
|
1365
|
+
'meta.refresh',
|
|
1328
1366
|
'meta.export',
|
|
1329
1367
|
'meta.import',
|
|
1330
1368
|
'paramset.get',
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* openccu-lite box, follows its change stream and **writes back**, because on that box this
|
|
7
7
|
* application is the editor of the store: there is no ReGaHSS and no WebUI to do it instead.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* `rega` (task 27) is the third: on a CCU with ReGaHSS the rooms and functions are ReGa's own enum
|
|
10
|
+
* objects, read and written through the script transport the names already use, so that one
|
|
11
|
+
* editing UI serves a CCU and a box. A box that has ReGa never has a metadata API, so the three
|
|
12
|
+
* never compete: the box's API wins when it answers, ReGa when it is on and answered, and `local`
|
|
13
|
+
* is what every other system - Homegear, a bare rfd, the desktop without a CCU - gets.
|
|
13
14
|
*/
|
|
14
15
|
import type { MetaDocument, MetaEnum, MetaImportMode, MetaNodePatch, MetaState } from '@homematic-manager/core';
|
|
15
16
|
/** One name to write: the store's identity is the ref, not the bare address. */
|
|
@@ -34,7 +35,7 @@ export interface MetaProviderEvents {
|
|
|
34
35
|
* box on the network; every read is synchronous because both keep the document in memory.
|
|
35
36
|
*/
|
|
36
37
|
export interface MetadataProvider {
|
|
37
|
-
readonly kind: 'local' | 'occulite';
|
|
38
|
+
readonly kind: 'local' | 'occulite' | 'rega';
|
|
38
39
|
state(): MetaState;
|
|
39
40
|
/** Loads the document (a file, or the box's snapshot) and starts following changes. */
|
|
40
41
|
start(): Promise<void>;
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* openccu-lite box, follows its change stream and **writes back**, because on that box this
|
|
7
7
|
* application is the editor of the store: there is no ReGaHSS and no WebUI to do it instead.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* `rega` (task 27) is the third: on a CCU with ReGaHSS the rooms and functions are ReGa's own enum
|
|
10
|
+
* objects, read and written through the script transport the names already use, so that one
|
|
11
|
+
* editing UI serves a CCU and a box. A box that has ReGa never has a metadata API, so the three
|
|
12
|
+
* never compete: the box's API wins when it answers, ReGa when it is on and answered, and `local`
|
|
13
|
+
* is what every other system - Homegear, a bare rfd, the desktop without a CCU - gets.
|
|
13
14
|
*/
|
|
14
15
|
/** The enums of a document, for a caller that only wants the trees. */
|
|
15
16
|
export function enumsOf(document) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `rega` metadata provider (task 27): the CCU's own rooms and functions, through ReGaHSS.
|
|
3
|
+
*
|
|
4
|
+
* On a CCU the rooms and functions the user sees in the WebUI live in ReGa's DOM - enum objects
|
|
5
|
+
* under `ID_ROOMS` and `ID_FUNCTIONS`, each holding channel ids - and a taxonomy kept in this
|
|
6
|
+
* profile beside them would be a second truth. So on a CCU with ReGa this provider *is* the store:
|
|
7
|
+
* the same {@link MetadataProvider} surface the box's provider has, over the same script transport
|
|
8
|
+
* the names already use, so that the editing UI of task 25 does not know which one it talks to.
|
|
9
|
+
*
|
|
10
|
+
* What ReGa's model is, and therefore what this provider is:
|
|
11
|
+
*
|
|
12
|
+
* - **Flat.** A room is a room; there is no room below another and no floor. `state().flat` says
|
|
13
|
+
* so, `createNode` with a parent and a move under a parent are refused.
|
|
14
|
+
* - **Two taxonomies, fixed.** Rooms and functions exist; nothing else can be created or deleted.
|
|
15
|
+
* - **Channels are members, devices are not.** The WebUI assigns channels. A device ref in a
|
|
16
|
+
* membership write is applied to every channel of the device except the `:0` maintenance
|
|
17
|
+
* channel, which is what a user who "puts the device in the kitchen" means.
|
|
18
|
+
* - **Identity is the ReGa id.** A node's id is `r<id>`, stable across renames, so a path never
|
|
19
|
+
* has to be rewritten when a room is renamed.
|
|
20
|
+
* - **No change stream.** ReGa tells nobody when the WebUI edits a room. This provider reads
|
|
21
|
+
* everything on `start()`, after every write of its own, and on `refresh()`, which the UI asks
|
|
22
|
+
* for through `meta.refresh`.
|
|
23
|
+
*
|
|
24
|
+
* Failure is a state, never an exception (D-2): a script that does not run leaves the last
|
|
25
|
+
* document in place, sets `reachable: false` and says so once.
|
|
26
|
+
*/
|
|
27
|
+
import { type Language, type MetaDocument, type MetaNodePatch, type MetaState } from '@homematic-manager/core';
|
|
28
|
+
import type { MetadataProvider, MetaMembershipEntry, MetaNameEntry, MetaProviderEvents } from './provider.js';
|
|
29
|
+
/** The part of the ReGa transport this provider uses: run a script, read what it wrote. */
|
|
30
|
+
export type RegaScriptRunner = (script: string) => Promise<{
|
|
31
|
+
output: string;
|
|
32
|
+
}>;
|
|
33
|
+
export interface RegaMetaProviderOptions extends MetaProviderEvents {
|
|
34
|
+
readonly exec: RegaScriptRunner;
|
|
35
|
+
/**
|
|
36
|
+
* The interface an address belongs to, from the device caches - the fallback for a device
|
|
37
|
+
* whose interface ReGa does not name (an older firmware, a CUxD device).
|
|
38
|
+
*/
|
|
39
|
+
readonly interfaceOf: (address: string) => string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The language the stock rooms and functions are shown in. ReGa stores them as translation keys
|
|
42
|
+
* (`roomKitchen`) and the WebUI translates them on display; the CCU's own default is German.
|
|
43
|
+
*/
|
|
44
|
+
readonly language?: Language | undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The rooms and functions a CCU comes with, as the WebUI translates them (`translate.lang.js` of
|
|
48
|
+
* the German and the English WebUI, firmware 3.89.8). ReGa's objects carry the *key* as their
|
|
49
|
+
* `Name()` - `roomKitchen`, not `Küche` - and every WebUI page translates it on display, so a list
|
|
50
|
+
* that shows the keys is not what the user sees on the CCU. Found in the first lab pass of task 27.
|
|
51
|
+
* A name that is not one of these keys is shown as it is; a rename writes the literal name.
|
|
52
|
+
*/
|
|
53
|
+
export declare const REGA_STOCK_NAMES: Readonly<Record<string, Readonly<Record<'de' | 'en', string>>>>;
|
|
54
|
+
/** `roomKitchen` -> `Küche`; anything that is not a stock key is answered as it is. */
|
|
55
|
+
export declare function regaStockName(name: string, language: Language | undefined): string;
|
|
56
|
+
/** `r4711` - the node id of a ReGa enum object; stable across renames. */
|
|
57
|
+
export declare function regaNodeId(id: number): string;
|
|
58
|
+
/** The ReGa id behind a node id, or `undefined` for anything that is not one. */
|
|
59
|
+
export declare function parseRegaNodeId(id: string): number | undefined;
|
|
60
|
+
export declare class RegaMetaProvider implements MetadataProvider {
|
|
61
|
+
#private;
|
|
62
|
+
readonly kind: "rega";
|
|
63
|
+
constructor(options: RegaMetaProviderOptions);
|
|
64
|
+
state(): MetaState;
|
|
65
|
+
start(): Promise<void>;
|
|
66
|
+
stop(): Promise<void>;
|
|
67
|
+
/** Reads everything again. Never throws: a ReGa that does not answer is a state. */
|
|
68
|
+
refresh(): Promise<void>;
|
|
69
|
+
document(): MetaDocument;
|
|
70
|
+
setNames(entries: readonly MetaNameEntry[]): Promise<void>;
|
|
71
|
+
setMembership(entries: readonly MetaMembershipEntry[]): Promise<void>;
|
|
72
|
+
createEnum(): Promise<void>;
|
|
73
|
+
updateEnum(): Promise<void>;
|
|
74
|
+
deleteEnum(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* A new room or function. The id the caller derived from the name is not used: ReGa hands out
|
|
77
|
+
* the id, and `r<id>` is what keeps the path stable when the room is renamed later.
|
|
78
|
+
*/
|
|
79
|
+
createNode(enumId: string, parent: string | null, _id: string, name: string): Promise<string>;
|
|
80
|
+
updateNode(path: string, patch: MetaNodePatch): Promise<void>;
|
|
81
|
+
deleteNode(path: string, detach: boolean): Promise<void>;
|
|
82
|
+
import(): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=regaProvider.d.ts.map
|