homebridge-tydom 0.30.1 → 0.31.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 +57 -5
- package/config.schema.json +11 -2
- package/dist/index.mjs +214 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -79,9 +79,59 @@ Then configure the platform, either in the Homebridge UI or directly in `config.
|
|
|
79
79
|
|
|
80
80
|
### Finding your credentials
|
|
81
81
|
|
|
82
|
-
Your **username** is the MAC address of your gateway: `001A25` followed by the 6-character home ID shown in the mobile app.
|
|
82
|
+
Your **username** is the MAC address of your gateway: `001A25` followed by the 6-character home ID shown in the mobile app. Separators and case do not matter — `00:1a:25:12:34:56` works too.
|
|
83
83
|
|
|
84
|
-
Your **password** is the one
|
|
84
|
+
Your **password** is the gateway's own, which is not the one you sign in to the Tydom app with. Newer setups have it generated by the app and never show it to you.
|
|
85
|
+
|
|
86
|
+
Rather than extract it, add your account **e-mail** and let the plugin fetch it for you:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"platform": "Tydom",
|
|
91
|
+
"hostname": "mediation.tydom.com",
|
|
92
|
+
"username": "001A25123456",
|
|
93
|
+
"email": "you@example.com",
|
|
94
|
+
"password": "YourDeltaDoreAccountPassw0rd"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
With `email` set, `password` is read as your **Delta Dore account** password: at startup the plugin signs in to Delta Dore, looks up the gateway named in `username`, and uses the gateway password it gets back. Nothing else changes, and your account password is never sent to the gateway. Leave `email` out — the default — and `password` keeps its old meaning of the gateway's own password.
|
|
99
|
+
|
|
100
|
+
The account has to already own that gateway. To check a pair without waiting on Homebridge:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pnpm resolve-credentials you@example.com 'YourDeltaDoreAccountPassw0rd' 001A25123456
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
If you would rather extract the gateway password by hand, you still can, by inspecting the app's traffic with an SSL proxy following [this guide](https://github.com/mgcrea/homebridge-tydom/issues/72#issuecomment-1315036089) from @aure-olivier.
|
|
107
|
+
|
|
108
|
+
### If your account has several houses
|
|
109
|
+
|
|
110
|
+
`username` is what picks the house. One Delta Dore account can hold several — a home, a holiday flat, a relative's place — and each is one gateway with its own MAC. Point `username` at the MAC of the house you want this Homebridge instance to expose, and that is the one you get. Adding `email` does not change that: the account lookup is scoped to the MAC you named, and hands back that gateway's password specifically.
|
|
111
|
+
|
|
112
|
+
To find the MAC of each house, open the Tydom app's home list and read the 6-character home ID next to each one; the MAC is `001A25` followed by that. An account holding three homes gives you three MACs, and each resolves to its own distinct gateway password:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
Maison -> 001A25 AAAAAA
|
|
116
|
+
Appartement -> 001A25 BBBBBB
|
|
117
|
+
Chalet -> 001A25 CCCCCC
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Pick the one you want and put it in `username`. Note that the account only ever *confirms* a gateway you name — there is no public Delta Dore API that lists the houses on an account, and the lookup carries no house name either, so the home ID from the app is how you tell them apart.
|
|
121
|
+
|
|
122
|
+
Being able to *see* a house is not the same as holding its credentials. A house attached to your account but registered by someone else's comes back from the lookup without a password, and the plugin will tell you so rather than pretend the MAC is wrong:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
Delta Dore returned no password for gateway 001A25XXXXXX. The account can see that house
|
|
126
|
+
but does not hold its credentials — sign in with the account that registered the gateway,
|
|
127
|
+
or set "password" to the gateway password directly and leave "email" out.
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
As that says, the fallback is to drop `email` and give that house's gateway password directly.
|
|
131
|
+
|
|
132
|
+
Each house gets its own accessory namespace, derived from that home ID, so switching `username` from one house to another retires the first house's accessories and publishes the second's rather than mixing them up. Expect a batch of `Deleting missing accessory` lines in the log the first time you do it — that is the sweep clearing out the house you moved away from.
|
|
133
|
+
|
|
134
|
+
A platform block is one house at a time; the plugin is declared `singular`, so the Homebridge UI allows a single Tydom block. To expose two houses at once you would need a second Homebridge instance, or a hand-written second platform block run as a child bridge.
|
|
85
135
|
|
|
86
136
|
Both secrets can be supplied out of band instead, base64-encoded, which is usually what you want in a container:
|
|
87
137
|
|
|
@@ -97,8 +147,9 @@ They take precedence over the corresponding config fields.
|
|
|
97
147
|
| Field | Type | Default | Description |
|
|
98
148
|
| --- | --- | --- | --- |
|
|
99
149
|
| `hostname` | `string` | — | `mediation.tydom.com` for the relay, or your gateway's LAN address (see [Connecting locally](#connecting-locally)). Required. |
|
|
100
|
-
| `username` | `string` | — | Gateway MAC address, e.g. `001A25123456`. Required. |
|
|
101
|
-
| `
|
|
150
|
+
| `username` | `string` | — | Gateway MAC address, e.g. `001A25123456`. This is what selects the house — see [If your account has several houses](#if-your-account-has-several-houses). Required. |
|
|
151
|
+
| `email` | `string` | — | Delta Dore account e-mail. Set it to have the gateway password fetched for you, which also changes what `password` below means. See [Finding your credentials](#finding-your-credentials). |
|
|
152
|
+
| `password` | `string` | — | Your Delta Dore account password if `email` is set, otherwise the gateway's own password. Required, unless supplied via the environment. |
|
|
102
153
|
| `pin` | `string` | — | TYXAL+ alarm PIN. Without it the alarm reports its state but cannot be armed or disarmed. |
|
|
103
154
|
| `locale` | `"fr" \| "en"` | `"fr"` | Language of the labels Delta Dore supplies for zones, detectors and thermostat modes. |
|
|
104
155
|
| `refreshInterval` | `number` | `14400` | Seconds between full state refreshes. The gateway pushes changes as they happen, so this is only a safety net; clamped to a minimum of 60. |
|
|
@@ -121,7 +172,8 @@ Environment variables:
|
|
|
121
172
|
|
|
122
173
|
| Variable | Description |
|
|
123
174
|
| --- | --- |
|
|
124
|
-
| `HOMEBRIDGE_TYDOM_PASSWORD` |
|
|
175
|
+
| `HOMEBRIDGE_TYDOM_PASSWORD` | Gateway or account password, base64-encoded. Overrides `password`. |
|
|
176
|
+
| `HOMEBRIDGE_TYDOM_EMAIL` | Delta Dore account e-mail, in plain text. Overrides `email`. |
|
|
125
177
|
| `HOMEBRIDGE_TYDOM_PIN` | TYXAL+ PIN, base64-encoded. Overrides `pin`. |
|
|
126
178
|
| `HOMEBRIDGE_TYDOM_LOCALE` | `fr` or `en`. Overrides `locale`. |
|
|
127
179
|
|
package/config.schema.json
CHANGED
|
@@ -20,12 +20,17 @@
|
|
|
20
20
|
"required": true,
|
|
21
21
|
"description": "Your gateway's MAC address, uppercase and without separators — e.g. `001A25123456`. It is printed underneath the Tydom box."
|
|
22
22
|
},
|
|
23
|
+
"email": {
|
|
24
|
+
"title": "Delta Dore account e-mail",
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Optional. The e-mail you sign in to the Tydom app with.\n\nFill it in and the password below is read as your **Delta Dore account** password: the plugin signs in at startup and fetches the gateway's own password for you. Leave it empty — the default — and the password below is the **gateway's** password, used as-is.\n\nThe account has to already own the gateway named above; this looks a password up, it cannot discover which gateways you have. Also accepted as a `HOMEBRIDGE_TYDOM_EMAIL` environment variable."
|
|
27
|
+
},
|
|
23
28
|
"password": {
|
|
24
29
|
"title": "Password",
|
|
25
30
|
"type": "string",
|
|
26
31
|
"required": true,
|
|
27
32
|
"x-schema-form": { "type": "password" },
|
|
28
|
-
"description": "
|
|
33
|
+
"description": "Your Delta Dore account password if an e-mail is set above, otherwise the gateway's own password. Can be supplied out of band instead, as a base64-encoded `HOMEBRIDGE_TYDOM_PASSWORD` environment variable, which takes precedence over this field."
|
|
29
34
|
},
|
|
30
35
|
"pin": {
|
|
31
36
|
"title": "Alarm PIN",
|
|
@@ -121,7 +126,11 @@
|
|
|
121
126
|
}
|
|
122
127
|
},
|
|
123
128
|
"layout": [
|
|
124
|
-
{
|
|
129
|
+
{
|
|
130
|
+
"type": "fieldset",
|
|
131
|
+
"title": "Connection",
|
|
132
|
+
"items": ["hostname", "username", "email", "password"]
|
|
133
|
+
},
|
|
125
134
|
{
|
|
126
135
|
"type": "fieldset",
|
|
127
136
|
"title": "Alarm",
|
package/dist/index.mjs
CHANGED
|
@@ -409,9 +409,10 @@ const parseConfig = (config, env = process.env) => {
|
|
|
409
409
|
const username = asString(config["username"]);
|
|
410
410
|
const password = env.HOMEBRIDGE_TYDOM_PASSWORD ? decode(env.HOMEBRIDGE_TYDOM_PASSWORD) : asString(config["password"]);
|
|
411
411
|
const pin = env.HOMEBRIDGE_TYDOM_PIN ? decode(env.HOMEBRIDGE_TYDOM_PIN) : asString(config["pin"]) || void 0;
|
|
412
|
+
const email = asString(env.HOMEBRIDGE_TYDOM_EMAIL) || asString(config["email"]) || void 0;
|
|
412
413
|
if (!hostname) throw new ConfigError("Missing \"hostname\" — use \"mediation.tydom.com\" for remote access, or your gateway's local address.");
|
|
413
414
|
if (!username) throw new ConfigError("Missing \"username\" — this is your Tydom gateway MAC address.");
|
|
414
|
-
if (!password) throw new ConfigError(
|
|
415
|
+
if (!password) throw new ConfigError(`Missing "password" — ${email ? `the password of the Delta Dore account ${email}.` : "your gateway's password, or set \"email\" to sign in with your Delta Dore account instead."} Can also be supplied as a base64-encoded HOMEBRIDGE_TYDOM_PASSWORD.`);
|
|
415
416
|
const locale = (asString(env.HOMEBRIDGE_TYDOM_LOCALE) || asString(config["locale"])) === "en" ? "en" : "fr";
|
|
416
417
|
const refreshSeconds = Number(config["refreshInterval"] ?? 144e5 / 1e3);
|
|
417
418
|
const refreshIntervalMs = Number.isFinite(refreshSeconds) ? Math.max(MIN_REFRESH_INTERVAL_MS, refreshSeconds * 1e3) : DEFAULT_REFRESH_INTERVAL_MS;
|
|
@@ -422,6 +423,7 @@ const parseConfig = (config, env = process.env) => {
|
|
|
422
423
|
hostname,
|
|
423
424
|
username,
|
|
424
425
|
password,
|
|
426
|
+
email,
|
|
425
427
|
pin,
|
|
426
428
|
locale,
|
|
427
429
|
debug: asBoolean(config["debug"], false),
|
|
@@ -2901,6 +2903,161 @@ const errorReplacer = (_key, value) => {
|
|
|
2901
2903
|
};
|
|
2902
2904
|
const stringifyError = (err) => JSON.stringify(err, errorReplacer);
|
|
2903
2905
|
//#endregion
|
|
2906
|
+
//#region src/util/deltadore.ts
|
|
2907
|
+
/**
|
|
2908
|
+
* Delta Dore account API — turns account credentials into gateway credentials.
|
|
2909
|
+
*
|
|
2910
|
+
* The gateway's own digest handshake (`mediation/client`) authenticates with the
|
|
2911
|
+
* gateway MAC as the username and a *gateway* password that is neither the
|
|
2912
|
+
* account password nor anything printed on the box. Delta Dore's own apps fetch
|
|
2913
|
+
* it at runtime from the account API, which is what this module does.
|
|
2914
|
+
*
|
|
2915
|
+
* The flow is three hops:
|
|
2916
|
+
*
|
|
2917
|
+
* 1. OpenID discovery against Delta Dore's Azure AD B2C tenant, to find the
|
|
2918
|
+
* token endpoint. Hard-coding it would break the day they rotate it, and
|
|
2919
|
+
* discovery is the documented way to avoid that.
|
|
2920
|
+
* 2. A ROPC (`grant_type=password`) token grant — B2C's non-interactive flow,
|
|
2921
|
+
* the only one usable from a headless plugin.
|
|
2922
|
+
* 3. `GET /sites?gateway_mac=…`, which returns the site and its gateway
|
|
2923
|
+
* credentials.
|
|
2924
|
+
*
|
|
2925
|
+
* Note that step 3 *validates* a MAC rather than enumerating: there is no
|
|
2926
|
+
* public endpoint that lists an account's gateways, so the MAC stays a required
|
|
2927
|
+
* piece of configuration. Only the password is derived here.
|
|
2928
|
+
*/
|
|
2929
|
+
const AUTH_CONFIG_URL = "https://deltadoreadb2ciot.b2clogin.com/deltadoreadb2ciot.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_AccountProviderROPC_SignIn";
|
|
2930
|
+
/** The Tydom mobile app's public client id. Not a secret. */
|
|
2931
|
+
const CLIENT_ID = "8782839f-3264-472a-ab87-4d4e23524da4";
|
|
2932
|
+
const SITES_URL = "https://prod.iotdeltadore.com/sitesmanagement/api/v1/sites";
|
|
2933
|
+
/**
|
|
2934
|
+
* Only the two scopes this actually needs.
|
|
2935
|
+
*
|
|
2936
|
+
* The app requests twenty-odd (video, metering, orchestration...); asking for
|
|
2937
|
+
* scopes we never exercise would hand the token far more authority than reading
|
|
2938
|
+
* one gateway password warrants.
|
|
2939
|
+
*/
|
|
2940
|
+
const SCOPE = [
|
|
2941
|
+
"openid",
|
|
2942
|
+
"profile",
|
|
2943
|
+
"offline_access",
|
|
2944
|
+
"https://deltadoreadb2ciot.onmicrosoft.com/iotapi/sites_management_allowed",
|
|
2945
|
+
"https://deltadoreadb2ciot.onmicrosoft.com/iotapi/sites_management_gateway_credentials"
|
|
2946
|
+
].join(" ");
|
|
2947
|
+
/** How long any one hop may take before we stop waiting on Delta Dore. */
|
|
2948
|
+
const REQUEST_TIMEOUT_MS = 15e3;
|
|
2949
|
+
/**
|
|
2950
|
+
* A credential problem the user has to fix, as opposed to a transport blip.
|
|
2951
|
+
*
|
|
2952
|
+
* Kept distinct so the platform can say "your account password is wrong" rather
|
|
2953
|
+
* than burning ten connection retries on something no retry will fix.
|
|
2954
|
+
*/
|
|
2955
|
+
var DeltaDoreAuthError = class extends Error {
|
|
2956
|
+
name = "DeltaDoreAuthError";
|
|
2957
|
+
};
|
|
2958
|
+
const openIdConfigSchema = z.object({ token_endpoint: z.string() }).loose();
|
|
2959
|
+
const tokenResponseSchema = z.object({ access_token: z.string() }).loose();
|
|
2960
|
+
const tokenErrorSchema = z.object({
|
|
2961
|
+
error: z.string().optional(),
|
|
2962
|
+
error_description: z.string().optional()
|
|
2963
|
+
}).loose();
|
|
2964
|
+
const siteSchema = z.object({
|
|
2965
|
+
id: z.string().optional(),
|
|
2966
|
+
gateway: z.object({
|
|
2967
|
+
mac: z.string().optional(),
|
|
2968
|
+
password: z.string().optional()
|
|
2969
|
+
}).loose().optional()
|
|
2970
|
+
}).loose();
|
|
2971
|
+
const sitesResponseSchema = z.object({ sites: z.array(siteSchema).default([]) }).loose();
|
|
2972
|
+
/**
|
|
2973
|
+
* Uppercase, separators stripped — the form the sites API matches on.
|
|
2974
|
+
*
|
|
2975
|
+
* Users copy the MAC off the label underneath the box, which prints it with
|
|
2976
|
+
* colons, so accepting only the bare form would reject a correct answer.
|
|
2977
|
+
*/
|
|
2978
|
+
const normalizeMacAddress = (mac) => mac.replace(/[^0-9a-zA-Z]/g, "").toUpperCase();
|
|
2979
|
+
const requestJson = async (fetchImpl, url, init) => {
|
|
2980
|
+
const { what, ...requestInit } = init;
|
|
2981
|
+
let response;
|
|
2982
|
+
try {
|
|
2983
|
+
response = await fetchImpl(url, {
|
|
2984
|
+
...requestInit,
|
|
2985
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
2986
|
+
});
|
|
2987
|
+
} catch (err) {
|
|
2988
|
+
throw new Error(`Failed to reach Delta Dore while ${what}: ${String(err)}`, { cause: err });
|
|
2989
|
+
}
|
|
2990
|
+
const text = await response.text();
|
|
2991
|
+
let body;
|
|
2992
|
+
try {
|
|
2993
|
+
body = text ? JSON.parse(text) : {};
|
|
2994
|
+
} catch {
|
|
2995
|
+
throw new Error(`Delta Dore returned a non-JSON response while ${what} (status=${response.status}): ${text.slice(0, 120)}`);
|
|
2996
|
+
}
|
|
2997
|
+
return {
|
|
2998
|
+
status: response.status,
|
|
2999
|
+
body
|
|
3000
|
+
};
|
|
3001
|
+
};
|
|
3002
|
+
/** Exchange account credentials for a bearer token. */
|
|
3003
|
+
const requestAccessToken = async ({ email, password, fetch: fetchImpl = globalThis.fetch }) => {
|
|
3004
|
+
const discovery = await requestJson(fetchImpl, AUTH_CONFIG_URL, { what: "discovering the account API" });
|
|
3005
|
+
const openIdConfig = openIdConfigSchema.safeParse(discovery.body);
|
|
3006
|
+
if (!openIdConfig.success) throw new Error("Delta Dore's OpenID configuration is missing a token endpoint.");
|
|
3007
|
+
const form = new FormData();
|
|
3008
|
+
form.set("username", email);
|
|
3009
|
+
form.set("password", password);
|
|
3010
|
+
form.set("grant_type", "password");
|
|
3011
|
+
form.set("client_id", CLIENT_ID);
|
|
3012
|
+
form.set("scope", SCOPE);
|
|
3013
|
+
form.set("response_type", "token");
|
|
3014
|
+
const { status, body } = await requestJson(fetchImpl, openIdConfig.data.token_endpoint, {
|
|
3015
|
+
method: "POST",
|
|
3016
|
+
body: form,
|
|
3017
|
+
what: "signing in to your Delta Dore account"
|
|
3018
|
+
});
|
|
3019
|
+
const token = tokenResponseSchema.safeParse(body);
|
|
3020
|
+
if (!token.success) {
|
|
3021
|
+
const error = tokenErrorSchema.safeParse(body);
|
|
3022
|
+
const detail = error.success ? error.data.error_description?.split("\n")[0] ?? error.data.error : void 0;
|
|
3023
|
+
throw new DeltaDoreAuthError(`Delta Dore rejected the account credentials (status=${status})${detail ? `: ${detail}` : "."}`);
|
|
3024
|
+
}
|
|
3025
|
+
return token.data.access_token;
|
|
3026
|
+
};
|
|
3027
|
+
/**
|
|
3028
|
+
* Look up the gateway password for `mac` on the signed-in account.
|
|
3029
|
+
*
|
|
3030
|
+
* Returns the password, or throws if the account does not own that gateway.
|
|
3031
|
+
*/
|
|
3032
|
+
const fetchGatewayPassword = async ({ accessToken, mac, fetch: fetchImpl = globalThis.fetch }) => {
|
|
3033
|
+
const normalizedMac = normalizeMacAddress(mac);
|
|
3034
|
+
const url = `${SITES_URL}?gateway_mac=${encodeURIComponent(normalizedMac)}`;
|
|
3035
|
+
const { status, body } = await requestJson(fetchImpl, url, {
|
|
3036
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
3037
|
+
what: "looking up your gateway"
|
|
3038
|
+
});
|
|
3039
|
+
if (status === 401 || status === 403) throw new DeltaDoreAuthError(`Your Delta Dore account is not allowed to read the credentials of gateway ${normalizedMac}.`);
|
|
3040
|
+
const parsed = sitesResponseSchema.safeParse(body);
|
|
3041
|
+
if (!parsed.success) throw new Error(`Delta Dore returned an unexpected site list (status=${status}) for gateway ${normalizedMac}.`);
|
|
3042
|
+
const site = parsed.data.sites.find((candidate) => normalizeMacAddress(candidate.gateway?.mac ?? "") === normalizedMac);
|
|
3043
|
+
if (!site) throw new DeltaDoreAuthError(`No gateway with MAC ${normalizedMac} is attached to this Delta Dore account. Check "username" against the MAC printed underneath your Tydom box.`);
|
|
3044
|
+
if (!site.gateway?.password) throw new DeltaDoreAuthError(`Delta Dore returned no password for gateway ${normalizedMac}. The account can see that house but does not hold its credentials — sign in with the account that registered the gateway, or set "password" to the gateway password directly and leave "email" out.`);
|
|
3045
|
+
return site.gateway.password;
|
|
3046
|
+
};
|
|
3047
|
+
/** Sign in, then read the gateway password. The whole flow, in one call. */
|
|
3048
|
+
const resolveGatewayPassword = async ({ email, password, mac, fetch: fetchImpl = globalThis.fetch }) => {
|
|
3049
|
+
const accessToken = await requestAccessToken({
|
|
3050
|
+
email,
|
|
3051
|
+
password,
|
|
3052
|
+
fetch: fetchImpl
|
|
3053
|
+
});
|
|
3054
|
+
return fetchGatewayPassword({
|
|
3055
|
+
accessToken,
|
|
3056
|
+
mac,
|
|
3057
|
+
fetch: fetchImpl
|
|
3058
|
+
});
|
|
3059
|
+
};
|
|
3060
|
+
//#endregion
|
|
2904
3061
|
//#region src/controller.ts
|
|
2905
3062
|
var TydomController = class extends EventEmitter {
|
|
2906
3063
|
client;
|
|
@@ -2925,6 +3082,14 @@ var TydomController = class extends EventEmitter {
|
|
|
2925
3082
|
emitted = /* @__PURE__ */ new Set();
|
|
2926
3083
|
refreshInterval;
|
|
2927
3084
|
hasConnectedOnce = false;
|
|
3085
|
+
/**
|
|
3086
|
+
* The gateway password actually in use.
|
|
3087
|
+
*
|
|
3088
|
+
* Empty until `connect` derives it, when the platform was configured with an
|
|
3089
|
+
* account e-mail — in which case `config.password` is the account's, not the
|
|
3090
|
+
* gateway's, and must never reach the gateway.
|
|
3091
|
+
*/
|
|
3092
|
+
gatewayPassword;
|
|
2928
3093
|
/** Delta Dore label lookups, bound to the configured locale. */
|
|
2929
3094
|
t;
|
|
2930
3095
|
constructor(log, config) {
|
|
@@ -2932,22 +3097,34 @@ var TydomController = class extends EventEmitter {
|
|
|
2932
3097
|
this.config = config;
|
|
2933
3098
|
this.log = log;
|
|
2934
3099
|
this.t = createTranslator(config.locale);
|
|
2935
|
-
const { hostname, username, password } = config;
|
|
3100
|
+
const { hostname, username, password, email } = config;
|
|
2936
3101
|
this.log.info(`Creating tydom client with username=${styleString(username)} and hostname=${styleString(hostname)}`);
|
|
2937
|
-
this.
|
|
3102
|
+
this.gatewayPassword = email ? "" : password;
|
|
3103
|
+
this.client = this.createClient(this.gatewayPassword);
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Build a client for `password` and wire its events up.
|
|
3107
|
+
*
|
|
3108
|
+
* Split out of the constructor because account-derived credentials only
|
|
3109
|
+
* arrive once `connect` has been able to await the account API, and the
|
|
3110
|
+
* client takes its password at construction time.
|
|
3111
|
+
*/
|
|
3112
|
+
createClient(password) {
|
|
3113
|
+
const { hostname, username } = this.config;
|
|
3114
|
+
const client = createClient({
|
|
2938
3115
|
username,
|
|
2939
3116
|
password,
|
|
2940
3117
|
hostname,
|
|
2941
3118
|
followUpDebounce: 500
|
|
2942
3119
|
});
|
|
2943
|
-
|
|
3120
|
+
client.on("message", (message) => {
|
|
2944
3121
|
try {
|
|
2945
3122
|
this.handleMessage(message);
|
|
2946
3123
|
} catch (err) {
|
|
2947
3124
|
this.log.error(`Encountered an uncaught error=${stringifyError(err)} while processing message=${styleJson(message)}"`);
|
|
2948
3125
|
}
|
|
2949
3126
|
});
|
|
2950
|
-
|
|
3127
|
+
client.on("connect", () => {
|
|
2951
3128
|
this.log.info(`Successfully connected to Tydom hostname=${styleString(hostname)} with username=${styleString(username)}`);
|
|
2952
3129
|
if (this.hasConnectedOnce) {
|
|
2953
3130
|
this.log.warn(`Reconnected to Tydom hostname=${styleString(hostname)}, re-syncing state...`);
|
|
@@ -2957,7 +3134,7 @@ var TydomController = class extends EventEmitter {
|
|
|
2957
3134
|
}
|
|
2958
3135
|
this.emit("connect");
|
|
2959
3136
|
});
|
|
2960
|
-
|
|
3137
|
+
client.on("disconnect", () => {
|
|
2961
3138
|
this.log.warn(`Disconnected from Tydom hostname=${styleString(hostname)}"`);
|
|
2962
3139
|
if (this.refreshInterval) {
|
|
2963
3140
|
clearInterval(this.refreshInterval);
|
|
@@ -2965,6 +3142,29 @@ var TydomController = class extends EventEmitter {
|
|
|
2965
3142
|
}
|
|
2966
3143
|
this.emit("disconnect");
|
|
2967
3144
|
});
|
|
3145
|
+
return client;
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Fetch the gateway password from the Delta Dore account, once.
|
|
3149
|
+
*
|
|
3150
|
+
* No-op unless an account e-mail was configured. The result is kept for the
|
|
3151
|
+
* process lifetime: the gateway password is stable, and `connect` is retried
|
|
3152
|
+
* with backoff on startup — re-running the whole OAuth flow on every attempt
|
|
3153
|
+
* would turn a flaky socket into repeated sign-ins.
|
|
3154
|
+
*/
|
|
3155
|
+
async resolveGatewayPassword() {
|
|
3156
|
+
const { email, password: accountPassword, username } = this.config;
|
|
3157
|
+
if (this.gatewayPassword || !email) return;
|
|
3158
|
+
this.log.info(`Resolving the gateway password from the Delta Dore account of ${styleString(email)}...`);
|
|
3159
|
+
const password = await resolveGatewayPassword({
|
|
3160
|
+
email,
|
|
3161
|
+
password: accountPassword,
|
|
3162
|
+
mac: username
|
|
3163
|
+
});
|
|
3164
|
+
this.gatewayPassword = password;
|
|
3165
|
+
this.client.removeAllListeners();
|
|
3166
|
+
this.client = this.createClient(password);
|
|
3167
|
+
this.log.info(`Resolved the gateway password for username=${styleString(username)}`);
|
|
2968
3168
|
}
|
|
2969
3169
|
getUniqueId(deviceId, endpointId) {
|
|
2970
3170
|
return deviceId === endpointId ? `${deviceId}` : `${deviceId}:${endpointId}`;
|
|
@@ -2977,6 +3177,7 @@ var TydomController = class extends EventEmitter {
|
|
|
2977
3177
|
const { hostname, username } = this.config;
|
|
2978
3178
|
debug(`Connecting to hostname=${styleString(hostname)}...`);
|
|
2979
3179
|
try {
|
|
3180
|
+
await this.resolveGatewayPassword();
|
|
2980
3181
|
await this.client.connect();
|
|
2981
3182
|
await asyncWait(250);
|
|
2982
3183
|
await this.client.get("/ping");
|
|
@@ -4947,8 +5148,14 @@ var TydomPlatform = class {
|
|
|
4947
5148
|
this.t = createTranslator(this.config.locale);
|
|
4948
5149
|
this.pluginLog = createPluginLogger(log, this.config.debug);
|
|
4949
5150
|
this.controller = new TydomController(log, this.config);
|
|
5151
|
+
const controller = this.controller;
|
|
4950
5152
|
this.apiClient = new TydomApiClient({
|
|
4951
|
-
transport:
|
|
5153
|
+
transport: {
|
|
5154
|
+
get: (uri) => controller.client.get(uri),
|
|
5155
|
+
put: (uri, body) => controller.client.put(uri, body),
|
|
5156
|
+
post: (uri, body) => controller.client.post(uri, body),
|
|
5157
|
+
command: (uri) => controller.client.command(uri)
|
|
5158
|
+
},
|
|
4952
5159
|
logger: this.pluginLog
|
|
4953
5160
|
});
|
|
4954
5161
|
this.api.on("didFinishLaunching", () => {
|