@camstack/addon-google-services 0.2.36 → 0.2.37
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 +6 -0
- package/dist/google-services.addon.js +855 -240
- package/dist/google-services.addon.mjs +855 -240
- package/package.json +5 -2
|
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
|
|
|
5981
5981
|
var ZodFirstPartyTypeKind;
|
|
5982
5982
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5983
5983
|
//#endregion
|
|
5984
|
-
//#region ../types/dist/sleep-
|
|
5984
|
+
//#region ../types/dist/sleep-BkzQdpzF.mjs
|
|
5985
5985
|
/**
|
|
5986
5986
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5987
5987
|
* window to float samples (D455).
|
|
@@ -6129,9 +6129,16 @@ function isSecretMarker(value) {
|
|
|
6129
6129
|
const ref = value["$secret"];
|
|
6130
6130
|
return typeof ref === "string" && ref.length > 0;
|
|
6131
6131
|
}
|
|
6132
|
+
/** Anything carrying a `$secret` key — a stored marker or a UI view of one. */
|
|
6133
|
+
function isSecretMarkerLike(value) {
|
|
6134
|
+
return isRecord$7(value) && Object.prototype.hasOwnProperty.call(value, "$secret");
|
|
6135
|
+
}
|
|
6132
6136
|
function isSecretClear(value) {
|
|
6133
6137
|
return isRecord$7(value) && value["$secretClear"] === true;
|
|
6134
6138
|
}
|
|
6139
|
+
function isSecretFieldView(value) {
|
|
6140
|
+
return isRecord$7(value) && value["set"] === true && isSecretMarkerLike(value);
|
|
6141
|
+
}
|
|
6135
6142
|
/**
|
|
6136
6143
|
* The UI view of a stored secret field value: a marker becomes "set", inline
|
|
6137
6144
|
* plaintext becomes "set (inline)", nothing becomes `null`. A value never
|
|
@@ -6781,11 +6788,36 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6781
6788
|
}
|
|
6782
6789
|
return out;
|
|
6783
6790
|
}
|
|
6791
|
+
/**
|
|
6792
|
+
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6793
|
+
*
|
|
6794
|
+
* A ref is DETERMINISTIC from where the value lives — the owner addon, the
|
|
6795
|
+
* scope (global, one node, one device, one integration, one row of a business
|
|
6796
|
+
* collection) and the field path inside that scope — so saving the same field
|
|
6797
|
+
* twice updates the same ref, and the ref itself says which settings blob
|
|
6798
|
+
* points at it. It is also the AAD of the value's ciphertext: a ciphertext
|
|
6799
|
+
* copied onto another ref does not decrypt.
|
|
6800
|
+
*
|
|
6801
|
+
* Array items are addressed by ITEM ID, never by index
|
|
6802
|
+
* (`providers[authentik].clientSecret`), so reordering a list keeps every ref.
|
|
6803
|
+
*
|
|
6804
|
+
* Pure and browser-safe: no node builtins, no I/O.
|
|
6805
|
+
*/
|
|
6806
|
+
/** The URI scheme of every keychain ref. */
|
|
6807
|
+
var SECRET_REF_SCHEME = "secret://";
|
|
6784
6808
|
var GLOBAL_SCOPE = { kind: "global" };
|
|
6785
6809
|
/** The scope every addon-wide (not per-node, not per-device) setting lives in. */
|
|
6786
6810
|
function globalSecretScope() {
|
|
6787
6811
|
return GLOBAL_SCOPE;
|
|
6788
6812
|
}
|
|
6813
|
+
/**
|
|
6814
|
+
* An addon id as a ref owner. A hub in-process builtin's context id is
|
|
6815
|
+
* `addon:<id>`; the owner of its secrets is `<id>`, exactly as for the same
|
|
6816
|
+
* addon running forked.
|
|
6817
|
+
*/
|
|
6818
|
+
function normalizeSecretOwner(addonId) {
|
|
6819
|
+
return addonId.startsWith("addon:") ? addonId.slice(6) : addonId;
|
|
6820
|
+
}
|
|
6789
6821
|
function encodeSegment(value) {
|
|
6790
6822
|
return encodeURIComponent(value);
|
|
6791
6823
|
}
|
|
@@ -6796,6 +6828,16 @@ function decodeSegment(value) {
|
|
|
6796
6828
|
return null;
|
|
6797
6829
|
}
|
|
6798
6830
|
}
|
|
6831
|
+
/** The scope as it appears inside a ref (`global`, `node/<id>`, …). */
|
|
6832
|
+
function formatSecretScope(scope) {
|
|
6833
|
+
switch (scope.kind) {
|
|
6834
|
+
case "global": return "global";
|
|
6835
|
+
case "node": return `node/${encodeSegment(scope.nodeId)}`;
|
|
6836
|
+
case "device": return `device/${scope.deviceId}`;
|
|
6837
|
+
case "integration": return `integration/${encodeSegment(scope.integrationId)}`;
|
|
6838
|
+
case "row": return `row/${encodeSegment(scope.collection)}/${encodeSegment(scope.rowId)}`;
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6799
6841
|
/**
|
|
6800
6842
|
* The field path of a value inside an array item, addressed by the item's id:
|
|
6801
6843
|
* `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
|
|
@@ -6804,6 +6846,13 @@ function decodeSegment(value) {
|
|
|
6804
6846
|
function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
|
|
6805
6847
|
return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
|
|
6806
6848
|
}
|
|
6849
|
+
/** Build a ref. Throws on an empty owner or field path — both are bugs. */
|
|
6850
|
+
function buildSecretRef(parts) {
|
|
6851
|
+
const owner = normalizeSecretOwner(parts.owner);
|
|
6852
|
+
if (owner.length === 0) throw new Error("buildSecretRef: owner is empty");
|
|
6853
|
+
if (parts.fieldPath.length === 0) throw new Error("buildSecretRef: fieldPath is empty");
|
|
6854
|
+
return `${SECRET_REF_SCHEME}${encodeSegment(owner)}/${formatSecretScope(parts.scope)}/${parts.fieldPath}`;
|
|
6855
|
+
}
|
|
6807
6856
|
function parseScope(segments) {
|
|
6808
6857
|
switch (segments[0]) {
|
|
6809
6858
|
case "global": return {
|
|
@@ -8069,6 +8118,10 @@ var BaseAddon = class {
|
|
|
8069
8118
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
8070
8119
|
const patch = {};
|
|
8071
8120
|
for (const key of keys) {
|
|
8121
|
+
if (tree.get(key)?.kind === "array") {
|
|
8122
|
+
patch[key] = [];
|
|
8123
|
+
continue;
|
|
8124
|
+
}
|
|
8072
8125
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8073
8126
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8074
8127
|
}
|
|
@@ -8757,6 +8810,8 @@ var RECORD_BATCH_MAX = 1e3;
|
|
|
8757
8810
|
var RECORD_QUERY_MAX = 1e3;
|
|
8758
8811
|
/** A kind in the `settings` file may not bound itself above this (D447: high volume is `events`). */
|
|
8759
8812
|
var RECORD_SETTINGS_MAX_ROWS = 5e4;
|
|
8813
|
+
/** The `Column`-free proof prefix: a ref collection for a kind's secret fields. */
|
|
8814
|
+
var RECORD_SECRET_COLLECTION_PREFIX = "records:";
|
|
8760
8815
|
/**
|
|
8761
8816
|
* Secret fields of a record, addressed the way a Zod schema declares them
|
|
8762
8817
|
* (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
|
|
@@ -8793,6 +8848,112 @@ function secretArrayPrefixes(path) {
|
|
|
8793
8848
|
}
|
|
8794
8849
|
return prefixes;
|
|
8795
8850
|
}
|
|
8851
|
+
function isRecord$4(value) {
|
|
8852
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8853
|
+
}
|
|
8854
|
+
function elementId(element, idKey, declared, prefix) {
|
|
8855
|
+
const id = isRecord$4(element) ? element[idKey] : void 0;
|
|
8856
|
+
if (typeof id !== "string" || id.length === 0) throw new InvalidRecordError(`"${declared}": every element of "${prefix}" needs a non-empty string "${idKey}" to carry a secret`);
|
|
8857
|
+
return id;
|
|
8858
|
+
}
|
|
8859
|
+
/**
|
|
8860
|
+
* Visit every secret leaf PRESENT in `data` (an absent branch has nothing to
|
|
8861
|
+
* visit). `replace` returns the new leaf value, or {@link REMOVE_LEAF}; the
|
|
8862
|
+
* result is a new body, `data` is untouched.
|
|
8863
|
+
*/
|
|
8864
|
+
var REMOVE_LEAF = Symbol("remove-leaf");
|
|
8865
|
+
function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arrayKeys, visit) {
|
|
8866
|
+
if (!isRecord$4(node)) return node;
|
|
8867
|
+
const segment = segments[index];
|
|
8868
|
+
if (segment === void 0) return node;
|
|
8869
|
+
if (!Object.prototype.hasOwnProperty.call(node, segment.key)) return node;
|
|
8870
|
+
const child = node[segment.key];
|
|
8871
|
+
const keys = [...walkedKeys, segment.key];
|
|
8872
|
+
const last = index === segments.length - 1;
|
|
8873
|
+
if (segment.each) {
|
|
8874
|
+
if (!Array.isArray(child)) return node;
|
|
8875
|
+
const prefix = keys.join(".");
|
|
8876
|
+
const idKey = arrayKeys[prefix];
|
|
8877
|
+
if (idKey === void 0) throw new InvalidRecordError(`"${declared}": no id key declared for the array "${prefix}"`);
|
|
8878
|
+
const mapped = child.map((element) => {
|
|
8879
|
+
const id = elementId(element, idKey, declared, prefix);
|
|
8880
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}[${encodeURIComponent(id)}]`;
|
|
8881
|
+
return last ? element : mapAt(element, segments, index + 1, keys, concrete, declared, arrayKeys, visit);
|
|
8882
|
+
});
|
|
8883
|
+
return {
|
|
8884
|
+
...node,
|
|
8885
|
+
[segment.key]: mapped
|
|
8886
|
+
};
|
|
8887
|
+
}
|
|
8888
|
+
const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
|
|
8889
|
+
if (last) {
|
|
8890
|
+
const replacement = visit({
|
|
8891
|
+
declared,
|
|
8892
|
+
concrete,
|
|
8893
|
+
value: child
|
|
8894
|
+
});
|
|
8895
|
+
if (replacement === REMOVE_LEAF) return Object.fromEntries(Object.entries(node).filter(([key]) => key !== segment.key));
|
|
8896
|
+
return {
|
|
8897
|
+
...node,
|
|
8898
|
+
[segment.key]: replacement
|
|
8899
|
+
};
|
|
8900
|
+
}
|
|
8901
|
+
return {
|
|
8902
|
+
...node,
|
|
8903
|
+
[segment.key]: mapAt(child, segments, index + 1, keys, concrete, declared, arrayKeys, visit)
|
|
8904
|
+
};
|
|
8905
|
+
}
|
|
8906
|
+
/** Replace/remove every present secret leaf; returns a NEW body. */
|
|
8907
|
+
function mapSecretLeaves(data, paths, arrayKeys, visit) {
|
|
8908
|
+
let current = data;
|
|
8909
|
+
for (const declared of paths) current = mapAt(current, parseSecretPath(declared), 0, [], "", declared, arrayKeys, visit);
|
|
8910
|
+
return current;
|
|
8911
|
+
}
|
|
8912
|
+
/**
|
|
8913
|
+
* The keychain collection name a kind's secrets live under. Prefixed so a
|
|
8914
|
+
* record's refs never collide with the refs a legacy collection of the same name
|
|
8915
|
+
* already holds during an adoption.
|
|
8916
|
+
*/
|
|
8917
|
+
function recordSecretCollection(kind) {
|
|
8918
|
+
return `${RECORD_SECRET_COLLECTION_PREFIX}${kind}`;
|
|
8919
|
+
}
|
|
8920
|
+
/** The ONE ref a row's secret field may carry: the engine and the facade both build it here. */
|
|
8921
|
+
function recordSecretRef(owner, kind, rowId, concretePath) {
|
|
8922
|
+
return buildSecretRef({
|
|
8923
|
+
owner,
|
|
8924
|
+
scope: {
|
|
8925
|
+
kind: "row",
|
|
8926
|
+
collection: recordSecretCollection(kind),
|
|
8927
|
+
rowId
|
|
8928
|
+
},
|
|
8929
|
+
fieldPath: concretePath
|
|
8930
|
+
});
|
|
8931
|
+
}
|
|
8932
|
+
/**
|
|
8933
|
+
* A stored secret, as an admin read shows it: a VIEW naming the row's own
|
|
8934
|
+
* keychain ref (`set: true`), never the value and no longer the bare redaction
|
|
8935
|
+
* token. The form hands it to the secret field, which reveals the value by ref
|
|
8936
|
+
* through the audited `keychain-admin.reveal`; sent back untouched it keeps the
|
|
8937
|
+
* stored secret (the records door reads a view as KEEP).
|
|
8938
|
+
*
|
|
8939
|
+
* ONE implementation for the entries service and for every addon that serves
|
|
8940
|
+
* its own instances (`RecordIntegrationInstances`).
|
|
8941
|
+
*/
|
|
8942
|
+
function isRecord$3(value) {
|
|
8943
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8944
|
+
}
|
|
8945
|
+
function secretViewsOf(owner, kind, id, decl, data) {
|
|
8946
|
+
if (decl.secretPaths.length === 0) return data;
|
|
8947
|
+
const shown = mapSecretLeaves(data, decl.secretPaths, decl.secretArrayKeys, (leaf) => {
|
|
8948
|
+
if (leaf.value !== "__camstack_redacted__") return leaf.value;
|
|
8949
|
+
return {
|
|
8950
|
+
$secret: recordSecretRef(owner, kind, id, leaf.concrete),
|
|
8951
|
+
v: 1,
|
|
8952
|
+
set: true
|
|
8953
|
+
};
|
|
8954
|
+
});
|
|
8955
|
+
return isRecord$3(shown) ? shown : data;
|
|
8956
|
+
}
|
|
8796
8957
|
/**
|
|
8797
8958
|
* The share-view scope kinds a share-link token can carry. The hub mints
|
|
8798
8959
|
* and validates them (`ShareViewGrantSchema` builds its
|
|
@@ -9015,7 +9176,7 @@ function defineRecordKinds(specs) {
|
|
|
9015
9176
|
retention: spec.retention,
|
|
9016
9177
|
deviceRemoval: spec.deviceRemoval,
|
|
9017
9178
|
maxDataBytes: spec.maxDataBytes,
|
|
9018
|
-
secretPaths: [...secretPaths],
|
|
9179
|
+
secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
|
|
9019
9180
|
secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
|
|
9020
9181
|
transactFamily: spec.transactFamily,
|
|
9021
9182
|
entry: spec.entry === void 0 ? void 0 : {
|
|
@@ -9060,6 +9221,7 @@ function defineEntryKind(name, spec) {
|
|
|
9060
9221
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9061
9222
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9062
9223
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
9224
|
+
...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
|
|
9063
9225
|
entry: {
|
|
9064
9226
|
title: spec.title,
|
|
9065
9227
|
...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
|
|
@@ -11735,7 +11897,7 @@ function userError(code, params, englishFallback) {
|
|
|
11735
11897
|
return new CamstackError(code, params, englishFallback);
|
|
11736
11898
|
}
|
|
11737
11899
|
//#endregion
|
|
11738
|
-
//#region ../types/dist/composition-
|
|
11900
|
+
//#region ../types/dist/composition-CRNwcFYY.mjs
|
|
11739
11901
|
/**
|
|
11740
11902
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11741
11903
|
*
|
|
@@ -12498,38 +12660,6 @@ function validateExpressionSource(src) {
|
|
|
12498
12660
|
return null;
|
|
12499
12661
|
}
|
|
12500
12662
|
/**
|
|
12501
|
-
* One power reading becomes a washer, dryer or dishwasher (D944 helper).
|
|
12502
|
-
*
|
|
12503
|
-
* Standby of the control board is under 5 W; a motor or a heater is far
|
|
12504
|
-
* above that, so 5 W is the line. A door lock or a short pump spike is not
|
|
12505
|
-
* a cycle, so running starts only after 30 s above the line.
|
|
12506
|
-
*
|
|
12507
|
-
* A wash, dry or dishwasher pause (soak, heat-wait, heater off between
|
|
12508
|
-
* bursts) lasts several minutes. Ending the cycle on the first dip would
|
|
12509
|
-
* report "finished" in the middle of the program, so the stop dwell is
|
|
12510
|
-
* 10 minutes on every type. The number is stored per type so one machine
|
|
12511
|
-
* can move later without moving the others.
|
|
12512
|
-
*/
|
|
12513
|
-
var APPLIANCE_TEMPLATE_ID = "appliance";
|
|
12514
|
-
var APPLIANCE_KINDS = [
|
|
12515
|
-
"washer",
|
|
12516
|
-
"dryer",
|
|
12517
|
-
"dishwasher"
|
|
12518
|
-
];
|
|
12519
|
-
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
12520
|
-
/**
|
|
12521
|
-
* A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
|
|
12522
|
-
*
|
|
12523
|
-
* The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
|
|
12524
|
-
* (D967), so the heater switches exactly as the plug does. Running is the
|
|
12525
|
-
* plug being on; when a power reading is also named, running is the draw
|
|
12526
|
-
* staying above the line instead (a plug left on with the stove's own
|
|
12527
|
-
* thermostat open is not heating). 50 W is far above any standby and below
|
|
12528
|
-
* the smallest radiator.
|
|
12529
|
-
*/
|
|
12530
|
-
var HEATER_TEMPLATE_ID = "heater";
|
|
12531
|
-
DeviceType.Heater;
|
|
12532
|
-
/**
|
|
12533
12663
|
* Statistics and integrals over a history read (D944).
|
|
12534
12664
|
*
|
|
12535
12665
|
* Pure: the same points always give the same answer, so a restart recomputes
|
|
@@ -12673,34 +12803,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
|
|
|
12673
12803
|
/** Actuator groups only. Readings leave this unset. */
|
|
12674
12804
|
active: ActiveCriterionSchema.optional()
|
|
12675
12805
|
}),
|
|
12676
|
-
object({
|
|
12677
|
-
kind: literal("appliance"),
|
|
12678
|
-
templateId: literal(APPLIANCE_TEMPLATE_ID),
|
|
12679
|
-
appliance: _enum(APPLIANCE_KINDS),
|
|
12680
|
-
thresholdWatts: number().positive(),
|
|
12681
|
-
startDwellMs: number().int().nonnegative(),
|
|
12682
|
-
stopDwellMs: number().int().nonnegative(),
|
|
12683
|
-
member: HelperMemberSchema,
|
|
12684
|
-
/**
|
|
12685
|
-
* The plug that powers the appliance (`switch.on`). Optional: when set, the
|
|
12686
|
-
* composition carries a `power-source` feature, so the hub can cut and restore
|
|
12687
|
-
* that plug (power-source link of a NEW-target composition).
|
|
12688
|
-
*/
|
|
12689
|
-
powerSwitch: HelperMemberSchema.optional(),
|
|
12690
|
-
/** How long a forced restart keeps the plug off; the cap's default when unset. */
|
|
12691
|
-
offSeconds: number().optional()
|
|
12692
|
-
}),
|
|
12693
|
-
object({
|
|
12694
|
-
kind: literal("heater"),
|
|
12695
|
-
templateId: literal(HEATER_TEMPLATE_ID),
|
|
12696
|
-
/** The plug, read and commanded at `switch.on`. */
|
|
12697
|
-
plug: HelperMemberSchema,
|
|
12698
|
-
/** The plug's power reading, when it has one: running is then the draw, not the switch. */
|
|
12699
|
-
meter: HelperMemberSchema.optional(),
|
|
12700
|
-
thresholdWatts: number().positive(),
|
|
12701
|
-
startDwellMs: number().int().nonnegative(),
|
|
12702
|
-
stopDwellMs: number().int().nonnegative()
|
|
12703
|
-
}),
|
|
12704
12806
|
object({
|
|
12705
12807
|
kind: literal("statistic"),
|
|
12706
12808
|
stat: _enum(STATISTIC_STATS),
|
|
@@ -12924,7 +13026,7 @@ var CompositionSchema = object({
|
|
|
12924
13026
|
kind: literal("existing"),
|
|
12925
13027
|
device: CompositionSourceRefSchema
|
|
12926
13028
|
})]),
|
|
12927
|
-
features: array(CompositionFeatureSchema).min(
|
|
13029
|
+
features: array(CompositionFeatureSchema).min(0).max(16),
|
|
12928
13030
|
/**
|
|
12929
13031
|
* Present when the block is a helper template. Absent on a free
|
|
12930
13032
|
* composition (the room example, device 4603). The editor reads this,
|
|
@@ -13259,6 +13361,157 @@ function buildCapturingReply(envelope) {
|
|
|
13259
13361
|
};
|
|
13260
13362
|
return wrapper;
|
|
13261
13363
|
}
|
|
13364
|
+
/** The most instances one addon lists (an integrations page, not a table). */
|
|
13365
|
+
var INSTANCES_LIST_MAX = 1e3;
|
|
13366
|
+
/** The keys of the kind's secret fields that sit at the top level of the body. */
|
|
13367
|
+
function topLevelSecretKeys(paths) {
|
|
13368
|
+
return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
|
|
13369
|
+
}
|
|
13370
|
+
var RecordIntegrationInstances = class {
|
|
13371
|
+
records;
|
|
13372
|
+
kind;
|
|
13373
|
+
hooks;
|
|
13374
|
+
handle;
|
|
13375
|
+
secretKeys;
|
|
13376
|
+
nameField;
|
|
13377
|
+
constructor(records, kind, hooks) {
|
|
13378
|
+
this.records = records;
|
|
13379
|
+
this.kind = kind;
|
|
13380
|
+
this.hooks = hooks;
|
|
13381
|
+
this.handle = records.kind(kind);
|
|
13382
|
+
this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
|
|
13383
|
+
this.nameField = kind.declaration.entry?.nameField ?? "name";
|
|
13384
|
+
}
|
|
13385
|
+
/**
|
|
13386
|
+
* One instance read for the addon's own runtime; null when absent. When the keychain is
|
|
13387
|
+
* locked the row is served WITHOUT its secrets and says so (`secrets: 'unavailable'`), so the
|
|
13388
|
+
* caller can keep what it had instead of reconfiguring with an empty secret (D49).
|
|
13389
|
+
*/
|
|
13390
|
+
async readResolved(id) {
|
|
13391
|
+
try {
|
|
13392
|
+
const record = await this.handle.getResolved(id);
|
|
13393
|
+
return record === null ? null : {
|
|
13394
|
+
id,
|
|
13395
|
+
body: record.data,
|
|
13396
|
+
enabled: record.data.enabled !== false,
|
|
13397
|
+
secrets: "resolved"
|
|
13398
|
+
};
|
|
13399
|
+
} catch (err) {
|
|
13400
|
+
if (!(err instanceof RecordSecretUnavailableError)) throw err;
|
|
13401
|
+
const stored = await this.handle.get(id);
|
|
13402
|
+
if (stored === null) return null;
|
|
13403
|
+
return {
|
|
13404
|
+
id,
|
|
13405
|
+
body: Object.fromEntries(Object.entries(stored.data).filter(([key]) => !this.secretKeys.has(key))),
|
|
13406
|
+
enabled: stored.data.enabled !== false,
|
|
13407
|
+
secrets: "unavailable"
|
|
13408
|
+
};
|
|
13409
|
+
}
|
|
13410
|
+
}
|
|
13411
|
+
/** The body of {@link readResolved} alone; a locked keychain yields the row without secrets. */
|
|
13412
|
+
async getResolved(id) {
|
|
13413
|
+
return (await this.readResolved(id))?.body ?? null;
|
|
13414
|
+
}
|
|
13415
|
+
/** Every instance, secrets resolved where the keychain allows; an absent one is skipped. */
|
|
13416
|
+
async listResolved() {
|
|
13417
|
+
const page = await this.handle.query({ limit: INSTANCES_LIST_MAX });
|
|
13418
|
+
const out = [];
|
|
13419
|
+
for (const record of page.records) {
|
|
13420
|
+
const read = await this.readResolved(record.id);
|
|
13421
|
+
if (read !== null) out.push(read);
|
|
13422
|
+
}
|
|
13423
|
+
return out;
|
|
13424
|
+
}
|
|
13425
|
+
viewOf(record) {
|
|
13426
|
+
const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
|
|
13427
|
+
const rawName = record.data[this.nameField];
|
|
13428
|
+
const kind = this.hooks.kindOf?.(record.data);
|
|
13429
|
+
const health = this.hooks.health?.(record.data, record.id);
|
|
13430
|
+
return {
|
|
13431
|
+
id: record.id,
|
|
13432
|
+
name: typeof rawName === "string" ? rawName : record.id,
|
|
13433
|
+
configured: this.hooks.configured(record.data),
|
|
13434
|
+
enabled: record.data.enabled !== false,
|
|
13435
|
+
formValues: shown,
|
|
13436
|
+
...kind !== void 0 ? { kind } : {},
|
|
13437
|
+
...health?.status !== void 0 ? { status: health.status } : {},
|
|
13438
|
+
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13439
|
+
};
|
|
13440
|
+
}
|
|
13441
|
+
describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
|
|
13442
|
+
...this.hooks.description,
|
|
13443
|
+
mintsId: true
|
|
13444
|
+
};
|
|
13445
|
+
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13446
|
+
listInstances = async () => {
|
|
13447
|
+
return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
|
|
13448
|
+
};
|
|
13449
|
+
getInstance = async ({ id }) => {
|
|
13450
|
+
const record = await this.handle.get(id);
|
|
13451
|
+
return record === null ? null : this.viewOf(record);
|
|
13452
|
+
};
|
|
13453
|
+
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13454
|
+
createInstance = async ({ id, name, kind }) => {
|
|
13455
|
+
const minted = id ?? this.hooks.mintId?.({
|
|
13456
|
+
name,
|
|
13457
|
+
...kind !== void 0 ? { kind } : {}
|
|
13458
|
+
});
|
|
13459
|
+
if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
|
|
13460
|
+
await this.handle.put({
|
|
13461
|
+
id: minted,
|
|
13462
|
+
data: this.hooks.initialBody(name, kind)
|
|
13463
|
+
}, { ifRev: 0 });
|
|
13464
|
+
await this.hooks.onChanged?.(minted);
|
|
13465
|
+
return { id: minted };
|
|
13466
|
+
};
|
|
13467
|
+
updateInstance = async ({ id, name, values }) => {
|
|
13468
|
+
const patch = {};
|
|
13469
|
+
for (const [key, value] of Object.entries(values)) {
|
|
13470
|
+
if (value === void 0) continue;
|
|
13471
|
+
if (!this.secretKeys.has(key)) patch[key] = value;
|
|
13472
|
+
else if (isSecretClear(value)) patch[key] = null;
|
|
13473
|
+
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13474
|
+
}
|
|
13475
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13476
|
+
await this.handle.patch(id, patch);
|
|
13477
|
+
await this.hooks.onChanged?.(id);
|
|
13478
|
+
return { ok: true };
|
|
13479
|
+
};
|
|
13480
|
+
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13481
|
+
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13482
|
+
await this.handle.patch(id, { enabled });
|
|
13483
|
+
await this.hooks.onChanged?.(id);
|
|
13484
|
+
return { ok: true };
|
|
13485
|
+
};
|
|
13486
|
+
/** The row and its keychain refs go together (the records door). */
|
|
13487
|
+
deleteInstance = async ({ id }) => {
|
|
13488
|
+
await this.handle.delete(id);
|
|
13489
|
+
await this.hooks.onChanged?.(id);
|
|
13490
|
+
return { ok: true };
|
|
13491
|
+
};
|
|
13492
|
+
testInstance = async (input) => {
|
|
13493
|
+
if (this.hooks.test === void 0) return {
|
|
13494
|
+
success: false,
|
|
13495
|
+
status: "unsupported",
|
|
13496
|
+
error: "This integration declares no connection test"
|
|
13497
|
+
};
|
|
13498
|
+
return this.hooks.test(input);
|
|
13499
|
+
};
|
|
13500
|
+
/** The flat provider object to hand to `registerProvider`. */
|
|
13501
|
+
toProvider() {
|
|
13502
|
+
return {
|
|
13503
|
+
describe: this.describe,
|
|
13504
|
+
instanceFormSchema: this.instanceFormSchema,
|
|
13505
|
+
listInstances: this.listInstances,
|
|
13506
|
+
getInstance: this.getInstance,
|
|
13507
|
+
createInstance: this.createInstance,
|
|
13508
|
+
updateInstance: this.updateInstance,
|
|
13509
|
+
setInstanceEnabled: this.setInstanceEnabled,
|
|
13510
|
+
deleteInstance: this.deleteInstance,
|
|
13511
|
+
testInstance: this.testInstance
|
|
13512
|
+
};
|
|
13513
|
+
}
|
|
13514
|
+
};
|
|
13262
13515
|
var EncodeProfileSchema = object({
|
|
13263
13516
|
video: object({
|
|
13264
13517
|
codec: _enum([
|
|
@@ -48712,49 +48965,6 @@ function actuator(input) {
|
|
|
48712
48965
|
};
|
|
48713
48966
|
}
|
|
48714
48967
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
48715
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
48716
|
-
"no-power-source",
|
|
48717
|
-
"switch-missing",
|
|
48718
|
-
"switch-offline",
|
|
48719
|
-
"switch-unknown",
|
|
48720
|
-
"restart-in-progress",
|
|
48721
|
-
"switch-call-failed"
|
|
48722
|
-
]);
|
|
48723
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
48724
|
-
accepted: literal(false),
|
|
48725
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
48726
|
-
/** One line for the operator, naming the switch. */
|
|
48727
|
-
message: string()
|
|
48728
|
-
})]);
|
|
48729
|
-
var PowerSourceInfoSchema = object({
|
|
48730
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
48731
|
-
linked: boolean(),
|
|
48732
|
-
switchDeviceId: number().nullable(),
|
|
48733
|
-
switchName: string().nullable(),
|
|
48734
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
48735
|
-
switchOn: boolean().nullable(),
|
|
48736
|
-
switchOnline: boolean().nullable(),
|
|
48737
|
-
restartInProgress: boolean(),
|
|
48738
|
-
offSeconds: number().int()
|
|
48739
|
-
});
|
|
48740
|
-
object({
|
|
48741
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
48742
|
-
switchOn: boolean().nullable(),
|
|
48743
|
-
offSeconds: number().int(),
|
|
48744
|
-
lastFetchedAt: number()
|
|
48745
|
-
});
|
|
48746
|
-
DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater;
|
|
48747
|
-
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
48748
|
-
systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48749
|
-
kind: "mutation",
|
|
48750
|
-
auth: "admin"
|
|
48751
|
-
}), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48752
|
-
kind: "mutation",
|
|
48753
|
-
auth: "admin"
|
|
48754
|
-
}), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
48755
|
-
kind: "mutation",
|
|
48756
|
-
auth: "admin"
|
|
48757
|
-
});
|
|
48758
48968
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
48759
48969
|
DeviceType.Camera;
|
|
48760
48970
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
@@ -48782,6 +48992,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
|
|
|
48782
48992
|
}).optional()
|
|
48783
48993
|
})) });
|
|
48784
48994
|
/**
|
|
48995
|
+
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
48996
|
+
* `z.infer`, they do not restate the shape.
|
|
48997
|
+
*/
|
|
48998
|
+
var OverrideSourceSchema = object({
|
|
48999
|
+
addonId: string().min(1),
|
|
49000
|
+
stableId: string().min(1),
|
|
49001
|
+
cap: string().min(1),
|
|
49002
|
+
fieldPath: string().min(1)
|
|
49003
|
+
});
|
|
49004
|
+
var IdentityTransformDraftSchema = object({ kind: literal("identity") });
|
|
49005
|
+
var InvertTransformDraftSchema = object({ kind: literal("invert") });
|
|
49006
|
+
var ThresholdTransformDraftSchema = object({
|
|
49007
|
+
kind: literal("threshold"),
|
|
49008
|
+
comparison: _enum(["gte", "lte"]),
|
|
49009
|
+
threshold: number(),
|
|
49010
|
+
startDwellMs: number().nonnegative(),
|
|
49011
|
+
stopDwellMs: number().nonnegative()
|
|
49012
|
+
});
|
|
49013
|
+
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
49014
|
+
IdentityTransformDraftSchema,
|
|
49015
|
+
InvertTransformDraftSchema,
|
|
49016
|
+
ThresholdTransformDraftSchema,
|
|
49017
|
+
object({
|
|
49018
|
+
kind: literal("linear"),
|
|
49019
|
+
scale: number(),
|
|
49020
|
+
offset: number(),
|
|
49021
|
+
clampMin: number().optional(),
|
|
49022
|
+
clampMax: number().optional()
|
|
49023
|
+
}),
|
|
49024
|
+
object({
|
|
49025
|
+
kind: literal("enum-map"),
|
|
49026
|
+
mapping: record(string(), string().nullable())
|
|
49027
|
+
}),
|
|
49028
|
+
object({ kind: literal("number") })
|
|
49029
|
+
]);
|
|
49030
|
+
var ScalarOverrideDraftSchema = object({
|
|
49031
|
+
kind: literal("scalar"),
|
|
49032
|
+
cap: string().min(1),
|
|
49033
|
+
targetId: string().min(1),
|
|
49034
|
+
source: OverrideSourceSchema,
|
|
49035
|
+
transform: CustomizeTransformDraftSchema
|
|
49036
|
+
});
|
|
49037
|
+
var KeyedArrayOverrideDraftSchema = object({
|
|
49038
|
+
kind: literal("keyedArray"),
|
|
49039
|
+
cap: string().min(1),
|
|
49040
|
+
targetId: string().min(1),
|
|
49041
|
+
key: string().min(1),
|
|
49042
|
+
label: string().min(1),
|
|
49043
|
+
fieldPath: string().min(1),
|
|
49044
|
+
source: OverrideSourceSchema,
|
|
49045
|
+
transform: CustomizeTransformDraftSchema
|
|
49046
|
+
});
|
|
49047
|
+
var PulseOverrideDraftSchema = object({
|
|
49048
|
+
kind: literal("pulse"),
|
|
49049
|
+
cap: string().min(1),
|
|
49050
|
+
targetId: string().min(1),
|
|
49051
|
+
enabled: boolean(),
|
|
49052
|
+
debounceMs: number().nonnegative(),
|
|
49053
|
+
edges: array(_enum([
|
|
49054
|
+
"rising",
|
|
49055
|
+
"button",
|
|
49056
|
+
"native-event"
|
|
49057
|
+
])).min(1).optional(),
|
|
49058
|
+
sources: array(OverrideSourceSchema).min(1)
|
|
49059
|
+
});
|
|
49060
|
+
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
49061
|
+
"in-addition",
|
|
49062
|
+
"confirm",
|
|
49063
|
+
"replace"
|
|
49064
|
+
]).optional() });
|
|
49065
|
+
discriminatedUnion("kind", [
|
|
49066
|
+
ScalarOverrideDraftSchema,
|
|
49067
|
+
KeyedArrayOverrideDraftSchema,
|
|
49068
|
+
PulseOverrideDraftSchema,
|
|
49069
|
+
object({
|
|
49070
|
+
kind: literal("span"),
|
|
49071
|
+
cap: string().min(1),
|
|
49072
|
+
targetId: string().min(1),
|
|
49073
|
+
timeoutMs: number().nonnegative(),
|
|
49074
|
+
cooldownMs: number().nonnegative().optional(),
|
|
49075
|
+
sources: array(SpanSourceDraftSchema)
|
|
49076
|
+
})
|
|
49077
|
+
]);
|
|
49078
|
+
object({
|
|
49079
|
+
targetId: string().min(1),
|
|
49080
|
+
configured: boolean(),
|
|
49081
|
+
source: _enum(["native", "assigned"]),
|
|
49082
|
+
state: _enum([
|
|
49083
|
+
"active",
|
|
49084
|
+
"stale",
|
|
49085
|
+
"released",
|
|
49086
|
+
"unavailable"
|
|
49087
|
+
]),
|
|
49088
|
+
reason: string().nullable()
|
|
49089
|
+
});
|
|
49090
|
+
/**
|
|
49091
|
+
* What a running-threshold proposal says (planning#405). The schema is the
|
|
49092
|
+
* type: the algorithm, the capability method and the UI all read `z.infer`.
|
|
49093
|
+
*
|
|
49094
|
+
* A proposal is read-only advice: it carries the numbers it was derived from
|
|
49095
|
+
* (`evidence`) so the operator can judge it, and an `insufficient` answer names
|
|
49096
|
+
* WHY with a typed reason and the catalog defaults, never an invented number.
|
|
49097
|
+
*/
|
|
49098
|
+
var RunningProposalTierSchema = _enum(["raw", "5m"]);
|
|
49099
|
+
var RunningProposalConfidenceSchema = _enum([
|
|
49100
|
+
"high",
|
|
49101
|
+
"medium",
|
|
49102
|
+
"low"
|
|
49103
|
+
]);
|
|
49104
|
+
var RunningProposalInsufficientReasonSchema = _enum([
|
|
49105
|
+
"no-history",
|
|
49106
|
+
"span-too-short",
|
|
49107
|
+
"truncated",
|
|
49108
|
+
"no-idle-floor",
|
|
49109
|
+
"no-runs",
|
|
49110
|
+
"no-separation"
|
|
49111
|
+
]);
|
|
49112
|
+
var RunningProposalReplaySchema = object({
|
|
49113
|
+
/** Intervals the real dwell expression held true over the window. */
|
|
49114
|
+
intervals: number().int().nonnegative(),
|
|
49115
|
+
/** Intervals outside every run, or a second interval inside one. */
|
|
49116
|
+
extra: number().int().nonnegative(),
|
|
49117
|
+
/** Runs without an interval of their own (none, or merged into a neighbour). */
|
|
49118
|
+
missed: number().int().nonnegative()
|
|
49119
|
+
});
|
|
49120
|
+
var RunningProposalEvidenceSchema = object({
|
|
49121
|
+
tier: RunningProposalTierSchema,
|
|
49122
|
+
spanMs: number().nonnegative(),
|
|
49123
|
+
from: number(),
|
|
49124
|
+
to: number(),
|
|
49125
|
+
points: number().int().nonnegative(),
|
|
49126
|
+
runsFound: number().int().nonnegative(),
|
|
49127
|
+
pausesFound: number().int().nonnegative(),
|
|
49128
|
+
longestPauseMs: number().nonnegative(),
|
|
49129
|
+
idleFloorW: number(),
|
|
49130
|
+
noiseCeilingW: number(),
|
|
49131
|
+
plateauW: number(),
|
|
49132
|
+
replay: RunningProposalReplaySchema
|
|
49133
|
+
});
|
|
49134
|
+
var RunningProposalInsufficientEvidenceSchema = object({
|
|
49135
|
+
spanMs: number().nonnegative(),
|
|
49136
|
+
points: number().int().nonnegative(),
|
|
49137
|
+
runsFound: number().int().nonnegative()
|
|
49138
|
+
});
|
|
49139
|
+
var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
|
|
49140
|
+
state: literal("proposal"),
|
|
49141
|
+
transform: ThresholdTransformDraftSchema,
|
|
49142
|
+
evidence: RunningProposalEvidenceSchema,
|
|
49143
|
+
confidence: RunningProposalConfidenceSchema,
|
|
49144
|
+
/** Why the confidence is not higher; empty when nothing held it back. */
|
|
49145
|
+
notes: array(string())
|
|
49146
|
+
}), object({
|
|
49147
|
+
state: literal("insufficient"),
|
|
49148
|
+
reason: RunningProposalInsufficientReasonSchema,
|
|
49149
|
+
evidence: RunningProposalInsufficientEvidenceSchema,
|
|
49150
|
+
/** The catalog defaults, offered as what stands when nothing better is known. */
|
|
49151
|
+
fallback: ThresholdTransformDraftSchema
|
|
49152
|
+
})]);
|
|
49153
|
+
/**
|
|
48785
49154
|
* The report of a one-shot "move this owner's plaintext secrets into the
|
|
48786
49155
|
* keychain" run (D878). COUNTS only - never a value, never a ref.
|
|
48787
49156
|
*
|
|
@@ -49071,6 +49440,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
49071
49440
|
kind: "mutation",
|
|
49072
49441
|
auth: "admin",
|
|
49073
49442
|
access: "view"
|
|
49443
|
+
}), method(object({
|
|
49444
|
+
deviceId: number().int(),
|
|
49445
|
+
cap: string().min(1),
|
|
49446
|
+
fieldPath: string().min(1),
|
|
49447
|
+
deviceType: _enum(DeviceType).optional()
|
|
49448
|
+
}), RunningThresholdProposalSchema, {
|
|
49449
|
+
auth: "admin",
|
|
49450
|
+
access: "view"
|
|
49074
49451
|
}), method(object({
|
|
49075
49452
|
blockId: string().min(1),
|
|
49076
49453
|
trace: AutomationRunTraceSchema.optional(),
|
|
@@ -49879,7 +50256,27 @@ method(object({
|
|
|
49879
50256
|
* exposes only the COMMON surface: link state, supported device
|
|
49880
50257
|
* kinds (so the UI can filter the device picker), and per-device
|
|
49881
50258
|
* expose/unexpose.
|
|
49882
|
-
|
|
50259
|
+
*
|
|
50260
|
+
* INSTANCES. One export addon can serve several independent targets (a
|
|
50261
|
+
* HomeKit bridge, a Home Assistant broker). `listInstances` names them.
|
|
50262
|
+
* `instanceId` on a READ is optional: absent = the whole addon (linked if any
|
|
50263
|
+
* enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
|
|
50264
|
+
* default instance's); present = that instance; an id the provider does not
|
|
50265
|
+
* serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
|
|
50266
|
+
* never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
|
|
50267
|
+
* change always names the target it changes. A single-instance provider
|
|
50268
|
+
* accepts an absent id on a read or its own id, and refuses any other.
|
|
50269
|
+
*/
|
|
50270
|
+
/** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
|
|
50271
|
+
var InstanceIdSchema$1 = string().min(1);
|
|
50272
|
+
var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
|
|
50273
|
+
/** One independent export target of an addon. */
|
|
50274
|
+
var ExportInstanceSchema = object({
|
|
50275
|
+
instanceId: InstanceIdSchema$1,
|
|
50276
|
+
name: string(),
|
|
50277
|
+
enabled: boolean(),
|
|
50278
|
+
kind: string().optional()
|
|
50279
|
+
});
|
|
49883
50280
|
var LinkStateSchema = _enum([
|
|
49884
50281
|
"unlinked",
|
|
49885
50282
|
"linked",
|
|
@@ -49930,10 +50327,14 @@ var ExposedDeviceSchema = object({
|
|
|
49930
50327
|
capabilities: array(string()).optional()
|
|
49931
50328
|
});
|
|
49932
50329
|
var ExposeInputSchema = object({
|
|
50330
|
+
instanceId: InstanceIdSchema$1,
|
|
49933
50331
|
deviceId: string(),
|
|
49934
50332
|
capabilities: array(string()).optional()
|
|
49935
50333
|
});
|
|
49936
|
-
var UnexposeInputSchema = object({
|
|
50334
|
+
var UnexposeInputSchema = object({
|
|
50335
|
+
instanceId: InstanceIdSchema$1,
|
|
50336
|
+
deviceId: string()
|
|
50337
|
+
});
|
|
49937
50338
|
/**
|
|
49938
50339
|
* What an exporter may expose besides devices (D968). An action and a
|
|
49939
50340
|
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
@@ -49962,15 +50363,21 @@ var ExposedEntitySchema = object({
|
|
|
49962
50363
|
entity: ExportEntityRefSchema,
|
|
49963
50364
|
exposedAs: string().optional()
|
|
49964
50365
|
});
|
|
49965
|
-
var ExposeEntityInputSchema = object({
|
|
49966
|
-
|
|
49967
|
-
|
|
50366
|
+
var ExposeEntityInputSchema = object({
|
|
50367
|
+
instanceId: InstanceIdSchema$1,
|
|
50368
|
+
entity: ExportEntityRefSchema
|
|
50369
|
+
});
|
|
50370
|
+
var UnexposeEntityInputSchema = object({
|
|
50371
|
+
instanceId: InstanceIdSchema$1,
|
|
50372
|
+
entity: ExportEntityRefSchema
|
|
50373
|
+
});
|
|
50374
|
+
method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
49968
50375
|
kind: "mutation",
|
|
49969
50376
|
auth: "admin"
|
|
49970
50377
|
}), method(UnexposeInputSchema, _void(), {
|
|
49971
50378
|
kind: "mutation",
|
|
49972
50379
|
auth: "admin"
|
|
49973
|
-
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(
|
|
50380
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
49974
50381
|
kind: "mutation",
|
|
49975
50382
|
auth: "admin"
|
|
49976
50383
|
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
@@ -53171,15 +53578,15 @@ var FanPresetCaptureResultSchema = object({
|
|
|
53171
53578
|
items: array(FanCaptureItemSchema),
|
|
53172
53579
|
omitted: array(FanCaptureOmissionSchema)
|
|
53173
53580
|
});
|
|
53174
|
-
var deviceInput$
|
|
53581
|
+
var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
|
|
53175
53582
|
DeviceType.Fan;
|
|
53176
|
-
method(deviceInput$
|
|
53583
|
+
method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
|
|
53177
53584
|
kind: "mutation",
|
|
53178
53585
|
auth: "admin"
|
|
53179
|
-
}), method(deviceInput$
|
|
53586
|
+
}), method(deviceInput$2.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
|
|
53180
53587
|
kind: "mutation",
|
|
53181
53588
|
auth: "protected"
|
|
53182
|
-
}), method(deviceInput$
|
|
53589
|
+
}), method(deviceInput$2.extend({
|
|
53183
53590
|
fields: array(FanCaptureFieldSchema).optional(),
|
|
53184
53591
|
steps: array(ActionStepSchema).max(20).default([])
|
|
53185
53592
|
}), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
@@ -53703,13 +54110,50 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
53703
54110
|
*/
|
|
53704
54111
|
var ConfigSchemaPassthrough$3 = custom();
|
|
53705
54112
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
54113
|
+
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
54114
|
+
/**
|
|
54115
|
+
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
54116
|
+
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
54117
|
+
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
54118
|
+
* interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
|
|
54119
|
+
* instance id.
|
|
54120
|
+
*/
|
|
54121
|
+
var IntegrationInstanceKindSchema = object({
|
|
54122
|
+
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
54123
|
+
id: string().min(1),
|
|
54124
|
+
label: string().min(1),
|
|
54125
|
+
/** The kind signs in through a server-held interactive session. */
|
|
54126
|
+
signIn: object({
|
|
54127
|
+
basePath: string().min(1),
|
|
54128
|
+
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
54129
|
+
}).optional(),
|
|
54130
|
+
/** Relative hub path to start an interactive account link of an instance of this kind. */
|
|
54131
|
+
connectPath: string().min(1).optional(),
|
|
54132
|
+
/** Relative hub path to disconnect that account. */
|
|
54133
|
+
disconnectPath: string().min(1).optional()
|
|
54134
|
+
});
|
|
53706
54135
|
var IntegrationInstanceDescriptionSchema = object({
|
|
53707
54136
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
53708
|
-
instanceMode:
|
|
54137
|
+
instanceMode: IntegrationInstanceModeSchema,
|
|
53709
54138
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
53710
54139
|
kind: string(),
|
|
53711
54140
|
/** True when `testInstance` really probes the remote. */
|
|
53712
|
-
canTest: boolean()
|
|
54141
|
+
canTest: boolean(),
|
|
54142
|
+
/**
|
|
54143
|
+
* The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
|
|
54144
|
+
* Add flow asks which first and sends it as `createInstance.kind`.
|
|
54145
|
+
*/
|
|
54146
|
+
kinds: array(IntegrationInstanceKindSchema).optional(),
|
|
54147
|
+
/**
|
|
54148
|
+
* The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
|
|
54149
|
+
* devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
|
|
54150
|
+
*/
|
|
54151
|
+
allowRestoreId: boolean().optional(),
|
|
54152
|
+
/**
|
|
54153
|
+
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
54154
|
+
* send an id (the UI then mints an opaque one).
|
|
54155
|
+
*/
|
|
54156
|
+
mintsId: boolean().optional()
|
|
53713
54157
|
});
|
|
53714
54158
|
var IntegrationInstanceStatusSchema = object({
|
|
53715
54159
|
state: _enum([
|
|
@@ -53718,7 +54162,9 @@ var IntegrationInstanceStatusSchema = object({
|
|
|
53718
54162
|
"error",
|
|
53719
54163
|
"unknown"
|
|
53720
54164
|
]),
|
|
53721
|
-
detail: string().optional()
|
|
54165
|
+
detail: string().optional(),
|
|
54166
|
+
/** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
|
|
54167
|
+
label: string().optional()
|
|
53722
54168
|
});
|
|
53723
54169
|
var IntegrationInstanceSchema = object({
|
|
53724
54170
|
id: IntegrationInstanceIdSchema,
|
|
@@ -53730,9 +54176,13 @@ var IntegrationInstanceSchema = object({
|
|
|
53730
54176
|
* (`enabled !== false`, so a row written before the field existed is on).
|
|
53731
54177
|
*/
|
|
53732
54178
|
enabled: boolean(),
|
|
54179
|
+
/** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
|
|
54180
|
+
kind: string().optional(),
|
|
53733
54181
|
/** Values for the instance form; a secret is a `SecretFieldView`, never text. */
|
|
53734
54182
|
formValues: record(string(), unknown()),
|
|
53735
|
-
status: IntegrationInstanceStatusSchema.optional()
|
|
54183
|
+
status: IntegrationInstanceStatusSchema.optional(),
|
|
54184
|
+
/** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
|
|
54185
|
+
lastCheckedAt: number().optional()
|
|
53736
54186
|
});
|
|
53737
54187
|
var IntegrationInstancePatchSchema = object({
|
|
53738
54188
|
id: IntegrationInstanceIdSchema,
|
|
@@ -53740,31 +54190,57 @@ var IntegrationInstancePatchSchema = object({
|
|
|
53740
54190
|
/** Omitted / '' / redaction token keeps a secret; `{ $secretClear: true }` clears it. */
|
|
53741
54191
|
values: record(string(), unknown())
|
|
53742
54192
|
});
|
|
53743
|
-
|
|
53744
|
-
|
|
53745
|
-
|
|
53746
|
-
|
|
53747
|
-
|
|
53748
|
-
|
|
53749
|
-
|
|
53750
|
-
|
|
53751
|
-
|
|
53752
|
-
|
|
53753
|
-
|
|
53754
|
-
|
|
53755
|
-
|
|
53756
|
-
|
|
53757
|
-
|
|
53758
|
-
|
|
53759
|
-
|
|
53760
|
-
|
|
53761
|
-
}),
|
|
53762
|
-
|
|
53763
|
-
|
|
53764
|
-
}),
|
|
53765
|
-
|
|
53766
|
-
|
|
53767
|
-
|
|
54193
|
+
var integrationInstancesCapability = {
|
|
54194
|
+
name: "integration-instances",
|
|
54195
|
+
scope: "system",
|
|
54196
|
+
mode: "collection",
|
|
54197
|
+
methods: {
|
|
54198
|
+
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
54199
|
+
/** The form; `id: null` is the create form. */
|
|
54200
|
+
instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
54201
|
+
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
54202
|
+
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
54203
|
+
/**
|
|
54204
|
+
* Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
|
|
54205
|
+
* without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
|
|
54206
|
+
*/
|
|
54207
|
+
createInstance: method(object({
|
|
54208
|
+
id: IntegrationInstanceIdSchema.optional(),
|
|
54209
|
+
name: string().min(1),
|
|
54210
|
+
kind: string().min(1).optional()
|
|
54211
|
+
}), object({ id: string() }), {
|
|
54212
|
+
kind: "mutation",
|
|
54213
|
+
auth: "admin"
|
|
54214
|
+
}),
|
|
54215
|
+
updateInstance: method(IntegrationInstancePatchSchema, object({ ok: literal(true) }), {
|
|
54216
|
+
kind: "mutation",
|
|
54217
|
+
auth: "admin"
|
|
54218
|
+
}),
|
|
54219
|
+
/**
|
|
54220
|
+
* Switch ONE instance on or off. A row control, not a form field: `updateInstance.values` is the
|
|
54221
|
+
* form patch (D62, D996). An off instance keeps its record and secrets and does no work.
|
|
54222
|
+
*/
|
|
54223
|
+
setInstanceEnabled: method(object({
|
|
54224
|
+
id: IntegrationInstanceIdSchema,
|
|
54225
|
+
enabled: boolean()
|
|
54226
|
+
}), object({ ok: literal(true) }), {
|
|
54227
|
+
kind: "mutation",
|
|
54228
|
+
auth: "admin"
|
|
54229
|
+
}),
|
|
54230
|
+
/** Delete the record and its keychain refs together. */
|
|
54231
|
+
deleteInstance: method(object({ id: IntegrationInstanceIdSchema }), object({ ok: literal(true) }), {
|
|
54232
|
+
kind: "mutation",
|
|
54233
|
+
auth: "admin"
|
|
54234
|
+
}),
|
|
54235
|
+
testInstance: optionalMethod(object({
|
|
54236
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
54237
|
+
values: record(string(), unknown())
|
|
54238
|
+
}), TestConnectionResultSchema$1, {
|
|
54239
|
+
kind: "mutation",
|
|
54240
|
+
auth: "admin"
|
|
54241
|
+
})
|
|
54242
|
+
}
|
|
54243
|
+
};
|
|
53768
54244
|
/**
|
|
53769
54245
|
* Two-way audio / talk-back cap. Clients open a WebRTC audio-only
|
|
53770
54246
|
* session (server-initiated offer pattern, identical to the main
|
|
@@ -55563,6 +56039,11 @@ var LogEntrySchema = object({
|
|
|
55563
56039
|
* flight, and not asking for the next slice ends the scan.
|
|
55564
56040
|
*/
|
|
55565
56041
|
var LogReadQuerySchema = object({
|
|
56042
|
+
/**
|
|
56043
|
+
* Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
|
|
56044
|
+
* the provider's first readable one, so a caller written before instances existed is unchanged.
|
|
56045
|
+
*/
|
|
56046
|
+
instanceId: string().min(1).max(200).optional(),
|
|
55566
56047
|
/** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
|
|
55567
56048
|
requestId: string().min(1).max(64).optional(),
|
|
55568
56049
|
/** The device the line is about (`tags.deviceId`, numeric). */
|
|
@@ -55625,6 +56106,8 @@ var LogReadResultSchema = object({
|
|
|
55625
56106
|
/** True when `cancelRead` ended this slice early. */
|
|
55626
56107
|
cancelled: boolean().optional()
|
|
55627
56108
|
});
|
|
56109
|
+
/** Names one destination instance; absent ⇒ the provider's first readable one. */
|
|
56110
|
+
var LogInstanceSelectorSchema = object({ instanceId: string().min(1).max(200).optional() });
|
|
55628
56111
|
var LogReadCapabilitiesSchema = object({
|
|
55629
56112
|
/** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
|
|
55630
56113
|
readable: boolean(),
|
|
@@ -55643,7 +56126,7 @@ var LogReadCapabilitiesSchema = object({
|
|
|
55643
56126
|
/** How far back the store reaches, when the provider knows (days). */
|
|
55644
56127
|
retentionDays: number().nullable()
|
|
55645
56128
|
});
|
|
55646
|
-
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(
|
|
56129
|
+
method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(LogInstanceSelectorSchema, LogReadCapabilitiesSchema, { auth: "admin" }), optionalMethod(LogReadQuerySchema, LogReadResultSchema, { auth: "admin" }), optionalMethod(object({ requestId: string().min(1).max(64) }), object({ cancelled: boolean() }), {
|
|
55647
56130
|
kind: "mutation",
|
|
55648
56131
|
auth: "admin"
|
|
55649
56132
|
});
|
|
@@ -67215,6 +67698,49 @@ object({
|
|
|
67215
67698
|
DeviceType.PetFeeder,
|
|
67216
67699
|
DeviceType.Heater
|
|
67217
67700
|
].filter((type) => type !== DeviceType.Sensor);
|
|
67701
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
67702
|
+
"no-power-source",
|
|
67703
|
+
"switch-missing",
|
|
67704
|
+
"switch-offline",
|
|
67705
|
+
"switch-unknown",
|
|
67706
|
+
"restart-in-progress",
|
|
67707
|
+
"switch-call-failed"
|
|
67708
|
+
]);
|
|
67709
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
67710
|
+
accepted: literal(false),
|
|
67711
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
67712
|
+
/** One line for the operator, naming the switch. */
|
|
67713
|
+
message: string()
|
|
67714
|
+
})]);
|
|
67715
|
+
var PowerSourceInfoSchema = object({
|
|
67716
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
67717
|
+
linked: boolean(),
|
|
67718
|
+
switchDeviceId: number().nullable(),
|
|
67719
|
+
switchName: string().nullable(),
|
|
67720
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
67721
|
+
switchOn: boolean().nullable(),
|
|
67722
|
+
switchOnline: boolean().nullable(),
|
|
67723
|
+
restartInProgress: boolean(),
|
|
67724
|
+
offSeconds: number().int()
|
|
67725
|
+
});
|
|
67726
|
+
object({
|
|
67727
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
67728
|
+
switchOn: boolean().nullable(),
|
|
67729
|
+
offSeconds: number().int(),
|
|
67730
|
+
lastFetchedAt: number()
|
|
67731
|
+
});
|
|
67732
|
+
DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater;
|
|
67733
|
+
var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
|
|
67734
|
+
systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67735
|
+
kind: "mutation",
|
|
67736
|
+
auth: "admin"
|
|
67737
|
+
}), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67738
|
+
kind: "mutation",
|
|
67739
|
+
auth: "admin"
|
|
67740
|
+
}), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
67741
|
+
kind: "mutation",
|
|
67742
|
+
auth: "admin"
|
|
67743
|
+
});
|
|
67218
67744
|
/**
|
|
67219
67745
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
67220
67746
|
* on `DeviceType.Presence`. Read-only — no setters: presence is
|
|
@@ -75281,6 +75807,25 @@ function buildDescriptor(capName, kind) {
|
|
|
75281
75807
|
* contributes one entry per bound cap present in this map.
|
|
75282
75808
|
*/
|
|
75283
75809
|
var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
|
|
75810
|
+
/**
|
|
75811
|
+
* What a remote key is CALLED and what it LOOKS like, for every role (D963),
|
|
75812
|
+
* shared by the admin UI and the viewer. A role is data on the wire; each
|
|
75813
|
+
* client resolves its name from `label` (English and Italian side by side) and
|
|
75814
|
+
* its glyph from `icon`, a lucide id from the shared icon catalogue — the
|
|
75815
|
+
* server sends an id, a client maps it to its own icon set.
|
|
75816
|
+
*
|
|
75817
|
+
* `legend` is text printed on the key INSTEAD of a glyph (a digit has no
|
|
75818
|
+
* pictogram): a client that can draw text shows it, one that cannot falls back
|
|
75819
|
+
* to `icon`.
|
|
75820
|
+
*/
|
|
75821
|
+
function digit(n) {
|
|
75822
|
+
return {
|
|
75823
|
+
label: t(n, n),
|
|
75824
|
+
icon: "hash",
|
|
75825
|
+
legend: n
|
|
75826
|
+
};
|
|
75827
|
+
}
|
|
75828
|
+
digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
|
|
75284
75829
|
DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
|
|
75285
75830
|
var btn = (idSuffix, row, col, init) => ({
|
|
75286
75831
|
idSuffix,
|
|
@@ -75408,6 +75953,34 @@ discriminatedUnion("kind", [object({
|
|
|
75408
75953
|
kind: literal("pronto"),
|
|
75409
75954
|
code: string()
|
|
75410
75955
|
})]);
|
|
75956
|
+
/**
|
|
75957
|
+
* Propose a running threshold from a device's own power history (planning#405).
|
|
75958
|
+
*
|
|
75959
|
+
* Pure and deterministic: the same series gives the same answer. The series is
|
|
75960
|
+
* a HELD-VALUE step series — a point's value stands until the next point — which
|
|
75961
|
+
* is how device-history stores a gauge, so "time at a level" is exact, not a
|
|
75962
|
+
* count of samples.
|
|
75963
|
+
*
|
|
75964
|
+
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
75965
|
+
* near F. A device that is not near one level most of the time has no floor.
|
|
75966
|
+
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
75967
|
+
* 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
|
|
75968
|
+
* the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
|
|
75969
|
+
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
75970
|
+
* grid a person would type; stop dwell covers the longest pause; start dwell
|
|
75971
|
+
* covers the longest standby blip above T.
|
|
75972
|
+
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
75973
|
+
* run must hold exactly one true interval and nothing may flap outside.
|
|
75974
|
+
*/
|
|
75975
|
+
var SECOND_MS = 1e3;
|
|
75976
|
+
var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
75977
|
+
24 * (60 * MINUTE_MS$1);
|
|
75978
|
+
10 * MINUTE_MS$1;
|
|
75979
|
+
30 * SECOND_MS;
|
|
75980
|
+
60 * SECOND_MS;
|
|
75981
|
+
20 * MINUTE_MS$1;
|
|
75982
|
+
10 * SECOND_MS;
|
|
75983
|
+
10 * SECOND_MS;
|
|
75411
75984
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
75412
75985
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
75413
75986
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
@@ -76949,6 +77522,13 @@ Object.freeze({
|
|
|
76949
77522
|
access: "create",
|
|
76950
77523
|
role: "admin"
|
|
76951
77524
|
},
|
|
77525
|
+
"coreBlocks.proposeRunningThreshold": {
|
|
77526
|
+
capName: "core-blocks",
|
|
77527
|
+
capScope: "system",
|
|
77528
|
+
addonId: null,
|
|
77529
|
+
access: "view",
|
|
77530
|
+
role: "admin"
|
|
77531
|
+
},
|
|
76952
77532
|
"coreBlocks.reportAutomationRun": {
|
|
76953
77533
|
capName: "core-blocks",
|
|
76954
77534
|
capScope: "system",
|
|
@@ -77460,6 +78040,13 @@ Object.freeze({
|
|
|
77460
78040
|
access: "view",
|
|
77461
78041
|
role: "admin"
|
|
77462
78042
|
},
|
|
78043
|
+
"deviceExport.listInstances": {
|
|
78044
|
+
capName: "device-export",
|
|
78045
|
+
capScope: "system",
|
|
78046
|
+
addonId: null,
|
|
78047
|
+
access: "view",
|
|
78048
|
+
role: "admin"
|
|
78049
|
+
},
|
|
77463
78050
|
"deviceExport.listSupportedDeviceKinds": {
|
|
77464
78051
|
capName: "device-export",
|
|
77465
78052
|
capScope: "system",
|
|
@@ -86407,6 +86994,11 @@ Object.freeze({
|
|
|
86407
86994
|
form: "single",
|
|
86408
86995
|
optional: false
|
|
86409
86996
|
}],
|
|
86997
|
+
"coreBlocks.proposeRunningThreshold": [{
|
|
86998
|
+
name: "deviceId",
|
|
86999
|
+
form: "single",
|
|
87000
|
+
optional: false
|
|
87001
|
+
}],
|
|
86410
87002
|
"cover.close": [{
|
|
86411
87003
|
name: "deviceId",
|
|
86412
87004
|
form: "single",
|
|
@@ -89070,99 +89662,6 @@ Object.freeze({
|
|
|
89070
89662
|
"network-access": "ingress",
|
|
89071
89663
|
"smtp-provider": "email"
|
|
89072
89664
|
});
|
|
89073
|
-
/**
|
|
89074
|
-
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
89075
|
-
* `z.infer`, they do not restate the shape.
|
|
89076
|
-
*/
|
|
89077
|
-
var OverrideSourceSchema = object({
|
|
89078
|
-
addonId: string().min(1),
|
|
89079
|
-
stableId: string().min(1),
|
|
89080
|
-
cap: string().min(1),
|
|
89081
|
-
fieldPath: string().min(1)
|
|
89082
|
-
});
|
|
89083
|
-
var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
|
|
89084
|
-
object({ kind: literal("identity") }),
|
|
89085
|
-
object({ kind: literal("invert") }),
|
|
89086
|
-
object({
|
|
89087
|
-
kind: literal("threshold"),
|
|
89088
|
-
comparison: _enum(["gte", "lte"]),
|
|
89089
|
-
threshold: number(),
|
|
89090
|
-
startDwellMs: number().nonnegative(),
|
|
89091
|
-
stopDwellMs: number().nonnegative()
|
|
89092
|
-
}),
|
|
89093
|
-
object({
|
|
89094
|
-
kind: literal("linear"),
|
|
89095
|
-
scale: number(),
|
|
89096
|
-
offset: number(),
|
|
89097
|
-
clampMin: number().optional(),
|
|
89098
|
-
clampMax: number().optional()
|
|
89099
|
-
}),
|
|
89100
|
-
object({
|
|
89101
|
-
kind: literal("enum-map"),
|
|
89102
|
-
mapping: record(string(), string().nullable())
|
|
89103
|
-
}),
|
|
89104
|
-
object({ kind: literal("number") })
|
|
89105
|
-
]);
|
|
89106
|
-
var ScalarOverrideDraftSchema = object({
|
|
89107
|
-
kind: literal("scalar"),
|
|
89108
|
-
cap: string().min(1),
|
|
89109
|
-
targetId: string().min(1),
|
|
89110
|
-
source: OverrideSourceSchema,
|
|
89111
|
-
transform: CustomizeTransformDraftSchema
|
|
89112
|
-
});
|
|
89113
|
-
var KeyedArrayOverrideDraftSchema = object({
|
|
89114
|
-
kind: literal("keyedArray"),
|
|
89115
|
-
cap: string().min(1),
|
|
89116
|
-
targetId: string().min(1),
|
|
89117
|
-
key: string().min(1),
|
|
89118
|
-
label: string().min(1),
|
|
89119
|
-
fieldPath: string().min(1),
|
|
89120
|
-
source: OverrideSourceSchema,
|
|
89121
|
-
transform: CustomizeTransformDraftSchema
|
|
89122
|
-
});
|
|
89123
|
-
var PulseOverrideDraftSchema = object({
|
|
89124
|
-
kind: literal("pulse"),
|
|
89125
|
-
cap: string().min(1),
|
|
89126
|
-
targetId: string().min(1),
|
|
89127
|
-
enabled: boolean(),
|
|
89128
|
-
debounceMs: number().nonnegative(),
|
|
89129
|
-
edges: array(_enum([
|
|
89130
|
-
"rising",
|
|
89131
|
-
"button",
|
|
89132
|
-
"native-event"
|
|
89133
|
-
])).min(1).optional(),
|
|
89134
|
-
sources: array(OverrideSourceSchema).min(1)
|
|
89135
|
-
});
|
|
89136
|
-
var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
|
|
89137
|
-
"in-addition",
|
|
89138
|
-
"confirm",
|
|
89139
|
-
"replace"
|
|
89140
|
-
]).optional() });
|
|
89141
|
-
discriminatedUnion("kind", [
|
|
89142
|
-
ScalarOverrideDraftSchema,
|
|
89143
|
-
KeyedArrayOverrideDraftSchema,
|
|
89144
|
-
PulseOverrideDraftSchema,
|
|
89145
|
-
object({
|
|
89146
|
-
kind: literal("span"),
|
|
89147
|
-
cap: string().min(1),
|
|
89148
|
-
targetId: string().min(1),
|
|
89149
|
-
timeoutMs: number().nonnegative(),
|
|
89150
|
-
cooldownMs: number().nonnegative().optional(),
|
|
89151
|
-
sources: array(SpanSourceDraftSchema)
|
|
89152
|
-
})
|
|
89153
|
-
]);
|
|
89154
|
-
object({
|
|
89155
|
-
targetId: string().min(1),
|
|
89156
|
-
configured: boolean(),
|
|
89157
|
-
source: _enum(["native", "assigned"]),
|
|
89158
|
-
state: _enum([
|
|
89159
|
-
"active",
|
|
89160
|
-
"stale",
|
|
89161
|
-
"released",
|
|
89162
|
-
"unavailable"
|
|
89163
|
-
]),
|
|
89164
|
-
reason: string().nullable()
|
|
89165
|
-
});
|
|
89166
89665
|
var G711_SCALE_CORRECTION_DB = {
|
|
89167
89666
|
PCMU: 20 * Math.log10(4),
|
|
89168
89667
|
PCMA: 20 * Math.log10(8)
|
|
@@ -91546,6 +92045,87 @@ var googleBrokersEntryKind = defineEntryKind("brokers", {
|
|
|
91546
92045
|
writeTrigger: "an operator adds or edits a Google OAuth broker"
|
|
91547
92046
|
});
|
|
91548
92047
|
//#endregion
|
|
92048
|
+
//#region src/google-instances.ts
|
|
92049
|
+
/**
|
|
92050
|
+
* The Google accounts as `integration-instances` (planning#408 P3): one instance per broker
|
|
92051
|
+
* record, over the SAME `brokers` records and ids the `broker` cap serves (`google_NNN`), so
|
|
92052
|
+
* nothing moves. The OAuth session lives beside the record (`oauthAccounts`), so an instance's
|
|
92053
|
+
* status is the registry's answer and a change re-seeds the registry.
|
|
92054
|
+
*/
|
|
92055
|
+
function asTestResult(r) {
|
|
92056
|
+
return r.ok ? {
|
|
92057
|
+
success: true,
|
|
92058
|
+
status: "validated",
|
|
92059
|
+
...r.latencyMs !== void 0 ? { latencyMs: r.latencyMs } : {}
|
|
92060
|
+
} : {
|
|
92061
|
+
success: false,
|
|
92062
|
+
status: "rejected",
|
|
92063
|
+
error: r.error ?? "Google rejected the check"
|
|
92064
|
+
};
|
|
92065
|
+
}
|
|
92066
|
+
function buildGoogleInstances(records, deps) {
|
|
92067
|
+
return new RecordIntegrationInstances(records, googleBrokersEntryKind, {
|
|
92068
|
+
description: {
|
|
92069
|
+
instanceMode: "multiple",
|
|
92070
|
+
kind: "account",
|
|
92071
|
+
canTest: true,
|
|
92072
|
+
kinds: [{
|
|
92073
|
+
id: GOOGLE_BROKER_KIND,
|
|
92074
|
+
label: GOOGLE_BROKER_KIND_LABEL,
|
|
92075
|
+
connectPath: deps.connectPath,
|
|
92076
|
+
disconnectPath: deps.disconnectPath
|
|
92077
|
+
}],
|
|
92078
|
+
allowRestoreId: true
|
|
92079
|
+
},
|
|
92080
|
+
formSchema: () => {
|
|
92081
|
+
const full = buildGoogleBrokerSettingsSchema(deps.publicOrigin());
|
|
92082
|
+
return {
|
|
92083
|
+
...full,
|
|
92084
|
+
sections: full.sections.filter((s) => s.id !== "google-calendars-import")
|
|
92085
|
+
};
|
|
92086
|
+
},
|
|
92087
|
+
initialBody: (name) => ({
|
|
92088
|
+
name,
|
|
92089
|
+
clientId: "",
|
|
92090
|
+
clientSecret: "",
|
|
92091
|
+
calendarFilter: "",
|
|
92092
|
+
fromAddress: "",
|
|
92093
|
+
fromName: "",
|
|
92094
|
+
calendarIds: []
|
|
92095
|
+
}),
|
|
92096
|
+
mintId: deps.mintId,
|
|
92097
|
+
kindOf: () => GOOGLE_BROKER_KIND,
|
|
92098
|
+
health: (_body, id) => {
|
|
92099
|
+
const session = deps.session(id);
|
|
92100
|
+
if (session === "connected") return { status: {
|
|
92101
|
+
state: "ok",
|
|
92102
|
+
label: "Signed in"
|
|
92103
|
+
} };
|
|
92104
|
+
if (session === "reauth") return { status: {
|
|
92105
|
+
state: "error",
|
|
92106
|
+
label: "Sign in again",
|
|
92107
|
+
detail: "Google session expired"
|
|
92108
|
+
} };
|
|
92109
|
+
return { status: {
|
|
92110
|
+
state: "unknown",
|
|
92111
|
+
label: "Not signed in"
|
|
92112
|
+
} };
|
|
92113
|
+
},
|
|
92114
|
+
configured: (body) => body.clientId.length > 0 && (body.clientSecret ?? "").length > 0,
|
|
92115
|
+
onChanged: deps.onChanged,
|
|
92116
|
+
test: async ({ id, values }) => {
|
|
92117
|
+
const clientId = values["clientId"];
|
|
92118
|
+
const secret = values["clientSecret"];
|
|
92119
|
+
if (typeof clientId === "string" && clientId.length > 0 && typeof secret === "string" && secret.length > 0 && secret !== "__camstack_redacted__") return asTestResult(await deps.testClient(clientId, secret));
|
|
92120
|
+
if (id === null) return asTestResult({
|
|
92121
|
+
ok: false,
|
|
92122
|
+
error: "Enter the Client ID and Client secret first"
|
|
92123
|
+
});
|
|
92124
|
+
return asTestResult(await deps.testSaved(id));
|
|
92125
|
+
}
|
|
92126
|
+
});
|
|
92127
|
+
}
|
|
92128
|
+
//#endregion
|
|
91549
92129
|
//#region src/migrations/calendar-ids-to-brokers.ts
|
|
91550
92130
|
/**
|
|
91551
92131
|
* The calendars a Google account imports are a fact of THAT account (D985): each broker record
|
|
@@ -91776,6 +92356,7 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
91776
92356
|
oauthAccountsHandle = null;
|
|
91777
92357
|
registry = new GoogleBrokerRegistry();
|
|
91778
92358
|
brokerStore = null;
|
|
92359
|
+
instances = null;
|
|
91779
92360
|
activeBrokers = [];
|
|
91780
92361
|
/** CSRF state → { brokerId, issuedAt } for the in-flight OAuth consents. */
|
|
91781
92362
|
pendingStates = /* @__PURE__ */ new Map();
|
|
@@ -91823,6 +92404,7 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
91823
92404
|
if (records === void 0) throw new Error("google-services: ctx.records is required (hub-only addon)");
|
|
91824
92405
|
await records.declare({ brokers: googleBrokersEntryKind });
|
|
91825
92406
|
this.brokerStore = GoogleBrokerStore.of(records);
|
|
92407
|
+
this.instances = this.buildInstances(records);
|
|
91826
92408
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
91827
92409
|
if ((await this.brokerStore.listResolved()).length === 0 && this.config.brokers.length > 0) {
|
|
91828
92410
|
await this.brokerStore.replaceAll(this.config.brokers);
|
|
@@ -91885,6 +92467,10 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
91885
92467
|
capability: brokerCapability,
|
|
91886
92468
|
provider: this.buildBrokerProvider()
|
|
91887
92469
|
},
|
|
92470
|
+
{
|
|
92471
|
+
capability: integrationInstancesCapability,
|
|
92472
|
+
provider: this.requireInstances().toProvider()
|
|
92473
|
+
},
|
|
91888
92474
|
{
|
|
91889
92475
|
capability: calendarSourceCapability,
|
|
91890
92476
|
provider: calendarProvider
|
|
@@ -91904,6 +92490,35 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
91904
92490
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
91905
92491
|
this.registry.seedFrom(this.activeBrokers, accounts);
|
|
91906
92492
|
}
|
|
92493
|
+
requireInstances() {
|
|
92494
|
+
if (this.instances === null) throw new Error("google-services: instances used before initialize");
|
|
92495
|
+
return this.instances;
|
|
92496
|
+
}
|
|
92497
|
+
/** The accounts as `integration-instances`, over the same `brokers` records (planning#408 P3). */
|
|
92498
|
+
buildInstances(records) {
|
|
92499
|
+
const brokerProvider = this.buildBrokerProvider();
|
|
92500
|
+
return buildGoogleInstances(records, {
|
|
92501
|
+
connectPath: `/addon/${ADDON_ID}${START_PATH}?brokerId={{brokerId}}`,
|
|
92502
|
+
disconnectPath: `/addon/${ADDON_ID}${DISCONNECT_PATH}?brokerId={{brokerId}}`,
|
|
92503
|
+
publicOrigin: () => this.publicOrigin,
|
|
92504
|
+
mintId: () => this.registry.allocateId(),
|
|
92505
|
+
session: (id) => this.registry.isConnected(id) ? "connected" : this.registry.needsReauth(id) ? "reauth" : "none",
|
|
92506
|
+
onChanged: async () => {
|
|
92507
|
+
const accounts = this.registry.listOauthAccounts();
|
|
92508
|
+
this.activeBrokers = await this.loadPersistedBrokers();
|
|
92509
|
+
this.registry.seedFrom(this.activeBrokers, accounts);
|
|
92510
|
+
await this.persistOauthAccounts();
|
|
92511
|
+
},
|
|
92512
|
+
testSaved: async (id) => brokerProvider.testConnection({ id }),
|
|
92513
|
+
testClient: async (clientId, clientSecret) => brokerProvider.testSettings({
|
|
92514
|
+
kind: GOOGLE_BROKER_KIND,
|
|
92515
|
+
settings: {
|
|
92516
|
+
clientId,
|
|
92517
|
+
clientSecret
|
|
92518
|
+
}
|
|
92519
|
+
})
|
|
92520
|
+
});
|
|
92521
|
+
}
|
|
91907
92522
|
buildBrokerProvider() {
|
|
91908
92523
|
const ownerAddonId = this.ctx.id;
|
|
91909
92524
|
const owns = (id) => this.registry.getEntry(id) !== null;
|