@camstack/types 1.2.450 → 1.2.452

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/addon/base-addon.d.ts +27 -0
  3. package/dist/addon.js +9 -4
  4. package/dist/addon.mjs +9 -4
  5. package/dist/automation/condition-eval.d.ts +20 -5
  6. package/dist/automation/detection-subject.d.ts +154 -0
  7. package/dist/automation/detection-trigger.d.ts +16 -6
  8. package/dist/automation/index.d.ts +10 -4
  9. package/dist/automation/legacy-recipe.d.ts +130 -0
  10. package/dist/automation/normalize-units.d.ts +4 -3
  11. package/dist/automation/recipe.d.ts +286 -142
  12. package/dist/automation/stationary-trigger.d.ts +4 -4
  13. package/dist/automation/step-runner.d.ts +3 -1
  14. package/dist/automation/trace.d.ts +51 -6
  15. package/dist/automation/when.d.ts +295 -0
  16. package/dist/capabilities/actions.cap.d.ts +10 -0
  17. package/dist/capabilities/addon-settings.cap.d.ts +18 -0
  18. package/dist/capabilities/broker.cap.d.ts +8 -0
  19. package/dist/capabilities/core-blocks.cap.d.ts +2546 -51
  20. package/dist/capabilities/device-discovery.cap.d.ts +2 -0
  21. package/dist/capabilities/device-manager.cap.d.ts +16 -1
  22. package/dist/capabilities/device-provider.cap.d.ts +165 -0
  23. package/dist/capabilities/index.d.ts +3 -3
  24. package/dist/capabilities/integrations.cap.d.ts +1 -0
  25. package/dist/capabilities/keychain.cap.d.ts +12 -0
  26. package/dist/capabilities/local-network.cap.d.ts +198 -0
  27. package/dist/capabilities/reboot.cap.d.ts +1 -1
  28. package/dist/capabilities/storage-admin.cap.d.ts +155 -0
  29. package/dist/client.js +21 -1
  30. package/dist/client.mjs +21 -1
  31. package/dist/{composition-kSuOifuG.mjs → composition-C7WdThmP.mjs} +1 -1
  32. package/dist/{composition-CO6Sak-s.js → composition-tP-vAKqU.js} +1 -1
  33. package/dist/device/base-device-provider.d.ts +17 -0
  34. package/dist/device/device-type.d.ts +7 -1
  35. package/dist/{err-msg-DWXWc0Di.mjs → err-msg-BD5buxvT.mjs} +6 -0
  36. package/dist/{err-msg-EJhsFHqa.js → err-msg-DkPZXb-g.js} +6 -0
  37. package/dist/generated/addon-api.d.ts +84 -0
  38. package/dist/generated/collection-array-methods.d.ts +1 -1
  39. package/dist/generated/customize-catalog.d.ts +1 -1
  40. package/dist/generated/method-access-map.d.ts +1 -1
  41. package/dist/generated/system-proxy.d.ts +5 -3
  42. package/dist/index.d.ts +2 -2
  43. package/dist/index.js +1992 -713
  44. package/dist/index.mjs +1957 -710
  45. package/dist/interfaces/addon.d.ts +16 -0
  46. package/dist/interfaces/config-ui-secrets.d.ts +5 -3
  47. package/dist/node.js +2 -2
  48. package/dist/node.mjs +2 -2
  49. package/dist/{sleep-BSPhInRm.js → sleep-CewR0zRz.js} +125 -6
  50. package/dist/{sleep-63gM_1JR.mjs → sleep-CeyhrcOk.mjs} +125 -6
  51. package/dist/vendor-login-proxy/cookie-jar.d.ts +30 -0
  52. package/dist/vendor-login-proxy/index.d.ts +1 -0
  53. package/dist/vendor-login-proxy/login-proxy.d.ts +6 -0
  54. package/dist/vendor-login-proxy/url-rewrite.d.ts +9 -2
  55. package/package.json +1 -1
@@ -895,6 +895,22 @@ export interface ICamstackAddon {
895
895
  * stays the one bare key. See `addon-settings.cap.ts`.
896
896
  */
897
897
  getIntegrationSettings?(nodeId?: string): Promise<ConfigUISchemaWithValues | null>;
898
+ /**
899
+ * Stale-data audit: the global-settings keys this addon still declares,
900
+ * answered in its own process. See `BaseAddon.getDeclaredSettingsKeys`.
901
+ */
902
+ getDeclaredSettingsKeys?(): Promise<{
903
+ readonly keys: readonly string[];
904
+ }>;
905
+ /** Stale-data audit: remove stored keys the addon no longer declares. */
906
+ unsetStoredSettingsKeys?(keys: readonly string[]): Promise<{
907
+ readonly removed: readonly string[];
908
+ readonly refused: ReadonlyArray<{
909
+ readonly key: string;
910
+ readonly reason: string;
911
+ }>;
912
+ readonly secretsCleared: number;
913
+ }>;
898
914
  /** Level 2 — per-device settings (schema + values). Appears in
899
915
  * Device Overrides. */
900
916
  getDeviceSettings?(deviceId: number): Promise<ConfigUISchemaWithValues>;
@@ -45,14 +45,16 @@ export declare function isKeychainStoredField(field: ConfigField): boolean;
45
45
  * inside `group` / `sub-tabs` containers.
46
46
  */
47
47
  export declare function collectSecretConfigKeys(schema: unknown): ReadonlySet<string>;
48
+ /** Drop fields marked `omitFromSettings` before an integration settings write. */
49
+ export declare function omitTransientConfig(schema: unknown, config: Record<string, unknown>): Record<string, unknown>;
48
50
  /**
49
- * Every top-level config key whose value the KEYCHAIN stores (D787) —
51
+ * Every config key whose value the KEYCHAIN stores (D787) —
50
52
  * {@link collectSecretConfigKeys} minus `storage: 'inline'` and minus JSON
51
53
  * blobs (see {@link isKeychainStoredField}). What a form sends as
52
54
  * `secretKeys` so the owner's door knows which values to keep out of the blob.
55
+ * A secret inside an `editable-array` row is named by its id-addressed path
56
+ * (`boards[id].encryptionKey`).
53
57
  */
54
- /** Drop fields marked `omitFromSettings` before an integration settings write. */
55
- export declare function omitTransientConfig(schema: unknown, config: Record<string, unknown>): Record<string, unknown>;
56
58
  export declare function collectKeychainStoredKeys(schema: unknown): ReadonlySet<string>;
57
59
  /**
58
60
  * True when the value is a readable schema declaring at least one field of any
package/dist/node.js CHANGED
@@ -21,8 +21,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- const require_composition = require("./composition-CO6Sak-s.js");
25
- const require_err_msg = require("./err-msg-EJhsFHqa.js");
24
+ const require_composition = require("./composition-tP-vAKqU.js");
25
+ const require_err_msg = require("./err-msg-DkPZXb-g.js");
26
26
  let node_crypto = require("node:crypto");
27
27
  let node_fs = require("node:fs");
28
28
  node_fs = __toESM(node_fs);
package/dist/node.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { Bn as getFfmpegDownloadUrl, Fn as FFMPEG_PATH_ENV, Hn as Fmp4BoxSplitter, In as chooseFfmpegBinary, L as compositionSourceKey, Ln as ffmpegBinaryName, Rn as FFMPEG_RELEASE, Vn as canonicalHash, Zn as isSoftwareDecode, a as CUSTOMIZATION_BLOCK_NAME_PREFIX, qn as buildFfmpegArgs, zn as getFfmpegArtifact } from "./composition-kSuOifuG.mjs";
2
- import { t as errMsg } from "./err-msg-DWXWc0Di.mjs";
1
+ import { Bn as getFfmpegDownloadUrl, Fn as FFMPEG_PATH_ENV, Hn as Fmp4BoxSplitter, In as chooseFfmpegBinary, L as compositionSourceKey, Ln as ffmpegBinaryName, Rn as FFMPEG_RELEASE, Vn as canonicalHash, Zn as isSoftwareDecode, a as CUSTOMIZATION_BLOCK_NAME_PREFIX, qn as buildFfmpegArgs, zn as getFfmpegArtifact } from "./composition-C7WdThmP.mjs";
2
+ import { t as errMsg } from "./err-msg-BD5buxvT.mjs";
3
3
  import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
4
4
  import * as fs from "node:fs";
5
5
  import { chmodSync, copyFileSync, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
@@ -150,12 +150,6 @@ function collectSecretConfigKeys(schema) {
150
150
  for (const section of sectionsOf(schema)) for (const field of fieldsOf(section)) walkField(field, keys);
151
151
  return keys;
152
152
  }
153
- /**
154
- * Every top-level config key whose value the KEYCHAIN stores (D787) —
155
- * {@link collectSecretConfigKeys} minus `storage: 'inline'` and minus JSON
156
- * blobs (see {@link isKeychainStoredField}). What a form sends as
157
- * `secretKeys` so the owner's door knows which values to keep out of the blob.
158
- */
159
153
  /** Drop fields marked `omitFromSettings` before an integration settings write. */
160
154
  function omitTransientConfig(schema, config) {
161
155
  const omit = /* @__PURE__ */ new Set();
@@ -180,6 +174,14 @@ function collectOmitted(field, out) {
180
174
  for (const tab of tabs) for (const child of fieldsOf(tab)) collectOmitted(child, out);
181
175
  }
182
176
  }
177
+ /**
178
+ * Every config key whose value the KEYCHAIN stores (D787) —
179
+ * {@link collectSecretConfigKeys} minus `storage: 'inline'` and minus JSON
180
+ * blobs (see {@link isKeychainStoredField}). What a form sends as
181
+ * `secretKeys` so the owner's door knows which values to keep out of the blob.
182
+ * A secret inside an `editable-array` row is named by its id-addressed path
183
+ * (`boards[id].encryptionKey`).
184
+ */
183
185
  function collectKeychainStoredKeys(schema) {
184
186
  const keys = /* @__PURE__ */ new Set();
185
187
  for (const section of sectionsOf(schema)) for (const field of fieldsOf(section)) walkKeychainField(field, keys);
@@ -192,6 +194,17 @@ function walkKeychainField(field, out) {
192
194
  const isSecret = type === "password" || field["secret"] === true;
193
195
  const isJsonBlob = type === "textarea" && field["isJson"] === true;
194
196
  if (isSecret && !isJsonBlob && field["storage"] !== "inline" && typeof key === "string" && key.length > 0) out.add(key);
197
+ if (type === "editable-array" && typeof key === "string" && key.length > 0) {
198
+ const idKey = typeof field["itemIdKey"] === "string" ? field["itemIdKey"] : "id";
199
+ const itemFields = field["itemFields"];
200
+ if (!Array.isArray(itemFields)) return;
201
+ for (const item of itemFields) {
202
+ const members = /* @__PURE__ */ new Set();
203
+ walkKeychainField(item, members);
204
+ for (const member of members) out.add(`${key}[${idKey}].${member}`);
205
+ }
206
+ return;
207
+ }
195
208
  if (type === "group") {
196
209
  for (const child of fieldsOf(field)) walkKeychainField(child, out);
197
210
  return;
@@ -2414,6 +2427,7 @@ var BaseAddon = class {
2414
2427
  * a warning and falls back rather than crashing boot.
2415
2428
  */
2416
2429
  state(key, schema, fallback) {
2430
+ this._declaredStateKeys.add(key);
2417
2431
  return createDurableState({
2418
2432
  key,
2419
2433
  schema,
@@ -2429,6 +2443,96 @@ var BaseAddon = class {
2429
2443
  } })
2430
2444
  });
2431
2445
  }
2446
+ /** Durable-state keys this process registered through {@link state}. */
2447
+ _declaredStateKeys = /* @__PURE__ */ new Set();
2448
+ /**
2449
+ * Stale-data audit: every global-settings key this addon still declares —
2450
+ * constructor defaults, settings-schema fields (layout containers included),
2451
+ * durable-state keys and the root of each `secretStatePaths` entry. Answered
2452
+ * here, in the addon's own process, so the hub keeps no copy of it.
2453
+ */
2454
+ async getDeclaredSettingsKeys() {
2455
+ return { keys: [...this.declaredKeySet()].toSorted() };
2456
+ }
2457
+ declaredKeySet() {
2458
+ const keys = new Set(Object.keys(this.defaults));
2459
+ const schema = this.globalSettingsSchema();
2460
+ if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
2461
+ for (const key of this._declaredStateKeys) keys.add(key);
2462
+ for (const path of this.secretStatePaths) {
2463
+ const root = path.split(/[.[]/, 1)[0];
2464
+ if (root) keys.add(root);
2465
+ }
2466
+ return keys;
2467
+ }
2468
+ /**
2469
+ * Stale-data audit: remove stored global-settings keys the addon no longer
2470
+ * declares, and clear the keychain values their markers held — except a value
2471
+ * a surviving key still points at. A declared or absent key is refused.
2472
+ */
2473
+ async unsetStoredSettingsKeys(keys) {
2474
+ const settings = this._ctx?.settings;
2475
+ const raw = this.rawSettings();
2476
+ if (!settings || !raw) return {
2477
+ removed: [],
2478
+ refused: keys.map((key) => ({
2479
+ key,
2480
+ reason: "no settings store"
2481
+ })),
2482
+ secretsCleared: 0
2483
+ };
2484
+ let stored;
2485
+ try {
2486
+ stored = await raw.readAddonStore();
2487
+ } catch (error) {
2488
+ const reason = `store unreadable: ${error instanceof Error ? error.message : "unknown"}`;
2489
+ return {
2490
+ removed: [],
2491
+ refused: keys.map((key) => ({
2492
+ key,
2493
+ reason
2494
+ })),
2495
+ secretsCleared: 0
2496
+ };
2497
+ }
2498
+ const declared = this.declaredKeySet();
2499
+ const removed = [];
2500
+ const refused = [];
2501
+ for (const key of keys) {
2502
+ const base = key.split("@", 1)[0] ?? key;
2503
+ if (declared.has(base)) refused.push({
2504
+ key,
2505
+ reason: "still declared by the addon"
2506
+ });
2507
+ else if (!(key in stored)) refused.push({
2508
+ key,
2509
+ reason: "not stored"
2510
+ });
2511
+ else removed.push(key);
2512
+ }
2513
+ if (removed.length === 0) return {
2514
+ removed,
2515
+ refused,
2516
+ secretsCleared: 0
2517
+ };
2518
+ const kept = /* @__PURE__ */ new Set();
2519
+ const held = /* @__PURE__ */ new Set();
2520
+ for (const [key, value] of Object.entries(stored)) {
2521
+ const target = removed.includes(key) ? held : kept;
2522
+ for (const ref of collectSecretMarkerRefs(value)) target.add(ref);
2523
+ }
2524
+ const refsToClear = [...held].filter((ref) => !kept.has(ref));
2525
+ const patch = {};
2526
+ for (const key of removed) patch[key] = void 0;
2527
+ await settings.writeAddonStore(patch);
2528
+ const secretsCleared = refsToClear.length > 0 && this._ctx?.secrets ? await this._ctx.secrets.delete(refsToClear) : 0;
2529
+ await this.resolveConfig();
2530
+ return {
2531
+ removed,
2532
+ refused,
2533
+ secretsCleared
2534
+ };
2535
+ }
2432
2536
  /** Per-device variant of {@link state}, backed by the per-device store. */
2433
2537
  deviceState(deviceId, key, schema, fallback) {
2434
2538
  return createDurableState({
@@ -2699,6 +2803,21 @@ function dropPerNodeFields(fields) {
2699
2803
  }
2700
2804
  return kept;
2701
2805
  }
2806
+ function collectFieldKeys(fields) {
2807
+ const collected = [];
2808
+ for (const field of fields) {
2809
+ if (field.type === "group") {
2810
+ collected.push(...collectFieldKeys(field.fields));
2811
+ continue;
2812
+ }
2813
+ if (field.type === "sub-tabs") {
2814
+ for (const tab of field.tabs) collected.push(...collectFieldKeys(tab.fields));
2815
+ continue;
2816
+ }
2817
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
2818
+ }
2819
+ return collected;
2820
+ }
2702
2821
  function collectPerNodeFieldKeys(fields) {
2703
2822
  const collected = [];
2704
2823
  for (const field of fields) {
@@ -150,12 +150,6 @@ function collectSecretConfigKeys(schema) {
150
150
  for (const section of sectionsOf(schema)) for (const field of fieldsOf(section)) walkField(field, keys);
151
151
  return keys;
152
152
  }
153
- /**
154
- * Every top-level config key whose value the KEYCHAIN stores (D787) —
155
- * {@link collectSecretConfigKeys} minus `storage: 'inline'` and minus JSON
156
- * blobs (see {@link isKeychainStoredField}). What a form sends as
157
- * `secretKeys` so the owner's door knows which values to keep out of the blob.
158
- */
159
153
  /** Drop fields marked `omitFromSettings` before an integration settings write. */
160
154
  function omitTransientConfig(schema, config) {
161
155
  const omit = /* @__PURE__ */ new Set();
@@ -180,6 +174,14 @@ function collectOmitted(field, out) {
180
174
  for (const tab of tabs) for (const child of fieldsOf(tab)) collectOmitted(child, out);
181
175
  }
182
176
  }
177
+ /**
178
+ * Every config key whose value the KEYCHAIN stores (D787) —
179
+ * {@link collectSecretConfigKeys} minus `storage: 'inline'` and minus JSON
180
+ * blobs (see {@link isKeychainStoredField}). What a form sends as
181
+ * `secretKeys` so the owner's door knows which values to keep out of the blob.
182
+ * A secret inside an `editable-array` row is named by its id-addressed path
183
+ * (`boards[id].encryptionKey`).
184
+ */
183
185
  function collectKeychainStoredKeys(schema) {
184
186
  const keys = /* @__PURE__ */ new Set();
185
187
  for (const section of sectionsOf(schema)) for (const field of fieldsOf(section)) walkKeychainField(field, keys);
@@ -192,6 +194,17 @@ function walkKeychainField(field, out) {
192
194
  const isSecret = type === "password" || field["secret"] === true;
193
195
  const isJsonBlob = type === "textarea" && field["isJson"] === true;
194
196
  if (isSecret && !isJsonBlob && field["storage"] !== "inline" && typeof key === "string" && key.length > 0) out.add(key);
197
+ if (type === "editable-array" && typeof key === "string" && key.length > 0) {
198
+ const idKey = typeof field["itemIdKey"] === "string" ? field["itemIdKey"] : "id";
199
+ const itemFields = field["itemFields"];
200
+ if (!Array.isArray(itemFields)) return;
201
+ for (const item of itemFields) {
202
+ const members = /* @__PURE__ */ new Set();
203
+ walkKeychainField(item, members);
204
+ for (const member of members) out.add(`${key}[${idKey}].${member}`);
205
+ }
206
+ return;
207
+ }
195
208
  if (type === "group") {
196
209
  for (const child of fieldsOf(field)) walkKeychainField(child, out);
197
210
  return;
@@ -2414,6 +2427,7 @@ var BaseAddon = class {
2414
2427
  * a warning and falls back rather than crashing boot.
2415
2428
  */
2416
2429
  state(key, schema, fallback) {
2430
+ this._declaredStateKeys.add(key);
2417
2431
  return createDurableState({
2418
2432
  key,
2419
2433
  schema,
@@ -2429,6 +2443,96 @@ var BaseAddon = class {
2429
2443
  } })
2430
2444
  });
2431
2445
  }
2446
+ /** Durable-state keys this process registered through {@link state}. */
2447
+ _declaredStateKeys = /* @__PURE__ */ new Set();
2448
+ /**
2449
+ * Stale-data audit: every global-settings key this addon still declares —
2450
+ * constructor defaults, settings-schema fields (layout containers included),
2451
+ * durable-state keys and the root of each `secretStatePaths` entry. Answered
2452
+ * here, in the addon's own process, so the hub keeps no copy of it.
2453
+ */
2454
+ async getDeclaredSettingsKeys() {
2455
+ return { keys: [...this.declaredKeySet()].toSorted() };
2456
+ }
2457
+ declaredKeySet() {
2458
+ const keys = new Set(Object.keys(this.defaults));
2459
+ const schema = this.globalSettingsSchema();
2460
+ if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
2461
+ for (const key of this._declaredStateKeys) keys.add(key);
2462
+ for (const path of this.secretStatePaths) {
2463
+ const root = path.split(/[.[]/, 1)[0];
2464
+ if (root) keys.add(root);
2465
+ }
2466
+ return keys;
2467
+ }
2468
+ /**
2469
+ * Stale-data audit: remove stored global-settings keys the addon no longer
2470
+ * declares, and clear the keychain values their markers held — except a value
2471
+ * a surviving key still points at. A declared or absent key is refused.
2472
+ */
2473
+ async unsetStoredSettingsKeys(keys) {
2474
+ const settings = this._ctx?.settings;
2475
+ const raw = this.rawSettings();
2476
+ if (!settings || !raw) return {
2477
+ removed: [],
2478
+ refused: keys.map((key) => ({
2479
+ key,
2480
+ reason: "no settings store"
2481
+ })),
2482
+ secretsCleared: 0
2483
+ };
2484
+ let stored;
2485
+ try {
2486
+ stored = await raw.readAddonStore();
2487
+ } catch (error) {
2488
+ const reason = `store unreadable: ${error instanceof Error ? error.message : "unknown"}`;
2489
+ return {
2490
+ removed: [],
2491
+ refused: keys.map((key) => ({
2492
+ key,
2493
+ reason
2494
+ })),
2495
+ secretsCleared: 0
2496
+ };
2497
+ }
2498
+ const declared = this.declaredKeySet();
2499
+ const removed = [];
2500
+ const refused = [];
2501
+ for (const key of keys) {
2502
+ const base = key.split("@", 1)[0] ?? key;
2503
+ if (declared.has(base)) refused.push({
2504
+ key,
2505
+ reason: "still declared by the addon"
2506
+ });
2507
+ else if (!(key in stored)) refused.push({
2508
+ key,
2509
+ reason: "not stored"
2510
+ });
2511
+ else removed.push(key);
2512
+ }
2513
+ if (removed.length === 0) return {
2514
+ removed,
2515
+ refused,
2516
+ secretsCleared: 0
2517
+ };
2518
+ const kept = /* @__PURE__ */ new Set();
2519
+ const held = /* @__PURE__ */ new Set();
2520
+ for (const [key, value] of Object.entries(stored)) {
2521
+ const target = removed.includes(key) ? held : kept;
2522
+ for (const ref of collectSecretMarkerRefs(value)) target.add(ref);
2523
+ }
2524
+ const refsToClear = [...held].filter((ref) => !kept.has(ref));
2525
+ const patch = {};
2526
+ for (const key of removed) patch[key] = void 0;
2527
+ await settings.writeAddonStore(patch);
2528
+ const secretsCleared = refsToClear.length > 0 && this._ctx?.secrets ? await this._ctx.secrets.delete(refsToClear) : 0;
2529
+ await this.resolveConfig();
2530
+ return {
2531
+ removed,
2532
+ refused,
2533
+ secretsCleared
2534
+ };
2535
+ }
2432
2536
  /** Per-device variant of {@link state}, backed by the per-device store. */
2433
2537
  deviceState(deviceId, key, schema, fallback) {
2434
2538
  return createDurableState({
@@ -2699,6 +2803,21 @@ function dropPerNodeFields(fields) {
2699
2803
  }
2700
2804
  return kept;
2701
2805
  }
2806
+ function collectFieldKeys(fields) {
2807
+ const collected = [];
2808
+ for (const field of fields) {
2809
+ if (field.type === "group") {
2810
+ collected.push(...collectFieldKeys(field.fields));
2811
+ continue;
2812
+ }
2813
+ if (field.type === "sub-tabs") {
2814
+ for (const tab of field.tabs) collected.push(...collectFieldKeys(tab.fields));
2815
+ continue;
2816
+ }
2817
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
2818
+ }
2819
+ return collected;
2820
+ }
2702
2821
  function collectPerNodeFieldKeys(fields) {
2703
2822
  const collected = [];
2704
2823
  for (const field of fields) {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Server-side cookie jar of ONE vendor sign-in attempt (D955).
3
+ *
4
+ * A vendor login (an identity provider's cookie/JS check page) refuses a flow that arrives
5
+ * without the cookies it set a step earlier. Relying on the browser to store them under the
6
+ * hub origin loses them whenever the browser refuses the rewritten cookie (a `Secure` cookie
7
+ * on a plain-http hub, a `__Host-` name with a narrowed Path, a `SameSite=None` without
8
+ * `Secure`). The proxy therefore keeps every vendor cookie itself and sends them upstream,
9
+ * whatever the browser did. Values never leave this object except as the upstream `Cookie`
10
+ * header; nothing here logs.
11
+ */
12
+ import { type LoginProxyScheme } from './url-rewrite.js';
13
+ export declare class VendorCookieJar {
14
+ #private;
15
+ /** Store (or delete, when expired) the cookies of one upstream response. */
16
+ store(setCookies: readonly string[], origin: {
17
+ readonly host: string;
18
+ readonly pathname: string;
19
+ }, nowMs: number): void;
20
+ /** The `Cookie` header value for a request to `host` + `pathname`, or null when none applies. */
21
+ headerFor(target: {
22
+ readonly host: string;
23
+ readonly pathname: string;
24
+ readonly scheme: LoginProxyScheme;
25
+ }, nowMs: number): string | null;
26
+ }
27
+ /** Cookie names carried by a `Cookie` header value. */
28
+ export declare function cookieHeaderNames(header: string): Set<string>;
29
+ /** `browser` cookies not shadowed by the jar's, then the jar's: the jar is the truth. */
30
+ export declare function mergeCookieHeaders(browser: string, jar: string | null): string;
@@ -1,3 +1,4 @@
1
1
  export { VendorLoginProxy, type LoginProxyAttemptSpec, type LoginProxyResponseView, type LoginProxyVerdict, type VendorLoginProxyOptions, } from './login-proxy.js';
2
2
  export { rewriteBody, rewriteLocation, rewriteReferer, rewriteFormBody, rewriteSetCookie, rewriteSetCookies, type LoginProxyRoute, type LoginProxyScheme, type LoginProxyUpstream, } from './url-rewrite.js';
3
3
  export { VENDOR_LOGIN_PROXY_PREFIX } from './prefix.js';
4
+ export { VendorCookieJar } from './cookie-jar.js';
@@ -28,6 +28,12 @@ export interface LoginProxyAttemptSpec<T> {
28
28
  readonly terminal: (view: LoginProxyResponseView) => LoginProxyVerdict<T> | Promise<LoginProxyVerdict<T>>;
29
29
  /** Finish the sign-in with the payload; the outcome picks the result page the browser lands on. */
30
30
  readonly onDone: (payload: T) => Promise<OauthLandingOutcome>;
31
+ /**
32
+ * Keep the vendor's cookies on the server for this attempt and send them upstream, whatever
33
+ * the browser stored. For a login that refuses a flow arriving without the cookies it set
34
+ * earlier (a browser-requirements check page). Default off.
35
+ */
36
+ readonly serverCookieJar?: boolean;
31
37
  }
32
38
  export interface VendorLoginProxyOptions {
33
39
  readonly addonId: string;
@@ -57,15 +57,22 @@ export declare function rewriteBody(body: string, kind: BodyKind, currentUpstrea
57
57
  /**
58
58
  * A vendor `Set-Cookie` for the browser: no `Domain` (the cookie belongs to the hub
59
59
  * origin), `Path` narrowed to this attempt's prefix, `Secure` kept (the hub is HTTPS).
60
+ * `browserSafe` (the server keeps its own jar, so this copy only serves the page's script):
61
+ * also no `Secure` / `SameSite`, which a plain-http hub makes the browser refuse, and no
62
+ * `__Host-` / `__Secure-` cookie, whose rules a narrowed Path breaks.
60
63
  */
61
- export declare function rewriteSetCookie(cookie: string, route: LoginProxyRoute): string;
64
+ export declare function rewriteSetCookie(cookie: string, route: LoginProxyRoute, options?: {
65
+ readonly browserSafe?: boolean;
66
+ }): string;
62
67
  /**
63
68
  * Every Set-Cookie of one vendor response, rewritten. Dropping `Domain` folds cookies
64
69
  * the vendor keeps apart (`session-id` on `.amazon.it` and on `.www.amazon.it`) into
65
70
  * one, so a past-dated set that only deleted the other domain's copy would delete the
66
71
  * live one the same response just set: that deletion is dropped.
67
72
  */
68
- export declare function rewriteSetCookies(cookies: readonly string[], route: LoginProxyRoute): string[];
73
+ export declare function rewriteSetCookies(cookies: readonly string[], route: LoginProxyRoute, options?: {
74
+ readonly browserSafe?: boolean;
75
+ }): string[];
69
76
  /**
70
77
  * A browser `Referer` that points into this proxy, turned back into the vendor URL it
71
78
  * stands for. A Referer that is not ours is dropped (null): it names the hub.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.450",
3
+ "version": "1.2.452",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",