@camstack/addon-provider-bosch 0.1.3 → 0.1.5
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/account-integration-offline-BhjIPfv5.js +119 -0
- package/dist/account-integration-offline-DW2D0vf8.mjs +102 -0
- package/dist/{dist-B-Un7bqT.js → dist-CIIror0W.js} +390 -294
- package/dist/{dist-BiZi19Lw.mjs → dist-DNznrlG_.mjs} +390 -294
- package/dist/home-connect/addon.js +50 -21
- package/dist/home-connect/addon.mjs +50 -21
- package/dist/homecom/addon.js +546 -123
- package/dist/homecom/addon.mjs +546 -123
- package/dist/shc/addon.js +1 -1
- package/dist/shc/addon.mjs +1 -1
- package/i18n/en.json +27 -1
- package/i18n/it.json +27 -1
- package/package.json +1 -1
- package/dist/account-integration-offline-BBqEtCgz.js +0 -50
- package/dist/account-integration-offline-BfoLYzxz.mjs +0 -39
package/dist/homecom/addon.js
CHANGED
|
@@ -1,6 +1,388 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("../dist-
|
|
3
|
-
const require_account_integration_offline = require("../account-integration-offline-
|
|
2
|
+
const require_dist = require("../dist-CIIror0W.js");
|
|
3
|
+
const require_account_integration_offline = require("../account-integration-offline-BhjIPfv5.js");
|
|
4
|
+
let node_crypto = require("node:crypto");
|
|
5
|
+
//#region src/homecom/custom-actions.ts
|
|
6
|
+
var homeComActions = require_dist.defineCustomActions({
|
|
7
|
+
beginSignIn: require_dist.customAction(require_dist.object({}), require_dist.object({
|
|
8
|
+
authorizeUrl: require_dist.string().min(1),
|
|
9
|
+
verifier: require_dist.string().min(1),
|
|
10
|
+
state: require_dist.string().min(1),
|
|
11
|
+
summary: require_dist.string()
|
|
12
|
+
}), {
|
|
13
|
+
kind: "mutation",
|
|
14
|
+
auth: "admin"
|
|
15
|
+
}),
|
|
16
|
+
finishSignIn: require_dist.customAction(require_dist.object({
|
|
17
|
+
verifier: require_dist.string().min(1),
|
|
18
|
+
callback: require_dist.string().min(1),
|
|
19
|
+
state: require_dist.string().min(1)
|
|
20
|
+
}), require_dist.object({
|
|
21
|
+
token: require_dist.string().min(1).meta({ secret: true }),
|
|
22
|
+
refresh: require_dist.string().min(1).meta({ secret: true }),
|
|
23
|
+
username: require_dist.string().meta({ secret: true }),
|
|
24
|
+
summary: require_dist.string()
|
|
25
|
+
}), {
|
|
26
|
+
kind: "mutation",
|
|
27
|
+
auth: "admin"
|
|
28
|
+
})
|
|
29
|
+
});
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/homecom/homecom-oauth.ts
|
|
32
|
+
/**
|
|
33
|
+
* Public OAuth client of the HomeCom Easy / PointT mobile app, signed in through SingleKey ID.
|
|
34
|
+
*
|
|
35
|
+
* These are the PUBLIC values the app itself ships (a PKCE client has no secret). They are not
|
|
36
|
+
* derivable from this repository; they come from the community PointT client (`homecom_alt`) and
|
|
37
|
+
* have to be confirmed against a live account — see docs/addons/addon-provider-bosch-homecom.md.
|
|
38
|
+
*/
|
|
39
|
+
var HOMECOM_CLIENT_ID = "762162C0-FA2D-4540-AE66-6489F189FADC";
|
|
40
|
+
var HOMECOM_REDIRECT_URI = "com.bosch.tt.dashtt.pointt://app/login";
|
|
41
|
+
var HOMECOM_AUTHORIZE_URL = "https://singlekey-id.com/auth/connect/authorize";
|
|
42
|
+
var HOMECOM_TOKEN_URL = "https://singlekey-id.com/auth/connect/token";
|
|
43
|
+
var HOMECOM_SCOPE = [
|
|
44
|
+
"openid",
|
|
45
|
+
"email",
|
|
46
|
+
"profile",
|
|
47
|
+
"offline_access",
|
|
48
|
+
"pointt.gateway.claiming",
|
|
49
|
+
"pointt.gateway.removal",
|
|
50
|
+
"pointt.gateway.list",
|
|
51
|
+
"pointt.gateway.users",
|
|
52
|
+
"pointt.gateway.resource.dopair",
|
|
53
|
+
"pointt.castt.flow.token-exchange",
|
|
54
|
+
"bacon",
|
|
55
|
+
"hcc.tariff.read"
|
|
56
|
+
].join(" ");
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/homecom/homecom-login.ts
|
|
59
|
+
var MESSAGES = {
|
|
60
|
+
empty: "Nothing was pasted. Paste the address the browser tried to open after you signed in.",
|
|
61
|
+
"no-code": "No sign-in code was found in what you pasted. Copy the whole address that starts with com.bosch.tt.dashtt.pointt://, or just the code.",
|
|
62
|
+
"state-missing": "The pasted address has no state. Copy the complete address, or create the login link again.",
|
|
63
|
+
"state-mismatch": "This address belongs to a different sign-in attempt. Create the login link again and use the address that follows it.",
|
|
64
|
+
denied: "Bosch refused the sign-in. Create the login link again and approve the access.",
|
|
65
|
+
"code-rejected": "The code was already used or has expired. Create the login link again; a code works once and only for a few minutes.",
|
|
66
|
+
rejected: "Bosch did not accept the sign-in. Create the login link again.",
|
|
67
|
+
"no-refresh": "Bosch signed you in but returned no long-lived token. Create the login link again."
|
|
68
|
+
};
|
|
69
|
+
/** A failure the operator can act on; `reason` is stable, the message is for the form. */
|
|
70
|
+
var HomeComLoginError = class extends Error {
|
|
71
|
+
name = "HomeComLoginError";
|
|
72
|
+
reason;
|
|
73
|
+
constructor(reason) {
|
|
74
|
+
super(MESSAGES[reason]);
|
|
75
|
+
this.reason = reason;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function b64url(bytes) {
|
|
79
|
+
return bytes.toString("base64url");
|
|
80
|
+
}
|
|
81
|
+
function beginHomeComSignIn() {
|
|
82
|
+
const verifier = b64url((0, node_crypto.randomBytes)(32));
|
|
83
|
+
const challenge = b64url((0, node_crypto.createHash)("sha256").update(verifier).digest());
|
|
84
|
+
const state = b64url((0, node_crypto.randomBytes)(16));
|
|
85
|
+
const url = new URL(HOMECOM_AUTHORIZE_URL);
|
|
86
|
+
url.searchParams.set("client_id", HOMECOM_CLIENT_ID);
|
|
87
|
+
url.searchParams.set("redirect_uri", HOMECOM_REDIRECT_URI);
|
|
88
|
+
url.searchParams.set("response_type", "code");
|
|
89
|
+
url.searchParams.set("scope", HOMECOM_SCOPE);
|
|
90
|
+
url.searchParams.set("code_challenge", challenge);
|
|
91
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
92
|
+
url.searchParams.set("state", state);
|
|
93
|
+
url.searchParams.set("nonce", b64url((0, node_crypto.randomBytes)(16)));
|
|
94
|
+
url.searchParams.set("prompt", "login");
|
|
95
|
+
return {
|
|
96
|
+
authorizeUrl: url.toString(),
|
|
97
|
+
verifier,
|
|
98
|
+
state
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The sign-in code from whatever the operator pasted: the full app address, an address copied
|
|
103
|
+
* from the browser's developer tools, a bare `code=…&state=…` query, or the bare code.
|
|
104
|
+
*/
|
|
105
|
+
function parseHomeComCallback(input, expectedState) {
|
|
106
|
+
const parsed = require_account_integration_offline.parseOAuthCallback(input, expectedState);
|
|
107
|
+
if (!parsed.ok) throw new HomeComLoginError(parsed.reason);
|
|
108
|
+
return parsed.code;
|
|
109
|
+
}
|
|
110
|
+
function isRecord$1(value) {
|
|
111
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
112
|
+
}
|
|
113
|
+
function emailFromIdToken(idToken) {
|
|
114
|
+
if (typeof idToken !== "string") return "";
|
|
115
|
+
const payload = idToken.split(".")[1];
|
|
116
|
+
if (!payload) return "";
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
119
|
+
return isRecord$1(parsed) && typeof parsed["email"] === "string" ? parsed["email"] : "";
|
|
120
|
+
} catch {
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function completeHomeComSignIn(input, fetchFn = globalThis.fetch.bind(globalThis)) {
|
|
125
|
+
const code = parseHomeComCallback(input.callback, input.state);
|
|
126
|
+
const res = await fetchFn(HOMECOM_TOKEN_URL, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
129
|
+
body: new URLSearchParams({
|
|
130
|
+
grant_type: "authorization_code",
|
|
131
|
+
client_id: HOMECOM_CLIENT_ID,
|
|
132
|
+
code,
|
|
133
|
+
code_verifier: input.verifier,
|
|
134
|
+
redirect_uri: HOMECOM_REDIRECT_URI
|
|
135
|
+
}).toString()
|
|
136
|
+
});
|
|
137
|
+
const body = await res.json().catch(() => null);
|
|
138
|
+
if (!res.ok) throw new HomeComLoginError((isRecord$1(body) ? body["error"] : void 0) === "invalid_grant" ? "code-rejected" : "rejected");
|
|
139
|
+
if (!isRecord$1(body) || typeof body["access_token"] !== "string" || body["access_token"] === "") throw new HomeComLoginError("rejected");
|
|
140
|
+
const refresh = body["refresh_token"];
|
|
141
|
+
if (typeof refresh !== "string" || refresh === "") throw new HomeComLoginError("no-refresh");
|
|
142
|
+
return {
|
|
143
|
+
token: body["access_token"],
|
|
144
|
+
refresh,
|
|
145
|
+
username: emailFromIdToken(body["id_token"])
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/homecom/config.ts
|
|
150
|
+
var HOMECOM_BRANDS = ["bosch", "buderus"];
|
|
151
|
+
var HomeComBrandSchema = require_dist._enum(HOMECOM_BRANDS);
|
|
152
|
+
var HOMECOM_REGIONS = ["eu", "us"];
|
|
153
|
+
var HomeComRegionSchema = require_dist._enum(HOMECOM_REGIONS);
|
|
154
|
+
var homeComConfigSchema = require_dist.object({
|
|
155
|
+
/** Account email; filled by the sign-in, informational only. */
|
|
156
|
+
username: require_dist.string().optional().describe("HomeCom / PointT account email").meta({ secret: true }),
|
|
157
|
+
token: require_dist.string().min(1).describe("OAuth access token").meta({ secret: true }),
|
|
158
|
+
refresh: require_dist.string().min(1).describe("OAuth refresh token").meta({ secret: true }),
|
|
159
|
+
brand: HomeComBrandSchema.default("bosch"),
|
|
160
|
+
region: HomeComRegionSchema.default("eu"),
|
|
161
|
+
/** The gateway this integration controls; chosen once from the account's gateways. */
|
|
162
|
+
gatewayId: require_dist.string().min(1).optional()
|
|
163
|
+
});
|
|
164
|
+
function connectionFromSettings(settings) {
|
|
165
|
+
const parsed = homeComConfigSchema.safeParse(settings);
|
|
166
|
+
return parsed.success ? parsed.data : null;
|
|
167
|
+
}
|
|
168
|
+
function sameHomeComConnection(a, b) {
|
|
169
|
+
if (a === null || b === null) return false;
|
|
170
|
+
return a.username === b.username && a.token === b.token && a.refresh === b.refresh && a.brand === b.brand && a.region === b.region && a.gatewayId === b.gatewayId;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* PointT REST origin (matches homecom_alt / HomeCom Easy). US and EU accounts use
|
|
174
|
+
* the same host; `region` is retained for Bacon MQTT (`euc1` / `use1`) when wired.
|
|
175
|
+
*/
|
|
176
|
+
var HOMECOM_POINTT_API_ORIGIN = "https://pointt-api.bosch-thermotechnology.com";
|
|
177
|
+
function homeComApiBase(_region) {
|
|
178
|
+
return HOMECOM_POINTT_API_ORIGIN;
|
|
179
|
+
}
|
|
180
|
+
var HOMECOM_SECRET_KEYS = [
|
|
181
|
+
"username",
|
|
182
|
+
"token",
|
|
183
|
+
"refresh"
|
|
184
|
+
];
|
|
185
|
+
function buildHomeComFormSchema() {
|
|
186
|
+
return { sections: [{
|
|
187
|
+
id: "account",
|
|
188
|
+
title: "HomeCom account",
|
|
189
|
+
titleKey: "provider-bosch:homecom.s_account.title",
|
|
190
|
+
description: "Create the login link and open it in a browser, then sign in with your SingleKey ID. The browser cannot open the app and stops on an address starting with com.bosch.tt.dashtt.pointt://app/login?code=. Paste that whole address below (or only the code) and press Complete sign-in. The address is shown in Chrome and Edge under DevTools (F12) > Network, with Preserve log ticked before you sign in: select the last redirect and copy its Location header. In Firefox use Network > Headers > Location; in Safari use Develop > Show Web Inspector > Network. A code works once and expires within minutes. Tokens can also be pasted by hand in the fields below.",
|
|
191
|
+
descriptionKey: "provider-bosch:homecom.s_account.description",
|
|
192
|
+
columns: 1,
|
|
193
|
+
fields: [
|
|
194
|
+
{
|
|
195
|
+
type: "addon-action-button",
|
|
196
|
+
addonId: "bosch-homecom",
|
|
197
|
+
key: "__homecom-begin",
|
|
198
|
+
label: "HomeCom",
|
|
199
|
+
labelKey: "provider-bosch:homecom.s_account.__homecom-begin.label",
|
|
200
|
+
buttonLabel: "Create login link",
|
|
201
|
+
action: "beginSignIn",
|
|
202
|
+
variant: "primary",
|
|
203
|
+
assignResult: {
|
|
204
|
+
authorizeUrl: "authorizeUrl",
|
|
205
|
+
verifier: "verifier",
|
|
206
|
+
state: "state"
|
|
207
|
+
},
|
|
208
|
+
successMessage: "Open the link in a browser and sign in."
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: "textarea",
|
|
212
|
+
key: "authorizeUrl",
|
|
213
|
+
label: "Login link",
|
|
214
|
+
labelKey: "provider-bosch:homecom.s_account.authorizeUrl.label",
|
|
215
|
+
required: false,
|
|
216
|
+
omitFromSettings: true
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: "textarea",
|
|
220
|
+
key: "callback",
|
|
221
|
+
label: "Redirect address or code",
|
|
222
|
+
labelKey: "provider-bosch:homecom.s_account.callback.label",
|
|
223
|
+
required: false,
|
|
224
|
+
placeholder: "com.bosch.tt.dashtt.pointt://app/login?code=…&state=…",
|
|
225
|
+
omitFromSettings: true
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: "password",
|
|
229
|
+
key: "state",
|
|
230
|
+
label: "Login state",
|
|
231
|
+
labelKey: "provider-bosch:homecom.s_account.state.label",
|
|
232
|
+
required: false,
|
|
233
|
+
omitFromSettings: true
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
type: "password",
|
|
237
|
+
key: "verifier",
|
|
238
|
+
label: "Login verifier",
|
|
239
|
+
labelKey: "provider-bosch:homecom.s_account.verifier.label",
|
|
240
|
+
required: false,
|
|
241
|
+
omitFromSettings: true
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
type: "addon-action-button",
|
|
245
|
+
addonId: "bosch-homecom",
|
|
246
|
+
key: "__homecom-finish",
|
|
247
|
+
label: "Finish",
|
|
248
|
+
labelKey: "provider-bosch:homecom.s_account.__homecom-finish.label",
|
|
249
|
+
buttonLabel: "Complete sign-in",
|
|
250
|
+
action: "finishSignIn",
|
|
251
|
+
variant: "primary",
|
|
252
|
+
paramsFromForm: {
|
|
253
|
+
verifier: "verifier",
|
|
254
|
+
callback: "callback",
|
|
255
|
+
state: "state"
|
|
256
|
+
},
|
|
257
|
+
assignResult: {
|
|
258
|
+
token: "token",
|
|
259
|
+
refresh: "refresh",
|
|
260
|
+
username: "username"
|
|
261
|
+
},
|
|
262
|
+
successMessage: "{summary}"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
type: "text",
|
|
266
|
+
key: "username",
|
|
267
|
+
label: "Account",
|
|
268
|
+
labelKey: "provider-bosch:homecom.s_account.username.label",
|
|
269
|
+
required: false
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
type: "password",
|
|
273
|
+
key: "token",
|
|
274
|
+
label: "Access token (filled by sign-in, or paste one)",
|
|
275
|
+
labelKey: "provider-bosch:homecom.s_account.token.label",
|
|
276
|
+
required: true,
|
|
277
|
+
showToggle: true
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
type: "password",
|
|
281
|
+
key: "refresh",
|
|
282
|
+
label: "Refresh token (filled by sign-in, or paste one)",
|
|
283
|
+
labelKey: "provider-bosch:homecom.s_account.refresh.label",
|
|
284
|
+
required: true,
|
|
285
|
+
showToggle: true
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
type: "text",
|
|
289
|
+
key: "gatewayId",
|
|
290
|
+
label: "Gateway id",
|
|
291
|
+
labelKey: "provider-bosch:homecom.s_account.gatewayId.label",
|
|
292
|
+
description: "Leave empty to use the first gateway of the account; the chosen one is stored here. For a second gateway add another HomeCom account entry and enter its id.",
|
|
293
|
+
descriptionKey: "provider-bosch:homecom.s_account.gatewayId.description",
|
|
294
|
+
required: false
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
type: "select",
|
|
298
|
+
key: "brand",
|
|
299
|
+
label: "Brand",
|
|
300
|
+
labelKey: "provider-bosch:homecom.s_account.brand.label",
|
|
301
|
+
default: "bosch",
|
|
302
|
+
options: HOMECOM_BRANDS.map((b) => ({
|
|
303
|
+
value: b,
|
|
304
|
+
label: b
|
|
305
|
+
}))
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
type: "select",
|
|
309
|
+
key: "region",
|
|
310
|
+
label: "Region",
|
|
311
|
+
labelKey: "provider-bosch:homecom.s_account.region.label",
|
|
312
|
+
default: "eu",
|
|
313
|
+
options: HOMECOM_REGIONS.map((r) => ({
|
|
314
|
+
value: r,
|
|
315
|
+
label: r.toUpperCase()
|
|
316
|
+
}))
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
}] };
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/homecom/homecom-token-persist.ts
|
|
323
|
+
function unreadable(value) {
|
|
324
|
+
return require_dist.isSecretMarker(value) || require_dist.isSecretMarkerLike(value);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Merge `patch` into the integration's stored settings in ONE `setSettings` write, one at a
|
|
328
|
+
* time per integration. Refuses (and says so, without values) when a secret the write
|
|
329
|
+
* touches cannot be read back from the keychain: the hub refuses an overwrite of a secret the
|
|
330
|
+
* addon could not read, so the write would only fail.
|
|
331
|
+
*/
|
|
332
|
+
function buildSerialPatcher(deps) {
|
|
333
|
+
const tails = /* @__PURE__ */ new Map();
|
|
334
|
+
return (integrationId, patch) => {
|
|
335
|
+
const run = async () => {
|
|
336
|
+
try {
|
|
337
|
+
const stored = await deps.getSettings(integrationId);
|
|
338
|
+
const locked = HOMECOM_SECRET_KEYS.filter((key) => unreadable(stored[key]));
|
|
339
|
+
if (locked.length > 0) {
|
|
340
|
+
deps.logger.warn(`HomeCom not persisting ${deps.what} — stored secrets are locked`, {
|
|
341
|
+
tags: { integrationId },
|
|
342
|
+
meta: { locked }
|
|
343
|
+
});
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
await deps.setSettings({
|
|
347
|
+
id: integrationId,
|
|
348
|
+
settings: {
|
|
349
|
+
...stored,
|
|
350
|
+
...patch
|
|
351
|
+
},
|
|
352
|
+
secretKeys: [...HOMECOM_SECRET_KEYS]
|
|
353
|
+
});
|
|
354
|
+
} catch (err) {
|
|
355
|
+
deps.logger.warn(`HomeCom failed to persist ${deps.what}`, {
|
|
356
|
+
tags: { integrationId },
|
|
357
|
+
meta: { error: require_dist.errMsg(err) }
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
const next = (tails.get(integrationId) ?? Promise.resolve()).then(run);
|
|
362
|
+
tails.set(integrationId, next);
|
|
363
|
+
return next;
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/** Persist a rotated token pair; never throws, never logs a value. */
|
|
367
|
+
function buildHomeComTokenPersister(deps) {
|
|
368
|
+
const patch = buildSerialPatcher({
|
|
369
|
+
...deps,
|
|
370
|
+
what: "rotated tokens"
|
|
371
|
+
});
|
|
372
|
+
return (integrationId, tokens) => patch(integrationId, {
|
|
373
|
+
token: tokens.token,
|
|
374
|
+
refresh: tokens.refresh
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/** Persist the gateway this integration settled on. */
|
|
378
|
+
function buildHomeComGatewayPersister(deps) {
|
|
379
|
+
const patch = buildSerialPatcher({
|
|
380
|
+
...deps,
|
|
381
|
+
what: "chosen gateway"
|
|
382
|
+
});
|
|
383
|
+
return (integrationId, gatewayId) => patch(integrationId, { gatewayId });
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
4
386
|
//#region src/homecom/homecom-discovery.ts
|
|
5
387
|
function buildHomeComCandidates(input) {
|
|
6
388
|
const out = [];
|
|
@@ -154,84 +536,6 @@ function buildHomeComAdoptionProvider(deps) {
|
|
|
154
536
|
};
|
|
155
537
|
}
|
|
156
538
|
//#endregion
|
|
157
|
-
//#region src/homecom/config.ts
|
|
158
|
-
var HOMECOM_BRANDS = ["bosch", "buderus"];
|
|
159
|
-
var HomeComBrandSchema = require_dist._enum(HOMECOM_BRANDS);
|
|
160
|
-
var HOMECOM_REGIONS = ["eu", "us"];
|
|
161
|
-
var HomeComRegionSchema = require_dist._enum(HOMECOM_REGIONS);
|
|
162
|
-
var homeComConfigSchema = require_dist.object({
|
|
163
|
-
username: require_dist.string().min(1).describe("HomeCom / PointT account email").meta({ secret: true }),
|
|
164
|
-
token: require_dist.string().min(1).describe("OAuth access token").meta({ secret: true }),
|
|
165
|
-
refresh: require_dist.string().min(1).describe("OAuth refresh token").meta({ secret: true }),
|
|
166
|
-
brand: HomeComBrandSchema.default("bosch"),
|
|
167
|
-
region: HomeComRegionSchema.default("eu")
|
|
168
|
-
});
|
|
169
|
-
function connectionFromSettings(settings) {
|
|
170
|
-
const parsed = homeComConfigSchema.safeParse(settings);
|
|
171
|
-
return parsed.success ? parsed.data : null;
|
|
172
|
-
}
|
|
173
|
-
function sameHomeComConnection(a, b) {
|
|
174
|
-
if (a === null || b === null) return false;
|
|
175
|
-
return a.username === b.username && a.token === b.token && a.refresh === b.refresh && a.brand === b.brand && a.region === b.region;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* PointT REST origin (matches homecom_alt / HomeCom Easy). US and EU accounts use
|
|
179
|
-
* the same host; `region` is retained for Bacon MQTT (`euc1` / `use1`) when wired.
|
|
180
|
-
*/
|
|
181
|
-
var HOMECOM_POINTT_API_ORIGIN = "https://pointt-api.bosch-thermotechnology.com";
|
|
182
|
-
function homeComApiBase(_region) {
|
|
183
|
-
return HOMECOM_POINTT_API_ORIGIN;
|
|
184
|
-
}
|
|
185
|
-
function buildHomeComFormSchema() {
|
|
186
|
-
return { sections: [{
|
|
187
|
-
id: "account",
|
|
188
|
-
title: "HomeCom account",
|
|
189
|
-
columns: 1,
|
|
190
|
-
fields: [
|
|
191
|
-
{
|
|
192
|
-
type: "text",
|
|
193
|
-
key: "username",
|
|
194
|
-
label: "Username",
|
|
195
|
-
required: true
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
type: "password",
|
|
199
|
-
key: "token",
|
|
200
|
-
label: "Access token",
|
|
201
|
-
required: true,
|
|
202
|
-
showToggle: true
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
type: "password",
|
|
206
|
-
key: "refresh",
|
|
207
|
-
label: "Refresh token",
|
|
208
|
-
required: true,
|
|
209
|
-
showToggle: true
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
type: "select",
|
|
213
|
-
key: "brand",
|
|
214
|
-
label: "Brand",
|
|
215
|
-
default: "bosch",
|
|
216
|
-
options: HOMECOM_BRANDS.map((b) => ({
|
|
217
|
-
value: b,
|
|
218
|
-
label: b
|
|
219
|
-
}))
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
type: "select",
|
|
223
|
-
key: "region",
|
|
224
|
-
label: "Region",
|
|
225
|
-
default: "eu",
|
|
226
|
-
options: HOMECOM_REGIONS.map((r) => ({
|
|
227
|
-
value: r,
|
|
228
|
-
label: r.toUpperCase()
|
|
229
|
-
}))
|
|
230
|
-
}
|
|
231
|
-
]
|
|
232
|
-
}] };
|
|
233
|
-
}
|
|
234
|
-
//#endregion
|
|
235
539
|
//#region src/homecom/homecom-gateway.ts
|
|
236
540
|
var HomeComConnectionResolver = class {
|
|
237
541
|
#surfaces = /* @__PURE__ */ new Map();
|
|
@@ -398,6 +702,38 @@ function readNum(raw) {
|
|
|
398
702
|
var HomeComAuthError = class extends Error {
|
|
399
703
|
name = "HomeComAuthError";
|
|
400
704
|
};
|
|
705
|
+
function isRecord(value) {
|
|
706
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
707
|
+
}
|
|
708
|
+
function rowId(row) {
|
|
709
|
+
for (const key of [
|
|
710
|
+
"deviceId",
|
|
711
|
+
"gatewayId",
|
|
712
|
+
"id"
|
|
713
|
+
]) {
|
|
714
|
+
const value = row[key];
|
|
715
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
716
|
+
if (typeof value === "number") return String(value);
|
|
717
|
+
}
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
/** Gateways from the account listing: a bare array or `{ gateways: [...] }`. */
|
|
721
|
+
function parseHomeComGatewayList(payload) {
|
|
722
|
+
const rows = isRecord(payload) ? payload["gateways"] : payload;
|
|
723
|
+
if (!Array.isArray(rows)) return [];
|
|
724
|
+
const out = [];
|
|
725
|
+
for (const row of rows) {
|
|
726
|
+
if (!isRecord(row)) continue;
|
|
727
|
+
const gatewayId = rowId(row);
|
|
728
|
+
if (gatewayId === null) continue;
|
|
729
|
+
const name = row["name"];
|
|
730
|
+
out.push({
|
|
731
|
+
gatewayId,
|
|
732
|
+
name: typeof name === "string" && name.length > 0 ? name : gatewayId
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
return out;
|
|
736
|
+
}
|
|
401
737
|
function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
402
738
|
const fetchFn = deps.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
403
739
|
let token = config.token;
|
|
@@ -422,10 +758,14 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
|
422
758
|
}
|
|
423
759
|
return {
|
|
424
760
|
async refreshTokens() {
|
|
425
|
-
const res = await fetchFn(
|
|
761
|
+
const res = await fetchFn(HOMECOM_TOKEN_URL, {
|
|
426
762
|
method: "POST",
|
|
427
763
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
428
|
-
body:
|
|
764
|
+
body: new URLSearchParams({
|
|
765
|
+
grant_type: "refresh_token",
|
|
766
|
+
refresh_token: refresh,
|
|
767
|
+
client_id: HOMECOM_CLIENT_ID
|
|
768
|
+
}).toString()
|
|
429
769
|
});
|
|
430
770
|
if (!res.ok) {
|
|
431
771
|
if (res.status === 401 || res.status === 403) throw new HomeComAuthError("refresh refused");
|
|
@@ -439,11 +779,18 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
|
|
|
439
779
|
if (typeof newToken !== "string" || typeof newRefresh !== "string") throw new Error("HomeCom token refresh: missing tokens");
|
|
440
780
|
token = newToken;
|
|
441
781
|
refresh = newRefresh;
|
|
782
|
+
await deps.onTokensRotated?.({
|
|
783
|
+
token,
|
|
784
|
+
refresh
|
|
785
|
+
});
|
|
442
786
|
return {
|
|
443
787
|
token,
|
|
444
788
|
refresh
|
|
445
789
|
};
|
|
446
790
|
},
|
|
791
|
+
async listGateways() {
|
|
792
|
+
return parseHomeComGatewayList(await request("/v2/gateways"));
|
|
793
|
+
},
|
|
447
794
|
async fetchSystem() {
|
|
448
795
|
const parsed = parseHomeComSystemPayload(await request(`/v2/gateways/${gatewayId}/system`));
|
|
449
796
|
return {
|
|
@@ -494,6 +841,8 @@ var HomeComIntegrationManager = class {
|
|
|
494
841
|
#surfaceSink;
|
|
495
842
|
#makeClient;
|
|
496
843
|
#pollIntervalMs;
|
|
844
|
+
#onGatewayChosen;
|
|
845
|
+
#gateways = [];
|
|
497
846
|
#client = null;
|
|
498
847
|
#gatewayId = null;
|
|
499
848
|
#pollTimer = null;
|
|
@@ -509,7 +858,26 @@ var HomeComIntegrationManager = class {
|
|
|
509
858
|
this.#onDisconnected = options.onDisconnected;
|
|
510
859
|
this.#surfaceSink = options.surfaceSink;
|
|
511
860
|
const fetchFn = options.fetchFn;
|
|
512
|
-
this.#
|
|
861
|
+
this.#onGatewayChosen = options.onGatewayChosen ?? (() => void 0);
|
|
862
|
+
const onTokensRotated = async (tokens) => {
|
|
863
|
+
this.#connection = {
|
|
864
|
+
...this.#connection,
|
|
865
|
+
token: tokens.token,
|
|
866
|
+
refresh: tokens.refresh
|
|
867
|
+
};
|
|
868
|
+
try {
|
|
869
|
+
await options.onTokensRotated?.(tokens);
|
|
870
|
+
} catch (err) {
|
|
871
|
+
this.#logger.warn("HomeCom could not store the rotated tokens", {
|
|
872
|
+
tags: { integrationId: this.#id },
|
|
873
|
+
meta: { error: require_dist.errMsg(err) }
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
this.#makeClient = options.makeClient ?? ((config, gatewayId) => createHomeComApiClient(config, gatewayId, {
|
|
878
|
+
fetchFn,
|
|
879
|
+
onTokensRotated
|
|
880
|
+
}));
|
|
513
881
|
this.#pollIntervalMs = options.pollIntervalMs ?? 6e4;
|
|
514
882
|
}
|
|
515
883
|
getConnection() {
|
|
@@ -522,8 +890,9 @@ var HomeComIntegrationManager = class {
|
|
|
522
890
|
kind: "homecom",
|
|
523
891
|
status: this.#status,
|
|
524
892
|
info: {
|
|
525
|
-
username: this.#connection.username,
|
|
526
|
-
gatewayId: this.#gatewayId
|
|
893
|
+
username: this.#connection.username ?? "",
|
|
894
|
+
gatewayId: this.#gatewayId,
|
|
895
|
+
gateways: this.#gateways
|
|
527
896
|
},
|
|
528
897
|
lastCheckedAt: this.#lastCheckedAt,
|
|
529
898
|
error: this.#error
|
|
@@ -533,14 +902,15 @@ var HomeComIntegrationManager = class {
|
|
|
533
902
|
if (this.#client !== null) return;
|
|
534
903
|
this.#status = "connecting";
|
|
535
904
|
this.#error = null;
|
|
536
|
-
|
|
537
|
-
this.#gatewayId = gatewayId;
|
|
538
|
-
const client = this.#makeClient(this.#connection, gatewayId);
|
|
539
|
-
this.#client = client;
|
|
905
|
+
let client = null;
|
|
540
906
|
try {
|
|
907
|
+
const gatewayId = await this.resolveGatewayId();
|
|
908
|
+
this.#gatewayId = gatewayId;
|
|
909
|
+
client = this.#makeClient(this.#connection, gatewayId);
|
|
910
|
+
this.#client = client;
|
|
541
911
|
const snapshot = await this.fetchWithTokenRefresh(client);
|
|
542
912
|
const surface = {
|
|
543
|
-
gatewayId,
|
|
913
|
+
gatewayId: this.#gatewayId ?? "",
|
|
544
914
|
client,
|
|
545
915
|
snapshot,
|
|
546
916
|
discoverables: buildDiscoverables(snapshot)
|
|
@@ -554,7 +924,8 @@ var HomeComIntegrationManager = class {
|
|
|
554
924
|
}, this.#pollIntervalMs);
|
|
555
925
|
} catch (err) {
|
|
556
926
|
this.#status = "error";
|
|
557
|
-
this.#error = err instanceof HomeComAuthError ? "authentication failed —
|
|
927
|
+
this.#error = err instanceof HomeComAuthError ? "authentication failed — sign in again" : require_dist.errMsg(err);
|
|
928
|
+
this.#client = null;
|
|
558
929
|
this.#surfaceSink.set(this.#id, null);
|
|
559
930
|
this.#onDisconnected(this.#id);
|
|
560
931
|
throw err;
|
|
@@ -567,6 +938,7 @@ var HomeComIntegrationManager = class {
|
|
|
567
938
|
}
|
|
568
939
|
this.#client = null;
|
|
569
940
|
this.#gatewayId = null;
|
|
941
|
+
this.#gateways = [];
|
|
570
942
|
this.#status = "disconnected";
|
|
571
943
|
this.#surfaceSink.set(this.#id, null);
|
|
572
944
|
this.#onDisconnected(this.#id);
|
|
@@ -591,17 +963,34 @@ var HomeComIntegrationManager = class {
|
|
|
591
963
|
}
|
|
592
964
|
}
|
|
593
965
|
async fetchWithTokenRefresh(client) {
|
|
966
|
+
return this.withTokenRefresh(client, () => client.fetchSystem());
|
|
967
|
+
}
|
|
968
|
+
async withTokenRefresh(client, call) {
|
|
594
969
|
try {
|
|
595
|
-
return await
|
|
970
|
+
return await call();
|
|
596
971
|
} catch (err) {
|
|
597
972
|
if (!(err instanceof HomeComAuthError)) throw err;
|
|
598
973
|
await client.refreshTokens();
|
|
599
|
-
return
|
|
974
|
+
return call();
|
|
600
975
|
}
|
|
601
976
|
}
|
|
602
|
-
/**
|
|
603
|
-
|
|
604
|
-
|
|
977
|
+
/**
|
|
978
|
+
* The gateway to poll: the stored choice when the account still has it, otherwise the
|
|
979
|
+
* account's first gateway, which is then stored. A stored choice that vanished is an error —
|
|
980
|
+
* silently switching to another gateway would control a different house.
|
|
981
|
+
*/
|
|
982
|
+
async resolveGatewayId() {
|
|
983
|
+
const lister = this.#makeClient(this.#connection, "");
|
|
984
|
+
this.#gateways = await this.withTokenRefresh(lister, () => lister.listGateways());
|
|
985
|
+
const stored = this.#connection.gatewayId;
|
|
986
|
+
if (stored !== void 0) {
|
|
987
|
+
if (!this.#gateways.some((g) => g.gatewayId === stored)) throw new Error(`HomeCom gateway ${stored} is no longer on this account`);
|
|
988
|
+
return stored;
|
|
989
|
+
}
|
|
990
|
+
const first = this.#gateways[0];
|
|
991
|
+
if (first === void 0) throw new Error("HomeCom account has no gateway");
|
|
992
|
+
this.#onGatewayChosen(first.gatewayId);
|
|
993
|
+
return first.gatewayId;
|
|
605
994
|
}
|
|
606
995
|
};
|
|
607
996
|
//#endregion
|
|
@@ -611,11 +1000,15 @@ var HomeComClientRegistry = class {
|
|
|
611
1000
|
#onConnected;
|
|
612
1001
|
#onDisconnected;
|
|
613
1002
|
#makeManager;
|
|
1003
|
+
#onTokensRotated;
|
|
1004
|
+
#onGatewayChosen;
|
|
614
1005
|
#entries = /* @__PURE__ */ new Map();
|
|
615
1006
|
constructor(logger, deps = {}) {
|
|
616
1007
|
this.#logger = logger;
|
|
617
1008
|
this.#onConnected = deps.onConnected ?? (() => void 0);
|
|
618
1009
|
this.#onDisconnected = deps.onDisconnected ?? (() => void 0);
|
|
1010
|
+
this.#onTokensRotated = deps.onTokensRotated ?? (async () => void 0);
|
|
1011
|
+
this.#onGatewayChosen = deps.onGatewayChosen ?? (() => void 0);
|
|
619
1012
|
const fetchFn = deps.fetchFn;
|
|
620
1013
|
const makeClient = deps.makeClient;
|
|
621
1014
|
this.#makeManager = deps.makeManager ?? ((opts) => new HomeComIntegrationManager({
|
|
@@ -625,6 +1018,8 @@ var HomeComClientRegistry = class {
|
|
|
625
1018
|
logger: opts.logger,
|
|
626
1019
|
onConnected: opts.onConnected,
|
|
627
1020
|
onDisconnected: opts.onDisconnected,
|
|
1021
|
+
onTokensRotated: opts.onTokensRotated,
|
|
1022
|
+
onGatewayChosen: opts.onGatewayChosen,
|
|
628
1023
|
surfaceSink: homeComConnections,
|
|
629
1024
|
fetchFn,
|
|
630
1025
|
makeClient,
|
|
@@ -640,7 +1035,7 @@ var HomeComClientRegistry = class {
|
|
|
640
1035
|
async upsert(integrationId, name, connection) {
|
|
641
1036
|
const existing = this.#entries.get(integrationId);
|
|
642
1037
|
if (existing) {
|
|
643
|
-
if (sameHomeComConnection(existing.connection, connection)) return;
|
|
1038
|
+
if (sameHomeComConnection(existing.connection, connection) || sameHomeComConnection(existing.manager.getConnection(), connection)) return;
|
|
644
1039
|
try {
|
|
645
1040
|
await existing.manager.applyConnection(connection);
|
|
646
1041
|
this.#entries.set(integrationId, {
|
|
@@ -661,7 +1056,9 @@ var HomeComClientRegistry = class {
|
|
|
661
1056
|
connection,
|
|
662
1057
|
logger: this.#logger,
|
|
663
1058
|
onConnected: (id) => this.#onConnected(id),
|
|
664
|
-
onDisconnected: (id) => this.#onDisconnected(id)
|
|
1059
|
+
onDisconnected: (id) => this.#onDisconnected(id),
|
|
1060
|
+
onTokensRotated: (tokens) => this.#onTokensRotated(integrationId, tokens),
|
|
1061
|
+
onGatewayChosen: (gatewayId) => this.#onGatewayChosen(integrationId, gatewayId)
|
|
665
1062
|
});
|
|
666
1063
|
this.#entries.set(integrationId, {
|
|
667
1064
|
manager,
|
|
@@ -719,14 +1116,10 @@ var HomeComClientRegistry = class {
|
|
|
719
1116
|
};
|
|
720
1117
|
//#endregion
|
|
721
1118
|
//#region src/homecom/homecom-connection-test.ts
|
|
722
|
-
|
|
723
|
-
return "gateway-k40";
|
|
724
|
-
}
|
|
725
|
-
var TEST_LABEL = "Refreshes HomeCom OAuth tokens and reads the heat-pump system snapshot";
|
|
1119
|
+
var TEST_LABEL = "Lists the account gateways and reads the heat-pump system snapshot";
|
|
726
1120
|
function buildHomeComConnectionTestProvider(deps) {
|
|
727
1121
|
const { logger } = deps;
|
|
728
1122
|
const makeClient = deps.makeClient ?? ((config, gatewayId) => createHomeComApiClient(config, gatewayId));
|
|
729
|
-
const resolveGatewayId = deps.resolveGatewayId ?? defaultResolveGatewayId;
|
|
730
1123
|
return {
|
|
731
1124
|
describeTest: async () => ({ label: TEST_LABEL }),
|
|
732
1125
|
testSettings: async ({ settings }) => {
|
|
@@ -736,11 +1129,18 @@ function buildHomeComConnectionTestProvider(deps) {
|
|
|
736
1129
|
error: `HomeCom settings incomplete: ${parsed.error.issues.map((i) => i.path.join(".") || "(root)").join(", ")}`
|
|
737
1130
|
};
|
|
738
1131
|
const startedAt = Date.now();
|
|
739
|
-
const gatewayId = await resolveGatewayId(parsed.data);
|
|
740
|
-
const client = makeClient(parsed.data, gatewayId);
|
|
741
1132
|
try {
|
|
742
|
-
await
|
|
743
|
-
|
|
1133
|
+
const gateways = await makeClient(parsed.data, "").listGateways();
|
|
1134
|
+
const gatewayId = parsed.data.gatewayId ?? gateways[0]?.gatewayId;
|
|
1135
|
+
if (gatewayId === void 0) return {
|
|
1136
|
+
outcome: "rejected",
|
|
1137
|
+
error: "This HomeCom account has no gateway."
|
|
1138
|
+
};
|
|
1139
|
+
if (!gateways.some((g) => g.gatewayId === gatewayId)) return {
|
|
1140
|
+
outcome: "rejected",
|
|
1141
|
+
error: `Gateway ${gatewayId} is not on this account.`
|
|
1142
|
+
};
|
|
1143
|
+
await makeClient(parsed.data, gatewayId).fetchSystem();
|
|
744
1144
|
return {
|
|
745
1145
|
outcome: "validated",
|
|
746
1146
|
latencyMs: Date.now() - startedAt
|
|
@@ -748,12 +1148,9 @@ function buildHomeComConnectionTestProvider(deps) {
|
|
|
748
1148
|
} catch (err) {
|
|
749
1149
|
if (err instanceof HomeComAuthError) return {
|
|
750
1150
|
outcome: "rejected",
|
|
751
|
-
error: "HomeCom refused
|
|
1151
|
+
error: "HomeCom refused the access token — sign in again to get a fresh one."
|
|
752
1152
|
};
|
|
753
|
-
logger.warn("HomeCom connection test inconclusive", { meta: {
|
|
754
|
-
username: parsed.data.username,
|
|
755
|
-
error: require_dist.errMsg(err)
|
|
756
|
-
} });
|
|
1153
|
+
logger.warn("HomeCom connection test inconclusive", { meta: { error: require_dist.errMsg(err) } });
|
|
757
1154
|
return {
|
|
758
1155
|
outcome: "inconclusive",
|
|
759
1156
|
error: "Could not reach the HomeCom cloud API."
|
|
@@ -959,7 +1356,17 @@ var BoschHomeComProviderAddon = class extends require_dist.BaseDeviceProvider {
|
|
|
959
1356
|
}
|
|
960
1357
|
async onInitialize() {
|
|
961
1358
|
const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
|
|
962
|
-
|
|
1359
|
+
const persistDeps = {
|
|
1360
|
+
getSettings: (id) => this.ctx.api.integrations.getSettings.query({ id }),
|
|
1361
|
+
setSettings: (input) => this.ctx.api.integrations.setSettings.mutate(input),
|
|
1362
|
+
logger: this.ctx.logger
|
|
1363
|
+
};
|
|
1364
|
+
const persistTokens = buildHomeComTokenPersister(persistDeps);
|
|
1365
|
+
const persistGateway = buildHomeComGatewayPersister(persistDeps);
|
|
1366
|
+
this.clients = new HomeComClientRegistry(this.ctx.logger, {
|
|
1367
|
+
onTokensRotated: (integrationId, tokens) => persistTokens(integrationId, tokens),
|
|
1368
|
+
onGatewayChosen: (integrationId, gatewayId) => void persistGateway(integrationId, gatewayId)
|
|
1369
|
+
});
|
|
963
1370
|
this.clients.setOnConnected((integrationId) => {
|
|
964
1371
|
this.setIntegrationDevicesOnline(integrationId, true);
|
|
965
1372
|
});
|
|
@@ -968,17 +1375,33 @@ var BoschHomeComProviderAddon = class extends require_dist.BaseDeviceProvider {
|
|
|
968
1375
|
});
|
|
969
1376
|
await this.reconcileIntegrations();
|
|
970
1377
|
this.subscribeIntegrationLifecycle();
|
|
971
|
-
return
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1378
|
+
return {
|
|
1379
|
+
providers: [
|
|
1380
|
+
...regs,
|
|
1381
|
+
{
|
|
1382
|
+
capability: require_dist.deviceAdoptionCapability,
|
|
1383
|
+
provider: this.buildAdoptionProvider()
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
capability: require_dist.connectionTestCapability,
|
|
1387
|
+
provider: this.buildConnectionTestProvider()
|
|
1388
|
+
}
|
|
1389
|
+
],
|
|
1390
|
+
customActions: homeComActions,
|
|
1391
|
+
actionHandlers: {
|
|
1392
|
+
beginSignIn: async () => ({
|
|
1393
|
+
...beginHomeComSignIn(),
|
|
1394
|
+
summary: "Open the link in a browser and sign in."
|
|
1395
|
+
}),
|
|
1396
|
+
finishSignIn: async (input) => {
|
|
1397
|
+
const tokens = await completeHomeComSignIn(input);
|
|
1398
|
+
return {
|
|
1399
|
+
...tokens,
|
|
1400
|
+
summary: tokens.username.length > 0 ? `Signed in as ${tokens.username}. Save to connect.` : "Signed in. Save to connect."
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
980
1403
|
}
|
|
981
|
-
|
|
1404
|
+
};
|
|
982
1405
|
}
|
|
983
1406
|
async onShutdown() {
|
|
984
1407
|
try {
|