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 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 for your Tydom account. Newer setups have it generated by the mobile app and never show it to you; the only known way to recover it is to inspect the app's traffic with an SSL proxy, following [this guide](https://github.com/mgcrea/homebridge-tydom/issues/72#issuecomment-1315036089) from @aure-olivier.
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
- | `password` | `string` | — | Tydom account password. Required, unless supplied via the environment. |
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` | Tydom password, base64-encoded. Overrides `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
 
@@ -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": "The password of your Tydom account. Can be supplied out of band instead, as a base64-encoded `HOMEBRIDGE_TYDOM_PASSWORD` environment variable, which takes precedence over this field."
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
- { "type": "fieldset", "title": "Connection", "items": ["hostname", "username", "password"] },
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("Missing \"password\" — set it in the platform config, or as a base64-encoded HOMEBRIDGE_TYDOM_PASSWORD.");
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.client = createClient({
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
- this.client.on("message", (message) => {
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
- this.client.on("connect", () => {
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
- this.client.on("disconnect", () => {
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: this.controller.client,
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", () => {