@camstack/addon-provider-bosch 0.1.29 → 0.1.30

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,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.30] - 2026-10-27
11
+
12
+ ### Changed
13
+
14
+ - Home Connect and HomeCom now sign in through a page served by the hub; no more copying the redirect address out of the browser's developer tools.
15
+
10
16
  ## [0.1.29] - 2026-10-27
11
17
 
12
18
  ### Changed
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-CyLs51d2.js");
1
+ const require_dist = require("./dist-BioIJ3NA.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  //#region src/shared/oauth-callback.ts
4
4
  /** A code on its own: no scheme, no query, no separators. */
@@ -118,6 +118,119 @@ function loginFailureForAttempt(reason) {
118
118
  return reason === "expired" ? "code-rejected" : "state-mismatch";
119
119
  }
120
120
  //#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";
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;
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
+ onDone: async (callback) => {
205
+ try {
206
+ const result = await input.finish(callback);
207
+ input.claims.settle(input.attemptId, {
208
+ ok: true,
209
+ result
210
+ }, input.nowMs());
211
+ return {
212
+ status: "success",
213
+ addonId: input.addonId
214
+ };
215
+ } catch (err) {
216
+ const message = err instanceof Error ? err.message : "The sign-in failed.";
217
+ input.claims.settle(input.attemptId, {
218
+ ok: false,
219
+ message
220
+ }, input.nowMs());
221
+ return {
222
+ status: "error",
223
+ addonId: input.addonId,
224
+ reason: landingReasonForLogin(reasonOf(err))
225
+ };
226
+ }
227
+ }
228
+ });
229
+ const entry = input.proxy.entryPath(input.attemptId, input.authorizeUrl);
230
+ if (entry === null) throw new Error("The sign-in page host is not reachable through the proxy.");
231
+ return entry;
232
+ }
233
+ //#endregion
121
234
  //#region src/shared/account-integration-cascade.ts
122
235
  async function cascadeRemoveDevicesForIntegration(input) {
123
236
  const { reg, devices, addonId, system, integrationId, logger } = input;
@@ -167,12 +280,24 @@ Object.defineProperty(exports, "createSignInAttemptStore", {
167
280
  return createSignInAttemptStore;
168
281
  }
169
282
  });
283
+ Object.defineProperty(exports, "createSignInClaims", {
284
+ enumerable: true,
285
+ get: function() {
286
+ return createSignInClaims;
287
+ }
288
+ });
170
289
  Object.defineProperty(exports, "loginFailureForAttempt", {
171
290
  enumerable: true,
172
291
  get: function() {
173
292
  return loginFailureForAttempt;
174
293
  }
175
294
  });
295
+ Object.defineProperty(exports, "openSignInProxy", {
296
+ enumerable: true,
297
+ get: function() {
298
+ return openSignInProxy;
299
+ }
300
+ });
176
301
  Object.defineProperty(exports, "parseOAuthCallback", {
177
302
  enumerable: true,
178
303
  get: function() {
@@ -1,4 +1,4 @@
1
- import { E as errMsg } from "./dist-DzSesBKb.mjs";
1
+ import { D as errMsg } from "./dist-DBUnIovD.mjs";
2
2
  import { randomBytes } from "node:crypto";
3
3
  //#region src/shared/oauth-callback.ts
4
4
  /** A code on its own: no scheme, no query, no separators. */
@@ -118,6 +118,119 @@ function loginFailureForAttempt(reason) {
118
118
  return reason === "expired" ? "code-rejected" : "state-mismatch";
119
119
  }
120
120
  //#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;
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
+ onDone: async (callback) => {
205
+ try {
206
+ const result = await input.finish(callback);
207
+ input.claims.settle(input.attemptId, {
208
+ ok: true,
209
+ result
210
+ }, input.nowMs());
211
+ return {
212
+ status: "success",
213
+ addonId: input.addonId
214
+ };
215
+ } catch (err) {
216
+ const message = err instanceof Error ? err.message : "The sign-in failed.";
217
+ input.claims.settle(input.attemptId, {
218
+ ok: false,
219
+ message
220
+ }, input.nowMs());
221
+ return {
222
+ status: "error",
223
+ addonId: input.addonId,
224
+ reason: landingReasonForLogin(reasonOf(err))
225
+ };
226
+ }
227
+ }
228
+ });
229
+ const entry = input.proxy.entryPath(input.attemptId, input.authorizeUrl);
230
+ if (entry === null) throw new Error("The sign-in page host is not reachable through the proxy.");
231
+ return entry;
232
+ }
233
+ //#endregion
121
234
  //#region src/shared/account-integration-cascade.ts
122
235
  async function cascadeRemoveDevicesForIntegration(input) {
123
236
  const { reg, devices, addonId, system, integrationId, logger } = input;
@@ -155,4 +268,4 @@ function setIntegrationDevicesOnline(devices, system, integrationId, online) {
155
268
  return count;
156
269
  }
157
270
  //#endregion
158
- export { parseOAuthCallback as a, loginFailureForAttempt as i, cascadeRemoveDevicesForIntegration as n, createSignInAttemptStore as r, setIntegrationDevicesOnline as t };
271
+ export { createSignInAttemptStore as a, openSignInProxy as i, cascadeRemoveDevicesForIntegration as n, loginFailureForAttempt as o, createSignInClaims as r, parseOAuthCallback as s, setIntegrationDevicesOnline as t };