@camstack/addon-provider-bosch 0.1.42 → 0.1.44

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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.44] - 2026-11-09
11
+
12
+ ### Changed
13
+
14
+ - Bosch HomeCom and Home Connect sign-in no longer go through the hub login proxy: the form explains how to sign in with SingleKey ID in your own browser and copy the address back, in English and Italian.
15
+
16
+ ## [0.1.43] - 2026-11-08
17
+
18
+ ### Changed
19
+
20
+ - Its integration-instances provider declares its instance kind and stamps it on every instance.
21
+
10
22
  ## [0.1.42] - 2026-11-06
11
23
 
12
24
  ### Changed
@@ -1,5 +1,10 @@
1
- const require_bosch_instances = require("./bosch-instances-CINVxMF_.js");
1
+ const require_bosch_instances = require("./bosch-instances-CXMUquuA.js");
2
2
  let node_crypto = require("node:crypto");
3
+ //#region src/shared/sign-in-redirects.ts
4
+ /** The addresses each vendor sends the browser to after a sign-in; no browser can open them. Kept free of imports so the form schema can name them. */
5
+ var HOMECOM_REDIRECT_URI = "com.bosch.tt.dashtt.pointt://app/login";
6
+ var HOME_CONNECT_REDIRECT_URI = "hcauth://auth/prod";
7
+ //#endregion
3
8
  //#region src/shared/oauth-callback.ts
4
9
  /** A code on its own: no scheme, no query, no separators. */
5
10
  var BARE_CODE = /^[A-Za-z0-9._~-]+$/;
@@ -118,118 +123,50 @@ function loginFailureForAttempt(reason) {
118
123
  return reason === "expired" ? "code-rejected" : "state-mismatch";
119
124
  }
120
125
  //#endregion
121
- //#region src/shared/login-proxy-sign-in.ts
122
- function createSignInClaims(ttlMs = SIGN_IN_ATTEMPT_TTL_MS) {
123
- const parked = /* @__PURE__ */ new Map();
124
- return {
125
- settle(attemptId, outcome, nowMs) {
126
- parked.set(attemptId, {
127
- outcome,
128
- expiresAtMs: nowMs + ttlMs
129
- });
130
- },
131
- claim(attemptId, nowMs) {
132
- const found = parked.get(attemptId);
133
- if (found === void 0) return { state: "pending" };
134
- parked.delete(attemptId);
135
- if (nowMs > found.expiresAtMs) return { state: "pending" };
136
- if (!found.outcome.ok) throw new Error(found.outcome.message);
137
- return {
138
- state: "done",
139
- result: found.outcome.result
140
- };
141
- }
142
- };
143
- }
144
- /** An `&`-escaped address inside HTML is the same address. */
145
- function unescapeHtml(text) {
146
- return text.replace(/&/g, "&");
147
- }
148
- /**
149
- * Terminal condition for an app-scheme redirect: the `Location` of a response, or — when the
150
- * vendor's page navigates by script — the first address in its body, that starts with the app
151
- * scheme.
152
- */
153
- function appSchemeTerminal(scheme) {
154
- const inBody = new RegExp(`${scheme.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^"'<>\\s\\\\]+`);
155
- return (view) => {
156
- if (view.location !== null && view.location.startsWith(scheme)) return {
157
- kind: "done",
158
- payload: view.location
159
- };
160
- const fromBody = view.bodyText === null ? null : inBody.exec(unescapeHtml(view.bodyText));
161
- if (fromBody !== null) return {
162
- kind: "done",
163
- payload: fromBody[0]
164
- };
165
- return { kind: "continue" };
166
- };
167
- }
168
- /** The result-page reason code for a sign-in failure reason (`HomeConnectLoginFailure`, …). */
169
- function landingReasonForLogin(reason) {
170
- switch (reason) {
171
- case "denied": return "access_denied";
172
- case "state-mismatch":
173
- case "state-missing": return "invalid_state";
174
- case "empty":
175
- case "no-code": return "invalid_request";
176
- case "code-rejected":
177
- case "rejected": return "exchange_failed";
178
- case "no-refresh": return "no_refresh_token";
179
- default: return "unknown";
126
+ //#region src/shared/sign-in-guide.ts
127
+ var SIGN_IN_GUIDE_STEPS = [
128
+ {
129
+ id: "open",
130
+ text: (g) => `1. Click “Sign in with ${g.providerName}”. The sign-in page opens in a new tab of your own browser (it cannot be shown inside CamStack).`
131
+ },
132
+ {
133
+ id: "login",
134
+ text: () => "2. Sign in there with your SingleKey ID: email, password and the one-time code if you are asked for one."
135
+ },
136
+ {
137
+ id: "redirect",
138
+ text: (g) => `3. When you finish, the browser tries to open the Bosch app and fails or shows an empty page. That is expected: the address it tried to open is what CamStack needs. It starts with ${g.redirectStart}`
139
+ },
140
+ {
141
+ id: "chrome",
142
+ text: (g) => `Chrome, Edge, Brave: before you sign in press F12, open Network and tick “Preserve log”. After the failure pick the last request whose address starts with ${g.redirectStart} and use right click, Copy, Copy URL (or copy the address from the error line in the Console).`
143
+ },
144
+ {
145
+ id: "firefox",
146
+ text: () => "Firefox: before you sign in press F12, open Network, open the gear menu and enable “Persist Logs”. After the failure right click the last request, Copy Value, Copy URL."
147
+ },
148
+ {
149
+ id: "safari",
150
+ text: () => "Safari: enable the Develop menu (Settings, Advanced), then Develop, Show Web Inspector, Network, and keep “Preserve Log” on. After the failure copy the address of the last request. Safari may also show the address in its “cannot open” message."
151
+ },
152
+ {
153
+ id: "phone",
154
+ text: () => "Phone or tablet: the failed address often stays in the address bar; press and hold it to copy. If it does not, repeat the sign-in from a computer."
155
+ },
156
+ {
157
+ id: "paste",
158
+ text: () => "4. Paste the whole address (or only the code after “code=”) in the box that appears under the button. The sign-in completes by itself; the code is valid for about ten minutes."
180
159
  }
181
- }
182
- function reasonOf(err) {
183
- if (typeof err === "object" && err !== null && "reason" in err) {
184
- const reason = err.reason;
185
- if (typeof reason === "string") return reason;
186
- }
187
- return "unknown";
188
- }
189
- /**
190
- * Open the proxied login for one attempt and return the hub-relative path the browser opens.
191
- * A failed exchange is parked as an operator-facing message and the browser lands on the
192
- * result page with a reason code; nothing about the captured address leaves this function.
193
- */
194
- function openSignInProxy(input) {
195
- const started = input.nowMs();
196
- input.proxy.open({
197
- attemptId: input.attemptId,
198
- expiresAtMs: started + SIGN_IN_ATTEMPT_TTL_MS,
199
- upstreams: input.hosts.map((host) => ({
200
- host,
201
- scheme: input.scheme ?? "https"
202
- })),
203
- terminal: appSchemeTerminal(input.appScheme),
204
- serverCookieJar: true,
205
- onDone: async (callback) => {
206
- try {
207
- const result = await input.finish(callback);
208
- input.claims.settle(input.attemptId, {
209
- ok: true,
210
- result
211
- }, input.nowMs());
212
- return {
213
- status: "success",
214
- addonId: input.addonId
215
- };
216
- } catch (err) {
217
- const message = err instanceof Error ? err.message : "The sign-in failed.";
218
- input.claims.settle(input.attemptId, {
219
- ok: false,
220
- message
221
- }, input.nowMs());
222
- return {
223
- status: "error",
224
- addonId: input.addonId,
225
- reason: landingReasonForLogin(reasonOf(err))
226
- };
227
- }
228
- }
229
- });
230
- const entry = input.proxy.entryPath(input.attemptId, input.authorizeUrl);
231
- if (entry === null) throw new Error("The sign-in page host is not reachable through the proxy.");
232
- return entry;
160
+ ];
161
+ /** The info rows shown above the sign-in button: what to do and where each value comes from. */
162
+ function buildSignInGuideFields(input) {
163
+ return SIGN_IN_GUIDE_STEPS.map((step) => ({
164
+ type: "info",
165
+ key: `${input.keyPrefix}-guide-${step.id}`,
166
+ label: step.text(input),
167
+ labelKey: `provider-bosch:${input.i18nPrefix}.${input.keyPrefix}-guide-${step.id}.label`,
168
+ content: ""
169
+ }));
233
170
  }
234
171
  //#endregion
235
172
  //#region src/shared/account-integration-cascade.ts
@@ -269,34 +206,40 @@ function setIntegrationDevicesOnline(devices, system, integrationId, online) {
269
206
  return count;
270
207
  }
271
208
  //#endregion
272
- Object.defineProperty(exports, "cascadeRemoveDevicesForIntegration", {
209
+ Object.defineProperty(exports, "HOMECOM_REDIRECT_URI", {
273
210
  enumerable: true,
274
211
  get: function() {
275
- return cascadeRemoveDevicesForIntegration;
212
+ return HOMECOM_REDIRECT_URI;
276
213
  }
277
214
  });
278
- Object.defineProperty(exports, "createSignInAttemptStore", {
215
+ Object.defineProperty(exports, "HOME_CONNECT_REDIRECT_URI", {
279
216
  enumerable: true,
280
217
  get: function() {
281
- return createSignInAttemptStore;
218
+ return HOME_CONNECT_REDIRECT_URI;
282
219
  }
283
220
  });
284
- Object.defineProperty(exports, "createSignInClaims", {
221
+ Object.defineProperty(exports, "buildSignInGuideFields", {
285
222
  enumerable: true,
286
223
  get: function() {
287
- return createSignInClaims;
224
+ return buildSignInGuideFields;
288
225
  }
289
226
  });
290
- Object.defineProperty(exports, "loginFailureForAttempt", {
227
+ Object.defineProperty(exports, "cascadeRemoveDevicesForIntegration", {
291
228
  enumerable: true,
292
229
  get: function() {
293
- return loginFailureForAttempt;
230
+ return cascadeRemoveDevicesForIntegration;
294
231
  }
295
232
  });
296
- Object.defineProperty(exports, "openSignInProxy", {
233
+ Object.defineProperty(exports, "createSignInAttemptStore", {
297
234
  enumerable: true,
298
235
  get: function() {
299
- return openSignInProxy;
236
+ return createSignInAttemptStore;
237
+ }
238
+ });
239
+ Object.defineProperty(exports, "loginFailureForAttempt", {
240
+ enumerable: true,
241
+ get: function() {
242
+ return loginFailureForAttempt;
300
243
  }
301
244
  });
302
245
  Object.defineProperty(exports, "parseOAuthCallback", {
@@ -1,5 +1,10 @@
1
- import { Z as errMsg } from "./bosch-instances-BmvHlGiY.mjs";
1
+ import { J as errMsg } from "./bosch-instances-C8FRjQzC.mjs";
2
2
  import { randomBytes } from "node:crypto";
3
+ //#region src/shared/sign-in-redirects.ts
4
+ /** The addresses each vendor sends the browser to after a sign-in; no browser can open them. Kept free of imports so the form schema can name them. */
5
+ var HOMECOM_REDIRECT_URI = "com.bosch.tt.dashtt.pointt://app/login";
6
+ var HOME_CONNECT_REDIRECT_URI = "hcauth://auth/prod";
7
+ //#endregion
3
8
  //#region src/shared/oauth-callback.ts
4
9
  /** A code on its own: no scheme, no query, no separators. */
5
10
  var BARE_CODE = /^[A-Za-z0-9._~-]+$/;
@@ -118,118 +123,50 @@ function loginFailureForAttempt(reason) {
118
123
  return reason === "expired" ? "code-rejected" : "state-mismatch";
119
124
  }
120
125
  //#endregion
121
- //#region src/shared/login-proxy-sign-in.ts
122
- function createSignInClaims(ttlMs = SIGN_IN_ATTEMPT_TTL_MS) {
123
- const parked = /* @__PURE__ */ new Map();
124
- return {
125
- settle(attemptId, outcome, nowMs) {
126
- parked.set(attemptId, {
127
- outcome,
128
- expiresAtMs: nowMs + ttlMs
129
- });
130
- },
131
- claim(attemptId, nowMs) {
132
- const found = parked.get(attemptId);
133
- if (found === void 0) return { state: "pending" };
134
- parked.delete(attemptId);
135
- if (nowMs > found.expiresAtMs) return { state: "pending" };
136
- if (!found.outcome.ok) throw new Error(found.outcome.message);
137
- return {
138
- state: "done",
139
- result: found.outcome.result
140
- };
141
- }
142
- };
143
- }
144
- /** An `&amp;`-escaped address inside HTML is the same address. */
145
- function unescapeHtml(text) {
146
- return text.replace(/&amp;/g, "&");
147
- }
148
- /**
149
- * Terminal condition for an app-scheme redirect: the `Location` of a response, or — when the
150
- * vendor's page navigates by script — the first address in its body, that starts with the app
151
- * scheme.
152
- */
153
- function appSchemeTerminal(scheme) {
154
- const inBody = new RegExp(`${scheme.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^"'<>\\s\\\\]+`);
155
- return (view) => {
156
- if (view.location !== null && view.location.startsWith(scheme)) return {
157
- kind: "done",
158
- payload: view.location
159
- };
160
- const fromBody = view.bodyText === null ? null : inBody.exec(unescapeHtml(view.bodyText));
161
- if (fromBody !== null) return {
162
- kind: "done",
163
- payload: fromBody[0]
164
- };
165
- return { kind: "continue" };
166
- };
167
- }
168
- /** The result-page reason code for a sign-in failure reason (`HomeConnectLoginFailure`, …). */
169
- function landingReasonForLogin(reason) {
170
- switch (reason) {
171
- case "denied": return "access_denied";
172
- case "state-mismatch":
173
- case "state-missing": return "invalid_state";
174
- case "empty":
175
- case "no-code": return "invalid_request";
176
- case "code-rejected":
177
- case "rejected": return "exchange_failed";
178
- case "no-refresh": return "no_refresh_token";
179
- default: return "unknown";
180
- }
181
- }
182
- function reasonOf(err) {
183
- if (typeof err === "object" && err !== null && "reason" in err) {
184
- const reason = err.reason;
185
- if (typeof reason === "string") return reason;
126
+ //#region src/shared/sign-in-guide.ts
127
+ var SIGN_IN_GUIDE_STEPS = [
128
+ {
129
+ id: "open",
130
+ text: (g) => `1. Click “Sign in with ${g.providerName}”. The sign-in page opens in a new tab of your own browser (it cannot be shown inside CamStack).`
131
+ },
132
+ {
133
+ id: "login",
134
+ text: () => "2. Sign in there with your SingleKey ID: email, password and the one-time code if you are asked for one."
135
+ },
136
+ {
137
+ id: "redirect",
138
+ text: (g) => `3. When you finish, the browser tries to open the Bosch app and fails or shows an empty page. That is expected: the address it tried to open is what CamStack needs. It starts with ${g.redirectStart}`
139
+ },
140
+ {
141
+ id: "chrome",
142
+ text: (g) => `Chrome, Edge, Brave: before you sign in press F12, open Network and tick “Preserve log”. After the failure pick the last request whose address starts with ${g.redirectStart} and use right click, Copy, Copy URL (or copy the address from the error line in the Console).`
143
+ },
144
+ {
145
+ id: "firefox",
146
+ text: () => "Firefox: before you sign in press F12, open Network, open the gear menu and enable “Persist Logs”. After the failure right click the last request, Copy Value, Copy URL."
147
+ },
148
+ {
149
+ id: "safari",
150
+ text: () => "Safari: enable the Develop menu (Settings, Advanced), then Develop, Show Web Inspector, Network, and keep “Preserve Log” on. After the failure copy the address of the last request. Safari may also show the address in its “cannot open” message."
151
+ },
152
+ {
153
+ id: "phone",
154
+ text: () => "Phone or tablet: the failed address often stays in the address bar; press and hold it to copy. If it does not, repeat the sign-in from a computer."
155
+ },
156
+ {
157
+ id: "paste",
158
+ text: () => "4. Paste the whole address (or only the code after “code=”) in the box that appears under the button. The sign-in completes by itself; the code is valid for about ten minutes."
186
159
  }
187
- return "unknown";
188
- }
189
- /**
190
- * Open the proxied login for one attempt and return the hub-relative path the browser opens.
191
- * A failed exchange is parked as an operator-facing message and the browser lands on the
192
- * result page with a reason code; nothing about the captured address leaves this function.
193
- */
194
- function openSignInProxy(input) {
195
- const started = input.nowMs();
196
- input.proxy.open({
197
- attemptId: input.attemptId,
198
- expiresAtMs: started + SIGN_IN_ATTEMPT_TTL_MS,
199
- upstreams: input.hosts.map((host) => ({
200
- host,
201
- scheme: input.scheme ?? "https"
202
- })),
203
- terminal: appSchemeTerminal(input.appScheme),
204
- serverCookieJar: true,
205
- onDone: async (callback) => {
206
- try {
207
- const result = await input.finish(callback);
208
- input.claims.settle(input.attemptId, {
209
- ok: true,
210
- result
211
- }, input.nowMs());
212
- return {
213
- status: "success",
214
- addonId: input.addonId
215
- };
216
- } catch (err) {
217
- const message = err instanceof Error ? err.message : "The sign-in failed.";
218
- input.claims.settle(input.attemptId, {
219
- ok: false,
220
- message
221
- }, input.nowMs());
222
- return {
223
- status: "error",
224
- addonId: input.addonId,
225
- reason: landingReasonForLogin(reasonOf(err))
226
- };
227
- }
228
- }
229
- });
230
- const entry = input.proxy.entryPath(input.attemptId, input.authorizeUrl);
231
- if (entry === null) throw new Error("The sign-in page host is not reachable through the proxy.");
232
- return entry;
160
+ ];
161
+ /** The info rows shown above the sign-in button: what to do and where each value comes from. */
162
+ function buildSignInGuideFields(input) {
163
+ return SIGN_IN_GUIDE_STEPS.map((step) => ({
164
+ type: "info",
165
+ key: `${input.keyPrefix}-guide-${step.id}`,
166
+ label: step.text(input),
167
+ labelKey: `provider-bosch:${input.i18nPrefix}.${input.keyPrefix}-guide-${step.id}.label`,
168
+ content: ""
169
+ }));
233
170
  }
234
171
  //#endregion
235
172
  //#region src/shared/account-integration-cascade.ts
@@ -269,4 +206,4 @@ function setIntegrationDevicesOnline(devices, system, integrationId, online) {
269
206
  return count;
270
207
  }
271
208
  //#endregion
272
- export { createSignInAttemptStore as a, openSignInProxy as i, cascadeRemoveDevicesForIntegration as n, loginFailureForAttempt as o, createSignInClaims as r, parseOAuthCallback as s, setIntegrationDevicesOnline as t };
209
+ export { loginFailureForAttempt as a, HOME_CONNECT_REDIRECT_URI as c, createSignInAttemptStore as i, cascadeRemoveDevicesForIntegration as n, parseOAuthCallback as o, buildSignInGuideFields as r, HOMECOM_REDIRECT_URI as s, setIntegrationDevicesOnline as t };