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