@camstack/system 1.2.304 → 1.2.305
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/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/addon-utils.d.ts +1 -1
- package/dist/addon-utils.js +3 -1
- package/dist/addon-utils.mjs +2 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-extension-linked-slot.d.ts +8 -0
- package/dist/builtins/device-manager/device-linked-devices.d.ts +34 -0
- package/dist/builtins/device-manager/device-manager.addon.js +188 -22
- package/dist/builtins/device-manager/device-manager.addon.mjs +188 -22
- package/dist/builtins/doorbell/binding-mirror.d.ts +24 -0
- package/dist/builtins/doorbell/doorbell-extension-slot.d.ts +66 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +14 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +307 -38
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +307 -38
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-C1c3rWaX.mjs → child-cap-dispatch-BCJjUtXV.mjs} +9 -1
- package/dist/{child-cap-dispatch-De5_HcvR.js → child-cap-dispatch-Be8e2cBP.js} +9 -1
- package/dist/{dist-C571Y95Z.js → dist-BK4iP6ab.js} +1069 -310
- package/dist/{dist-Duqf80QM.mjs → dist-Df1NhKxm.mjs} +1046 -311
- package/dist/download/model-downloader.d.ts +15 -0
- package/dist/{event-loop-stall-monitor-GPrVvx14.mjs → event-loop-stall-monitor-EXjzIxDz.mjs} +25 -3
- package/dist/{event-loop-stall-monitor-CAsHdMwP.js → event-loop-stall-monitor-ey4mbofz.js} +36 -2
- package/dist/index.js +21 -3
- package/dist/index.mjs +21 -3
- package/dist/kernel/config-manager.d.ts +3 -1
- package/dist/{retired-settings-keys-0j8rNoa8.js → retired-settings-keys-DwWMph6F.js} +1 -1
- package/dist/{retired-settings-keys-CeO_dBHU.mjs → retired-settings-keys-dC4ckbXJ.mjs} +1 -1
- package/package.json +7 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-
|
|
1
|
+
import { A as STREAM_PROFILE_META, Bt as normalizeUnit, Cn as DeviceType, Dt as legacyAutoLinkIds, En as WELL_KNOWN_TAB_MAP, Fn as isDeviceConfigCap, G as buildStreamParamsConfigSchema, Sn as DeviceRole, Un as sleep$1, Ut as parseStreamParamsFormPatch, Wn as EventCategory, Z as createDeviceExtensionProvider, Zt as runtimeStatePolicyFor, _n as BaseAddon, _t as formatMinutes, at as deviceStateCapability, d as DAY_NAMES, h as DeviceStatusSchema, hn as errMsg, it as deviceManagerCapability, jt as locationIconKey, lt as enumerateItemArrayFields, nn as siteZoneKey, o as CAMERA_SWITCH_ORDER, ot as deviceStatusCapability, pt as expandContainer, rt as deviceExtensionCapability, s as CAP_NAMES_WITH_STATUS, t as ALL_CAPABILITY_DEFINITIONS, tt as defineExtensionSlotProvider, ut as enumerateSchemaFields, x as MINUTES_PER_DAY, xn as DeviceFeature, y as LocationIconIdSchema } from "../../dist-Df1NhKxm.mjs";
|
|
2
|
+
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-dC4ckbXJ.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
5
5
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -2937,6 +2937,47 @@ async function applyLinkedDevicesPatch(pctx, deviceId, patch) {
|
|
|
2937
2937
|
await pctx.settings.writeDeviceStore(deviceId, next);
|
|
2938
2938
|
return { success: true };
|
|
2939
2939
|
}
|
|
2940
|
+
/**
|
|
2941
|
+
* Which NEW picks a linked-devices write must refuse: the camera itself, a
|
|
2942
|
+
* container (never a pick — its children are, D356 and the file header), and
|
|
2943
|
+
* an id no device has.
|
|
2944
|
+
*
|
|
2945
|
+
* Only ids that are not ALREADY persisted are judged. A list written before
|
|
2946
|
+
* this rule existed may hold a container or a device that has since been
|
|
2947
|
+
* deleted; the editor shows those as unavailable / missing, and keeping them
|
|
2948
|
+
* is not a new link. Removing them is the operator's call, never this
|
|
2949
|
+
* function's (a missing source is shown, never silently dropped).
|
|
2950
|
+
*
|
|
2951
|
+
* Deliberately NOT applied to the settings-section save
|
|
2952
|
+
* (`applyLinkedDevicesPatch` via `DEVICE_STORE_SECTIONS`): that surface has
|
|
2953
|
+
* never refused, its picker never offers a container, and the Locations page
|
|
2954
|
+
* writes the list through it. Slice 1 changes no behaviour on the old path;
|
|
2955
|
+
* both paths persist through `applyLinkedDevicesPatch`.
|
|
2956
|
+
*/
|
|
2957
|
+
function refusedLinkedPicks(params) {
|
|
2958
|
+
const kept = new Set(params.persisted);
|
|
2959
|
+
const out = [];
|
|
2960
|
+
for (const deviceId of params.picked) {
|
|
2961
|
+
if (kept.has(deviceId)) continue;
|
|
2962
|
+
if (deviceId === params.selfId) {
|
|
2963
|
+
out.push({
|
|
2964
|
+
deviceId,
|
|
2965
|
+
reason: "self"
|
|
2966
|
+
});
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
2969
|
+
const device = params.fleet.get(deviceId);
|
|
2970
|
+
if (device === void 0) out.push({
|
|
2971
|
+
deviceId,
|
|
2972
|
+
reason: "unknown"
|
|
2973
|
+
});
|
|
2974
|
+
else if (device.type === DeviceType.Container) out.push({
|
|
2975
|
+
deviceId,
|
|
2976
|
+
reason: "container"
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
return out;
|
|
2980
|
+
}
|
|
2940
2981
|
//#endregion
|
|
2941
2982
|
//#region src/builtins/device-manager/device-store-sections.ts
|
|
2942
2983
|
/** The device-manager's device-store-backed sections. Add an entry to extend. */
|
|
@@ -3926,6 +3967,124 @@ var DeviceEventPropagator = class {
|
|
|
3926
3967
|
return chain;
|
|
3927
3968
|
}
|
|
3928
3969
|
};
|
|
3970
|
+
//#endregion
|
|
3971
|
+
//#region src/builtins/device-manager/device-extension-linked-slot.ts
|
|
3972
|
+
/** The routing prefix of this owner's `slotRef`s. */
|
|
3973
|
+
var DEVICE_MANAGER_EXTENSION_SOURCE = "device-manager";
|
|
3974
|
+
/** The store the slot reads and writes, named (D62). */
|
|
3975
|
+
var LINKED_DEVICES_AUTHORITY = `device-manager:${LINKED_IDS_CONFIG_KEY}`;
|
|
3976
|
+
/**
|
|
3977
|
+
* The picked ids' ledger rows, in ONE indexed read of the device manager's own
|
|
3978
|
+
* store (`rows.getMany`) — never the whole fleet to label a handful of ids.
|
|
3979
|
+
* An id no device has is absent from the map.
|
|
3980
|
+
*/
|
|
3981
|
+
async function pickedDevices(pctx, ids) {
|
|
3982
|
+
const rows = await pctx.metaStore.rows.getMany(ids);
|
|
3983
|
+
const out = /* @__PURE__ */ new Map();
|
|
3984
|
+
for (const [id, row] of rows) out.set(id, {
|
|
3985
|
+
name: row.meta.name,
|
|
3986
|
+
type: row.meta.type
|
|
3987
|
+
});
|
|
3988
|
+
return out;
|
|
3989
|
+
}
|
|
3990
|
+
/** What a camera's picked id is, said once for the editor. */
|
|
3991
|
+
function sourceOf(deviceId, byId) {
|
|
3992
|
+
const device = byId.get(deviceId);
|
|
3993
|
+
if (device === void 0) return {
|
|
3994
|
+
deviceId,
|
|
3995
|
+
name: null,
|
|
3996
|
+
health: "missing",
|
|
3997
|
+
detail: "No device has this id any more — it links nothing until removed."
|
|
3998
|
+
};
|
|
3999
|
+
if (device.type === DeviceType.Container) return {
|
|
4000
|
+
deviceId,
|
|
4001
|
+
name: device.name,
|
|
4002
|
+
health: "unavailable",
|
|
4003
|
+
detail: "A container is never a pick (D356): its children are linked in its place. Pick the children instead."
|
|
4004
|
+
};
|
|
4005
|
+
return {
|
|
4006
|
+
deviceId,
|
|
4007
|
+
name: device.name,
|
|
4008
|
+
health: "ok"
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
function describeRefusal(deviceId, reason) {
|
|
4012
|
+
if (reason === "self") return `${deviceId} is the camera itself`;
|
|
4013
|
+
if (reason === "container") return `${deviceId} is a container — pick its children`;
|
|
4014
|
+
return `${deviceId} is not a device`;
|
|
4015
|
+
}
|
|
4016
|
+
async function fleetOf(pctx) {
|
|
4017
|
+
const all = await listAll(pctx, LINK_CANDIDATE_PROJECTION);
|
|
4018
|
+
return new Map(all.map((d) => [d.id, d]));
|
|
4019
|
+
}
|
|
4020
|
+
function candidatesFor(deviceId, byId) {
|
|
4021
|
+
const location = byId.get(deviceId)?.location ?? null;
|
|
4022
|
+
const out = [];
|
|
4023
|
+
for (const device of byId.values()) {
|
|
4024
|
+
if (device.id === deviceId) continue;
|
|
4025
|
+
if (device.parentDeviceId === deviceId) continue;
|
|
4026
|
+
if (device.type === DeviceType.Container) continue;
|
|
4027
|
+
const suggested = location !== null && device.location === location;
|
|
4028
|
+
const parent = device.parentDeviceId === null ? void 0 : byId.get(device.parentDeviceId);
|
|
4029
|
+
out.push({
|
|
4030
|
+
deviceId: device.id,
|
|
4031
|
+
name: device.name,
|
|
4032
|
+
type: device.type,
|
|
4033
|
+
location: device.location,
|
|
4034
|
+
parentName: parent?.name ?? null,
|
|
4035
|
+
suggested,
|
|
4036
|
+
reason: suggested ? "same location" : null
|
|
4037
|
+
});
|
|
4038
|
+
}
|
|
4039
|
+
return out.toSorted((a, b) => Number(b.suggested) - Number(a.suggested) || a.name.localeCompare(b.name));
|
|
4040
|
+
}
|
|
4041
|
+
/** Build the `device-extension` provider the device manager registers. */
|
|
4042
|
+
function createDeviceManagerExtensionProvider(pctx) {
|
|
4043
|
+
return createDeviceExtensionProvider({
|
|
4044
|
+
source: DEVICE_MANAGER_EXTENSION_SOURCE,
|
|
4045
|
+
label: "Device manager",
|
|
4046
|
+
slots: [defineExtensionSlotProvider("linked-devices", {
|
|
4047
|
+
list: async (deviceId) => {
|
|
4048
|
+
const { manualIds } = parseLinkedDevicesConfig(await pctx.settings.readDeviceStore(deviceId));
|
|
4049
|
+
const byId = await pickedDevices(pctx, manualIds);
|
|
4050
|
+
return {
|
|
4051
|
+
authority: LINKED_DEVICES_AUTHORITY,
|
|
4052
|
+
enabled: null,
|
|
4053
|
+
activation: null,
|
|
4054
|
+
value: {
|
|
4055
|
+
slot: "linked-devices",
|
|
4056
|
+
deviceIds: [...manualIds]
|
|
4057
|
+
},
|
|
4058
|
+
sources: manualIds.map((id) => sourceOf(id, byId))
|
|
4059
|
+
};
|
|
4060
|
+
},
|
|
4061
|
+
set: async (deviceId, value) => {
|
|
4062
|
+
const [byId, blob] = await Promise.all([pickedDevices(pctx, value.deviceIds), pctx.settings.readDeviceStore(deviceId)]);
|
|
4063
|
+
const refused = refusedLinkedPicks({
|
|
4064
|
+
selfId: deviceId,
|
|
4065
|
+
picked: value.deviceIds,
|
|
4066
|
+
persisted: parseLinkedDevicesConfig(blob).manualIds,
|
|
4067
|
+
fleet: byId
|
|
4068
|
+
});
|
|
4069
|
+
if (refused.length > 0) {
|
|
4070
|
+
pctx.host.ctx.logger.warn("linked-devices: write refused — nothing persisted", {
|
|
4071
|
+
tags: { deviceId },
|
|
4072
|
+
meta: { refused }
|
|
4073
|
+
});
|
|
4074
|
+
throw new Error(`linked-devices on ${deviceId}: refused — ` + refused.map((r) => describeRefusal(r.deviceId, r.reason)).join("; "));
|
|
4075
|
+
}
|
|
4076
|
+
await applyLinkedDevicesPatch(pctx, deviceId, { [LINKED_IDS_CONFIG_KEY]: value.deviceIds });
|
|
4077
|
+
return {
|
|
4078
|
+
applied: true,
|
|
4079
|
+
reload: "none"
|
|
4080
|
+
};
|
|
4081
|
+
},
|
|
4082
|
+
candidates: async (deviceId) => candidatesFor(deviceId, await fleetOf(pctx))
|
|
4083
|
+
})],
|
|
4084
|
+
hostTypeOf: async (deviceId) => (await pctx.metaStore.resolvePersistedById(deviceId))?.meta.type ?? null,
|
|
4085
|
+
logger: pctx.host.ctx.logger
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
3929
4088
|
/** A refusal that names a switch the camera does not offer, rather than one it
|
|
3930
4089
|
* could not be asked about. The cap documents rejecting the former; anything
|
|
3931
4090
|
* else is treated as the camera being out of reach, which is the safe reading:
|
|
@@ -8044,28 +8203,35 @@ var DeviceManagerAddon = class extends BaseAddon {
|
|
|
8044
8203
|
this.propagator.start();
|
|
8045
8204
|
this.ctx.logger.info("device-event-propagator started");
|
|
8046
8205
|
}
|
|
8047
|
-
return [
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8206
|
+
return [
|
|
8207
|
+
{
|
|
8208
|
+
capability: deviceManagerCapability,
|
|
8209
|
+
provider
|
|
8210
|
+
},
|
|
8211
|
+
{
|
|
8212
|
+
capability: deviceStateCapability,
|
|
8213
|
+
provider: {
|
|
8214
|
+
getSnapshot: async (input) => {
|
|
8215
|
+
return stateMirror.snapshotForDevice(input.deviceId);
|
|
8216
|
+
},
|
|
8217
|
+
getCapSlice: async (input) => {
|
|
8218
|
+
return stateMirror.capSlice(input.deviceId, input.capName);
|
|
8219
|
+
},
|
|
8220
|
+
getAllSnapshots: async () => {
|
|
8221
|
+
return stateMirror.allSnapshots();
|
|
8222
|
+
},
|
|
8223
|
+
setCapSlice: async (input) => {
|
|
8224
|
+
const { deviceId, capName, slice } = input;
|
|
8225
|
+
if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] setCapSlice: unknown device id=${deviceId}`);
|
|
8226
|
+
if (stateMirror.applySingleCapUpdate(deviceId, capName, slice)) stateMirror.scheduleRuntimeStateDiskWrite(deviceId, settings, capName);
|
|
8227
|
+
}
|
|
8066
8228
|
}
|
|
8229
|
+
},
|
|
8230
|
+
{
|
|
8231
|
+
capability: deviceExtensionCapability,
|
|
8232
|
+
provider: createDeviceManagerExtensionProvider(pctx)
|
|
8067
8233
|
}
|
|
8068
|
-
|
|
8234
|
+
];
|
|
8069
8235
|
}
|
|
8070
8236
|
async onShutdown() {
|
|
8071
8237
|
this.propagator?.stop();
|
|
@@ -113,3 +113,27 @@ export type DurableBindingVerdict = 'bound' | 'unbound' | 'unreadable';
|
|
|
113
113
|
* from an empty snapshot, whereas a genuine unbind leaves the key present.
|
|
114
114
|
*/
|
|
115
115
|
export declare function readDurableWrapperBinding(record: unknown, deviceId: number, capName: string, ownAddonId: string): DurableBindingVerdict;
|
|
116
|
+
/**
|
|
117
|
+
* What a press on this camera would do right now.
|
|
118
|
+
*
|
|
119
|
+
* `rings` — the binding is confirmed (mirror `bound`, or the durable store
|
|
120
|
+
* rescued an `unbound` mirror). `rings-pending` — one negative read, not yet
|
|
121
|
+
* believed. `rings-unconfirmed` — no read has happened yet; the ring path errs
|
|
122
|
+
* open. `unknown` — the mirror says unbound but the durable store could not
|
|
123
|
+
* be read: a press RINGS (an unanswerable store never destroys a one-shot
|
|
124
|
+
* press), and nobody can say whether that is right. `silent` — the durable
|
|
125
|
+
* store agrees with an unbound mirror: the only verdict that drops a press.
|
|
126
|
+
*/
|
|
127
|
+
export type RingVerdict = 'rings' | 'rings-pending' | 'rings-unconfirmed' | 'unknown' | 'silent';
|
|
128
|
+
/** Does this mirror state need the durable authority before a verdict? Only `unbound` does. */
|
|
129
|
+
export declare function needsDurableRead(state: DoorbellBindingState | undefined): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* THE decision — the ring gate (`fireDoorbell` / `ringsDespiteUnboundMirror`)
|
|
132
|
+
* and the Extensions card's activation both call it, so what the card says
|
|
133
|
+
* and what a press does cannot disagree. `durable` is `null` when it was not
|
|
134
|
+
* read, which {@link needsDurableRead} allows for every state but `unbound`.
|
|
135
|
+
* Pure: it reads nothing and writes nothing.
|
|
136
|
+
*/
|
|
137
|
+
export declare function ringVerdict(state: DoorbellBindingState | undefined, durable: DurableBindingVerdict | null): RingVerdict;
|
|
138
|
+
/** Whether a press proceeds under this verdict. */
|
|
139
|
+
export declare function verdictRings(verdict: RingVerdict): boolean;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ExtensionSlotActivation, IDeviceExtensionProvider, IScopedLogger } from '@camstack/types';
|
|
2
|
+
import { DoorbellBindingState, DurableBindingVerdict, RingVerdict } from './binding-mirror.js';
|
|
3
|
+
import { DoorbellSource } from './doorbell-settings.js';
|
|
4
|
+
/** The routing prefix of this owner's `slotRef`s. */
|
|
5
|
+
export declare const VIRTUAL_DOORBELL_EXTENSION_SOURCE = "virtual-doorbell";
|
|
6
|
+
/** The store the slot reads and writes, named (D62). */
|
|
7
|
+
export declare const DOORBELL_SOURCES_AUTHORITY = "virtual-doorbell:doorbellSources";
|
|
8
|
+
/** The authority that decides whether the wrapper serves a camera at all. */
|
|
9
|
+
export declare const WRAPPER_ACTIVATION_AUTHORITY = "device-manager:setWrapperActive";
|
|
10
|
+
/** The compact device row this slot needs from the device manager. */
|
|
11
|
+
export interface DoorbellSlotDevice {
|
|
12
|
+
readonly id: number;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly type: string;
|
|
15
|
+
readonly location: string | null;
|
|
16
|
+
readonly parentDeviceId: number | null;
|
|
17
|
+
}
|
|
18
|
+
/** What the slot needs from the addon — its store, its mirror, and the fleet's reads. */
|
|
19
|
+
export interface DoorbellSlotDeps {
|
|
20
|
+
readonly ownAddonId: string;
|
|
21
|
+
readonly logger: IScopedLogger;
|
|
22
|
+
readonly readSources: (deviceId: number) => Promise<readonly DoorbellSource[]>;
|
|
23
|
+
/** The addon's own settings-save path (normalise, store, invalidate). */
|
|
24
|
+
readonly persistSources: (deviceId: number, deviceIds: readonly number[]) => Promise<void>;
|
|
25
|
+
/** The host's own row — ONE read, for its type. */
|
|
26
|
+
readonly getDevice: (deviceId: number) => Promise<DoorbellSlotDevice | null>;
|
|
27
|
+
/** Several devices in ONE read. Ids no device has are simply absent. */
|
|
28
|
+
readonly listDevices: (deviceIds: readonly number[]) => Promise<readonly DoorbellSlotDevice[]>;
|
|
29
|
+
/** The D49 mirror's verdict for this camera, or undefined when it has none. */
|
|
30
|
+
readonly readBindingMirror: (deviceId: number) => DoorbellBindingState | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* The DURABLE authority, read-only — the same read the ring gate makes
|
|
33
|
+
* before it drops a press. Never throws: a store that cannot answer is
|
|
34
|
+
* `unreadable`, which carries no information.
|
|
35
|
+
*/
|
|
36
|
+
readonly readDurableBinding: (deviceId: number) => Promise<DurableBindingVerdict>;
|
|
37
|
+
readonly sourceCapableDeviceIds: () => Promise<readonly number[]>;
|
|
38
|
+
readonly listFleet: () => Promise<readonly DoorbellSlotDevice[]>;
|
|
39
|
+
}
|
|
40
|
+
/** How the card SAYS a verdict. The verdict itself is `ringVerdict`'s, never the card's. */
|
|
41
|
+
export declare function activationFromVerdict(verdict: RingVerdict): ExtensionSlotActivation;
|
|
42
|
+
/**
|
|
43
|
+
* The card's activation — READ-ONLY (D648 fix round 3).
|
|
44
|
+
*
|
|
45
|
+
* The verdict is `ringVerdict`, the function the ring gate decides with, fed
|
|
46
|
+
* the addon's mirror as it stands. The card never refreshes the mirror and
|
|
47
|
+
* never writes it: the mirror gates the ring, and a view that could move it
|
|
48
|
+
* would let a page refetch during a store blip silence a doorbell. The only
|
|
49
|
+
* read the card may make is the durable authority, and only where the ring
|
|
50
|
+
* gate would make it too (`needsDurableRead`: an `unbound` mirror). A camera
|
|
51
|
+
* the mirror has never seen costs nothing — the ring path errs open there and
|
|
52
|
+
* so does the card.
|
|
53
|
+
*/
|
|
54
|
+
/** The card's durable read for one camera, shared by every view asking at once. */
|
|
55
|
+
type DurableReader = (deviceId: number) => Promise<DurableBindingVerdict>;
|
|
56
|
+
/**
|
|
57
|
+
* Concurrent card views of the same `unbound` camera share ONE durable read,
|
|
58
|
+
* per camera, for as long as it is in flight — a page with several viewers
|
|
59
|
+
* (or a refetch storm) must not multiply the store round trip. Only the
|
|
60
|
+
* CARD's reads are shared: the ring gate calls the addon's
|
|
61
|
+
* `readDurableAuthority` on its own path and never waits behind a card.
|
|
62
|
+
*/
|
|
63
|
+
export declare function shareDurableReads(read: DurableReader): DurableReader;
|
|
64
|
+
/** Build the `device-extension` provider the virtual doorbell registers. */
|
|
65
|
+
export declare function createDoorbellExtensionProvider(deps: DoorbellSlotDeps): IDeviceExtensionProvider;
|
|
66
|
+
export {};
|
|
@@ -53,6 +53,11 @@ export declare class VirtualDoorbellAddon extends BaseAddon<VirtualDoorbellAddon
|
|
|
53
53
|
private readonly bindingMirror;
|
|
54
54
|
constructor();
|
|
55
55
|
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
56
|
+
/**
|
|
57
|
+
* The `device-extension` provider: the same store and the same save path as
|
|
58
|
+
* the settings section below (see `doorbell-extension-slot.ts`).
|
|
59
|
+
*/
|
|
60
|
+
private buildExtensionProvider;
|
|
56
61
|
private handleStateChanged;
|
|
57
62
|
/**
|
|
58
63
|
* Say out loud every case where a rise did NOT become a ring. Silence
|
|
@@ -188,5 +193,14 @@ export declare class VirtualDoorbellAddon extends BaseAddon<VirtualDoorbellAddon
|
|
|
188
193
|
*/
|
|
189
194
|
private buildDeviceSettingsContribution;
|
|
190
195
|
private saveDeviceSettingsPatch;
|
|
196
|
+
/**
|
|
197
|
+
* THE write path for a camera's trigger sources — the settings save above
|
|
198
|
+
* and the `doorbell-source` extension slot both land here (D648), so the
|
|
199
|
+
* two surfaces cannot store different shapes: normalise to
|
|
200
|
+
* `doorbellSources: [{deviceId}]`, strip the legacy keys, and invalidate the
|
|
201
|
+
* assignment cache so the next rise (and the binding mirror's next
|
|
202
|
+
* refresh, off the ring path) sees the new wiring.
|
|
203
|
+
*/
|
|
204
|
+
private persistDoorbellSources;
|
|
191
205
|
}
|
|
192
206
|
export default VirtualDoorbellAddon;
|