@camstack/addon-google-services 0.2.38 → 0.2.40
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 +13 -0
- package/dist/google-services.addon.js +1410 -963
- package/dist/google-services.addon.mjs +1411 -964
- package/i18n/en.json +0 -4
- package/i18n/it.json +0 -4
- package/package.json +2 -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-BX_MqzUe.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).
|
|
@@ -6585,7 +6585,7 @@ var NOT_RUN = {
|
|
|
6585
6585
|
* throws: an unreachable or refusing engine is logged and reported `declared: false`,
|
|
6586
6586
|
* because the hub (and this addon's boot) must not hang on a migration.
|
|
6587
6587
|
*/
|
|
6588
|
-
async function runAddonMigrations(host, migrations, logger) {
|
|
6588
|
+
async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
6589
6589
|
const first = migrations[0];
|
|
6590
6590
|
if (first === void 0) return NOT_RUN;
|
|
6591
6591
|
const owner = first.meta.owner;
|
|
@@ -6614,7 +6614,9 @@ async function runAddonMigrations(host, migrations, logger) {
|
|
|
6614
6614
|
else if (status === "blocked") blocked.push(id);
|
|
6615
6615
|
else skipped.push(id);
|
|
6616
6616
|
};
|
|
6617
|
+
const selected = options.only === void 0 ? null : new Set(options.only);
|
|
6617
6618
|
for (const step of declaration.steps) {
|
|
6619
|
+
if (selected !== null && !selected.has(step.id)) continue;
|
|
6618
6620
|
const bound = byId.get(step.id);
|
|
6619
6621
|
if (bound === void 0 || step.action === "skip") {
|
|
6620
6622
|
skipped.push(step.id);
|
|
@@ -6792,6 +6794,12 @@ function projectStore(store, perNodeKeys, nodeId) {
|
|
|
6792
6794
|
}
|
|
6793
6795
|
return out;
|
|
6794
6796
|
}
|
|
6797
|
+
/** Every scope, in the order the guard and the docs list them. */
|
|
6798
|
+
var SCOPED_SETTINGS_SCOPES = [
|
|
6799
|
+
"cluster",
|
|
6800
|
+
"device-defaults",
|
|
6801
|
+
"node"
|
|
6802
|
+
];
|
|
6795
6803
|
/**
|
|
6796
6804
|
* The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
|
|
6797
6805
|
*
|
|
@@ -7606,6 +7614,28 @@ var SECRET_RETRY_MAX_MS = 5 * 6e4;
|
|
|
7606
7614
|
/** Coalesces a burst of `keychain.changed` events into one re-resolve. */
|
|
7607
7615
|
var SECRET_CHANGE_DEBOUNCE_MS = 250;
|
|
7608
7616
|
/**
|
|
7617
|
+
* Pair a catalogue with the function that builds its ports FROM THE MIGRATION API. The one
|
|
7618
|
+
* place a catalogue is bound: a migration reaches nothing a ports binder cannot reach
|
|
7619
|
+
* through `api` (guard `check-migration-ports-surface`).
|
|
7620
|
+
*/
|
|
7621
|
+
function migrationSet(catalogue, bindPorts) {
|
|
7622
|
+
return { bind: (api) => defineAddonMigrations(catalogue, bindPorts(api)) };
|
|
7623
|
+
}
|
|
7624
|
+
/** A mixed array is a mistake: the other kind would be dropped without a word. */
|
|
7625
|
+
function refuseMixed(form, kept, dropped) {
|
|
7626
|
+
if (dropped > 0) throw new Error(`runMigrations(${form}): ${dropped} of ${kept + dropped} entries are of the other kind and would be dropped; pass one kind per call`);
|
|
7627
|
+
}
|
|
7628
|
+
function catalogueOf(list) {
|
|
7629
|
+
const kept = list.filter((entry) => !("execute" in entry));
|
|
7630
|
+
refuseMixed("catalogue, (api) => ports", kept.length, list.length - kept.length);
|
|
7631
|
+
return kept;
|
|
7632
|
+
}
|
|
7633
|
+
function boundOf(list) {
|
|
7634
|
+
const kept = list.filter((entry) => "execute" in entry);
|
|
7635
|
+
refuseMixed("bound migrations", kept.length, list.length - kept.length);
|
|
7636
|
+
return kept;
|
|
7637
|
+
}
|
|
7638
|
+
/**
|
|
7609
7639
|
* Base class for CamStack addons. Eliminates settings boilerplate:
|
|
7610
7640
|
*
|
|
7611
7641
|
* - Typed `config` property with automatic resolution from store + defaults
|
|
@@ -7683,6 +7713,13 @@ var BaseAddon = class {
|
|
|
7683
7713
|
*/
|
|
7684
7714
|
secretStatePaths = [];
|
|
7685
7715
|
_addonSecretTree = null;
|
|
7716
|
+
/**
|
|
7717
|
+
* D986 M4: the scoped schemas this addon declared, resolved once per capability
|
|
7718
|
+
* (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
|
|
7719
|
+
* see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
|
|
7720
|
+
*/
|
|
7721
|
+
_scopedByCap = /* @__PURE__ */ new Map();
|
|
7722
|
+
_nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
7686
7723
|
_deviceSecretTree = null;
|
|
7687
7724
|
/** Refs the last resolved config pointed at — a change to one re-resolves. */
|
|
7688
7725
|
_configSecretRefs = /* @__PURE__ */ new Set();
|
|
@@ -7752,6 +7789,7 @@ var BaseAddon = class {
|
|
|
7752
7789
|
this._ctx = context;
|
|
7753
7790
|
this._secretSettings = null;
|
|
7754
7791
|
}
|
|
7792
|
+
await this.loadScopedSchemas();
|
|
7755
7793
|
await this.resolveConfig();
|
|
7756
7794
|
if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
|
|
7757
7795
|
if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
|
|
@@ -7761,27 +7799,30 @@ var BaseAddon = class {
|
|
|
7761
7799
|
const normalized = normalizeAddonInitResult(result);
|
|
7762
7800
|
const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
|
|
7763
7801
|
this._registeredCapNames = providers.map((p) => p.capability.name);
|
|
7802
|
+
await this.loadScopedSchemas();
|
|
7764
7803
|
return normalized;
|
|
7765
7804
|
}
|
|
7805
|
+
async runMigrations(first, second, third) {
|
|
7806
|
+
if (typeof second === "function") return this.runMigrationSets([migrationSet(catalogueOf(first), second)], third);
|
|
7807
|
+
return this.runBound(boundOf(first), second);
|
|
7808
|
+
}
|
|
7766
7809
|
/**
|
|
7767
|
-
*
|
|
7768
|
-
*
|
|
7769
|
-
*
|
|
7770
|
-
* restored devices — with the catalogue bound to the addon's ports
|
|
7771
|
-
* (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
|
|
7772
|
-
*
|
|
7773
|
-
* An engine that cannot be reached or refuses the catalogue is logged and runs
|
|
7774
|
-
* nothing; the boot continues. A migration that FAILED (or was blocked by one)
|
|
7775
|
-
* and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
|
|
7776
|
-
* serve data whose shape it cannot trust; `tolerate` migrations never do.
|
|
7810
|
+
* Several catalogues of ONE owner in one declaration (the engine replaces an owner's
|
|
7811
|
+
* catalogue on every declare, so two calls would each judge the other's ids "newer").
|
|
7812
|
+
* Build each entry with `migrationSet(catalogue, (api) => ports)`.
|
|
7777
7813
|
*/
|
|
7778
|
-
async
|
|
7814
|
+
async runMigrationSets(sets, options) {
|
|
7815
|
+
const api = this.ctx.migrationApi;
|
|
7816
|
+
if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
|
|
7817
|
+
return this.runBound(sets.flatMap((set) => set.bind(api)), options);
|
|
7818
|
+
}
|
|
7819
|
+
async runBound(migrations, options) {
|
|
7779
7820
|
const api = this.ctx.api.migrations;
|
|
7780
7821
|
const report = await runAddonMigrations({
|
|
7781
7822
|
declareOwner: (input) => api.declareOwner.mutate(input),
|
|
7782
7823
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7783
7824
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7784
|
-
}, migrations, this.ctx.logger);
|
|
7825
|
+
}, migrations, this.ctx.logger, options);
|
|
7785
7826
|
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7786
7827
|
return report;
|
|
7787
7828
|
}
|
|
@@ -7845,6 +7886,16 @@ var BaseAddon = class {
|
|
|
7845
7886
|
schema(schema) {
|
|
7846
7887
|
return schema;
|
|
7847
7888
|
}
|
|
7889
|
+
/**
|
|
7890
|
+
* The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
|
|
7891
|
+
* {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
|
|
7892
|
+
* `onInitialize`, when the providers it registers are not known yet: the first config resolve
|
|
7893
|
+
* must already know which keys are per node. Names the providers register at init are added
|
|
7894
|
+
* once known.
|
|
7895
|
+
*/
|
|
7896
|
+
scopedSettingsCapabilities() {
|
|
7897
|
+
return [];
|
|
7898
|
+
}
|
|
7848
7899
|
/** Override to provide global-level settings UI schema. */
|
|
7849
7900
|
globalSettingsSchema(_cap) {
|
|
7850
7901
|
return null;
|
|
@@ -7888,7 +7939,8 @@ var BaseAddon = class {
|
|
|
7888
7939
|
return [];
|
|
7889
7940
|
}
|
|
7890
7941
|
async getGlobalSettings(overlay, cap, nodeId) {
|
|
7891
|
-
|
|
7942
|
+
await this.loadScopedSchemas(cap);
|
|
7943
|
+
const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
|
|
7892
7944
|
if (!schema) return { sections: [] };
|
|
7893
7945
|
const projected = await this.readRawProjectedStore(nodeId, cap);
|
|
7894
7946
|
return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
|
|
@@ -7923,10 +7975,9 @@ var BaseAddon = class {
|
|
|
7923
7975
|
*
|
|
7924
7976
|
* Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
|
|
7925
7977
|
* needs its connection fields, the default integration the addon's own sections. It is the
|
|
7926
|
-
* SHAPE of the form only — the values live
|
|
7927
|
-
* unchanged (`integrations.setSettings` for an account integration).
|
|
7978
|
+
* SHAPE of the form only — the values live on the instance record.
|
|
7928
7979
|
*/
|
|
7929
|
-
async
|
|
7980
|
+
async integrationFormSchema(_instance) {
|
|
7930
7981
|
return null;
|
|
7931
7982
|
}
|
|
7932
7983
|
/**
|
|
@@ -7972,7 +8023,7 @@ var BaseAddon = class {
|
|
|
7972
8023
|
* per-instance schema when it declares one, else its global schema (what they always showed).
|
|
7973
8024
|
*/
|
|
7974
8025
|
async getIntegrationFormSchema(instance, nodeId) {
|
|
7975
|
-
const scoped = await this.
|
|
8026
|
+
const scoped = await this.integrationFormSchema(instance);
|
|
7976
8027
|
if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
|
|
7977
8028
|
return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
|
|
7978
8029
|
}
|
|
@@ -8020,6 +8071,7 @@ var BaseAddon = class {
|
|
|
8020
8071
|
* resolved blob back is a no-op for every unchanged secret.
|
|
8021
8072
|
*/
|
|
8022
8073
|
async resolveGlobalStore(nodeId, cap) {
|
|
8074
|
+
await this.loadScopedSchemas(cap);
|
|
8023
8075
|
const raw = await this._ctx?.settings?.readAddonStore() ?? {};
|
|
8024
8076
|
return this.projectForNode(raw, nodeId, cap);
|
|
8025
8077
|
}
|
|
@@ -8030,6 +8082,7 @@ var BaseAddon = class {
|
|
|
8030
8082
|
}
|
|
8031
8083
|
/** The PROJECTED store with its markers — the form hydrate only (D798). */
|
|
8032
8084
|
async readRawProjectedStore(nodeId, cap) {
|
|
8085
|
+
await this.loadScopedSchemas(cap);
|
|
8033
8086
|
const raw = await this.rawSettings()?.readAddonStore() ?? {};
|
|
8034
8087
|
return this.projectForNode(raw, nodeId, cap);
|
|
8035
8088
|
}
|
|
@@ -8132,6 +8185,7 @@ var BaseAddon = class {
|
|
|
8132
8185
|
await this.applyGlobalPatch(patch, nodeId);
|
|
8133
8186
|
}
|
|
8134
8187
|
async applyGlobalPatch(barePatch, nodeId) {
|
|
8188
|
+
await this.loadScopedSchemas();
|
|
8135
8189
|
const keys = this.perNodeKeys();
|
|
8136
8190
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
8137
8191
|
const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
@@ -8145,7 +8199,8 @@ var BaseAddon = class {
|
|
|
8145
8199
|
await this.resolveConfig();
|
|
8146
8200
|
await this.onConfigChanged();
|
|
8147
8201
|
this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
|
|
8148
|
-
|
|
8202
|
+
const legacy = this.globalSettingsSchema();
|
|
8203
|
+
this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
|
|
8149
8204
|
}
|
|
8150
8205
|
/**
|
|
8151
8206
|
* The set of field keys the global settings schema declares `perNode: true`
|
|
@@ -8153,14 +8208,67 @@ var BaseAddon = class {
|
|
|
8153
8208
|
* declarations). Empty set ⇒ every per-node code path is bypassed and the
|
|
8154
8209
|
* settings API behaves exactly like the legacy node-agnostic one.
|
|
8155
8210
|
*/
|
|
8156
|
-
|
|
8211
|
+
/**
|
|
8212
|
+
* Load the scoped schemas of every capability this addon declares (the override and the
|
|
8213
|
+
* providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
|
|
8214
|
+
* derived from the previous set. Idempotent.
|
|
8215
|
+
*/
|
|
8216
|
+
async loadScopedSchemas(extraCap) {
|
|
8217
|
+
const caps = new Set([
|
|
8218
|
+
...this.scopedSettingsCapabilities(),
|
|
8219
|
+
...this._registeredCapNames,
|
|
8220
|
+
...extraCap === void 0 ? [] : [extraCap]
|
|
8221
|
+
]);
|
|
8222
|
+
let added = false;
|
|
8223
|
+
for (const cap of caps) {
|
|
8224
|
+
if (this._scopedByCap.has(cap)) continue;
|
|
8225
|
+
const loaded = [];
|
|
8226
|
+
for (const scope of SCOPED_SETTINGS_SCOPES) {
|
|
8227
|
+
const schema = await this.getScopedSettingsSchema(scope, cap);
|
|
8228
|
+
if (schema !== null) loaded.push({
|
|
8229
|
+
scope,
|
|
8230
|
+
schema
|
|
8231
|
+
});
|
|
8232
|
+
}
|
|
8233
|
+
this._scopedByCap.set(cap, loaded);
|
|
8234
|
+
added = true;
|
|
8235
|
+
}
|
|
8236
|
+
if (!added) return;
|
|
8237
|
+
this._nodeKeysByCap = /* @__PURE__ */ new Map();
|
|
8238
|
+
this._addonSecretTree = null;
|
|
8239
|
+
}
|
|
8240
|
+
/**
|
|
8241
|
+
* The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
|
|
8242
|
+
* sections of its scoped schemas, so dropping the old hook does not blank the form.
|
|
8243
|
+
*/
|
|
8244
|
+
scopedFormSchema(cap) {
|
|
8245
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8246
|
+
for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
|
|
8247
|
+
return byId.size === 0 ? null : { sections: [...byId.values()] };
|
|
8248
|
+
}
|
|
8249
|
+
/** Every loaded scoped schema of `cap`, or of every loaded capability. */
|
|
8250
|
+
loadedScopedSchemas(cap) {
|
|
8251
|
+
if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
|
|
8252
|
+
return [...this._scopedByCap.values()].flat();
|
|
8253
|
+
}
|
|
8254
|
+
/**
|
|
8255
|
+
* The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
|
|
8256
|
+
* the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
|
|
8257
|
+
* per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
|
|
8258
|
+
* per-node code path is bypassed and the settings API behaves like the node-agnostic one.
|
|
8259
|
+
* Call {@link loadScopedSchemas} first (the async seams do).
|
|
8260
|
+
*/
|
|
8157
8261
|
perNodeKeys(cap) {
|
|
8158
8262
|
const cacheKey = cap ?? "";
|
|
8159
|
-
const cached = this.
|
|
8263
|
+
const cached = this._nodeKeysByCap.get(cacheKey);
|
|
8160
8264
|
if (cached) return cached;
|
|
8161
|
-
const
|
|
8162
|
-
const keys = new Set(
|
|
8163
|
-
this.
|
|
8265
|
+
const legacy = this.globalSettingsSchema(cap);
|
|
8266
|
+
const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
|
|
8267
|
+
for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
|
|
8268
|
+
if (scope !== "node") continue;
|
|
8269
|
+
for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
|
|
8270
|
+
}
|
|
8271
|
+
this._nodeKeysByCap.set(cacheKey, keys);
|
|
8164
8272
|
return keys;
|
|
8165
8273
|
}
|
|
8166
8274
|
/**
|
|
@@ -8170,10 +8278,9 @@ var BaseAddon = class {
|
|
|
8170
8278
|
* time to return its response before the addon is torn down and
|
|
8171
8279
|
* re-initialised by `AddonRegistryService.restartAddon`.
|
|
8172
8280
|
*/
|
|
8173
|
-
maybeAutoRestart(patchKeys,
|
|
8174
|
-
if (!schema) return;
|
|
8281
|
+
maybeAutoRestart(patchKeys, schemas) {
|
|
8175
8282
|
const restartKeys = /* @__PURE__ */ new Set();
|
|
8176
|
-
for (const section of schema.sections) for (const field of section.fields) {
|
|
8283
|
+
for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
|
|
8177
8284
|
if (field.type === "separator" || field.type === "info") continue;
|
|
8178
8285
|
if (field.requiresRestart) restartKeys.add(field.key);
|
|
8179
8286
|
}
|
|
@@ -8369,12 +8476,14 @@ var BaseAddon = class {
|
|
|
8369
8476
|
* here, in the addon's own process, so the hub keeps no copy of it.
|
|
8370
8477
|
*/
|
|
8371
8478
|
async getDeclaredSettingsKeys() {
|
|
8479
|
+
await this.loadScopedSchemas();
|
|
8372
8480
|
return { keys: [...this.declaredKeySet()].toSorted() };
|
|
8373
8481
|
}
|
|
8374
8482
|
declaredKeySet() {
|
|
8375
8483
|
const keys = new Set(Object.keys(this.defaults));
|
|
8376
8484
|
const schema = this.globalSettingsSchema();
|
|
8377
8485
|
if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8486
|
+
for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
|
|
8378
8487
|
for (const key of this._declaredStateKeys) keys.add(key);
|
|
8379
8488
|
for (const path of this.secretStatePaths) {
|
|
8380
8489
|
const root = path.split(/[.[]/, 1)[0];
|
|
@@ -8470,7 +8579,11 @@ var BaseAddon = class {
|
|
|
8470
8579
|
}
|
|
8471
8580
|
/** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
|
|
8472
8581
|
addonSecretTree() {
|
|
8473
|
-
this._addonSecretTree
|
|
8582
|
+
if (this._addonSecretTree === null) {
|
|
8583
|
+
const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
|
|
8584
|
+
for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
|
|
8585
|
+
this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
|
|
8586
|
+
}
|
|
8474
8587
|
return this._addonSecretTree;
|
|
8475
8588
|
}
|
|
8476
8589
|
deviceSecretTree() {
|
|
@@ -8720,6 +8833,23 @@ function dropPerNodeFields(fields) {
|
|
|
8720
8833
|
}
|
|
8721
8834
|
return kept;
|
|
8722
8835
|
}
|
|
8836
|
+
/** The keys of the value-holding fields, recursing into layout containers. */
|
|
8837
|
+
function collectValueFieldKeys(fields) {
|
|
8838
|
+
const collected = [];
|
|
8839
|
+
for (const field of fields) {
|
|
8840
|
+
if (field.type === "group") {
|
|
8841
|
+
collected.push(...collectValueFieldKeys(field.fields));
|
|
8842
|
+
continue;
|
|
8843
|
+
}
|
|
8844
|
+
if (field.type === "sub-tabs") {
|
|
8845
|
+
for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
|
|
8846
|
+
continue;
|
|
8847
|
+
}
|
|
8848
|
+
if (field.type === "separator" || field.type === "info") continue;
|
|
8849
|
+
if ("key" in field && typeof field.key === "string") collected.push(field.key);
|
|
8850
|
+
}
|
|
8851
|
+
return collected;
|
|
8852
|
+
}
|
|
8723
8853
|
function collectFieldKeys(fields) {
|
|
8724
8854
|
const collected = [];
|
|
8725
8855
|
for (const field of fields) {
|
|
@@ -9246,6 +9376,7 @@ function defineEntryKind(name, spec) {
|
|
|
9246
9376
|
class: "config",
|
|
9247
9377
|
writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
|
|
9248
9378
|
retention: { none: "an entry goes only when the operator deletes it" },
|
|
9379
|
+
...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
|
|
9249
9380
|
...spec.database === void 0 ? {} : { database: spec.database },
|
|
9250
9381
|
...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
|
|
9251
9382
|
...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
|
|
@@ -11925,7 +12056,7 @@ function userError(code, params, englishFallback) {
|
|
|
11925
12056
|
return new CamstackError(code, params, englishFallback);
|
|
11926
12057
|
}
|
|
11927
12058
|
//#endregion
|
|
11928
|
-
//#region ../types/dist/composition-
|
|
12059
|
+
//#region ../types/dist/composition-pOHbTzRB.mjs
|
|
11929
12060
|
/**
|
|
11930
12061
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11931
12062
|
*
|
|
@@ -13390,6 +13521,39 @@ function buildCapturingReply(envelope) {
|
|
|
13390
13521
|
return wrapper;
|
|
13391
13522
|
}
|
|
13392
13523
|
/**
|
|
13524
|
+
* The link primitive's declaration half (D1013 rule 5): what a link field names and how a kind
|
|
13525
|
+
* records it. Split from `instance-link.ts` so `RecordIntegrationInstances` can read the links of
|
|
13526
|
+
* its kind without importing the builders that import it.
|
|
13527
|
+
*/
|
|
13528
|
+
/** The body field of a link: the id of the instance it names; the target rides in the field meta. */
|
|
13529
|
+
function linkField(target) {
|
|
13530
|
+
return string().meta({ link: target });
|
|
13531
|
+
}
|
|
13532
|
+
function linkTargetOf(meta) {
|
|
13533
|
+
if (typeof meta !== "object" || meta === null) return void 0;
|
|
13534
|
+
const link = Reflect.get(meta, "link");
|
|
13535
|
+
const parsed = LinkTargetSchema.safeParse(link);
|
|
13536
|
+
return parsed.success ? parsed.data : void 0;
|
|
13537
|
+
}
|
|
13538
|
+
var LinkTargetSchema = object({
|
|
13539
|
+
cap: string().min(1),
|
|
13540
|
+
kind: string().min(1).optional()
|
|
13541
|
+
});
|
|
13542
|
+
/** The links a kind declares, read from the meta of its body fields. */
|
|
13543
|
+
function linksOfKind(kind) {
|
|
13544
|
+
const schema = kind.schema;
|
|
13545
|
+
if (!(schema instanceof ZodObject)) return [];
|
|
13546
|
+
const links = [];
|
|
13547
|
+
for (const [field, fieldSchema] of Object.entries(schema.shape)) {
|
|
13548
|
+
const to = linkTargetOf(globalRegistry.get(fieldSchema));
|
|
13549
|
+
if (to !== void 0) links.push({
|
|
13550
|
+
field,
|
|
13551
|
+
to
|
|
13552
|
+
});
|
|
13553
|
+
}
|
|
13554
|
+
return links;
|
|
13555
|
+
}
|
|
13556
|
+
/**
|
|
13393
13557
|
* `RecordIntegrationInstances` - the `integration-instances` provider over ONE
|
|
13394
13558
|
* entry kind, so an addon that holds a connection serves it in ~30 lines.
|
|
13395
13559
|
*
|
|
@@ -13510,9 +13674,18 @@ var RecordIntegrationInstances = class {
|
|
|
13510
13674
|
...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
|
|
13511
13675
|
};
|
|
13512
13676
|
}
|
|
13513
|
-
describe = async () =>
|
|
13514
|
-
|
|
13515
|
-
|
|
13677
|
+
describe = async () => {
|
|
13678
|
+
const declared = linksOfKind(this.kind);
|
|
13679
|
+
const links = this.hooks.description.links !== void 0 || declared.length === 0 ? {} : { links: declared.map((link) => ({
|
|
13680
|
+
kind: this.kind.name,
|
|
13681
|
+
field: link.field,
|
|
13682
|
+
to: link.to
|
|
13683
|
+
})) };
|
|
13684
|
+
return {
|
|
13685
|
+
...this.hooks.description,
|
|
13686
|
+
...links,
|
|
13687
|
+
...this.hooks.mintId === void 0 ? {} : { mintsId: true }
|
|
13688
|
+
};
|
|
13516
13689
|
};
|
|
13517
13690
|
instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
|
|
13518
13691
|
listInstances = async () => {
|
|
@@ -13523,7 +13696,7 @@ var RecordIntegrationInstances = class {
|
|
|
13523
13696
|
return record === null ? null : this.viewOf(record);
|
|
13524
13697
|
};
|
|
13525
13698
|
/** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
|
|
13526
|
-
createInstance = async ({ id, name, kind }) => {
|
|
13699
|
+
createInstance = async ({ id, name, kind, values }) => {
|
|
13527
13700
|
const minted = id ?? this.hooks.mintId?.({
|
|
13528
13701
|
name,
|
|
13529
13702
|
...kind !== void 0 ? { kind } : {}
|
|
@@ -13533,10 +13706,20 @@ var RecordIntegrationInstances = class {
|
|
|
13533
13706
|
id: minted,
|
|
13534
13707
|
data: this.hooks.initialBody(name, kind)
|
|
13535
13708
|
}, { ifRev: 0 });
|
|
13709
|
+
const patch = this.patchOf(values ?? {});
|
|
13710
|
+
if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
|
|
13536
13711
|
await this.hooks.onChanged?.(minted, "created");
|
|
13537
13712
|
return { id: minted };
|
|
13538
13713
|
};
|
|
13539
13714
|
updateInstance = async ({ id, name, values }) => {
|
|
13715
|
+
const patch = this.patchOf(values);
|
|
13716
|
+
if (name !== void 0) patch[this.nameField] = name;
|
|
13717
|
+
await this.handle.patch(id, patch);
|
|
13718
|
+
await this.hooks.onChanged?.(id, "updated");
|
|
13719
|
+
return { ok: true };
|
|
13720
|
+
};
|
|
13721
|
+
/** The record patch a form `values` object means: secrets kept, cleared or replaced. */
|
|
13722
|
+
patchOf(values) {
|
|
13540
13723
|
const patch = {};
|
|
13541
13724
|
for (const [key, value] of Object.entries(values)) {
|
|
13542
13725
|
if (value === void 0) continue;
|
|
@@ -13547,11 +13730,8 @@ var RecordIntegrationInstances = class {
|
|
|
13547
13730
|
else if (isSecretClear(value)) patch[key] = null;
|
|
13548
13731
|
else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
|
|
13549
13732
|
}
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
await this.hooks.onChanged?.(id, "updated");
|
|
13553
|
-
return { ok: true };
|
|
13554
|
-
};
|
|
13733
|
+
return patch;
|
|
13734
|
+
}
|
|
13555
13735
|
/** The switch lives in the record body; the addon is told so it applies it (D996). */
|
|
13556
13736
|
setInstanceEnabled = async ({ id, enabled }) => {
|
|
13557
13737
|
if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
|
|
@@ -13588,6 +13768,80 @@ var RecordIntegrationInstances = class {
|
|
|
13588
13768
|
};
|
|
13589
13769
|
}
|
|
13590
13770
|
};
|
|
13771
|
+
/** No part holds the id a call named. */
|
|
13772
|
+
var UnknownInstanceError = class extends Error {
|
|
13773
|
+
instanceId;
|
|
13774
|
+
constructor(instanceId) {
|
|
13775
|
+
super(`Unknown instance '${instanceId}'`);
|
|
13776
|
+
this.instanceId = instanceId;
|
|
13777
|
+
this.name = "UnknownInstanceError";
|
|
13778
|
+
}
|
|
13779
|
+
};
|
|
13780
|
+
/** A create or a form named no kind, or a kind no part serves. */
|
|
13781
|
+
var UnknownInstanceKindError = class extends Error {
|
|
13782
|
+
kindId;
|
|
13783
|
+
constructor(kindId) {
|
|
13784
|
+
super(kindId === void 0 ? "This provider has several kinds: name one" : `Unknown instance kind '${kindId}'`);
|
|
13785
|
+
this.kindId = kindId;
|
|
13786
|
+
this.name = "UnknownInstanceKindError";
|
|
13787
|
+
}
|
|
13788
|
+
};
|
|
13789
|
+
function composeInstanceKinds(parts, base) {
|
|
13790
|
+
const byKind = (kindId) => {
|
|
13791
|
+
if (kindId === void 0) {
|
|
13792
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
13793
|
+
if (only === void 0) throw new UnknownInstanceKindError(void 0);
|
|
13794
|
+
return only;
|
|
13795
|
+
}
|
|
13796
|
+
const part = parts.find((candidate) => candidate.kind.id === kindId);
|
|
13797
|
+
if (part === void 0) throw new UnknownInstanceKindError(kindId);
|
|
13798
|
+
return part;
|
|
13799
|
+
};
|
|
13800
|
+
const holderOf = async (id) => {
|
|
13801
|
+
for (const part of parts) if (await part.provider.getInstance({ id }) !== null) return part;
|
|
13802
|
+
throw new UnknownInstanceError(id);
|
|
13803
|
+
};
|
|
13804
|
+
const tagged = (part, instance) => ({
|
|
13805
|
+
...instance,
|
|
13806
|
+
kind: part.kind.id
|
|
13807
|
+
});
|
|
13808
|
+
return {
|
|
13809
|
+
describe: async () => {
|
|
13810
|
+
const links = (await Promise.all(parts.map(async (part) => (await part.provider.describe()).links ?? []))).flat();
|
|
13811
|
+
return {
|
|
13812
|
+
...base,
|
|
13813
|
+
kinds: parts.map((part) => part.kind),
|
|
13814
|
+
...links.length > 0 ? { links } : {}
|
|
13815
|
+
};
|
|
13816
|
+
},
|
|
13817
|
+
instanceFormSchema: async ({ id, kind }) => {
|
|
13818
|
+
return (id === null ? byKind(kind) : await holderOf(id)).provider.instanceFormSchema({ id });
|
|
13819
|
+
},
|
|
13820
|
+
listInstances: async () => {
|
|
13821
|
+
return (await Promise.all(parts.map(async (part) => (await part.provider.listInstances()).map((instance) => tagged(part, instance))))).flat();
|
|
13822
|
+
},
|
|
13823
|
+
getInstance: async ({ id }) => {
|
|
13824
|
+
for (const part of parts) {
|
|
13825
|
+
const found = await part.provider.getInstance({ id });
|
|
13826
|
+
if (found !== null) return tagged(part, found);
|
|
13827
|
+
}
|
|
13828
|
+
return null;
|
|
13829
|
+
},
|
|
13830
|
+
createInstance: async (input) => byKind(input.kind).provider.createInstance(input),
|
|
13831
|
+
updateInstance: async (input) => (await holderOf(input.id)).provider.updateInstance(input),
|
|
13832
|
+
setInstanceEnabled: async (input) => (await holderOf(input.id)).provider.setInstanceEnabled(input),
|
|
13833
|
+
deleteInstance: async (input) => (await holderOf(input.id)).provider.deleteInstance(input),
|
|
13834
|
+
testInstance: async (input) => {
|
|
13835
|
+
const test = (input.id === null ? byKind(input.kind) : await holderOf(input.id)).provider.testInstance;
|
|
13836
|
+
if (test === void 0) return {
|
|
13837
|
+
success: false,
|
|
13838
|
+
status: "inconclusive",
|
|
13839
|
+
error: "This kind cannot be tested"
|
|
13840
|
+
};
|
|
13841
|
+
return test(input);
|
|
13842
|
+
}
|
|
13843
|
+
};
|
|
13844
|
+
}
|
|
13591
13845
|
var EncodeProfileSchema = object({
|
|
13592
13846
|
video: object({
|
|
13593
13847
|
codec: _enum([
|
|
@@ -14633,7 +14887,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
14633
14887
|
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
14634
14888
|
* catalogue lists the id (a newer database).
|
|
14635
14889
|
*/
|
|
14636
|
-
meta: MigrationMetaSchema.optional()
|
|
14890
|
+
meta: MigrationMetaSchema.optional(),
|
|
14891
|
+
/**
|
|
14892
|
+
* Set when this is an applied row of an addon owner whose catalogue the answering
|
|
14893
|
+
* process has not loaded (the owner has not declared yet): the row is applied, the
|
|
14894
|
+
* process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
|
|
14895
|
+
*/
|
|
14896
|
+
ownerNotLoaded: boolean().optional()
|
|
14637
14897
|
});
|
|
14638
14898
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
14639
14899
|
var MigrationPendingEntrySchema = object({
|
|
@@ -15754,6 +16014,39 @@ var UnstampedEventMediaCountSchema = object({
|
|
|
15754
16014
|
/** Sum across both, or `null` when either lane could not be counted. */
|
|
15755
16015
|
total: number().int().nonnegative().nullable()
|
|
15756
16016
|
}).nullable();
|
|
16017
|
+
/**
|
|
16018
|
+
* Where the stored event media sits, per storage location: how many media rows
|
|
16019
|
+
* and how many bytes carry each location id, how many carry none (legacy,
|
|
16020
|
+
* resolved to the default `eventMedia` location), and how many name a location
|
|
16021
|
+
* that was not asked about (a deleted or never-listed one).
|
|
16022
|
+
*
|
|
16023
|
+
* One covering-index aggregate per asked location plus the NULL one, and one
|
|
16024
|
+
* unfiltered total that scans the covering index: O(rows), not a walk of the
|
|
16025
|
+
* rows themselves. `unlisted` is total minus the ASKED locations minus NULL, so
|
|
16026
|
+
* it is a deleted location only when every location id was asked.
|
|
16027
|
+
* `null` is "could not be measured", never zero.
|
|
16028
|
+
*/
|
|
16029
|
+
var MediaLocationCensusInputSchema = object({ locationIds: array(string().min(1)).max(50).readonly() });
|
|
16030
|
+
var MediaLocationCensusSchema = object({
|
|
16031
|
+
locations: array(object({
|
|
16032
|
+
locationId: string(),
|
|
16033
|
+
rows: number().int().nonnegative(),
|
|
16034
|
+
bytes: number().nonnegative()
|
|
16035
|
+
})).readonly(),
|
|
16036
|
+
unstamped: object({
|
|
16037
|
+
rows: number().int().nonnegative(),
|
|
16038
|
+
bytes: number().nonnegative()
|
|
16039
|
+
}),
|
|
16040
|
+
/** Rows stamped with a location id that was not in the asked list. */
|
|
16041
|
+
unlisted: object({
|
|
16042
|
+
rows: number().int().nonnegative(),
|
|
16043
|
+
bytes: number().nonnegative()
|
|
16044
|
+
}),
|
|
16045
|
+
total: object({
|
|
16046
|
+
rows: number().int().nonnegative(),
|
|
16047
|
+
bytes: number().nonnegative()
|
|
16048
|
+
})
|
|
16049
|
+
});
|
|
15757
16050
|
var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
|
|
15758
16051
|
/** The independently selectable logical storage classes — every class
|
|
15759
16052
|
* `storage.listLocationDeclarations` reports, so an operator never meets a
|
|
@@ -46293,52 +46586,7 @@ DeviceType.Light, method(object({
|
|
|
46293
46586
|
level: number().min(0).max(100),
|
|
46294
46587
|
lastChangedAt: number()
|
|
46295
46588
|
});
|
|
46296
|
-
|
|
46297
|
-
* `broker` — unified pub/sub broker registry, system-scoped collection.
|
|
46298
|
-
*
|
|
46299
|
-
* The cap models any kind-tagged message broker the user wants to
|
|
46300
|
-
* register and that other addons might consume. The first two kinds
|
|
46301
|
-
* are MQTT (mosquitto / aedes embedded / cloud bridge) and
|
|
46302
|
-
* Home Assistant (HA WebSocket — subscribe_entities + call_service).
|
|
46303
|
-
* Future kinds (Zigbee2MQTT bridge, ZHA, KNX, Telegram, …) plug in
|
|
46304
|
-
* the same surface.
|
|
46305
|
-
*
|
|
46306
|
-
* Why one cap, not one cap per kind:
|
|
46307
|
-
* - The integrations page wants a single table of "every broker"
|
|
46308
|
-
* across kinds; a unified cap drives it without join logic.
|
|
46309
|
-
* - Generic admin operations (add / remove / test / status) are
|
|
46310
|
-
* identical across kinds — duplicating them per cap was busywork.
|
|
46311
|
-
* - The `network-access` cap precedent: same interface, many
|
|
46312
|
-
* `providerKind: 'ingress'` implementations (Tailscale, ngrok, …).
|
|
46313
|
-
*
|
|
46314
|
-
* What stays kind-specific:
|
|
46315
|
-
* - `getBrokerConfig` payload (kind decides whether it returns
|
|
46316
|
-
* mqtt URL+creds, HA baseUrl+token, …) — typed as
|
|
46317
|
-
* `Record<string, unknown>` at the wire; consumers narrow.
|
|
46318
|
-
* - `publish` / `subscribe` / `getState` arguments (target / filter /
|
|
46319
|
-
* key) — also typed as `Record<string, unknown>` so each kind can
|
|
46320
|
-
* evolve its surface (MQTT topic+qos vs HA entity_id+domain)
|
|
46321
|
-
* without rebreaking the cap signature.
|
|
46322
|
-
* - `add` `settings` payload (different fields per kind, validated by
|
|
46323
|
-
* the kind-specific provider).
|
|
46324
|
-
*
|
|
46325
|
-
* Bidirectionality:
|
|
46326
|
-
* - `publish` is RPC — synchronous result (provider-defined).
|
|
46327
|
-
* - `subscribe` returns a subscription handle; the actual message
|
|
46328
|
-
* stream flows over the typed event-bus as `broker.message` events
|
|
46329
|
-
* keyed by `(brokerId, subscriptionId)`. Consumers filter in their
|
|
46330
|
-
* event handler. Subscriptions persist across reconnects — the
|
|
46331
|
-
* provider re-subscribes upstream after a transport drop.
|
|
46332
|
-
*
|
|
46333
|
-
* Why event-bus push (not a subscription RPC stream):
|
|
46334
|
-
* - Matches the rest of CamStack's D8 contract — events for telemetry,
|
|
46335
|
-
* RPC for loss-is-a-bug. Broker messages are telemetry (a single
|
|
46336
|
-
* drop is recoverable via `getState({key})`).
|
|
46337
|
-
* - Frees `broker.subscribe` from holding a long-lived RPC channel.
|
|
46338
|
-
* - Cross-process: events already route through `$event-bus`; no
|
|
46339
|
-
* per-broker wire-shaping needed.
|
|
46340
|
-
*/
|
|
46341
|
-
var BrokerStatusEnum = _enum([
|
|
46589
|
+
_enum([
|
|
46342
46590
|
"connected",
|
|
46343
46591
|
"disconnected",
|
|
46344
46592
|
"connecting",
|
|
@@ -46346,141 +46594,12 @@ var BrokerStatusEnum = _enum([
|
|
|
46346
46594
|
"unreachable",
|
|
46347
46595
|
"error"
|
|
46348
46596
|
]);
|
|
46349
|
-
var BrokerInfoSchema = object({
|
|
46350
|
-
/** Stable broker id. Persisted; survives addon restarts. */
|
|
46351
|
-
id: string(),
|
|
46352
|
-
/** Addon id of the provider that OWNS this broker.
|
|
46353
|
-
*
|
|
46354
|
-
* The `broker` cap is a system-scoped collection: several addons
|
|
46355
|
-
* register a `broker` provider, each owning a DISJOINT set of
|
|
46356
|
-
* brokers (mqtt-broker owns `mqtt_*`, provider-homeassistant owns
|
|
46357
|
-
* `ha_*`). A broker therefore belongs to exactly one addon — like a
|
|
46358
|
-
* device belongs to one integration. The admin UI threads this id
|
|
46359
|
-
* back as the `{ addonId }` system-collection selector on every
|
|
46360
|
-
* id-keyed call (`get` / `getSettings` / `setSettings` / `remove` /
|
|
46361
|
-
* `testConnection`), so the call routes to the OWNING provider
|
|
46362
|
-
* instead of defaulting to the first-registered one. */
|
|
46363
|
-
addonId: string(),
|
|
46364
|
-
/** Human-readable name (operator-chosen at add-time). */
|
|
46365
|
-
name: string(),
|
|
46366
|
-
/** Provider-defined kind tag — `mqtt` / `home-assistant` / future. */
|
|
46367
|
-
kind: string(),
|
|
46368
|
-
status: BrokerStatusEnum,
|
|
46369
|
-
/** Free-form provider-specific info (HA version, MQTT broker
|
|
46370
|
-
* flavour, latency, mTLS active, …). */
|
|
46371
|
-
info: record(string(), unknown()),
|
|
46372
|
-
/** Ms epoch of the last connection probe / status update. */
|
|
46373
|
-
lastCheckedAt: number().nullable(),
|
|
46374
|
-
/** Last error message, when `status` indicates failure. */
|
|
46375
|
-
error: string().nullable()
|
|
46376
|
-
});
|
|
46377
46597
|
var RegistryStatusSchema = object({
|
|
46378
46598
|
brokerCount: number().int().nonnegative(),
|
|
46379
46599
|
connectedCount: number().int().nonnegative()
|
|
46380
46600
|
});
|
|
46381
|
-
/**
|
|
46382
|
-
* One entry per `broker` provider — which addon provides which broker
|
|
46383
|
-
* kind(s). The unified create picker reads this to know, for each
|
|
46384
|
-
* (addonId, kind) pair the operator can pick, where to route the
|
|
46385
|
-
* follow-up `getSettingsSchema` / `testSettings` / `add` calls (via the
|
|
46386
|
-
* `{ addonId }` selector). A provider only advertises a kind here when
|
|
46387
|
-
* it implements `getSettingsSchema` for that kind (so the picker never
|
|
46388
|
-
* offers a kind whose creation form can't be rendered).
|
|
46389
|
-
*/
|
|
46390
|
-
var BrokerProviderInfoSchema = object({
|
|
46391
|
-
/** Addon id of the `broker` provider this entry describes. */
|
|
46392
|
-
addonId: string(),
|
|
46393
|
-
/** Broker kinds this provider can create, with a display label. */
|
|
46394
|
-
kinds: array(object({
|
|
46395
|
-
kind: string(),
|
|
46396
|
-
label: string(),
|
|
46397
|
-
/**
|
|
46398
|
-
* Relative hub path to start an interactive account link (OAuth popup).
|
|
46399
|
-
* May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
|
|
46400
|
-
*/
|
|
46401
|
-
interactiveConnectPath: string().min(1).optional(),
|
|
46402
|
-
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
46403
|
-
interactiveDisconnectPath: string().min(1).optional(),
|
|
46404
|
-
/**
|
|
46405
|
-
* The kind signs in through a server-held interactive session instead of
|
|
46406
|
-
* (or before) typed credentials. `basePath` is a hub-relative route set
|
|
46407
|
-
* served by the owning addon (`/addon/<id>/...`) speaking the sign-in
|
|
46408
|
-
* contract: `POST {basePath}/start` (body: the form's settings) answers
|
|
46409
|
-
* `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
|
|
46410
|
-
* `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
|
|
46411
|
-
* 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
|
|
46412
|
-
* (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
|
|
46413
|
-
* existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
|
|
46414
|
-
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
46415
|
-
* offers it as the default path; the fields the session fills are
|
|
46416
|
-
* marked `filledBySignIn` in the kind's settings schema.
|
|
46417
|
-
*
|
|
46418
|
-
* `methods` lists the ways the session can sign in, recommended first:
|
|
46419
|
-
* `'device-code'` adds `POST {basePath}/code/start` (body: the form's
|
|
46420
|
-
* settings) answering `{ sessionId, userCode, verificationUrl,
|
|
46421
|
-
* expiresAt }` — the operator types `userCode` at `verificationUrl` on
|
|
46422
|
-
* any device, and the same `status` / `commit` / `cancel` routes follow
|
|
46423
|
-
* (status then carries `method`, `userCode`, `verificationUrl`,
|
|
46424
|
-
* `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
|
|
46425
|
-
* `'proxy'` is the `{basePath}/start` flow above. Absent means
|
|
46426
|
-
* `['proxy']`.
|
|
46427
|
-
*/
|
|
46428
|
-
signIn: object({
|
|
46429
|
-
basePath: string().min(1),
|
|
46430
|
-
methods: array(_enum(["device-code", "proxy"])).optional()
|
|
46431
|
-
}).optional()
|
|
46432
|
-
}))
|
|
46433
|
-
});
|
|
46434
|
-
var ListInputSchema = object({
|
|
46435
|
-
/** Optional kind filter — `list({kind:'home-assistant'})` returns
|
|
46436
|
-
* only HA brokers. Omit to list every broker. */
|
|
46437
|
-
kind: string().optional() });
|
|
46438
46601
|
var GetInputSchema = object({ id: string() });
|
|
46439
|
-
var AddInputSchema = object({
|
|
46440
|
-
kind: string().min(1),
|
|
46441
|
-
name: string().min(1),
|
|
46442
|
-
/**
|
|
46443
|
-
* ADOPT an existing id instead of minting a new one.
|
|
46444
|
-
*
|
|
46445
|
-
* Written the day this cost an outage. A broker's id is not a detail: HA
|
|
46446
|
-
* devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
|
|
46447
|
-
* lost from config and re-added as `ha_001` leaves every one of its devices
|
|
46448
|
-
* bound to a broker that no longer exists. Re-entering the password under the
|
|
46449
|
-
* ORIGINAL id turns a multi-step device migration back into re-entering a
|
|
46450
|
-
* password.
|
|
46451
|
-
*
|
|
46452
|
-
* A provider MUST refuse an id that is already in use — adopting a live
|
|
46453
|
-
* broker's id would silently take it over.
|
|
46454
|
-
*/
|
|
46455
|
-
id: string().min(1).optional(),
|
|
46456
|
-
/** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
|
|
46457
|
-
* `{baseUrl,accessToken}`). Validated by the kind-specific provider
|
|
46458
|
-
* branch on receipt — invalid shape rejects the add. */
|
|
46459
|
-
settings: record(string(), unknown())
|
|
46460
|
-
});
|
|
46461
|
-
var AddResultSchema = object({ id: string() });
|
|
46462
|
-
var RemoveInputSchema = object({ id: string() });
|
|
46463
|
-
var TestConnectionResultSchema = discriminatedUnion("ok", [object({
|
|
46464
|
-
ok: literal(true),
|
|
46465
|
-
latencyMs: number().nonnegative()
|
|
46466
|
-
}), object({
|
|
46467
|
-
ok: literal(false),
|
|
46468
|
-
error: string()
|
|
46469
|
-
})]);
|
|
46470
46602
|
var SettingsRecordSchema$1 = record(string(), unknown());
|
|
46471
|
-
var SettingsSchemaInputSchema = object({ kind: string() });
|
|
46472
|
-
var TestSettingsInputSchema = object({
|
|
46473
|
-
kind: string(),
|
|
46474
|
-
settings: SettingsRecordSchema$1
|
|
46475
|
-
});
|
|
46476
|
-
var TestSettingsResultSchema = discriminatedUnion("ok", [object({
|
|
46477
|
-
ok: literal(true),
|
|
46478
|
-
latencyMs: number().nonnegative().optional()
|
|
46479
|
-
}).strict(), object({
|
|
46480
|
-
ok: literal(false),
|
|
46481
|
-
error: string()
|
|
46482
|
-
})]);
|
|
46483
|
-
var SettingsSchemaResultSchema = unknown().nullable();
|
|
46484
46603
|
var PublishInputSchema = object({
|
|
46485
46604
|
brokerId: string(),
|
|
46486
46605
|
/** Kind-specific routing target.
|
|
@@ -46524,51 +46643,12 @@ var brokerCapability = {
|
|
|
46524
46643
|
kind: "poll"
|
|
46525
46644
|
},
|
|
46526
46645
|
methods: {
|
|
46527
|
-
list: method(ListInputSchema, array(BrokerInfoSchema)),
|
|
46528
|
-
get: method(GetInputSchema, BrokerInfoSchema.nullable()),
|
|
46529
|
-
/** Enumerate which addon provides which broker kind(s) for the
|
|
46530
|
-
* unified create picker. The auto-mount fans this array across
|
|
46531
|
-
* every registered `broker` provider (array-output method), so the
|
|
46532
|
-
* picker sees every kind from every provider in one call. */
|
|
46533
|
-
listProviders: method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }),
|
|
46534
|
-
add: method(AddInputSchema, AddResultSchema, {
|
|
46535
|
-
kind: "mutation",
|
|
46536
|
-
auth: "admin"
|
|
46537
|
-
}),
|
|
46538
|
-
remove: method(RemoveInputSchema, _void(), {
|
|
46539
|
-
kind: "mutation",
|
|
46540
|
-
auth: "admin"
|
|
46541
|
-
}),
|
|
46542
|
-
testConnection: method(GetInputSchema, TestConnectionResultSchema, {
|
|
46543
|
-
kind: "mutation",
|
|
46544
|
-
auth: "admin"
|
|
46545
|
-
}),
|
|
46546
|
-
/** Read the persisted settings record for a broker (kind-specific
|
|
46547
|
-
* shape). Admin-only — settings may contain secrets. Returns `null`
|
|
46548
|
-
* when the broker id is unknown to the provider (the collection
|
|
46549
|
-
* fallback may route a foreign id to the first provider). */
|
|
46550
|
-
getSettings: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
46551
|
-
/** Overwrite the persisted settings record. The kind-specific
|
|
46552
|
-
* provider validates the shape and applies the change (reconnects
|
|
46553
|
-
* if credentials changed). */
|
|
46554
|
-
setSettings: method(object({
|
|
46555
|
-
id: string(),
|
|
46556
|
-
settings: SettingsRecordSchema$1
|
|
46557
|
-
}), _void(), {
|
|
46558
|
-
kind: "mutation",
|
|
46559
|
-
auth: "admin"
|
|
46560
|
-
}),
|
|
46561
46646
|
/** Returns the kind-specific connection config the consumer needs
|
|
46562
46647
|
* to open its own client (MQTT pattern: `{url, username, password,
|
|
46563
46648
|
* clientIdPrefix}`). HA providers MAY return the auth envelope
|
|
46564
46649
|
* but typical HA consumers use `publish` / `subscribe` instead.
|
|
46565
46650
|
* Returns `null` when the broker id is unknown to the provider. */
|
|
46566
46651
|
getBrokerConfig: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
46567
|
-
getSettingsSchema: method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }),
|
|
46568
|
-
testSettings: method(TestSettingsInputSchema, TestSettingsResultSchema, {
|
|
46569
|
-
kind: "mutation",
|
|
46570
|
-
auth: "admin"
|
|
46571
|
-
}),
|
|
46572
46652
|
publish: method(PublishInputSchema, unknown(), {
|
|
46573
46653
|
kind: "mutation",
|
|
46574
46654
|
auth: "admin"
|
|
@@ -49202,6 +49282,9 @@ var RunningProposalEvidenceSchema = object({
|
|
|
49202
49282
|
runsFound: number().int().nonnegative(),
|
|
49203
49283
|
pausesFound: number().int().nonnegative(),
|
|
49204
49284
|
longestPauseMs: number().nonnegative(),
|
|
49285
|
+
/** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
|
|
49286
|
+
pulsesFound: number().int().nonnegative(),
|
|
49287
|
+
longestPulseMs: number().nonnegative(),
|
|
49205
49288
|
idleFloorW: number(),
|
|
49206
49289
|
noiseCeilingW: number(),
|
|
49207
49290
|
plateauW: number(),
|
|
@@ -50648,7 +50731,14 @@ var DiscoveryCandidateSchema = object({
|
|
|
50648
50731
|
/** `stableId` of the gateway candidate this device was found BEHIND. */
|
|
50649
50732
|
parentStableId: string().optional(),
|
|
50650
50733
|
/** `false` = shown for context but not pickable. Default true. */
|
|
50651
|
-
selectable: boolean().optional()
|
|
50734
|
+
selectable: boolean().optional(),
|
|
50735
|
+
/**
|
|
50736
|
+
* Keys of the provider's creation form (`getChildCreationSchema`) this candidate
|
|
50737
|
+
* cannot be adopted without — a key for an encrypted board, a pairing code. The
|
|
50738
|
+
* admin asks for exactly these, on the creation form prefilled with
|
|
50739
|
+
* `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
|
|
50740
|
+
*/
|
|
50741
|
+
needsParams: array(string()).optional()
|
|
50652
50742
|
});
|
|
50653
50743
|
/**
|
|
50654
50744
|
* Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
|
|
@@ -50707,12 +50797,24 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
|
|
|
50707
50797
|
}), array(DiscoveryCandidateSchema), {
|
|
50708
50798
|
kind: "mutation",
|
|
50709
50799
|
auth: "admin"
|
|
50710
|
-
}), method(object({}), CreationSchemaOutputSchema), method(object({
|
|
50800
|
+
}), method(object({}), CreationSchemaOutputSchema), method(object({
|
|
50801
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
50802
|
+
integrationId: string().optional() }), object({ deviceType: _enum(DeviceType).nullable() })), method(object({
|
|
50803
|
+
candidate: DiscoveryCandidateSchema,
|
|
50804
|
+
/** The integration the operator adopts into; a provider with several instances needs it. */
|
|
50805
|
+
integrationId: string().optional()
|
|
50806
|
+
}), DeviceSummarySchema, {
|
|
50711
50807
|
kind: "mutation",
|
|
50712
50808
|
auth: "admin"
|
|
50713
|
-
}), method(object({}), boolean()), method(object({
|
|
50809
|
+
}), method(object({}), boolean()), method(object({
|
|
50714
50810
|
type: _enum(DeviceType),
|
|
50715
|
-
|
|
50811
|
+
/** The integration asked about; a provider whose integrations differ answers per one. */
|
|
50812
|
+
integrationId: string().optional()
|
|
50813
|
+
}), CreationSchemaOutputSchema), method(object({
|
|
50814
|
+
type: _enum(DeviceType),
|
|
50815
|
+
config: record(string(), unknown()),
|
|
50816
|
+
/** The integration the operator adds the device to; a provider with several instances needs it. */
|
|
50817
|
+
integrationId: string().optional()
|
|
50716
50818
|
}), DeviceSummarySchema, {
|
|
50717
50819
|
kind: "mutation",
|
|
50718
50820
|
auth: "admin"
|
|
@@ -51422,7 +51524,10 @@ method(object({
|
|
|
51422
51524
|
auth: "admin"
|
|
51423
51525
|
}), method(object({
|
|
51424
51526
|
addonId: string(),
|
|
51425
|
-
type: _enum(DeviceType)
|
|
51527
|
+
type: _enum(DeviceType),
|
|
51528
|
+
/** The integration the form is for; a provider whose integrations differ (a hub
|
|
51529
|
+
* integration takes no manual add) answers per integration. Absent = the addon's own. */
|
|
51530
|
+
integrationId: string().optional()
|
|
51426
51531
|
}), unknown().nullable()), method(object({
|
|
51427
51532
|
addonId: string(),
|
|
51428
51533
|
type: _enum(DeviceType),
|
|
@@ -51506,7 +51611,11 @@ method(object({
|
|
|
51506
51611
|
kind: "mutation",
|
|
51507
51612
|
auth: "admin",
|
|
51508
51613
|
timeoutMs: 12e4
|
|
51509
|
-
}), method(object({
|
|
51614
|
+
}), method(object({
|
|
51615
|
+
addonId: string(),
|
|
51616
|
+
/** The integration the Add-Device dialog is open on; absent = the addon's own. */
|
|
51617
|
+
integrationId: string().optional()
|
|
51618
|
+
}), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
|
|
51510
51619
|
deviceId: number(),
|
|
51511
51620
|
key: string(),
|
|
51512
51621
|
value: unknown()
|
|
@@ -54102,7 +54211,7 @@ var TestConnectionStatusEnum = _enum([
|
|
|
54102
54211
|
"inconclusive",
|
|
54103
54212
|
"unsupported"
|
|
54104
54213
|
]);
|
|
54105
|
-
var TestConnectionResultSchema
|
|
54214
|
+
var TestConnectionResultSchema = object({
|
|
54106
54215
|
/** True ONLY for `validated`. Never true for a test that did not run. */
|
|
54107
54216
|
success: boolean(),
|
|
54108
54217
|
error: string().optional(),
|
|
@@ -54123,11 +54232,7 @@ var CreateIntegrationInputSchema = object({
|
|
|
54123
54232
|
info: record(string(), unknown()).optional(),
|
|
54124
54233
|
/** `stableId` of the discovery candidate the form was seeded from (D984). */
|
|
54125
54234
|
originStableId: string().optional(),
|
|
54126
|
-
settings: record(string(), unknown()).optional()
|
|
54127
|
-
/** Keys of `settings` the form's schema declares secret (by type) — kept in
|
|
54128
|
-
* the keychain, not the table (D787). */
|
|
54129
|
-
secretKeys: array(string()).optional(),
|
|
54130
|
-
skipRestart: boolean().optional()
|
|
54235
|
+
settings: record(string(), unknown()).optional()
|
|
54131
54236
|
});
|
|
54132
54237
|
var UpdateIntegrationInputSchema = object({
|
|
54133
54238
|
id: string(),
|
|
@@ -54148,20 +54253,10 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
|
|
|
54148
54253
|
}), method(object({ id: string() }), DeleteIntegrationResultSchema, {
|
|
54149
54254
|
kind: "mutation",
|
|
54150
54255
|
auth: "admin"
|
|
54151
|
-
}), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
|
|
54152
|
-
id: string(),
|
|
54153
|
-
settings: record(string(), unknown()),
|
|
54154
|
-
/** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
|
|
54155
|
-
secretKeys: array(string()).optional(),
|
|
54156
|
-
/** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
|
|
54157
|
-
skipRestart: boolean().optional()
|
|
54158
|
-
}), object({ success: boolean() }), {
|
|
54159
|
-
kind: "mutation",
|
|
54160
|
-
auth: "admin"
|
|
54161
54256
|
}), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
|
|
54162
54257
|
addonId: string(),
|
|
54163
54258
|
settings: record(string(), unknown())
|
|
54164
|
-
}), TestConnectionResultSchema
|
|
54259
|
+
}), TestConnectionResultSchema, {
|
|
54165
54260
|
kind: "mutation",
|
|
54166
54261
|
auth: "admin"
|
|
54167
54262
|
});
|
|
@@ -54188,6 +54283,14 @@ var ConfigSchemaPassthrough$3 = custom();
|
|
|
54188
54283
|
var IntegrationInstanceIdSchema = string().min(1);
|
|
54189
54284
|
var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
|
|
54190
54285
|
/**
|
|
54286
|
+
* What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
|
|
54287
|
+
* instance is an integration that brings devices (the registry owns its on/off switch);
|
|
54288
|
+
* `services`: a connection configured from the addon's own function page, never listed on
|
|
54289
|
+
* Integrations.
|
|
54290
|
+
* It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
|
|
54291
|
+
*/
|
|
54292
|
+
var IntegrationInstanceRoleSchema = _enum(["devices", "services"]);
|
|
54293
|
+
/**
|
|
54191
54294
|
* One kind of instance a provider can create (an adapter, an account type). The sign-in and
|
|
54192
54295
|
* connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
|
|
54193
54296
|
* hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
|
|
@@ -54198,6 +54301,22 @@ var IntegrationInstanceKindSchema = object({
|
|
|
54198
54301
|
/** Stable id of the kind, sent back as `createInstance.kind`. */
|
|
54199
54302
|
id: string().min(1),
|
|
54200
54303
|
label: string().min(1),
|
|
54304
|
+
/**
|
|
54305
|
+
* The function capability this kind is configured for (`broker`, `smtp-provider`,
|
|
54306
|
+
* `device-provider`, ...). A page section is the kinds whose `functionCap` it names (D1013); a
|
|
54307
|
+
* provider with more than one section must name it on every kind.
|
|
54308
|
+
*/
|
|
54309
|
+
functionCap: string().min(1),
|
|
54310
|
+
/**
|
|
54311
|
+
* For `functionCap: 'broker'`: the registry kind of the broker an instance of this kind is
|
|
54312
|
+
* (`matter`, `home-assistant`, ...). A caller that needs a broker of one kind (a link field)
|
|
54313
|
+
* finds the instances whose kind names it; absent takes the kind `id`.
|
|
54314
|
+
*/
|
|
54315
|
+
brokerKind: string().min(1).optional(),
|
|
54316
|
+
/** What instances of this kind are for the Integrations page; absent takes the provider's `role`. */
|
|
54317
|
+
role: IntegrationInstanceRoleSchema.optional(),
|
|
54318
|
+
/** `testInstance` really probes the remote for this kind; absent takes the provider's `canTest`. */
|
|
54319
|
+
canTest: boolean().optional(),
|
|
54201
54320
|
/** The kind signs in through a server-held interactive session. */
|
|
54202
54321
|
signIn: object({
|
|
54203
54322
|
basePath: string().min(1),
|
|
@@ -54208,22 +54327,38 @@ var IntegrationInstanceKindSchema = object({
|
|
|
54208
54327
|
/** Relative hub path to disconnect that account. */
|
|
54209
54328
|
disconnectPath: string().min(1).optional()
|
|
54210
54329
|
});
|
|
54330
|
+
/**
|
|
54331
|
+
* How devices of an instance are found. `none`: the operator adds them by hand; `lan`: the addon
|
|
54332
|
+
* scans the network before any instance exists (the wizard offers the candidates); `account`: the
|
|
54333
|
+
* devices appear after the instance signs in. `scopedToInstance`: a discovery run belongs to ONE
|
|
54334
|
+
* instance and its candidates are claimed by it.
|
|
54335
|
+
*/
|
|
54336
|
+
var IntegrationInstanceDiscoverySchema = object({
|
|
54337
|
+
mode: _enum([
|
|
54338
|
+
"none",
|
|
54339
|
+
"lan",
|
|
54340
|
+
"account"
|
|
54341
|
+
]),
|
|
54342
|
+
scopedToInstance: boolean()
|
|
54343
|
+
});
|
|
54344
|
+
/**
|
|
54345
|
+
* One link a kind of this provider declares (D1013 rule 5): the body `field` of an instance of
|
|
54346
|
+
* `kind` holds the id of an instance of the function cap `to.cap` (of kind `to.kind`, when set).
|
|
54347
|
+
* The hub indexes these so the target's delete is refused while a link names it.
|
|
54348
|
+
*/
|
|
54349
|
+
var IntegrationInstanceLinkSchema = object({
|
|
54350
|
+
kind: string().min(1),
|
|
54351
|
+
field: string().min(1),
|
|
54352
|
+
to: object({
|
|
54353
|
+
cap: string().min(1),
|
|
54354
|
+
kind: string().min(1).optional()
|
|
54355
|
+
})
|
|
54356
|
+
});
|
|
54211
54357
|
var IntegrationInstanceDescriptionSchema = object({
|
|
54212
54358
|
/** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
|
|
54213
|
-
role:
|
|
54214
|
-
"devices",
|
|
54215
|
-
"services",
|
|
54216
|
-
"both"
|
|
54217
|
-
]),
|
|
54359
|
+
role: IntegrationInstanceRoleSchema,
|
|
54218
54360
|
/** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
|
|
54219
|
-
discovery:
|
|
54220
|
-
mode: _enum([
|
|
54221
|
-
"none",
|
|
54222
|
-
"lan",
|
|
54223
|
-
"account"
|
|
54224
|
-
]),
|
|
54225
|
-
scopedToInstance: boolean()
|
|
54226
|
-
}).optional(),
|
|
54361
|
+
discovery: IntegrationInstanceDiscoverySchema.optional(),
|
|
54227
54362
|
/** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
|
|
54228
54363
|
instanceMode: IntegrationInstanceModeSchema,
|
|
54229
54364
|
/** What the instance is, for the UI (`destination`, `relay`, `account`). */
|
|
@@ -54244,7 +54379,9 @@ var IntegrationInstanceDescriptionSchema = object({
|
|
|
54244
54379
|
* `createInstance` without an `id` works: the provider mints it. Absent means the caller must
|
|
54245
54380
|
* send an id (the UI then mints an opaque one).
|
|
54246
54381
|
*/
|
|
54247
|
-
mintsId: boolean().optional()
|
|
54382
|
+
mintsId: boolean().optional(),
|
|
54383
|
+
/** The links to other instances the provider's kinds declare; absent means none. */
|
|
54384
|
+
links: array(IntegrationInstanceLinkSchema).optional()
|
|
54248
54385
|
});
|
|
54249
54386
|
var IntegrationInstanceStatusSchema = object({
|
|
54250
54387
|
state: _enum([
|
|
@@ -54289,7 +54426,11 @@ var integrationInstancesCapability = {
|
|
|
54289
54426
|
methods: {
|
|
54290
54427
|
describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
|
|
54291
54428
|
/** The form; `id: null` is the create form. */
|
|
54292
|
-
instanceFormSchema: method(object({
|
|
54429
|
+
instanceFormSchema: method(object({
|
|
54430
|
+
id: IntegrationInstanceIdSchema.nullable(),
|
|
54431
|
+
/** The kind of the create form (`id: null`) of a provider with several. */
|
|
54432
|
+
kind: string().min(1).optional()
|
|
54433
|
+
}), ConfigSchemaPassthrough$3, { auth: "admin" }),
|
|
54293
54434
|
listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
|
|
54294
54435
|
getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
|
|
54295
54436
|
/**
|
|
@@ -54299,7 +54440,9 @@ var integrationInstancesCapability = {
|
|
|
54299
54440
|
createInstance: method(object({
|
|
54300
54441
|
id: IntegrationInstanceIdSchema.optional(),
|
|
54301
54442
|
name: string().min(1),
|
|
54302
|
-
kind: string().min(1).optional()
|
|
54443
|
+
kind: string().min(1).optional(),
|
|
54444
|
+
/** Form values written with the record (a link picked in the create dialog). */
|
|
54445
|
+
values: record(string(), unknown()).optional()
|
|
54303
54446
|
}), object({ id: string() }), {
|
|
54304
54447
|
kind: "mutation",
|
|
54305
54448
|
auth: "admin"
|
|
@@ -54326,8 +54469,10 @@ var integrationInstancesCapability = {
|
|
|
54326
54469
|
}),
|
|
54327
54470
|
testInstance: optionalMethod(object({
|
|
54328
54471
|
id: IntegrationInstanceIdSchema.nullable(),
|
|
54329
|
-
values: record(string(), unknown())
|
|
54330
|
-
|
|
54472
|
+
values: record(string(), unknown()),
|
|
54473
|
+
/** The kind to test when `id` is null and the provider has several. */
|
|
54474
|
+
kind: string().min(1).optional()
|
|
54475
|
+
}), TestConnectionResultSchema, {
|
|
54331
54476
|
kind: "mutation",
|
|
54332
54477
|
auth: "admin"
|
|
54333
54478
|
})
|
|
@@ -56832,6 +56977,380 @@ var SystemMetricsSchema = object({
|
|
|
56832
56977
|
gpuMemoryPercent: number().optional()
|
|
56833
56978
|
});
|
|
56834
56979
|
method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
|
|
56980
|
+
/**
|
|
56981
|
+
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
56982
|
+
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
56983
|
+
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
56984
|
+
*
|
|
56985
|
+
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
56986
|
+
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
56987
|
+
* that with the addon id closed over). It is deliberately NOT in any input
|
|
56988
|
+
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
56989
|
+
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
56990
|
+
* addons go through the owner's capability.
|
|
56991
|
+
*
|
|
56992
|
+
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
56993
|
+
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
56994
|
+
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
56995
|
+
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
56996
|
+
* row's own ref.
|
|
56997
|
+
*
|
|
56998
|
+
* Internal: addons reach it through `ctx.records`, never directly.
|
|
56999
|
+
*/
|
|
57000
|
+
var RecordIdSchema = string().min(1).max(256);
|
|
57001
|
+
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
57002
|
+
var RecordBodySchema = record(string(), unknown());
|
|
57003
|
+
var RecordInputSchema = object({
|
|
57004
|
+
id: RecordIdSchema,
|
|
57005
|
+
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
57006
|
+
deviceId: number().int().optional(),
|
|
57007
|
+
/** The domain clock, ms: observed / created / opened. */
|
|
57008
|
+
at: number().int().optional(),
|
|
57009
|
+
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
57010
|
+
expiresAt: number().int().optional(),
|
|
57011
|
+
data: RecordBodySchema
|
|
57012
|
+
});
|
|
57013
|
+
var RecordViewSchema = object({
|
|
57014
|
+
id: string(),
|
|
57015
|
+
deviceId: number().int().nullable(),
|
|
57016
|
+
at: number().nullable(),
|
|
57017
|
+
expiresAt: number().nullable(),
|
|
57018
|
+
rev: number().int(),
|
|
57019
|
+
updatedAt: number(),
|
|
57020
|
+
data: RecordBodySchema
|
|
57021
|
+
});
|
|
57022
|
+
/** Declared index fields only. Anything else does not compile, by design. */
|
|
57023
|
+
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
57024
|
+
string(),
|
|
57025
|
+
number(),
|
|
57026
|
+
boolean(),
|
|
57027
|
+
object({ in: array(union([
|
|
57028
|
+
string(),
|
|
57029
|
+
number(),
|
|
57030
|
+
boolean()
|
|
57031
|
+
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
57032
|
+
object({
|
|
57033
|
+
gt: union([string(), number()]).optional(),
|
|
57034
|
+
gte: union([string(), number()]).optional(),
|
|
57035
|
+
lt: union([string(), number()]).optional(),
|
|
57036
|
+
lte: union([string(), number()]).optional()
|
|
57037
|
+
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
57038
|
+
]));
|
|
57039
|
+
var KindShape = { kind: RecordKindNameSchema };
|
|
57040
|
+
var TimeWindowShape = {
|
|
57041
|
+
deviceId: number().int().optional(),
|
|
57042
|
+
atFrom: number().optional(),
|
|
57043
|
+
atTo: number().optional()
|
|
57044
|
+
};
|
|
57045
|
+
var RecordQueryInputSchema = object({
|
|
57046
|
+
...KindShape,
|
|
57047
|
+
...TimeWindowShape,
|
|
57048
|
+
where: RecordWhereSchema.optional(),
|
|
57049
|
+
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
57050
|
+
order: _enum(["at", "id"]).optional(),
|
|
57051
|
+
desc: boolean().optional(),
|
|
57052
|
+
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
57053
|
+
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
57054
|
+
cursor: string().min(1).optional()
|
|
57055
|
+
});
|
|
57056
|
+
var RecordQueryResultSchema = object({
|
|
57057
|
+
records: array(RecordViewSchema),
|
|
57058
|
+
nextCursor: string().nullable()
|
|
57059
|
+
});
|
|
57060
|
+
var RecordKindStateSchema = _enum([
|
|
57061
|
+
"created",
|
|
57062
|
+
"unchanged",
|
|
57063
|
+
"updated"
|
|
57064
|
+
]);
|
|
57065
|
+
var DeclareKindResultSchema = object({
|
|
57066
|
+
kindId: number().int(),
|
|
57067
|
+
state: RecordKindStateSchema,
|
|
57068
|
+
createdIndexes: array(string())
|
|
57069
|
+
});
|
|
57070
|
+
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
57071
|
+
var SecretRefsList = array(string());
|
|
57072
|
+
/**
|
|
57073
|
+
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
57074
|
+
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
57075
|
+
*/
|
|
57076
|
+
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
57077
|
+
object({
|
|
57078
|
+
op: literal("put"),
|
|
57079
|
+
kind: RecordKindNameSchema,
|
|
57080
|
+
record: RecordInputSchema,
|
|
57081
|
+
ifRev: number().int().min(0).optional()
|
|
57082
|
+
}),
|
|
57083
|
+
object({
|
|
57084
|
+
op: literal("patch"),
|
|
57085
|
+
kind: RecordKindNameSchema,
|
|
57086
|
+
id: RecordIdSchema,
|
|
57087
|
+
data: RecordBodySchema,
|
|
57088
|
+
at: number().int().nullable().optional(),
|
|
57089
|
+
expiresAt: number().int().nullable().optional(),
|
|
57090
|
+
ifRev: number().int().min(1).optional()
|
|
57091
|
+
}),
|
|
57092
|
+
object({
|
|
57093
|
+
op: literal("delete"),
|
|
57094
|
+
kind: RecordKindNameSchema,
|
|
57095
|
+
id: RecordIdSchema
|
|
57096
|
+
})
|
|
57097
|
+
]);
|
|
57098
|
+
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
57099
|
+
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
57100
|
+
kind: "mutation",
|
|
57101
|
+
auth: "admin"
|
|
57102
|
+
}), method(object({
|
|
57103
|
+
...KindShape,
|
|
57104
|
+
id: RecordIdSchema
|
|
57105
|
+
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
57106
|
+
...KindShape,
|
|
57107
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
57108
|
+
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
57109
|
+
...KindShape,
|
|
57110
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
57111
|
+
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
57112
|
+
...KindShape,
|
|
57113
|
+
record: RecordInputSchema,
|
|
57114
|
+
/** Compare-and-set. `0` means "the row must not exist". */
|
|
57115
|
+
ifRev: number().int().min(0).optional()
|
|
57116
|
+
}), object({
|
|
57117
|
+
rev: number().int(),
|
|
57118
|
+
releasedRefs: SecretRefsList
|
|
57119
|
+
}), {
|
|
57120
|
+
kind: "mutation",
|
|
57121
|
+
auth: "admin"
|
|
57122
|
+
}), method(object({
|
|
57123
|
+
...KindShape,
|
|
57124
|
+
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
57125
|
+
}), object({
|
|
57126
|
+
count: number().int(),
|
|
57127
|
+
releasedRefs: SecretRefsList
|
|
57128
|
+
}), {
|
|
57129
|
+
kind: "mutation",
|
|
57130
|
+
auth: "admin"
|
|
57131
|
+
}), method(object({
|
|
57132
|
+
...KindShape,
|
|
57133
|
+
id: RecordIdSchema,
|
|
57134
|
+
data: RecordBodySchema,
|
|
57135
|
+
at: number().int().nullable().optional(),
|
|
57136
|
+
expiresAt: number().int().nullable().optional(),
|
|
57137
|
+
ifRev: number().int().min(1).optional()
|
|
57138
|
+
}), object({
|
|
57139
|
+
rev: number().int(),
|
|
57140
|
+
releasedRefs: SecretRefsList
|
|
57141
|
+
}), {
|
|
57142
|
+
kind: "mutation",
|
|
57143
|
+
auth: "admin"
|
|
57144
|
+
}), method(object({
|
|
57145
|
+
...KindShape,
|
|
57146
|
+
id: RecordIdSchema
|
|
57147
|
+
}), object({
|
|
57148
|
+
deleted: boolean(),
|
|
57149
|
+
secretRefs: SecretRefsList
|
|
57150
|
+
}), {
|
|
57151
|
+
kind: "mutation",
|
|
57152
|
+
auth: "admin"
|
|
57153
|
+
}), method(object({
|
|
57154
|
+
...KindShape,
|
|
57155
|
+
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
57156
|
+
}), object({
|
|
57157
|
+
deleted: number().int(),
|
|
57158
|
+
secretRefs: SecretRefsList
|
|
57159
|
+
}), {
|
|
57160
|
+
kind: "mutation",
|
|
57161
|
+
auth: "admin"
|
|
57162
|
+
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
57163
|
+
...KindShape,
|
|
57164
|
+
...TimeWindowShape,
|
|
57165
|
+
where: RecordWhereSchema.optional()
|
|
57166
|
+
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
57167
|
+
...KindShape,
|
|
57168
|
+
deviceId: number().int().optional(),
|
|
57169
|
+
from: number(),
|
|
57170
|
+
to: number(),
|
|
57171
|
+
bucketMs: number().int().positive()
|
|
57172
|
+
}), object({ buckets: array(object({
|
|
57173
|
+
bucketStart: number(),
|
|
57174
|
+
count: number().int()
|
|
57175
|
+
})) }), { auth: "admin" }), method(object({
|
|
57176
|
+
...KindShape,
|
|
57177
|
+
op: _enum([
|
|
57178
|
+
"sum",
|
|
57179
|
+
"min",
|
|
57180
|
+
"max",
|
|
57181
|
+
"count"
|
|
57182
|
+
]),
|
|
57183
|
+
field: RecordFieldPathSchema.optional(),
|
|
57184
|
+
groupBy: RecordFieldPathSchema.optional(),
|
|
57185
|
+
where: RecordWhereSchema.optional()
|
|
57186
|
+
}), object({ rows: array(object({
|
|
57187
|
+
group: union([
|
|
57188
|
+
string(),
|
|
57189
|
+
number(),
|
|
57190
|
+
_null()
|
|
57191
|
+
]),
|
|
57192
|
+
value: number().nullable()
|
|
57193
|
+
})) }), { auth: "admin" }), method(object({
|
|
57194
|
+
...KindShape,
|
|
57195
|
+
deviceId: number().int().optional(),
|
|
57196
|
+
atBefore: number().optional(),
|
|
57197
|
+
where: RecordWhereSchema.optional()
|
|
57198
|
+
}), object({
|
|
57199
|
+
deleted: number().int(),
|
|
57200
|
+
secretRefs: SecretRefsList
|
|
57201
|
+
}), {
|
|
57202
|
+
kind: "mutation",
|
|
57203
|
+
auth: "admin"
|
|
57204
|
+
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
57205
|
+
results: array(object({
|
|
57206
|
+
rev: number().int().nullable(),
|
|
57207
|
+
deleted: boolean()
|
|
57208
|
+
})),
|
|
57209
|
+
releasedRefs: SecretRefsList,
|
|
57210
|
+
secretRefs: SecretRefsList
|
|
57211
|
+
}), {
|
|
57212
|
+
kind: "mutation",
|
|
57213
|
+
auth: "admin"
|
|
57214
|
+
}), method(object({
|
|
57215
|
+
deviceId: number().int(),
|
|
57216
|
+
kinds: array(RecordKindNameSchema).optional()
|
|
57217
|
+
}), object({
|
|
57218
|
+
deleted: record(string(), number().int()),
|
|
57219
|
+
secretRefs: SecretRefsList
|
|
57220
|
+
}), {
|
|
57221
|
+
kind: "mutation",
|
|
57222
|
+
auth: "admin"
|
|
57223
|
+
}), method(object(KindShape), object({ id: number().int() }), {
|
|
57224
|
+
kind: "mutation",
|
|
57225
|
+
auth: "admin"
|
|
57226
|
+
});
|
|
57227
|
+
/**
|
|
57228
|
+
* The wire shapes of the migration API, version 1 (planning#447).
|
|
57229
|
+
*
|
|
57230
|
+
* A migration is code that outlives the server it was written against: an addon at
|
|
57231
|
+
* version 1.4 that is brought to 1.9 in one jump still runs its 1.4 catalogue, against
|
|
57232
|
+
* the 1.9 kernel. The only thing that code may call is {@link MigrationApiV1}; this file
|
|
57233
|
+
* is the contract of every method of it, one Zod pair per method.
|
|
57234
|
+
*
|
|
57235
|
+
* APPEND-ONLY. A released method is never removed and never changes meaning: an input
|
|
57236
|
+
* field is never removed or made required, an enum never narrows, an output field is
|
|
57237
|
+
* never removed. A change that cannot be expressed that way is `migration-api.v2`, next
|
|
57238
|
+
* to this file. `scripts/check-migration-api-append-only.ts` holds the line against
|
|
57239
|
+
* `scripts/migration-api-contract.json`, and every `behaviours` id below must have an
|
|
57240
|
+
* `it('<id>'` in `migration-api.contract.spec.ts`.
|
|
57241
|
+
*/
|
|
57242
|
+
var Rows = record(string(), unknown());
|
|
57243
|
+
var IntegrationId = object({ integrationId: string().min(1) });
|
|
57244
|
+
var RecordId = object({ id: string().min(1) });
|
|
57245
|
+
var RecordRowSchema = object({
|
|
57246
|
+
id: string(),
|
|
57247
|
+
deviceId: number().int().nullable(),
|
|
57248
|
+
at: number().nullable(),
|
|
57249
|
+
expiresAt: number().nullable(),
|
|
57250
|
+
rev: number().int(),
|
|
57251
|
+
updatedAt: number(),
|
|
57252
|
+
data: Rows
|
|
57253
|
+
});
|
|
57254
|
+
var RecordPutShape = {
|
|
57255
|
+
id: string().min(1),
|
|
57256
|
+
deviceId: number().int().optional(),
|
|
57257
|
+
at: number().int().optional(),
|
|
57258
|
+
expiresAt: number().int().optional(),
|
|
57259
|
+
data: Rows
|
|
57260
|
+
};
|
|
57261
|
+
var MigrationIntegrationSchema = object({
|
|
57262
|
+
id: string(),
|
|
57263
|
+
addonId: string(),
|
|
57264
|
+
name: string(),
|
|
57265
|
+
info: Rows,
|
|
57266
|
+
originStableId: string().optional()
|
|
57267
|
+
});
|
|
57268
|
+
var MigrationDeviceSchema = object({
|
|
57269
|
+
id: number().int(),
|
|
57270
|
+
integrationId: string().nullable().optional(),
|
|
57271
|
+
/** The owning device; null or absent for a root. */
|
|
57272
|
+
parentDeviceId: number().int().nullable().optional(),
|
|
57273
|
+
type: string().optional(),
|
|
57274
|
+
name: string().optional(),
|
|
57275
|
+
stableId: string().optional()
|
|
57276
|
+
});
|
|
57277
|
+
/** What the engine says about the migration API it serves: lets an addon notice an older server. */
|
|
57278
|
+
var MigrationApiInfoSchema = object({
|
|
57279
|
+
version: number().int(),
|
|
57280
|
+
methods: array(string())
|
|
57281
|
+
});
|
|
57282
|
+
object({
|
|
57283
|
+
message: string(),
|
|
57284
|
+
meta: Rows.optional(),
|
|
57285
|
+
/** Grouping tags of the line; a line about a device carries `{ deviceId }`. */
|
|
57286
|
+
tags: object({ deviceId: number().int().optional() }).optional()
|
|
57287
|
+
});
|
|
57288
|
+
object({
|
|
57289
|
+
owner: string().min(1),
|
|
57290
|
+
metas: array(MigrationMetaSchema)
|
|
57291
|
+
}), object({
|
|
57292
|
+
bootId: string(),
|
|
57293
|
+
snapshot: _enum([
|
|
57294
|
+
"none",
|
|
57295
|
+
"written",
|
|
57296
|
+
"refused"
|
|
57297
|
+
]),
|
|
57298
|
+
steps: array(MigrationOwnerStepSchema),
|
|
57299
|
+
/** The migration API the engine serves; absent from a server that predates it. */
|
|
57300
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
57301
|
+
}), object({ id: string().min(1) }), object({
|
|
57302
|
+
proceed: boolean(),
|
|
57303
|
+
state: union([literal("pending"), MigrationStatusSchema]),
|
|
57304
|
+
reason: string().nullable()
|
|
57305
|
+
}), object({
|
|
57306
|
+
id: string().min(1),
|
|
57307
|
+
result: MigrationOwnerResultSchema
|
|
57308
|
+
}), object({
|
|
57309
|
+
status: MigrationStatusSchema,
|
|
57310
|
+
error: string().nullable(),
|
|
57311
|
+
rowsChanged: number().int().nonnegative()
|
|
57312
|
+
}), object({
|
|
57313
|
+
rows: number().int().nonnegative(),
|
|
57314
|
+
secrets: number().int().nonnegative()
|
|
57315
|
+
}), IntegrationId.extend({ patch: Rows }), object({ written: boolean() }), IntegrationId.extend({ keys: array(string().min(1)) }), object({
|
|
57316
|
+
present: boolean(),
|
|
57317
|
+
removed: Rows
|
|
57318
|
+
}), RecordRowSchema.nullable(), RecordRowSchema.nullable(), object({
|
|
57319
|
+
...RecordPutShape,
|
|
57320
|
+
ifRev: number().int().optional()
|
|
57321
|
+
}), object({ rev: number().int() }), object({ records: array(object(RecordPutShape)) }), object({ count: number().int().nonnegative() }), RecordId.extend({
|
|
57322
|
+
partial: Rows,
|
|
57323
|
+
ifRev: number().int().optional()
|
|
57324
|
+
}), object({ rev: number().int() }), object({ deleted: boolean() }), object({
|
|
57325
|
+
deviceId: number().int().optional(),
|
|
57326
|
+
atFrom: number().optional(),
|
|
57327
|
+
atTo: number().optional(),
|
|
57328
|
+
where: RecordWhereSchema.optional(),
|
|
57329
|
+
limit: number().int().min(1).optional(),
|
|
57330
|
+
cursor: string().min(1).optional(),
|
|
57331
|
+
resolveSecrets: boolean().optional()
|
|
57332
|
+
}), object({
|
|
57333
|
+
records: array(RecordRowSchema),
|
|
57334
|
+
nextCursor: string().nullable()
|
|
57335
|
+
}), object({
|
|
57336
|
+
deviceId: number().int().optional(),
|
|
57337
|
+
atBefore: number().optional(),
|
|
57338
|
+
where: RecordWhereSchema.optional()
|
|
57339
|
+
}), object({ count: number().int().nonnegative() }), object({}), object({ store: Rows }), object({ patch: Rows }), object({}), object({ keys: array(string().min(1)) }), object({ removed: array(string()) }), object({ refs: array(string()) }), object({ values: record(string(), string().nullable()) }), object({
|
|
57340
|
+
ref: string().min(1),
|
|
57341
|
+
value: string()
|
|
57342
|
+
}), object({}), object({ refs: array(string()) }), object({ deleted: number().int().nonnegative() }), object({}), object({ integrations: array(MigrationIntegrationSchema) }), object({ id: string().min(1) }), MigrationIntegrationSchema.nullable(), object({
|
|
57343
|
+
addonId: string().min(1),
|
|
57344
|
+
integrationId: string().min(1).optional()
|
|
57345
|
+
}), object({ devices: array(MigrationDeviceSchema) }), object({ deviceId: number().int() }), object({
|
|
57346
|
+
id: number().int(),
|
|
57347
|
+
integrationId: string().nullable(),
|
|
57348
|
+
/** Whether the device carries any stored config; its content is not offered. */
|
|
57349
|
+
hasConfig: boolean()
|
|
57350
|
+
}).nullable(), object({
|
|
57351
|
+
deviceId: number().int(),
|
|
57352
|
+
integrationId: string().min(1)
|
|
57353
|
+
}), object({}), object({}), object({ nodeId: string() }), object({}), object({});
|
|
56835
57354
|
method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
|
|
56836
57355
|
id: string().min(1),
|
|
56837
57356
|
error: string().min(1)
|
|
@@ -56861,7 +57380,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
56861
57380
|
"written",
|
|
56862
57381
|
"refused"
|
|
56863
57382
|
]),
|
|
56864
|
-
steps: array(MigrationOwnerStepSchema)
|
|
57383
|
+
steps: array(MigrationOwnerStepSchema),
|
|
57384
|
+
/** The migration API this engine serves (additive: a server that predates it omits it). */
|
|
57385
|
+
migrationApi: MigrationApiInfoSchema.optional()
|
|
56865
57386
|
}), {
|
|
56866
57387
|
kind: "mutation",
|
|
56867
57388
|
auth: "admin"
|
|
@@ -56882,6 +57403,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
|
|
|
56882
57403
|
}), {
|
|
56883
57404
|
kind: "mutation",
|
|
56884
57405
|
auth: "admin"
|
|
57406
|
+
}), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
|
|
57407
|
+
rows: number().int().nonnegative(),
|
|
57408
|
+
secrets: number().int().nonnegative()
|
|
57409
|
+
}), {
|
|
57410
|
+
kind: "mutation",
|
|
57411
|
+
auth: "admin"
|
|
57412
|
+
}), method(object({
|
|
57413
|
+
integrationId: string().min(1),
|
|
57414
|
+
patch: record(string(), unknown())
|
|
57415
|
+
}), object({ written: boolean() }), {
|
|
57416
|
+
kind: "mutation",
|
|
57417
|
+
auth: "admin"
|
|
57418
|
+
}), method(object({
|
|
57419
|
+
integrationId: string().min(1),
|
|
57420
|
+
keys: array(string().min(1))
|
|
57421
|
+
}), object({
|
|
57422
|
+
present: boolean(),
|
|
57423
|
+
removed: record(string(), unknown())
|
|
57424
|
+
}), {
|
|
57425
|
+
kind: "mutation",
|
|
57426
|
+
auth: "admin"
|
|
56885
57427
|
});
|
|
56886
57428
|
method(object({
|
|
56887
57429
|
sourceUrl: string(),
|
|
@@ -59507,7 +60049,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
59507
60049
|
}), method(RelocateMediaInputSchema, object({ jobId: string() }), {
|
|
59508
60050
|
kind: "mutation",
|
|
59509
60051
|
auth: "admin"
|
|
59510
|
-
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(
|
|
60052
|
+
}), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(MediaLocationCensusInputSchema, MediaLocationCensusSchema, {
|
|
60053
|
+
kind: "query",
|
|
60054
|
+
auth: "admin"
|
|
60055
|
+
}), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
|
|
59511
60056
|
kind: "query",
|
|
59512
60057
|
auth: "admin"
|
|
59513
60058
|
}), method(object({}), array(RelocateJobSchema).readonly(), {
|
|
@@ -61914,7 +62459,7 @@ var BrokerStatusSchema$1 = _enum([
|
|
|
61914
62459
|
"unreachable",
|
|
61915
62460
|
"tls-error"
|
|
61916
62461
|
]);
|
|
61917
|
-
var BrokerInfoSchema
|
|
62462
|
+
var BrokerInfoSchema = object({
|
|
61918
62463
|
id: string(),
|
|
61919
62464
|
name: string(),
|
|
61920
62465
|
url: string(),
|
|
@@ -61977,7 +62522,7 @@ var StatusSchema = object({
|
|
|
61977
62522
|
brokerCount: number(),
|
|
61978
62523
|
embeddedRunning: boolean()
|
|
61979
62524
|
});
|
|
61980
|
-
method(_void(), array(BrokerInfoSchema
|
|
62525
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
61981
62526
|
kind: "mutation",
|
|
61982
62527
|
auth: "admin"
|
|
61983
62528
|
}), method(IdInputSchema, _void(), {
|
|
@@ -68295,253 +68840,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, met
|
|
|
68295
68840
|
auth: "admin"
|
|
68296
68841
|
});
|
|
68297
68842
|
/**
|
|
68298
|
-
* records - an addon's own facts and documents (D881): ledgers, audit rows,
|
|
68299
|
-
* config documents, mirrors. The third storage API next to `ctx.settings`
|
|
68300
|
-
* (operator intent, D870) and device-history (numbers over time, D850).
|
|
68301
|
-
*
|
|
68302
|
-
* Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
|
|
68303
|
-
* crossed a process and stated by a hub in-process caller (`ctx.records` does
|
|
68304
|
-
* that with the addon id closed over). It is deliberately NOT in any input
|
|
68305
|
-
* schema, so a session can never supply it, and a call with no stamp is refused
|
|
68306
|
-
* `records/no-caller`. A kind is readable and writable only by its owner; other
|
|
68307
|
-
* addons go through the owner's capability.
|
|
68308
|
-
*
|
|
68309
|
-
* Every read is three-way: a row, `null`, or the call THROWS. A failed read is
|
|
68310
|
-
* never `[]`. Secret fields are never on the wire: a read shows the
|
|
68311
|
-
* redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
|
|
68312
|
-
* resolves itself, and a write may carry only a keychain marker bound to the
|
|
68313
|
-
* row's own ref.
|
|
68314
|
-
*
|
|
68315
|
-
* Internal: addons reach it through `ctx.records`, never directly.
|
|
68316
|
-
*/
|
|
68317
|
-
var RecordIdSchema = string().min(1).max(256);
|
|
68318
|
-
/** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
|
|
68319
|
-
var RecordBodySchema = record(string(), unknown());
|
|
68320
|
-
var RecordInputSchema = object({
|
|
68321
|
-
id: RecordIdSchema,
|
|
68322
|
-
/** The device this row belongs to (D865 removal, per-device reads). */
|
|
68323
|
-
deviceId: number().int().optional(),
|
|
68324
|
-
/** The domain clock, ms: observed / created / opened. */
|
|
68325
|
-
at: number().int().optional(),
|
|
68326
|
-
/** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
|
|
68327
|
-
expiresAt: number().int().optional(),
|
|
68328
|
-
data: RecordBodySchema
|
|
68329
|
-
});
|
|
68330
|
-
var RecordViewSchema = object({
|
|
68331
|
-
id: string(),
|
|
68332
|
-
deviceId: number().int().nullable(),
|
|
68333
|
-
at: number().nullable(),
|
|
68334
|
-
expiresAt: number().nullable(),
|
|
68335
|
-
rev: number().int(),
|
|
68336
|
-
updatedAt: number(),
|
|
68337
|
-
data: RecordBodySchema
|
|
68338
|
-
});
|
|
68339
|
-
/** Declared index fields only. Anything else does not compile, by design. */
|
|
68340
|
-
var RecordWhereSchema = record(RecordFieldPathSchema, union([
|
|
68341
|
-
string(),
|
|
68342
|
-
number(),
|
|
68343
|
-
boolean(),
|
|
68344
|
-
object({ in: array(union([
|
|
68345
|
-
string(),
|
|
68346
|
-
number(),
|
|
68347
|
-
boolean()
|
|
68348
|
-
])).min(1).max(RECORD_BATCH_MAX) }),
|
|
68349
|
-
object({
|
|
68350
|
-
gt: union([string(), number()]).optional(),
|
|
68351
|
-
gte: union([string(), number()]).optional(),
|
|
68352
|
-
lt: union([string(), number()]).optional(),
|
|
68353
|
-
lte: union([string(), number()]).optional()
|
|
68354
|
-
}).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
|
|
68355
|
-
]));
|
|
68356
|
-
var KindShape = { kind: RecordKindNameSchema };
|
|
68357
|
-
var TimeWindowShape = {
|
|
68358
|
-
deviceId: number().int().optional(),
|
|
68359
|
-
atFrom: number().optional(),
|
|
68360
|
-
atTo: number().optional()
|
|
68361
|
-
};
|
|
68362
|
-
var RecordQueryInputSchema = object({
|
|
68363
|
-
...KindShape,
|
|
68364
|
-
...TimeWindowShape,
|
|
68365
|
-
where: RecordWhereSchema.optional(),
|
|
68366
|
-
/** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
|
|
68367
|
-
order: _enum(["at", "id"]).optional(),
|
|
68368
|
-
desc: boolean().optional(),
|
|
68369
|
-
limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
|
|
68370
|
-
/** Opaque keyset cursor from a previous answer. There is no offset. */
|
|
68371
|
-
cursor: string().min(1).optional()
|
|
68372
|
-
});
|
|
68373
|
-
var RecordQueryResultSchema = object({
|
|
68374
|
-
records: array(RecordViewSchema),
|
|
68375
|
-
nextCursor: string().nullable()
|
|
68376
|
-
});
|
|
68377
|
-
var RecordKindStateSchema = _enum([
|
|
68378
|
-
"created",
|
|
68379
|
-
"unchanged",
|
|
68380
|
-
"updated"
|
|
68381
|
-
]);
|
|
68382
|
-
var DeclareKindResultSchema = object({
|
|
68383
|
-
kindId: number().int(),
|
|
68384
|
-
state: RecordKindStateSchema,
|
|
68385
|
-
createdIndexes: array(string())
|
|
68386
|
-
});
|
|
68387
|
-
/** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
|
|
68388
|
-
var SecretRefsList = array(string());
|
|
68389
|
-
/**
|
|
68390
|
-
* One op of a `transact`: the single-row writes, each on a kind of the caller.
|
|
68391
|
-
* All ops of one call share ONE database file and ONE `transactFamily`.
|
|
68392
|
-
*/
|
|
68393
|
-
var RecordTransactOpSchema = discriminatedUnion("op", [
|
|
68394
|
-
object({
|
|
68395
|
-
op: literal("put"),
|
|
68396
|
-
kind: RecordKindNameSchema,
|
|
68397
|
-
record: RecordInputSchema,
|
|
68398
|
-
ifRev: number().int().min(0).optional()
|
|
68399
|
-
}),
|
|
68400
|
-
object({
|
|
68401
|
-
op: literal("patch"),
|
|
68402
|
-
kind: RecordKindNameSchema,
|
|
68403
|
-
id: RecordIdSchema,
|
|
68404
|
-
data: RecordBodySchema,
|
|
68405
|
-
at: number().int().nullable().optional(),
|
|
68406
|
-
expiresAt: number().int().nullable().optional(),
|
|
68407
|
-
ifRev: number().int().min(1).optional()
|
|
68408
|
-
}),
|
|
68409
|
-
object({
|
|
68410
|
-
op: literal("delete"),
|
|
68411
|
-
kind: RecordKindNameSchema,
|
|
68412
|
-
id: RecordIdSchema
|
|
68413
|
-
})
|
|
68414
|
-
]);
|
|
68415
|
-
var RecordSecretRefsSchema = record(string(), record(string(), string()));
|
|
68416
|
-
method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
|
|
68417
|
-
kind: "mutation",
|
|
68418
|
-
auth: "admin"
|
|
68419
|
-
}), method(object({
|
|
68420
|
-
...KindShape,
|
|
68421
|
-
id: RecordIdSchema
|
|
68422
|
-
}), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
|
|
68423
|
-
...KindShape,
|
|
68424
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
68425
|
-
}), array(RecordViewSchema), { auth: "admin" }), method(object({
|
|
68426
|
-
...KindShape,
|
|
68427
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
68428
|
-
}), RecordSecretRefsSchema, { auth: "admin" }), method(object({
|
|
68429
|
-
...KindShape,
|
|
68430
|
-
record: RecordInputSchema,
|
|
68431
|
-
/** Compare-and-set. `0` means "the row must not exist". */
|
|
68432
|
-
ifRev: number().int().min(0).optional()
|
|
68433
|
-
}), object({
|
|
68434
|
-
rev: number().int(),
|
|
68435
|
-
releasedRefs: SecretRefsList
|
|
68436
|
-
}), {
|
|
68437
|
-
kind: "mutation",
|
|
68438
|
-
auth: "admin"
|
|
68439
|
-
}), method(object({
|
|
68440
|
-
...KindShape,
|
|
68441
|
-
records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
|
|
68442
|
-
}), object({
|
|
68443
|
-
count: number().int(),
|
|
68444
|
-
releasedRefs: SecretRefsList
|
|
68445
|
-
}), {
|
|
68446
|
-
kind: "mutation",
|
|
68447
|
-
auth: "admin"
|
|
68448
|
-
}), method(object({
|
|
68449
|
-
...KindShape,
|
|
68450
|
-
id: RecordIdSchema,
|
|
68451
|
-
data: RecordBodySchema,
|
|
68452
|
-
at: number().int().nullable().optional(),
|
|
68453
|
-
expiresAt: number().int().nullable().optional(),
|
|
68454
|
-
ifRev: number().int().min(1).optional()
|
|
68455
|
-
}), object({
|
|
68456
|
-
rev: number().int(),
|
|
68457
|
-
releasedRefs: SecretRefsList
|
|
68458
|
-
}), {
|
|
68459
|
-
kind: "mutation",
|
|
68460
|
-
auth: "admin"
|
|
68461
|
-
}), method(object({
|
|
68462
|
-
...KindShape,
|
|
68463
|
-
id: RecordIdSchema
|
|
68464
|
-
}), object({
|
|
68465
|
-
deleted: boolean(),
|
|
68466
|
-
secretRefs: SecretRefsList
|
|
68467
|
-
}), {
|
|
68468
|
-
kind: "mutation",
|
|
68469
|
-
auth: "admin"
|
|
68470
|
-
}), method(object({
|
|
68471
|
-
...KindShape,
|
|
68472
|
-
ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
|
|
68473
|
-
}), object({
|
|
68474
|
-
deleted: number().int(),
|
|
68475
|
-
secretRefs: SecretRefsList
|
|
68476
|
-
}), {
|
|
68477
|
-
kind: "mutation",
|
|
68478
|
-
auth: "admin"
|
|
68479
|
-
}), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
|
|
68480
|
-
...KindShape,
|
|
68481
|
-
...TimeWindowShape,
|
|
68482
|
-
where: RecordWhereSchema.optional()
|
|
68483
|
-
}), object({ count: number().int() }), { auth: "admin" }), method(object({
|
|
68484
|
-
...KindShape,
|
|
68485
|
-
deviceId: number().int().optional(),
|
|
68486
|
-
from: number(),
|
|
68487
|
-
to: number(),
|
|
68488
|
-
bucketMs: number().int().positive()
|
|
68489
|
-
}), object({ buckets: array(object({
|
|
68490
|
-
bucketStart: number(),
|
|
68491
|
-
count: number().int()
|
|
68492
|
-
})) }), { auth: "admin" }), method(object({
|
|
68493
|
-
...KindShape,
|
|
68494
|
-
op: _enum([
|
|
68495
|
-
"sum",
|
|
68496
|
-
"min",
|
|
68497
|
-
"max",
|
|
68498
|
-
"count"
|
|
68499
|
-
]),
|
|
68500
|
-
field: RecordFieldPathSchema.optional(),
|
|
68501
|
-
groupBy: RecordFieldPathSchema.optional(),
|
|
68502
|
-
where: RecordWhereSchema.optional()
|
|
68503
|
-
}), object({ rows: array(object({
|
|
68504
|
-
group: union([
|
|
68505
|
-
string(),
|
|
68506
|
-
number(),
|
|
68507
|
-
_null()
|
|
68508
|
-
]),
|
|
68509
|
-
value: number().nullable()
|
|
68510
|
-
})) }), { auth: "admin" }), method(object({
|
|
68511
|
-
...KindShape,
|
|
68512
|
-
deviceId: number().int().optional(),
|
|
68513
|
-
atBefore: number().optional(),
|
|
68514
|
-
where: RecordWhereSchema.optional()
|
|
68515
|
-
}), object({
|
|
68516
|
-
deleted: number().int(),
|
|
68517
|
-
secretRefs: SecretRefsList
|
|
68518
|
-
}), {
|
|
68519
|
-
kind: "mutation",
|
|
68520
|
-
auth: "admin"
|
|
68521
|
-
}), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
|
|
68522
|
-
results: array(object({
|
|
68523
|
-
rev: number().int().nullable(),
|
|
68524
|
-
deleted: boolean()
|
|
68525
|
-
})),
|
|
68526
|
-
releasedRefs: SecretRefsList,
|
|
68527
|
-
secretRefs: SecretRefsList
|
|
68528
|
-
}), {
|
|
68529
|
-
kind: "mutation",
|
|
68530
|
-
auth: "admin"
|
|
68531
|
-
}), method(object({
|
|
68532
|
-
deviceId: number().int(),
|
|
68533
|
-
kinds: array(RecordKindNameSchema).optional()
|
|
68534
|
-
}), object({
|
|
68535
|
-
deleted: record(string(), number().int()),
|
|
68536
|
-
secretRefs: SecretRefsList
|
|
68537
|
-
}), {
|
|
68538
|
-
kind: "mutation",
|
|
68539
|
-
auth: "admin"
|
|
68540
|
-
}), method(object(KindShape), object({ id: number().int() }), {
|
|
68541
|
-
kind: "mutation",
|
|
68542
|
-
auth: "admin"
|
|
68543
|
-
});
|
|
68544
|
-
/**
|
|
68545
68843
|
* record-entries - the ONE admin surface for every list of user-created entries
|
|
68546
68844
|
* (D881): notification targets, brokers, LLM profiles, anything an addon
|
|
68547
68845
|
* declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
|
|
@@ -76056,11 +76354,15 @@ discriminatedUnion("kind", [object({
|
|
|
76056
76354
|
* 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
|
|
76057
76355
|
* near F. A device that is not near one level most of the time has no floor.
|
|
76058
76356
|
* 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
|
|
76059
|
-
* 3.
|
|
76060
|
-
*
|
|
76357
|
+
* 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
|
|
76358
|
+
* display waking): it is neither a run nor a pause. Among the remaining
|
|
76359
|
+
* episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
|
|
76360
|
+
* two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
|
|
76361
|
+
* a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
|
|
76362
|
+
* is no run.
|
|
76061
76363
|
* 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
|
|
76062
|
-
* grid a person would type; stop dwell covers the longest pause
|
|
76063
|
-
* covers the longest standby blip above T.
|
|
76364
|
+
* grid a person would type; stop dwell covers the longest pause and the
|
|
76365
|
+
* kind's floor; start dwell covers the longest pulse and standby blip above T.
|
|
76064
76366
|
* 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
|
|
76065
76367
|
* run must hold exactly one true interval and nothing may flap outside.
|
|
76066
76368
|
*/
|
|
@@ -76071,6 +76373,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
|
|
|
76071
76373
|
30 * SECOND_MS;
|
|
76072
76374
|
60 * SECOND_MS;
|
|
76073
76375
|
20 * MINUTE_MS$1;
|
|
76376
|
+
90 * SECOND_MS;
|
|
76377
|
+
30 * MINUTE_MS$1;
|
|
76378
|
+
10 * MINUTE_MS$1;
|
|
76379
|
+
DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
|
|
76074
76380
|
10 * SECOND_MS;
|
|
76075
76381
|
10 * SECOND_MS;
|
|
76076
76382
|
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
@@ -77089,20 +77395,6 @@ Object.freeze({
|
|
|
77089
77395
|
access: "create",
|
|
77090
77396
|
role: "viewer"
|
|
77091
77397
|
},
|
|
77092
|
-
"broker.add": {
|
|
77093
|
-
capName: "broker",
|
|
77094
|
-
capScope: "system",
|
|
77095
|
-
addonId: null,
|
|
77096
|
-
access: "create",
|
|
77097
|
-
role: "admin"
|
|
77098
|
-
},
|
|
77099
|
-
"broker.get": {
|
|
77100
|
-
capName: "broker",
|
|
77101
|
-
capScope: "system",
|
|
77102
|
-
addonId: null,
|
|
77103
|
-
access: "view",
|
|
77104
|
-
role: "admin"
|
|
77105
|
-
},
|
|
77106
77398
|
"broker.getBrokerConfig": {
|
|
77107
77399
|
capName: "broker",
|
|
77108
77400
|
capScope: "system",
|
|
@@ -77110,20 +77402,6 @@ Object.freeze({
|
|
|
77110
77402
|
access: "view",
|
|
77111
77403
|
role: "admin"
|
|
77112
77404
|
},
|
|
77113
|
-
"broker.getSettings": {
|
|
77114
|
-
capName: "broker",
|
|
77115
|
-
capScope: "system",
|
|
77116
|
-
addonId: null,
|
|
77117
|
-
access: "view",
|
|
77118
|
-
role: "admin"
|
|
77119
|
-
},
|
|
77120
|
-
"broker.getSettingsSchema": {
|
|
77121
|
-
capName: "broker",
|
|
77122
|
-
capScope: "system",
|
|
77123
|
-
addonId: null,
|
|
77124
|
-
access: "view",
|
|
77125
|
-
role: "admin"
|
|
77126
|
-
},
|
|
77127
77405
|
"broker.getState": {
|
|
77128
77406
|
capName: "broker",
|
|
77129
77407
|
capScope: "system",
|
|
@@ -77138,20 +77416,6 @@ Object.freeze({
|
|
|
77138
77416
|
access: "view",
|
|
77139
77417
|
role: "admin"
|
|
77140
77418
|
},
|
|
77141
|
-
"broker.list": {
|
|
77142
|
-
capName: "broker",
|
|
77143
|
-
capScope: "system",
|
|
77144
|
-
addonId: null,
|
|
77145
|
-
access: "view",
|
|
77146
|
-
role: "admin"
|
|
77147
|
-
},
|
|
77148
|
-
"broker.listProviders": {
|
|
77149
|
-
capName: "broker",
|
|
77150
|
-
capScope: "system",
|
|
77151
|
-
addonId: null,
|
|
77152
|
-
access: "view",
|
|
77153
|
-
role: "admin"
|
|
77154
|
-
},
|
|
77155
77419
|
"broker.publish": {
|
|
77156
77420
|
capName: "broker",
|
|
77157
77421
|
capScope: "system",
|
|
@@ -77159,20 +77423,6 @@ Object.freeze({
|
|
|
77159
77423
|
access: "create",
|
|
77160
77424
|
role: "admin"
|
|
77161
77425
|
},
|
|
77162
|
-
"broker.remove": {
|
|
77163
|
-
capName: "broker",
|
|
77164
|
-
capScope: "system",
|
|
77165
|
-
addonId: null,
|
|
77166
|
-
access: "delete",
|
|
77167
|
-
role: "admin"
|
|
77168
|
-
},
|
|
77169
|
-
"broker.setSettings": {
|
|
77170
|
-
capName: "broker",
|
|
77171
|
-
capScope: "system",
|
|
77172
|
-
addonId: null,
|
|
77173
|
-
access: "create",
|
|
77174
|
-
role: "admin"
|
|
77175
|
-
},
|
|
77176
77426
|
"broker.subscribe": {
|
|
77177
77427
|
capName: "broker",
|
|
77178
77428
|
capScope: "system",
|
|
@@ -77180,20 +77430,6 @@ Object.freeze({
|
|
|
77180
77430
|
access: "create",
|
|
77181
77431
|
role: "admin"
|
|
77182
77432
|
},
|
|
77183
|
-
"broker.testConnection": {
|
|
77184
|
-
capName: "broker",
|
|
77185
|
-
capScope: "system",
|
|
77186
|
-
addonId: null,
|
|
77187
|
-
access: "create",
|
|
77188
|
-
role: "admin"
|
|
77189
|
-
},
|
|
77190
|
-
"broker.testSettings": {
|
|
77191
|
-
capName: "broker",
|
|
77192
|
-
capScope: "system",
|
|
77193
|
-
addonId: null,
|
|
77194
|
-
access: "create",
|
|
77195
|
-
role: "admin"
|
|
77196
|
-
},
|
|
77197
77433
|
"broker.unsubscribe": {
|
|
77198
77434
|
capName: "broker",
|
|
77199
77435
|
capScope: "system",
|
|
@@ -79826,13 +80062,6 @@ Object.freeze({
|
|
|
79826
80062
|
access: "view",
|
|
79827
80063
|
role: "admin"
|
|
79828
80064
|
},
|
|
79829
|
-
"integrations.getSettings": {
|
|
79830
|
-
capName: "integrations",
|
|
79831
|
-
capScope: "system",
|
|
79832
|
-
addonId: null,
|
|
79833
|
-
access: "view",
|
|
79834
|
-
role: "admin"
|
|
79835
|
-
},
|
|
79836
80065
|
"integrations.list": {
|
|
79837
80066
|
capName: "integrations",
|
|
79838
80067
|
capScope: "system",
|
|
@@ -79840,13 +80069,6 @@ Object.freeze({
|
|
|
79840
80069
|
access: "view",
|
|
79841
80070
|
role: "admin"
|
|
79842
80071
|
},
|
|
79843
|
-
"integrations.setSettings": {
|
|
79844
|
-
capName: "integrations",
|
|
79845
|
-
capScope: "system",
|
|
79846
|
-
addonId: null,
|
|
79847
|
-
access: "create",
|
|
79848
|
-
role: "admin"
|
|
79849
|
-
},
|
|
79850
80072
|
"integrations.testConnection": {
|
|
79851
80073
|
capName: "integrations",
|
|
79852
80074
|
capScope: "system",
|
|
@@ -80841,6 +81063,13 @@ Object.freeze({
|
|
|
80841
81063
|
access: "view",
|
|
80842
81064
|
role: "admin"
|
|
80843
81065
|
},
|
|
81066
|
+
"migrations.clearLegacyIntegrationRows": {
|
|
81067
|
+
capName: "migrations",
|
|
81068
|
+
capScope: "system",
|
|
81069
|
+
addonId: null,
|
|
81070
|
+
access: "delete",
|
|
81071
|
+
role: "admin"
|
|
81072
|
+
},
|
|
80844
81073
|
"migrations.declareOwner": {
|
|
80845
81074
|
capName: "migrations",
|
|
80846
81075
|
capScope: "system",
|
|
@@ -80848,6 +81077,13 @@ Object.freeze({
|
|
|
80848
81077
|
access: "create",
|
|
80849
81078
|
role: "admin"
|
|
80850
81079
|
},
|
|
81080
|
+
"migrations.deleteLegacyIntegrationRowKeys": {
|
|
81081
|
+
capName: "migrations",
|
|
81082
|
+
capScope: "system",
|
|
81083
|
+
addonId: null,
|
|
81084
|
+
access: "delete",
|
|
81085
|
+
role: "admin"
|
|
81086
|
+
},
|
|
80851
81087
|
"migrations.dryRun": {
|
|
80852
81088
|
capName: "migrations",
|
|
80853
81089
|
capScope: "system",
|
|
@@ -80883,6 +81119,20 @@ Object.freeze({
|
|
|
80883
81119
|
access: "create",
|
|
80884
81120
|
role: "admin"
|
|
80885
81121
|
},
|
|
81122
|
+
"migrations.patchLegacyIntegrationRows": {
|
|
81123
|
+
capName: "migrations",
|
|
81124
|
+
capScope: "system",
|
|
81125
|
+
addonId: null,
|
|
81126
|
+
access: "create",
|
|
81127
|
+
role: "admin"
|
|
81128
|
+
},
|
|
81129
|
+
"migrations.readLegacyIntegrationRows": {
|
|
81130
|
+
capName: "migrations",
|
|
81131
|
+
capScope: "system",
|
|
81132
|
+
addonId: null,
|
|
81133
|
+
access: "view",
|
|
81134
|
+
role: "admin"
|
|
81135
|
+
},
|
|
80886
81136
|
"migrations.restorePreMigration": {
|
|
80887
81137
|
capName: "migrations",
|
|
80888
81138
|
capScope: "system",
|
|
@@ -82143,6 +82393,13 @@ Object.freeze({
|
|
|
82143
82393
|
access: "view",
|
|
82144
82394
|
role: "viewer"
|
|
82145
82395
|
},
|
|
82396
|
+
"pipelineAnalytics.getMediaLocationCensus": {
|
|
82397
|
+
capName: "pipeline-analytics",
|
|
82398
|
+
capScope: "device",
|
|
82399
|
+
addonId: null,
|
|
82400
|
+
access: "view",
|
|
82401
|
+
role: "admin"
|
|
82402
|
+
},
|
|
82146
82403
|
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
82147
82404
|
capName: "pipeline-analytics",
|
|
82148
82405
|
capScope: "device",
|
|
@@ -91115,6 +91372,165 @@ object({
|
|
|
91115
91372
|
delay: number().nonnegative().optional(),
|
|
91116
91373
|
commands: record(string(), unknown())
|
|
91117
91374
|
});
|
|
91375
|
+
/** The `instance-select` field of a link, keyed like the body field it fills. */
|
|
91376
|
+
function linkFieldDescriptor(key, target, labels) {
|
|
91377
|
+
return {
|
|
91378
|
+
type: "instance-select",
|
|
91379
|
+
key,
|
|
91380
|
+
label: labels.label,
|
|
91381
|
+
...labels.description !== void 0 ? { description: labels.description } : {},
|
|
91382
|
+
required: labels.required ?? true,
|
|
91383
|
+
...labels.noneLabel !== void 0 ? { noneLabel: labels.noneLabel } : {},
|
|
91384
|
+
functionCap: target.cap,
|
|
91385
|
+
...target.kind !== void 0 ? { kind: target.kind } : {}
|
|
91386
|
+
};
|
|
91387
|
+
}
|
|
91388
|
+
/**
|
|
91389
|
+
* Declare the record kind of a service that links to other instances. The body is
|
|
91390
|
+
* `{ name, ...links, ...fields }`; the links are recorded in the kind (read back by
|
|
91391
|
+
* {@link linksOfKind}) so the hub can index who points at what.
|
|
91392
|
+
*/
|
|
91393
|
+
function declareLinkKind(spec) {
|
|
91394
|
+
const linkShape = {};
|
|
91395
|
+
for (const [field, target] of Object.entries(spec.links)) linkShape[field] = linkField(target);
|
|
91396
|
+
const typed = entryBody({
|
|
91397
|
+
...linkShape,
|
|
91398
|
+
...spec.fields
|
|
91399
|
+
});
|
|
91400
|
+
return defineEntryKind(spec.name, {
|
|
91401
|
+
title: spec.title,
|
|
91402
|
+
schema: typed,
|
|
91403
|
+
writeTrigger: spec.writeTrigger
|
|
91404
|
+
});
|
|
91405
|
+
}
|
|
91406
|
+
/** The id a record holds in a link field; undefined while it names none. */
|
|
91407
|
+
function linkedId(body, field) {
|
|
91408
|
+
const value = body[field];
|
|
91409
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
91410
|
+
}
|
|
91411
|
+
/** The `integration-instances` provider of a service whose records link to other instances. */
|
|
91412
|
+
function buildLinkedInstances(records, spec) {
|
|
91413
|
+
const links = linksOfKind(spec.kind);
|
|
91414
|
+
const kindId = spec.instanceKind ?? spec.kind.name;
|
|
91415
|
+
const instances = new RecordIntegrationInstances(records, spec.kind, {
|
|
91416
|
+
description: {
|
|
91417
|
+
...spec.description,
|
|
91418
|
+
links: links.map((link) => ({
|
|
91419
|
+
kind: kindId,
|
|
91420
|
+
field: link.field,
|
|
91421
|
+
to: link.to
|
|
91422
|
+
}))
|
|
91423
|
+
},
|
|
91424
|
+
formSchema: async (id) => {
|
|
91425
|
+
const first = links[0];
|
|
91426
|
+
const chosen = id === null || first === void 0 || spec.dynamicSections === void 0 ? void 0 : linkedId(await instances.getResolved(id) ?? {}, first.field);
|
|
91427
|
+
const dynamic = chosen !== void 0 && spec.dynamicSections !== void 0 ? await spec.dynamicSections(chosen) : [];
|
|
91428
|
+
return { sections: [
|
|
91429
|
+
{
|
|
91430
|
+
id: "links",
|
|
91431
|
+
title: "Linked instances",
|
|
91432
|
+
columns: 1,
|
|
91433
|
+
fields: links.map((link) => linkFieldDescriptor(link.field, link.to, spec.labels?.[link.field] ?? { label: link.field }))
|
|
91434
|
+
},
|
|
91435
|
+
...spec.linkSections ?? [],
|
|
91436
|
+
...dynamic
|
|
91437
|
+
] };
|
|
91438
|
+
},
|
|
91439
|
+
initialBody: (name) => spec.kind.schema.parse({
|
|
91440
|
+
name,
|
|
91441
|
+
...Object.fromEntries(links.map((link) => [link.field, ""]))
|
|
91442
|
+
}),
|
|
91443
|
+
configured: (body) => links.every((link) => linkedId(body, link.field) !== void 0) && (spec.alsoConfigured?.(body) ?? true),
|
|
91444
|
+
...spec.mintId !== void 0 ? { mintId: spec.mintId } : {},
|
|
91445
|
+
...spec.onChanged !== void 0 ? { onChanged: spec.onChanged } : {}
|
|
91446
|
+
});
|
|
91447
|
+
return instances;
|
|
91448
|
+
}
|
|
91449
|
+
entryBody({ brokerId: string() });
|
|
91450
|
+
/**
|
|
91451
|
+
* The calendars an account imports leave the account (D1013). An account record used to carry a
|
|
91452
|
+
* `calendarIds` array; each calendar is now a record of its own that LINKS to the account
|
|
91453
|
+
* (`calendars`, one per account and calendar). For every account row with an array:
|
|
91454
|
+
*
|
|
91455
|
+
* 1. each entry becomes a calendar record under a deterministic id (an existing one is kept);
|
|
91456
|
+
* 2. each record is read back and must name this account and calendar;
|
|
91457
|
+
* 3. only then is the array cleared (a patch: nothing else on the account is touched).
|
|
91458
|
+
*
|
|
91459
|
+
* An entry that names another account, or is not a calendar id at all, selected nothing before
|
|
91460
|
+
* (the reader only matched its own account's ids): it is counted, named in a warning and goes with
|
|
91461
|
+
* the array. An array that is not a list of strings is left where it is. A failure on one account
|
|
91462
|
+
* does not stop the others; the run then throws, and the accounts that failed keep their array.
|
|
91463
|
+
* Accounts are never created or deleted. Idempotent: with the arrays cleared nothing is left to do.
|
|
91464
|
+
*/
|
|
91465
|
+
/** The id of the calendar record of one (account, calendar): the same pair is always the same row. */
|
|
91466
|
+
function calendarRecordId(accountId, calendarId) {
|
|
91467
|
+
return `cal_${(0, node_crypto.createHash)("sha1").update(`${accountId}\0${calendarId}`).digest("hex").slice(0, 16)}`;
|
|
91468
|
+
}
|
|
91469
|
+
function entriesOf(value) {
|
|
91470
|
+
if (!Array.isArray(value)) return null;
|
|
91471
|
+
const out = [];
|
|
91472
|
+
for (const item of value) {
|
|
91473
|
+
if (typeof item !== "string") return null;
|
|
91474
|
+
out.push(item);
|
|
91475
|
+
}
|
|
91476
|
+
return out;
|
|
91477
|
+
}
|
|
91478
|
+
async function moveCalendarIdsToRecords(ports, spec) {
|
|
91479
|
+
let created = 0;
|
|
91480
|
+
let cleared = 0;
|
|
91481
|
+
let dropped = 0;
|
|
91482
|
+
const failed = [];
|
|
91483
|
+
for (const account of await ports.listAccounts()) {
|
|
91484
|
+
if (account.calendarIds === void 0 || account.calendarIds === null) continue;
|
|
91485
|
+
const entries = entriesOf(account.calendarIds);
|
|
91486
|
+
if (entries === null) {
|
|
91487
|
+
ports.warn(`account '${account.id}': calendarIds is not a list of ids and stays as it is`);
|
|
91488
|
+
failed.push(account.id);
|
|
91489
|
+
continue;
|
|
91490
|
+
}
|
|
91491
|
+
try {
|
|
91492
|
+
const own = /* @__PURE__ */ new Set();
|
|
91493
|
+
const inert = [];
|
|
91494
|
+
for (const entry of entries) {
|
|
91495
|
+
const calendarId = spec.calendarIdOf(account.id, entry);
|
|
91496
|
+
if (calendarId === null || calendarId === "") inert.push(entry);
|
|
91497
|
+
else own.add(calendarId);
|
|
91498
|
+
}
|
|
91499
|
+
for (const calendarId of own) {
|
|
91500
|
+
const recordId = calendarRecordId(account.id, calendarId);
|
|
91501
|
+
const body = {
|
|
91502
|
+
name: calendarId,
|
|
91503
|
+
accountId: account.id,
|
|
91504
|
+
calendarId
|
|
91505
|
+
};
|
|
91506
|
+
if (await ports.readCalendar(recordId) === null) {
|
|
91507
|
+
await ports.putCalendar(recordId, body);
|
|
91508
|
+
created += 1;
|
|
91509
|
+
}
|
|
91510
|
+
const back = await ports.readCalendar(recordId);
|
|
91511
|
+
if (back?.accountId !== account.id || back.calendarId !== calendarId) throw new Error(`account '${account.id}': the calendar record '${recordId}' did not read back; the array stays`);
|
|
91512
|
+
}
|
|
91513
|
+
if (inert.length > 0) {
|
|
91514
|
+
ports.warn(`account '${account.id}': ${inert.length} entr${inert.length === 1 ? "y" : "ies"} of calendarIds named another account or no calendar and selected nothing: ${inert.join(", ")}`);
|
|
91515
|
+
dropped += inert.length;
|
|
91516
|
+
}
|
|
91517
|
+
await ports.clearCalendarIds(account.id);
|
|
91518
|
+
cleared += 1;
|
|
91519
|
+
} catch (error) {
|
|
91520
|
+
ports.warn(error instanceof Error ? error.message : String(error));
|
|
91521
|
+
failed.push(account.id);
|
|
91522
|
+
}
|
|
91523
|
+
}
|
|
91524
|
+
if (failed.length > 0) throw new Error(`calendarIds kept on ${failed.length} account(s) (${failed.join(", ")}); ${created} calendar record(s) written, ${cleared} account(s) cleared`);
|
|
91525
|
+
return {
|
|
91526
|
+
outcome: created === 0 && cleared === 0 ? "already-done" : "done",
|
|
91527
|
+
summary: {
|
|
91528
|
+
created,
|
|
91529
|
+
cleared,
|
|
91530
|
+
dropped
|
|
91531
|
+
}
|
|
91532
|
+
};
|
|
91533
|
+
}
|
|
91118
91534
|
//#endregion
|
|
91119
91535
|
//#region src/google-types.ts
|
|
91120
91536
|
/** One Google account connection (broker entry). Credentials for the OAuth
|
|
@@ -91125,28 +91541,16 @@ var GoogleBrokerEntrySchema = object({
|
|
|
91125
91541
|
clientId: string().default(""),
|
|
91126
91542
|
clientSecret: string().default("").meta({ secret: true }),
|
|
91127
91543
|
/**
|
|
91128
|
-
* @deprecated Per-broker filter;
|
|
91129
|
-
*
|
|
91544
|
+
* @deprecated Per-broker filter; the calendars an account imports are `calendars` records
|
|
91545
|
+
* (Connectivity → Calendars). Kept for migrate only.
|
|
91130
91546
|
*/
|
|
91131
91547
|
calendarFilter: string().default(""),
|
|
91132
91548
|
/** Default From for Gmail send — usually the connected account email. */
|
|
91133
91549
|
fromAddress: string().default(""),
|
|
91134
|
-
fromName: string().default("")
|
|
91135
|
-
/**
|
|
91136
|
-
* The stamped calendar ids (`<brokerId>:<googleCalendarId>`) this broker imports. `undefined`
|
|
91137
|
-
* means "not moved here yet": the reader falls back to the shared `calendarIds` setting of
|
|
91138
|
-
* the addon until the migration copied it. Empty = import none.
|
|
91139
|
-
*/
|
|
91140
|
-
calendarIds: array(string()).optional()
|
|
91550
|
+
fromName: string().default("")
|
|
91141
91551
|
});
|
|
91142
91552
|
var GoogleServicesConfigSchema = object({
|
|
91143
91553
|
brokers: array(GoogleBrokerEntrySchema).default([]),
|
|
91144
|
-
/**
|
|
91145
|
-
* @deprecated The shared set (`brokerId:googleCalendarId`). Each broker owns its own
|
|
91146
|
-
* `calendarIds` now; this key is read only while a broker has none, and the migration
|
|
91147
|
-
* `google-services/2026-11-03-drop-shared-calendar-ids` deletes it.
|
|
91148
|
-
*/
|
|
91149
|
-
calendarIds: array(string()).default([]),
|
|
91150
91554
|
/** @deprecated Pre-broker singleton — cleared after migrateLegacyToBrokers. */
|
|
91151
91555
|
clientId: string().optional(),
|
|
91152
91556
|
/** @deprecated Pre-broker singleton — cleared after migrateLegacyToBrokers. */
|
|
@@ -91200,37 +91604,32 @@ var OAUTH_START_PATH = `/addon/google-services/oauth/start`;
|
|
|
91200
91604
|
var OAUTH_CALLBACK_SUFFIX = `/addon/google-services/oauth/callback`;
|
|
91201
91605
|
//#endregion
|
|
91202
91606
|
//#region src/google-broker-registry.ts
|
|
91607
|
+
/**
|
|
91608
|
+
* Google broker registry — N Google accounts under kind `google`.
|
|
91609
|
+
* No live WS; "connected" means a refresh token exists for the broker id.
|
|
91610
|
+
*/
|
|
91203
91611
|
var GoogleAddSettingsSchema = object({
|
|
91204
91612
|
clientId: string().min(1),
|
|
91205
91613
|
clientSecret: string().min(1).meta({ secret: true }),
|
|
91206
|
-
/**
|
|
91207
|
-
calendarIds: array(string()).optional(),
|
|
91208
|
-
/** @deprecated Prefer calendarIds — still accepted on save. */
|
|
91614
|
+
/** @deprecated Calendars are `calendars` records now — still accepted on save. */
|
|
91209
91615
|
calendarFilter: string().optional(),
|
|
91210
91616
|
fromAddress: string().optional(),
|
|
91211
91617
|
fromName: string().optional()
|
|
91212
91618
|
});
|
|
91213
|
-
/** Persist UI calendarIds (or legacy filter string) as comma-separated filter. */
|
|
91214
|
-
function calendarIdsToFilter(settings) {
|
|
91215
|
-
if (settings.calendarIds !== void 0) return settings.calendarIds.map((s) => s.trim()).filter((s) => s.length > 0).join(",");
|
|
91216
|
-
return settings.calendarFilter ?? "";
|
|
91217
|
-
}
|
|
91218
91619
|
function settingsToBrokerFields(settings) {
|
|
91219
91620
|
const parsed = GoogleAddSettingsSchema.parse(settings);
|
|
91220
91621
|
return {
|
|
91221
91622
|
clientId: parsed.clientId,
|
|
91222
91623
|
clientSecret: parsed.clientSecret,
|
|
91223
|
-
calendarFilter:
|
|
91624
|
+
calendarFilter: parsed.calendarFilter ?? "",
|
|
91224
91625
|
fromAddress: parsed.fromAddress ?? "",
|
|
91225
|
-
fromName: parsed.fromName ?? ""
|
|
91226
|
-
...parsed.calendarIds !== void 0 ? { calendarIds: [...parsed.calendarIds] } : {}
|
|
91626
|
+
fromName: parsed.fromName ?? ""
|
|
91227
91627
|
};
|
|
91228
91628
|
}
|
|
91229
91629
|
function brokerEntryToSettings(entry) {
|
|
91230
91630
|
return {
|
|
91231
91631
|
clientId: entry.clientId,
|
|
91232
91632
|
clientSecret: entry.clientSecret,
|
|
91233
|
-
calendarIds: [...entry.calendarIds ?? parseCalendarFilter(entry.calendarFilter)],
|
|
91234
91633
|
fromAddress: entry.fromAddress,
|
|
91235
91634
|
fromName: entry.fromName
|
|
91236
91635
|
};
|
|
@@ -91281,18 +91680,6 @@ function migrateLegacyToBrokers(rawConfig, legacyOauth) {
|
|
|
91281
91680
|
didMigrate: true
|
|
91282
91681
|
};
|
|
91283
91682
|
}
|
|
91284
|
-
/**
|
|
91285
|
-
* The stamped calendar ids one broker imports: its own set, or — while the migration has not
|
|
91286
|
-
* reached it — the shared setting older versions wrote, so the move never changes what is imported.
|
|
91287
|
-
*/
|
|
91288
|
-
function calendarIdsOfEntry(entry, shared) {
|
|
91289
|
-
return new Set(entry?.calendarIds ?? shared);
|
|
91290
|
-
}
|
|
91291
|
-
function parseCalendarFilter(raw) {
|
|
91292
|
-
const trimmed = raw.trim();
|
|
91293
|
-
if (!trimmed) return /* @__PURE__ */ new Set();
|
|
91294
|
-
return new Set(trimmed.split(",").map((s) => s.trim()).filter((s) => s.length > 0));
|
|
91295
|
-
}
|
|
91296
91683
|
/** Local calendar id stamped as `brokerId:googleCalendarId`. */
|
|
91297
91684
|
function stampCalendarId(brokerId, googleCalendarId) {
|
|
91298
91685
|
return `${brokerId}:${googleCalendarId}`;
|
|
@@ -91307,7 +91694,7 @@ function parseStampedCalendarId(stamped) {
|
|
|
91307
91694
|
}
|
|
91308
91695
|
/**
|
|
91309
91696
|
* In-memory index over broker entries + oauth accounts. The addon owns
|
|
91310
|
-
* persistence; this class only allocates ids and
|
|
91697
|
+
* persistence; this class only allocates ids and tracks the OAuth state.
|
|
91311
91698
|
*/
|
|
91312
91699
|
var GoogleBrokerRegistry = class {
|
|
91313
91700
|
nextIdCounter = 1;
|
|
@@ -91374,7 +91761,6 @@ var GoogleBrokerRegistry = class {
|
|
|
91374
91761
|
const entry = GoogleBrokerEntrySchema.parse({
|
|
91375
91762
|
id,
|
|
91376
91763
|
name,
|
|
91377
|
-
calendarIds: [],
|
|
91378
91764
|
...fields
|
|
91379
91765
|
});
|
|
91380
91766
|
this.entries = [...this.entries, entry];
|
|
@@ -91388,7 +91774,6 @@ var GoogleBrokerRegistry = class {
|
|
|
91388
91774
|
const next = GoogleBrokerEntrySchema.parse({
|
|
91389
91775
|
id: prev.id,
|
|
91390
91776
|
name: prev.name,
|
|
91391
|
-
...prev.calendarIds !== void 0 ? { calendarIds: prev.calendarIds } : {},
|
|
91392
91777
|
...fields
|
|
91393
91778
|
});
|
|
91394
91779
|
this.entries = this.entries.map((e, i) => i === idx ? next : e);
|
|
@@ -91404,39 +91789,14 @@ var GoogleBrokerRegistry = class {
|
|
|
91404
91789
|
this.entries = entries.map((e) => GoogleBrokerEntrySchema.parse(e));
|
|
91405
91790
|
this.seedCounterFromEntries(this.entries);
|
|
91406
91791
|
}
|
|
91407
|
-
toBrokerInfo(entry, addonId) {
|
|
91408
|
-
const oauth = this.oauthById.get(entry.id);
|
|
91409
|
-
const connected = this.isConnected(entry.id);
|
|
91410
|
-
const reauth = this.needsReauth(entry.id);
|
|
91411
|
-
return {
|
|
91412
|
-
id: entry.id,
|
|
91413
|
-
addonId,
|
|
91414
|
-
name: entry.name,
|
|
91415
|
-
kind: GOOGLE_BROKER_KIND,
|
|
91416
|
-
status: connected ? "connected" : reauth ? "auth-failed" : "disconnected",
|
|
91417
|
-
info: {
|
|
91418
|
-
accountEmail: oauth?.accountEmail ?? "",
|
|
91419
|
-
hasClient: entry.clientId.length > 0
|
|
91420
|
-
},
|
|
91421
|
-
lastCheckedAt: connected ? Date.now() : null,
|
|
91422
|
-
error: connected ? null : reauth ? "Google session expired — sign in again" : entry.clientId.length === 0 ? "Configure Client ID / secret, then Connect Google" : "Not connected — open Connect Google on this broker"
|
|
91423
|
-
};
|
|
91424
|
-
}
|
|
91425
|
-
listBrokerInfos(addonId, kind) {
|
|
91426
|
-
if (kind !== void 0 && kind !== "google") return [];
|
|
91427
|
-
return this.entries.map((e) => this.toBrokerInfo(e, addonId));
|
|
91428
|
-
}
|
|
91429
91792
|
size() {
|
|
91430
91793
|
return this.entries.length;
|
|
91431
91794
|
}
|
|
91432
91795
|
connectedSize() {
|
|
91433
91796
|
return this.entries.filter((e) => this.isConnected(e.id)).length;
|
|
91434
91797
|
}
|
|
91435
|
-
toConfig(
|
|
91436
|
-
return {
|
|
91437
|
-
brokers: [...this.entries],
|
|
91438
|
-
calendarIds: [...calendarIds]
|
|
91439
|
-
};
|
|
91798
|
+
toConfig() {
|
|
91799
|
+
return { brokers: [...this.entries] };
|
|
91440
91800
|
}
|
|
91441
91801
|
seedCounterFromEntries(entries) {
|
|
91442
91802
|
let maxSeen = 0;
|
|
@@ -91531,8 +91891,8 @@ function escapeHtml$1(s) {
|
|
|
91531
91891
|
//#region src/google-broker-settings-schema.ts
|
|
91532
91892
|
/**
|
|
91533
91893
|
* Per-kind broker creation/edit form for kind=`google`.
|
|
91534
|
-
* Account linking (Sign in) is driven by `
|
|
91535
|
-
*
|
|
91894
|
+
* Account linking (Sign in) is driven by the kind's `connectPath`
|
|
91895
|
+
* on the instances page — not a hard-coded Connect link in this form.
|
|
91536
91896
|
*/
|
|
91537
91897
|
function consoleLinksHtml() {
|
|
91538
91898
|
return [
|
|
@@ -91594,24 +91954,6 @@ function buildGoogleBrokerSettingsSchema(publicOrigin) {
|
|
|
91594
91954
|
showToggle: true
|
|
91595
91955
|
}]
|
|
91596
91956
|
},
|
|
91597
|
-
{
|
|
91598
|
-
id: "google-calendars-import",
|
|
91599
|
-
title: "Calendars to import",
|
|
91600
|
-
titleKey: "google-services:googleBrokerSettings.s_google-calendars-import.title",
|
|
91601
|
-
description: "Nothing selected = no calendars imported from this account. Options appear once the account is signed in.",
|
|
91602
|
-
descriptionKey: "google-services:googleBrokerSettings.s_google-calendars-import.description",
|
|
91603
|
-
columns: 1,
|
|
91604
|
-
fields: [{
|
|
91605
|
-
type: "multiselect",
|
|
91606
|
-
key: "calendarIds",
|
|
91607
|
-
label: "Calendars",
|
|
91608
|
-
labelKey: "google-services:googleBrokerSettings.s_google-calendars-import.calendarIds.label",
|
|
91609
|
-
options: [],
|
|
91610
|
-
optionsFrom: "_calendarCatalog",
|
|
91611
|
-
description: "The calendars of this Google account that CamStack imports.",
|
|
91612
|
-
descriptionKey: "google-services:googleBrokerSettings.s_google-calendars-import.calendarIds.description"
|
|
91613
|
-
}]
|
|
91614
|
-
},
|
|
91615
91957
|
{
|
|
91616
91958
|
id: "gmail-send",
|
|
91617
91959
|
title: "Gmail send",
|
|
@@ -91651,7 +91993,7 @@ function buildGoogleFeatureSettingsSchema(cap, publicOrigin) {
|
|
|
91651
91993
|
format: "html",
|
|
91652
91994
|
content: [
|
|
91653
91995
|
"<p>Link a Google account under Connectivity → <strong>Brokers</strong> ",
|
|
91654
|
-
"(Client ID / secret + Sign in)
|
|
91996
|
+
"(Client ID / secret + Sign in), then add a calendar under Connectivity → <strong>Calendars</strong>. ",
|
|
91655
91997
|
`OAuth redirect URI: <code>${redirect}</code>.</p>`
|
|
91656
91998
|
].join("")
|
|
91657
91999
|
}]
|
|
@@ -92131,11 +92473,76 @@ var googleBrokersEntryKind = defineEntryKind("brokers", {
|
|
|
92131
92473
|
clientSecret: GoogleBrokerEntrySchema.shape.clientSecret,
|
|
92132
92474
|
calendarFilter: GoogleBrokerEntrySchema.shape.calendarFilter,
|
|
92133
92475
|
fromAddress: GoogleBrokerEntrySchema.shape.fromAddress,
|
|
92134
|
-
fromName: GoogleBrokerEntrySchema.shape.fromName
|
|
92135
|
-
calendarIds: GoogleBrokerEntrySchema.shape.calendarIds
|
|
92476
|
+
fromName: GoogleBrokerEntrySchema.shape.fromName
|
|
92136
92477
|
}),
|
|
92137
92478
|
writeTrigger: "an operator adds or edits a Google OAuth broker"
|
|
92138
92479
|
});
|
|
92480
|
+
/**
|
|
92481
|
+
* One calendar an account imports (D1013): a record that LINKS to the account it belongs to and
|
|
92482
|
+
* names the Google calendar by its own id (`primary`, `name@group.calendar.google.com`). The id the
|
|
92483
|
+
* calendar answers to elsewhere stays `<accountId>:<calendarId>` (`stampCalendarId`).
|
|
92484
|
+
*/
|
|
92485
|
+
var googleCalendarsEntryKind = declareLinkKind({
|
|
92486
|
+
name: "calendars",
|
|
92487
|
+
title: "Google calendars",
|
|
92488
|
+
writeTrigger: "an operator adds or removes a calendar an account imports",
|
|
92489
|
+
links: { accountId: {
|
|
92490
|
+
cap: "broker",
|
|
92491
|
+
kind: GOOGLE_BROKER_KIND
|
|
92492
|
+
} },
|
|
92493
|
+
fields: { calendarId: string().default("") }
|
|
92494
|
+
});
|
|
92495
|
+
//#endregion
|
|
92496
|
+
//#region src/google-calendar-instances.ts
|
|
92497
|
+
/**
|
|
92498
|
+
* The calendars a Google account imports as `integration-instances` (D1013): one `calendars`
|
|
92499
|
+
* record per (account, calendar), linking to its account through the link primitive. The calendar
|
|
92500
|
+
* source reads the records; nothing is stored on the account.
|
|
92501
|
+
*/
|
|
92502
|
+
var GOOGLE_CALENDAR_INSTANCE_KIND = "calendar";
|
|
92503
|
+
/** The stamped ids each account imports, from the calendar records. */
|
|
92504
|
+
function selectedStampedIds(rows) {
|
|
92505
|
+
const out = /* @__PURE__ */ new Map();
|
|
92506
|
+
for (const row of rows) {
|
|
92507
|
+
const { accountId, calendarId } = row.body;
|
|
92508
|
+
if (!row.enabled || accountId === "" || calendarId === "") continue;
|
|
92509
|
+
const set = out.get(accountId) ?? /* @__PURE__ */ new Set();
|
|
92510
|
+
set.add(stampCalendarId(accountId, calendarId));
|
|
92511
|
+
out.set(accountId, set);
|
|
92512
|
+
}
|
|
92513
|
+
return out;
|
|
92514
|
+
}
|
|
92515
|
+
function buildGoogleCalendarInstances(records, deps) {
|
|
92516
|
+
return buildLinkedInstances(records, {
|
|
92517
|
+
kind: googleCalendarsEntryKind,
|
|
92518
|
+
instanceKind: GOOGLE_CALENDAR_INSTANCE_KIND,
|
|
92519
|
+
description: {
|
|
92520
|
+
role: "services",
|
|
92521
|
+
instanceMode: "multiple",
|
|
92522
|
+
kind: GOOGLE_CALENDAR_INSTANCE_KIND,
|
|
92523
|
+
canTest: false
|
|
92524
|
+
},
|
|
92525
|
+
labels: { accountId: {
|
|
92526
|
+
label: "Google account",
|
|
92527
|
+
description: "The account this calendar belongs to."
|
|
92528
|
+
} },
|
|
92529
|
+
alsoConfigured: (body) => body.calendarId !== "",
|
|
92530
|
+
dynamicSections: async (accountId) => [{
|
|
92531
|
+
id: "google-calendar-pick",
|
|
92532
|
+
title: "Calendar",
|
|
92533
|
+
columns: 1,
|
|
92534
|
+
fields: [{
|
|
92535
|
+
type: "select",
|
|
92536
|
+
key: "calendarId",
|
|
92537
|
+
label: "Calendar",
|
|
92538
|
+
options: [...await deps.catalog(accountId)],
|
|
92539
|
+
description: "The calendars of that account. Sign the account in to see them."
|
|
92540
|
+
}]
|
|
92541
|
+
}],
|
|
92542
|
+
mintId: () => `cal_${(0, node_crypto.randomBytes)(6).toString("hex")}`,
|
|
92543
|
+
onChanged: deps.onChanged
|
|
92544
|
+
});
|
|
92545
|
+
}
|
|
92139
92546
|
//#endregion
|
|
92140
92547
|
//#region src/google-instances.ts
|
|
92141
92548
|
/**
|
|
@@ -92155,6 +92562,17 @@ function asTestResult(r) {
|
|
|
92155
92562
|
error: r.error ?? "Google rejected the check"
|
|
92156
92563
|
};
|
|
92157
92564
|
}
|
|
92565
|
+
/** The `account` kind of the composed provider: where the OAuth sign-in starts and ends. */
|
|
92566
|
+
function googleAccountKind(deps) {
|
|
92567
|
+
return {
|
|
92568
|
+
id: GOOGLE_BROKER_KIND,
|
|
92569
|
+
label: GOOGLE_BROKER_KIND_LABEL,
|
|
92570
|
+
functionCap: "broker",
|
|
92571
|
+
brokerKind: GOOGLE_BROKER_KIND,
|
|
92572
|
+
connectPath: deps.connectPath,
|
|
92573
|
+
disconnectPath: deps.disconnectPath
|
|
92574
|
+
};
|
|
92575
|
+
}
|
|
92158
92576
|
function buildGoogleInstances(records, deps) {
|
|
92159
92577
|
return new RecordIntegrationInstances(records, googleBrokersEntryKind, {
|
|
92160
92578
|
description: {
|
|
@@ -92162,29 +92580,16 @@ function buildGoogleInstances(records, deps) {
|
|
|
92162
92580
|
instanceMode: "multiple",
|
|
92163
92581
|
kind: "account",
|
|
92164
92582
|
canTest: true,
|
|
92165
|
-
kinds: [{
|
|
92166
|
-
id: GOOGLE_BROKER_KIND,
|
|
92167
|
-
label: GOOGLE_BROKER_KIND_LABEL,
|
|
92168
|
-
connectPath: deps.connectPath,
|
|
92169
|
-
disconnectPath: deps.disconnectPath
|
|
92170
|
-
}],
|
|
92171
92583
|
allowRestoreId: true
|
|
92172
92584
|
},
|
|
92173
|
-
formSchema: () =>
|
|
92174
|
-
const full = buildGoogleBrokerSettingsSchema(deps.publicOrigin());
|
|
92175
|
-
return {
|
|
92176
|
-
...full,
|
|
92177
|
-
sections: full.sections.filter((s) => s.id !== "google-calendars-import")
|
|
92178
|
-
};
|
|
92179
|
-
},
|
|
92585
|
+
formSchema: () => buildGoogleBrokerSettingsSchema(deps.publicOrigin()),
|
|
92180
92586
|
initialBody: (name) => ({
|
|
92181
92587
|
name,
|
|
92182
92588
|
clientId: "",
|
|
92183
92589
|
clientSecret: "",
|
|
92184
92590
|
calendarFilter: "",
|
|
92185
92591
|
fromAddress: "",
|
|
92186
|
-
fromName: ""
|
|
92187
|
-
calendarIds: []
|
|
92592
|
+
fromName: ""
|
|
92188
92593
|
}),
|
|
92189
92594
|
mintId: deps.mintId,
|
|
92190
92595
|
kindOf: () => GOOGLE_BROKER_KIND,
|
|
@@ -92303,42 +92708,66 @@ async function planDropSharedCalendarIds(ports) {
|
|
|
92303
92708
|
} })
|
|
92304
92709
|
}] };
|
|
92305
92710
|
}
|
|
92306
|
-
//#endregion
|
|
92307
|
-
//#region src/migrations/index.ts
|
|
92308
92711
|
/** The `google-services` catalogue (D987): offered to the engine when the addon initialises. */
|
|
92309
|
-
var ADDON_MIGRATIONS = [
|
|
92310
|
-
|
|
92311
|
-
|
|
92312
|
-
|
|
92313
|
-
|
|
92314
|
-
|
|
92315
|
-
|
|
92316
|
-
|
|
92317
|
-
|
|
92318
|
-
|
|
92319
|
-
|
|
92320
|
-
|
|
92321
|
-
|
|
92322
|
-
|
|
92323
|
-
|
|
92324
|
-
|
|
92325
|
-
|
|
92326
|
-
|
|
92327
|
-
|
|
92328
|
-
|
|
92329
|
-
|
|
92330
|
-
|
|
92331
|
-
|
|
92332
|
-
|
|
92333
|
-
|
|
92334
|
-
|
|
92335
|
-
|
|
92336
|
-
|
|
92337
|
-
|
|
92338
|
-
|
|
92339
|
-
}
|
|
92712
|
+
var ADDON_MIGRATIONS = [
|
|
92713
|
+
{
|
|
92714
|
+
meta: {
|
|
92715
|
+
id: "google-services/2026-11-03-calendar-ids-to-brokers",
|
|
92716
|
+
owner: "google-services",
|
|
92717
|
+
level: "addon-state",
|
|
92718
|
+
seq: 1,
|
|
92719
|
+
dependsOn: [],
|
|
92720
|
+
store: "settings",
|
|
92721
|
+
scope: "cluster",
|
|
92722
|
+
onFailure: "tolerate",
|
|
92723
|
+
bodyKind: "procedure",
|
|
92724
|
+
description: "Copy the shared calendar set to every Google broker record that has none of its own",
|
|
92725
|
+
fixtureMark: "the client secret marker of a broker record the migration does not know"
|
|
92726
|
+
},
|
|
92727
|
+
procedure: copyCalendarIdsToBrokers
|
|
92728
|
+
},
|
|
92729
|
+
{
|
|
92730
|
+
meta: {
|
|
92731
|
+
id: "google-services/2026-11-03-drop-shared-calendar-ids",
|
|
92732
|
+
owner: "google-services",
|
|
92733
|
+
level: "addon-state",
|
|
92734
|
+
seq: 2,
|
|
92735
|
+
dependsOn: ["google-services/2026-11-03-calendar-ids-to-brokers"],
|
|
92736
|
+
store: "settings",
|
|
92737
|
+
scope: "cluster",
|
|
92738
|
+
onFailure: "tolerate",
|
|
92739
|
+
bodyKind: "plan",
|
|
92740
|
+
description: "Delete the shared calendar set once every broker shows its own",
|
|
92741
|
+
fixtureMark: "the shared set parked when a broker holds a different one"
|
|
92742
|
+
},
|
|
92743
|
+
plan: planDropSharedCalendarIds
|
|
92744
|
+
},
|
|
92745
|
+
{
|
|
92746
|
+
meta: {
|
|
92747
|
+
id: "google-services/2026-11-07-calendar-ids-to-calendar-records",
|
|
92748
|
+
owner: "google-services",
|
|
92749
|
+
level: "addon-state",
|
|
92750
|
+
seq: 3,
|
|
92751
|
+
dependsOn: ["google-services/2026-11-03-drop-shared-calendar-ids"],
|
|
92752
|
+
store: "settings",
|
|
92753
|
+
scope: "cluster",
|
|
92754
|
+
onFailure: "tolerate",
|
|
92755
|
+
bodyKind: "procedure",
|
|
92756
|
+
description: "Write one calendar record per calendar an account imports (linked to the account), read each back, then clear the calendar array of the account row",
|
|
92757
|
+
fixtureMark: "the client secret marker of the account that keeps its calendars"
|
|
92758
|
+
},
|
|
92759
|
+
procedure: (ports) => moveCalendarIdsToRecords(ports, { calendarIdOf: (accountId, entry) => {
|
|
92760
|
+
const parsed = parseStampedCalendarId(entry);
|
|
92761
|
+
return parsed?.brokerId === accountId ? parsed.googleCalendarId : null;
|
|
92762
|
+
} })
|
|
92763
|
+
}
|
|
92764
|
+
];
|
|
92340
92765
|
//#endregion
|
|
92341
92766
|
//#region src/migrations/ports.ts
|
|
92767
|
+
function listOf(value) {
|
|
92768
|
+
if (!Array.isArray(value)) return void 0;
|
|
92769
|
+
return value.filter((item) => typeof item === "string");
|
|
92770
|
+
}
|
|
92342
92771
|
/** The migration's ports over the `brokers` record kind and the addon's raw settings read. */
|
|
92343
92772
|
function calendarIdsPortsOver(kind, readStored) {
|
|
92344
92773
|
return {
|
|
@@ -92346,18 +92775,43 @@ function calendarIdsPortsOver(kind, readStored) {
|
|
|
92346
92775
|
listBrokers: async () => {
|
|
92347
92776
|
return (await kind.query({ limit: 1e3 })).records.map((record) => ({
|
|
92348
92777
|
id: record.id,
|
|
92349
|
-
calendarIds: record.data
|
|
92778
|
+
calendarIds: listOf(record.data["calendarIds"])
|
|
92350
92779
|
}));
|
|
92351
92780
|
},
|
|
92352
92781
|
setBrokerCalendarIds: async (id, ids) => {
|
|
92353
92782
|
const row = await kind.get(id);
|
|
92354
92783
|
if (row === null) return false;
|
|
92355
|
-
if (row.data
|
|
92784
|
+
if (row.data["calendarIds"] !== void 0) return true;
|
|
92356
92785
|
await kind.patch(id, { calendarIds: [...ids] });
|
|
92357
92786
|
return true;
|
|
92358
92787
|
}
|
|
92359
92788
|
};
|
|
92360
92789
|
}
|
|
92790
|
+
/** The ports of the calendars move: the raw `brokers` rows and the `calendars` records. */
|
|
92791
|
+
function calendarRecordPortsOver(deps) {
|
|
92792
|
+
return {
|
|
92793
|
+
warn: deps.warn,
|
|
92794
|
+
listAccounts: async () => {
|
|
92795
|
+
return (await deps.brokers.query({ limit: 1e3 })).records.map((record) => ({
|
|
92796
|
+
id: record.id,
|
|
92797
|
+
calendarIds: record.data["calendarIds"]
|
|
92798
|
+
}));
|
|
92799
|
+
},
|
|
92800
|
+
readCalendar: async (recordId) => {
|
|
92801
|
+
const row = await deps.calendars.get(recordId);
|
|
92802
|
+
return row === null ? null : row.data;
|
|
92803
|
+
},
|
|
92804
|
+
putCalendar: async (recordId, body) => {
|
|
92805
|
+
await deps.calendars.put({
|
|
92806
|
+
id: recordId,
|
|
92807
|
+
data: body
|
|
92808
|
+
}, { ifRev: 0 });
|
|
92809
|
+
},
|
|
92810
|
+
clearCalendarIds: async (accountId) => {
|
|
92811
|
+
await deps.brokers.patch(accountId, { calendarIds: null });
|
|
92812
|
+
}
|
|
92813
|
+
};
|
|
92814
|
+
}
|
|
92361
92815
|
//#endregion
|
|
92362
92816
|
//#region src/google-broker-store.ts
|
|
92363
92817
|
function rowToEntry(id, data) {
|
|
@@ -92396,15 +92850,14 @@ var GoogleBrokerStore = class GoogleBrokerStore {
|
|
|
92396
92850
|
const nextIds = new Set(entries.map((e) => e.id));
|
|
92397
92851
|
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
92398
92852
|
for (const entry of entries) {
|
|
92399
|
-
const { id, name, clientId, clientSecret, calendarFilter, fromAddress, fromName
|
|
92853
|
+
const { id, name, clientId, clientSecret, calendarFilter, fromAddress, fromName } = entry;
|
|
92400
92854
|
const body = {
|
|
92401
92855
|
name,
|
|
92402
92856
|
clientId,
|
|
92403
92857
|
clientSecret,
|
|
92404
92858
|
calendarFilter,
|
|
92405
92859
|
fromAddress,
|
|
92406
|
-
fromName
|
|
92407
|
-
...calendarIds !== void 0 ? { calendarIds } : {}
|
|
92860
|
+
fromName
|
|
92408
92861
|
};
|
|
92409
92862
|
const parsed = googleBrokersEntryKind.schema.safeParse(body);
|
|
92410
92863
|
if (!parsed.success) throw new Error(`invalid Google broker '${id}': ${parsed.error.message}`);
|
|
@@ -92450,6 +92903,9 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92450
92903
|
registry = new GoogleBrokerRegistry();
|
|
92451
92904
|
brokerStore = null;
|
|
92452
92905
|
instances = null;
|
|
92906
|
+
calendarInstances = null;
|
|
92907
|
+
/** The stamped calendar ids each account imports, from the `calendars` records. */
|
|
92908
|
+
calendarSelection = /* @__PURE__ */ new Map();
|
|
92453
92909
|
activeBrokers = [];
|
|
92454
92910
|
/** CSRF state → { brokerId, issuedAt } for the in-flight OAuth consents. */
|
|
92455
92911
|
pendingStates = /* @__PURE__ */ new Map();
|
|
@@ -92495,15 +92951,23 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92495
92951
|
}
|
|
92496
92952
|
const records = this.ctx.records;
|
|
92497
92953
|
if (records === void 0) throw new Error("google-services: ctx.records is required (hub-only addon)");
|
|
92498
|
-
await records.declare({
|
|
92954
|
+
await records.declare({
|
|
92955
|
+
brokers: googleBrokersEntryKind,
|
|
92956
|
+
calendars: googleCalendarsEntryKind
|
|
92957
|
+
});
|
|
92499
92958
|
this.brokerStore = GoogleBrokerStore.of(records);
|
|
92500
92959
|
this.instances = this.buildInstances(records);
|
|
92960
|
+
this.calendarInstances = buildGoogleCalendarInstances(records, {
|
|
92961
|
+
catalog: (accountId) => this.loadCalendarCatalog(accountId),
|
|
92962
|
+
onChanged: () => this.refreshCalendarSelection()
|
|
92963
|
+
});
|
|
92501
92964
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
92502
92965
|
if ((await this.brokerStore.listResolved()).length === 0 && this.config.brokers.length > 0) {
|
|
92503
92966
|
await this.brokerStore.replaceAll(this.config.brokers);
|
|
92504
92967
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
92505
92968
|
}
|
|
92506
92969
|
await this.migrateCalendarIds(records);
|
|
92970
|
+
await this.refreshCalendarSelection();
|
|
92507
92971
|
this.registry.seedFrom(this.activeBrokers, oauthAccounts.accounts);
|
|
92508
92972
|
await this.refreshPublicOrigin();
|
|
92509
92973
|
const calendarProvider = createGoogleCalendarSource({
|
|
@@ -92513,7 +92977,7 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92513
92977
|
connected: this.registry.isConnected(entry.id),
|
|
92514
92978
|
api: createGoogleCalendarApi(() => this.ensureAccessToken(entry.id))
|
|
92515
92979
|
})),
|
|
92516
|
-
selectedCalendarIds: (brokerId) => this.
|
|
92980
|
+
selectedCalendarIds: (brokerId) => this.calendarSelection.get(brokerId) ?? /* @__PURE__ */ new Set()
|
|
92517
92981
|
});
|
|
92518
92982
|
const smtpProvider = createGmailSmtpProvider({ accounts: () => this.registry.listEntries().map((entry) => {
|
|
92519
92983
|
const oauth = this.registry.getOauth(entry.id);
|
|
@@ -92562,7 +93026,7 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92562
93026
|
},
|
|
92563
93027
|
{
|
|
92564
93028
|
capability: integrationInstancesCapability,
|
|
92565
|
-
provider: this.
|
|
93029
|
+
provider: this.buildInstancesProvider()
|
|
92566
93030
|
},
|
|
92567
93031
|
{
|
|
92568
93032
|
capability: calendarSourceCapability,
|
|
@@ -92587,9 +93051,43 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92587
93051
|
if (this.instances === null) throw new Error("google-services: instances used before initialize");
|
|
92588
93052
|
return this.instances;
|
|
92589
93053
|
}
|
|
93054
|
+
/** One provider over the two record kinds: the Google accounts and the calendars they import. */
|
|
93055
|
+
buildInstancesProvider() {
|
|
93056
|
+
const calendars = this.calendarInstances;
|
|
93057
|
+
if (calendars === null) throw new Error("google-services: instances used before initialize");
|
|
93058
|
+
return composeInstanceKinds([{
|
|
93059
|
+
kind: {
|
|
93060
|
+
...googleAccountKind({
|
|
93061
|
+
connectPath: `/addon/${ADDON_ID}${START_PATH}?brokerId={{brokerId}}`,
|
|
93062
|
+
disconnectPath: `/addon/${ADDON_ID}${DISCONNECT_PATH}?brokerId={{brokerId}}`
|
|
93063
|
+
}),
|
|
93064
|
+
functionCap: "broker"
|
|
93065
|
+
},
|
|
93066
|
+
provider: this.requireInstances().toProvider()
|
|
93067
|
+
}, {
|
|
93068
|
+
kind: {
|
|
93069
|
+
id: GOOGLE_CALENDAR_INSTANCE_KIND,
|
|
93070
|
+
label: "Google calendar",
|
|
93071
|
+
functionCap: "calendar-source",
|
|
93072
|
+
role: "services"
|
|
93073
|
+
},
|
|
93074
|
+
provider: calendars.toProvider()
|
|
93075
|
+
}], {
|
|
93076
|
+
role: "services",
|
|
93077
|
+
instanceMode: "multiple",
|
|
93078
|
+
kind: "account",
|
|
93079
|
+
canTest: true,
|
|
93080
|
+
allowRestoreId: true
|
|
93081
|
+
});
|
|
93082
|
+
}
|
|
93083
|
+
/** Re-read which calendars each account imports from the records. */
|
|
93084
|
+
async refreshCalendarSelection() {
|
|
93085
|
+
const calendars = this.calendarInstances;
|
|
93086
|
+
if (calendars === null) return;
|
|
93087
|
+
this.calendarSelection = selectedStampedIds(await calendars.listResolved());
|
|
93088
|
+
}
|
|
92590
93089
|
/** The accounts as `integration-instances`, over the same `brokers` records (planning#408 P3). */
|
|
92591
93090
|
buildInstances(records) {
|
|
92592
|
-
const brokerProvider = this.buildBrokerProvider();
|
|
92593
93091
|
return buildGoogleInstances(records, {
|
|
92594
93092
|
connectPath: `/addon/${ADDON_ID}${START_PATH}?brokerId={{brokerId}}`,
|
|
92595
93093
|
disconnectPath: `/addon/${ADDON_ID}${DISCONNECT_PATH}?brokerId={{brokerId}}`,
|
|
@@ -92602,169 +93100,29 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92602
93100
|
this.registry.seedFrom(this.activeBrokers, accounts);
|
|
92603
93101
|
await this.persistOauthAccounts();
|
|
92604
93102
|
},
|
|
92605
|
-
testSaved:
|
|
92606
|
-
testClient:
|
|
92607
|
-
kind: GOOGLE_BROKER_KIND,
|
|
92608
|
-
settings: {
|
|
92609
|
-
clientId,
|
|
92610
|
-
clientSecret
|
|
92611
|
-
}
|
|
92612
|
-
})
|
|
93103
|
+
testSaved: (id) => this.probeSavedAccount(id),
|
|
93104
|
+
testClient: (clientId, clientSecret) => this.probeOauthClient(clientId, clientSecret)
|
|
92613
93105
|
});
|
|
92614
93106
|
}
|
|
93107
|
+
/**
|
|
93108
|
+
* The `broker` cap is the bus only (D1013): the accounts are the `integration-instances`
|
|
93109
|
+
* provider; calendar and mail go through their own caps, so publish and subscribe are refused.
|
|
93110
|
+
*/
|
|
92615
93111
|
buildBrokerProvider() {
|
|
92616
|
-
const ownerAddonId = this.ctx.id;
|
|
92617
|
-
const owns = (id) => this.registry.getEntry(id) !== null;
|
|
92618
93112
|
const notSupported = async () => {
|
|
92619
93113
|
throw new Error("google-services broker: publish/subscribe/getState are not supported — use calendar-source / smtp-provider");
|
|
92620
93114
|
};
|
|
92621
93115
|
return {
|
|
92622
|
-
list: async ({ kind }) => this.registry.listBrokerInfos(ownerAddonId, kind),
|
|
92623
|
-
get: async ({ id }) => {
|
|
92624
|
-
const entry = this.registry.getEntry(id);
|
|
92625
|
-
return entry ? this.registry.toBrokerInfo(entry, ownerAddonId) : null;
|
|
92626
|
-
},
|
|
92627
|
-
listProviders: async () => [{
|
|
92628
|
-
addonId: ownerAddonId,
|
|
92629
|
-
kinds: [{
|
|
92630
|
-
kind: GOOGLE_BROKER_KIND,
|
|
92631
|
-
label: GOOGLE_BROKER_KIND_LABEL,
|
|
92632
|
-
interactiveConnectPath: `/addon/${ADDON_ID}${START_PATH}?brokerId={{brokerId}}`,
|
|
92633
|
-
interactiveDisconnectPath: `/addon/${ADDON_ID}${DISCONNECT_PATH}?brokerId={{brokerId}}`
|
|
92634
|
-
}]
|
|
92635
|
-
}],
|
|
92636
|
-
add: async ({ kind, name, settings, id }) => {
|
|
92637
|
-
if (kind !== "google") throw new Error(`google-services: only kind '${GOOGLE_BROKER_KIND}' is handled here (got '${kind}')`);
|
|
92638
|
-
const entry = this.registry.createEntry(name, settings, id !== void 0 ? { id } : {});
|
|
92639
|
-
await this.persistBrokers();
|
|
92640
|
-
return { id: entry.id };
|
|
92641
|
-
},
|
|
92642
|
-
remove: async ({ id }) => {
|
|
92643
|
-
if (!owns(id)) return;
|
|
92644
|
-
this.registry.removeEntry(id);
|
|
92645
|
-
await this.persistBrokers();
|
|
92646
|
-
await this.persistOauthAccounts();
|
|
92647
|
-
},
|
|
92648
|
-
testConnection: async ({ id }) => {
|
|
92649
|
-
if (!owns(id)) return {
|
|
92650
|
-
ok: false,
|
|
92651
|
-
error: "unknown broker"
|
|
92652
|
-
};
|
|
92653
|
-
const entry = this.registry.getEntry(id);
|
|
92654
|
-
if (entry === null) return {
|
|
92655
|
-
ok: false,
|
|
92656
|
-
error: "unknown broker"
|
|
92657
|
-
};
|
|
92658
|
-
if (!entry.clientId || !entry.clientSecret) return {
|
|
92659
|
-
ok: false,
|
|
92660
|
-
error: "Configure Client ID and Client secret first"
|
|
92661
|
-
};
|
|
92662
|
-
let redirectUri;
|
|
92663
|
-
try {
|
|
92664
|
-
redirectUri = await this.resolveRedirectUri();
|
|
92665
|
-
} catch (err) {
|
|
92666
|
-
return {
|
|
92667
|
-
ok: false,
|
|
92668
|
-
error: err instanceof Error ? err.message : String(err)
|
|
92669
|
-
};
|
|
92670
|
-
}
|
|
92671
|
-
if (!this.registry.isConnected(id)) return probeGoogleOauthClient({
|
|
92672
|
-
clientId: entry.clientId,
|
|
92673
|
-
clientSecret: entry.clientSecret,
|
|
92674
|
-
redirectUri
|
|
92675
|
-
});
|
|
92676
|
-
try {
|
|
92677
|
-
const started = Date.now();
|
|
92678
|
-
const token = await this.ensureAccessToken(id, { forceRefresh: true });
|
|
92679
|
-
if (token.trim().length === 0) return {
|
|
92680
|
-
ok: false,
|
|
92681
|
-
error: "No access token after refresh — Sign out and Sign in again"
|
|
92682
|
-
};
|
|
92683
|
-
let res = await fetch(GOOGLE_CALENDAR_LIST_URL, { headers: { Authorization: `Bearer ${token}` } });
|
|
92684
|
-
if (res.status === 401) {
|
|
92685
|
-
const retryToken = await this.ensureAccessToken(id, { forceRefresh: true });
|
|
92686
|
-
res = await fetch(GOOGLE_CALENDAR_LIST_URL, { headers: { Authorization: `Bearer ${retryToken}` } });
|
|
92687
|
-
}
|
|
92688
|
-
if (!res.ok) {
|
|
92689
|
-
const text = await res.text();
|
|
92690
|
-
return {
|
|
92691
|
-
ok: false,
|
|
92692
|
-
error: `Google Calendar probe failed (${res.status}): ${text.slice(0, 200)}`
|
|
92693
|
-
};
|
|
92694
|
-
}
|
|
92695
|
-
const email = await this.fetchAccountEmail(token);
|
|
92696
|
-
if (email.length > 0) {
|
|
92697
|
-
const oauth = this.registry.getOauth(id);
|
|
92698
|
-
if (oauth !== null && oauth.accountEmail !== email) {
|
|
92699
|
-
this.registry.setOauth({
|
|
92700
|
-
...oauth,
|
|
92701
|
-
accountEmail: email
|
|
92702
|
-
});
|
|
92703
|
-
await this.persistOauthAccounts();
|
|
92704
|
-
}
|
|
92705
|
-
}
|
|
92706
|
-
return {
|
|
92707
|
-
ok: true,
|
|
92708
|
-
latencyMs: Date.now() - started
|
|
92709
|
-
};
|
|
92710
|
-
} catch (err) {
|
|
92711
|
-
return {
|
|
92712
|
-
ok: false,
|
|
92713
|
-
error: err instanceof Error ? err.message : String(err)
|
|
92714
|
-
};
|
|
92715
|
-
}
|
|
92716
|
-
},
|
|
92717
|
-
getSettings: async ({ id }) => {
|
|
92718
|
-
const entry = this.registry.getEntry(id);
|
|
92719
|
-
if (entry === null) return null;
|
|
92720
|
-
return {
|
|
92721
|
-
...brokerEntryToSettings(entry),
|
|
92722
|
-
_calendarCatalog: await this.loadStampedCalendarCatalog(id)
|
|
92723
|
-
};
|
|
92724
|
-
},
|
|
92725
|
-
setSettings: async ({ id, settings }) => {
|
|
92726
|
-
if (!owns(id)) return;
|
|
92727
|
-
this.registry.updateEntry(id, settings);
|
|
92728
|
-
await this.persistBrokers();
|
|
92729
|
-
},
|
|
92730
93116
|
getBrokerConfig: async ({ id }) => {
|
|
92731
93117
|
const entry = this.registry.getEntry(id);
|
|
92732
93118
|
if (entry === null) return null;
|
|
92733
93119
|
const oauth = this.registry.getOauth(id);
|
|
92734
93120
|
return {
|
|
92735
93121
|
...brokerEntryToSettings(entry),
|
|
92736
|
-
_calendarCatalog: await this.loadStampedCalendarCatalog(id),
|
|
92737
93122
|
accountEmail: oauth?.accountEmail ?? "",
|
|
92738
93123
|
connected: this.registry.isConnected(id)
|
|
92739
93124
|
};
|
|
92740
93125
|
},
|
|
92741
|
-
getSettingsSchema: async ({ kind }) => {
|
|
92742
|
-
if (kind !== "google") return null;
|
|
92743
|
-
return buildGoogleBrokerSettingsSchema(this.publicOrigin);
|
|
92744
|
-
},
|
|
92745
|
-
testSettings: async ({ kind, settings }) => {
|
|
92746
|
-
if (kind !== "google") return {
|
|
92747
|
-
ok: false,
|
|
92748
|
-
error: `unsupported kind: ${kind}`
|
|
92749
|
-
};
|
|
92750
|
-
try {
|
|
92751
|
-
const parsed = object({
|
|
92752
|
-
clientId: string().min(1),
|
|
92753
|
-
clientSecret: string().min(1).meta({ secret: true })
|
|
92754
|
-
}).parse(settings);
|
|
92755
|
-
const redirectUri = await this.resolveRedirectUri();
|
|
92756
|
-
return probeGoogleOauthClient({
|
|
92757
|
-
clientId: parsed.clientId,
|
|
92758
|
-
clientSecret: parsed.clientSecret,
|
|
92759
|
-
redirectUri
|
|
92760
|
-
});
|
|
92761
|
-
} catch (err) {
|
|
92762
|
-
return {
|
|
92763
|
-
ok: false,
|
|
92764
|
-
error: err instanceof Error ? err.message : String(err)
|
|
92765
|
-
};
|
|
92766
|
-
}
|
|
92767
|
-
},
|
|
92768
93126
|
publish: notSupported,
|
|
92769
93127
|
subscribe: notSupported,
|
|
92770
93128
|
unsubscribe: notSupported,
|
|
@@ -92775,6 +93133,95 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92775
93133
|
})
|
|
92776
93134
|
};
|
|
92777
93135
|
}
|
|
93136
|
+
/** Probe a saved account: the OAuth client, then (when signed in) a Calendar call. */
|
|
93137
|
+
async probeSavedAccount(id) {
|
|
93138
|
+
const entry = this.registry.getEntry(id);
|
|
93139
|
+
if (entry === null) return {
|
|
93140
|
+
ok: false,
|
|
93141
|
+
error: "unknown broker"
|
|
93142
|
+
};
|
|
93143
|
+
if (!entry.clientId || !entry.clientSecret) return {
|
|
93144
|
+
ok: false,
|
|
93145
|
+
error: "Configure Client ID and Client secret first"
|
|
93146
|
+
};
|
|
93147
|
+
let redirectUri;
|
|
93148
|
+
try {
|
|
93149
|
+
redirectUri = await this.resolveRedirectUri();
|
|
93150
|
+
} catch (err) {
|
|
93151
|
+
return {
|
|
93152
|
+
ok: false,
|
|
93153
|
+
error: err instanceof Error ? err.message : String(err)
|
|
93154
|
+
};
|
|
93155
|
+
}
|
|
93156
|
+
if (!this.registry.isConnected(id)) return probeGoogleOauthClient({
|
|
93157
|
+
clientId: entry.clientId,
|
|
93158
|
+
clientSecret: entry.clientSecret,
|
|
93159
|
+
redirectUri
|
|
93160
|
+
});
|
|
93161
|
+
try {
|
|
93162
|
+
const started = Date.now();
|
|
93163
|
+
const token = await this.ensureAccessToken(id, { forceRefresh: true });
|
|
93164
|
+
if (token.trim().length === 0) return {
|
|
93165
|
+
ok: false,
|
|
93166
|
+
error: "No access token after refresh — Sign out and Sign in again"
|
|
93167
|
+
};
|
|
93168
|
+
let res = await fetch(GOOGLE_CALENDAR_LIST_URL, { headers: { Authorization: `Bearer ${token}` } });
|
|
93169
|
+
if (res.status === 401) {
|
|
93170
|
+
const retryToken = await this.ensureAccessToken(id, { forceRefresh: true });
|
|
93171
|
+
res = await fetch(GOOGLE_CALENDAR_LIST_URL, { headers: { Authorization: `Bearer ${retryToken}` } });
|
|
93172
|
+
}
|
|
93173
|
+
if (!res.ok) {
|
|
93174
|
+
const text = await res.text();
|
|
93175
|
+
return {
|
|
93176
|
+
ok: false,
|
|
93177
|
+
error: `Google Calendar probe failed (${res.status}): ${text.slice(0, 200)}`
|
|
93178
|
+
};
|
|
93179
|
+
}
|
|
93180
|
+
const email = await this.fetchAccountEmail(token);
|
|
93181
|
+
if (email.length > 0) {
|
|
93182
|
+
const oauth = this.registry.getOauth(id);
|
|
93183
|
+
if (oauth !== null && oauth.accountEmail !== email) {
|
|
93184
|
+
this.registry.setOauth({
|
|
93185
|
+
...oauth,
|
|
93186
|
+
accountEmail: email
|
|
93187
|
+
});
|
|
93188
|
+
await this.persistOauthAccounts();
|
|
93189
|
+
}
|
|
93190
|
+
}
|
|
93191
|
+
return {
|
|
93192
|
+
ok: true,
|
|
93193
|
+
latencyMs: Date.now() - started
|
|
93194
|
+
};
|
|
93195
|
+
} catch (err) {
|
|
93196
|
+
return {
|
|
93197
|
+
ok: false,
|
|
93198
|
+
error: err instanceof Error ? err.message : String(err)
|
|
93199
|
+
};
|
|
93200
|
+
}
|
|
93201
|
+
}
|
|
93202
|
+
/** Probe an OAuth client that is not saved yet. */
|
|
93203
|
+
async probeOauthClient(clientId, clientSecret) {
|
|
93204
|
+
try {
|
|
93205
|
+
const parsed = object({
|
|
93206
|
+
clientId: string().min(1),
|
|
93207
|
+
clientSecret: string().min(1).meta({ secret: true })
|
|
93208
|
+
}).parse({
|
|
93209
|
+
clientId,
|
|
93210
|
+
clientSecret
|
|
93211
|
+
});
|
|
93212
|
+
const redirectUri = await this.resolveRedirectUri();
|
|
93213
|
+
return probeGoogleOauthClient({
|
|
93214
|
+
clientId: parsed.clientId,
|
|
93215
|
+
clientSecret: parsed.clientSecret,
|
|
93216
|
+
redirectUri
|
|
93217
|
+
});
|
|
93218
|
+
} catch (err) {
|
|
93219
|
+
return {
|
|
93220
|
+
ok: false,
|
|
93221
|
+
error: err instanceof Error ? err.message : String(err)
|
|
93222
|
+
};
|
|
93223
|
+
}
|
|
93224
|
+
}
|
|
92778
93225
|
async handleStart(req, reply) {
|
|
92779
93226
|
const brokerIdRaw = req.query["brokerId"];
|
|
92780
93227
|
const brokerId = typeof brokerIdRaw === "string" ? brokerIdRaw.trim() : "";
|
|
@@ -92940,11 +93387,25 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92940
93387
|
for (const [k, v] of this.pendingStates) if (v.issuedAt < cutoff) this.pendingStates.delete(k);
|
|
92941
93388
|
}
|
|
92942
93389
|
/**
|
|
92943
|
-
* The shared calendar set becomes each broker's own (D985, D987)
|
|
92944
|
-
*
|
|
93390
|
+
* The shared calendar set becomes each broker's own (D985, D987), then each broker's set becomes
|
|
93391
|
+
* `calendars` records linked to the account (D1013). Brokers are reloaded once a migration
|
|
93392
|
+
* landed, so the registry is seeded with what it wrote.
|
|
92945
93393
|
*/
|
|
92946
93394
|
async migrateCalendarIds(records) {
|
|
92947
|
-
|
|
93395
|
+
const brokers = records.kind({
|
|
93396
|
+
name: googleBrokersEntryKind.name,
|
|
93397
|
+
declaration: googleBrokersEntryKind.declaration,
|
|
93398
|
+
schema: record(string(), unknown())
|
|
93399
|
+
});
|
|
93400
|
+
const ports = {
|
|
93401
|
+
...calendarIdsPortsOver(brokers, (key) => this.readStoredGlobalKey(key)),
|
|
93402
|
+
...calendarRecordPortsOver({
|
|
93403
|
+
brokers,
|
|
93404
|
+
calendars: records.kind(googleCalendarsEntryKind),
|
|
93405
|
+
warn: (message) => this.ctx.logger.warn(message)
|
|
93406
|
+
})
|
|
93407
|
+
};
|
|
93408
|
+
if ((await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, ports))).applied.length > 0) this.activeBrokers = await this.loadPersistedBrokers();
|
|
92948
93409
|
}
|
|
92949
93410
|
async loadPersistedBrokers() {
|
|
92950
93411
|
const store = this.brokerStore;
|
|
@@ -92968,30 +93429,24 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
92968
93429
|
if (!this.oauthAccountsHandle) return;
|
|
92969
93430
|
await this.oauthAccountsHandle.set({ accounts: [...this.registry.listOauthAccounts()] });
|
|
92970
93431
|
}
|
|
92971
|
-
/**
|
|
92972
|
-
async
|
|
92973
|
-
const
|
|
92974
|
-
|
|
92975
|
-
|
|
92976
|
-
|
|
92977
|
-
|
|
92978
|
-
|
|
92979
|
-
|
|
92980
|
-
|
|
92981
|
-
|
|
92982
|
-
|
|
92983
|
-
|
|
92984
|
-
|
|
92985
|
-
|
|
92986
|
-
|
|
92987
|
-
|
|
92988
|
-
this.ctx.logger.warn("google calendar catalog failed", { meta: {
|
|
92989
|
-
brokerId: entry.id,
|
|
92990
|
-
error: err instanceof Error ? err.message : String(err)
|
|
92991
|
-
} });
|
|
92992
|
-
}
|
|
93432
|
+
/** The calendars one signed-in account lists, by their own id (the picker of a calendar record). */
|
|
93433
|
+
async loadCalendarCatalog(accountId) {
|
|
93434
|
+
const entry = this.registry.getEntry(accountId);
|
|
93435
|
+
if (entry === null || !this.registry.isConnected(entry.id)) return [];
|
|
93436
|
+
try {
|
|
93437
|
+
const api = createGoogleCalendarApi(() => this.ensureAccessToken(entry.id));
|
|
93438
|
+
const accountLabel = this.registry.getOauth(entry.id)?.accountEmail || entry.name;
|
|
93439
|
+
return (await api.listCalendars()).map((c) => ({
|
|
93440
|
+
value: c.id,
|
|
93441
|
+
label: `${c.summary.trim().length > 0 ? c.summary : c.id} (${accountLabel})`
|
|
93442
|
+
})).toSorted((a, b) => a.label.localeCompare(b.label));
|
|
93443
|
+
} catch (err) {
|
|
93444
|
+
this.ctx.logger.warn("google calendar catalog failed", { meta: {
|
|
93445
|
+
brokerId: entry.id,
|
|
93446
|
+
error: err instanceof Error ? err.message : String(err)
|
|
93447
|
+
} });
|
|
93448
|
+
return [];
|
|
92993
93449
|
}
|
|
92994
|
-
return out.toSorted((a, b) => a.label.localeCompare(b.label));
|
|
92995
93450
|
}
|
|
92996
93451
|
async emitGoogleBrokerReconnectAlert(brokerId, brokerName) {
|
|
92997
93452
|
const emit = this.ctx.api.alerts?.emit;
|
|
@@ -93114,14 +93569,6 @@ var GoogleServicesAddon = class extends BaseAddon {
|
|
|
93114
93569
|
await this.refreshPublicOrigin();
|
|
93115
93570
|
return super.getGlobalSettings(overlay, cap, nodeId);
|
|
93116
93571
|
}
|
|
93117
|
-
/**
|
|
93118
|
-
* The stamped calendar ids ONE broker imports. A broker the migration has not reached yet
|
|
93119
|
-
* (no `calendarIds` of its own) reads the shared setting older versions wrote, so the move
|
|
93120
|
-
* never changes what is imported mid-way.
|
|
93121
|
-
*/
|
|
93122
|
-
calendarIdsOf(brokerId) {
|
|
93123
|
-
return calendarIdsOfEntry(this.registry.getEntry(brokerId), this.config.calendarIds ?? []);
|
|
93124
|
-
}
|
|
93125
93572
|
};
|
|
93126
93573
|
function asSettingsCap(cap) {
|
|
93127
93574
|
if (cap === "calendar-source" || cap === "smtp-provider") return cap;
|