@camstack/addon-provider-frigate 0.1.0 → 0.1.1
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/assets/icon.svg +5 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +105 -44
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +105 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +27 -5
package/assets/icon.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2
|
+
<path d="M7 2h10v4H7z"/>
|
|
3
|
+
<rect x="2" y="6" width="20" height="12" rx="2"/>
|
|
4
|
+
<circle cx="12" cy="12" r="3"/>
|
|
5
|
+
</svg>
|
package/dist/index.d.mts
CHANGED
|
@@ -36,13 +36,21 @@ declare class FrigateProvider implements IDeviceProvider {
|
|
|
36
36
|
private devices;
|
|
37
37
|
private readonly liveEventListeners;
|
|
38
38
|
private mqttUnsubscribe?;
|
|
39
|
+
/** Cached Frigate config, refreshed on start() and discoverDevices() */
|
|
40
|
+
private frigateConfig;
|
|
41
|
+
private go2rtcStreams;
|
|
42
|
+
private frigateHost;
|
|
39
43
|
constructor(config: FrigateProviderConfig, ctx: CamstackContext);
|
|
40
44
|
start(): Promise<void>;
|
|
41
45
|
stop(): Promise<void>;
|
|
42
46
|
getStatus(): ProviderStatus;
|
|
43
47
|
discoverDevices(): Promise<DiscoveredDevice[]>;
|
|
48
|
+
adoptDevice(externalId: string, _config?: Record<string, unknown>): Promise<IDevice>;
|
|
44
49
|
getDevices(): IDevice[];
|
|
45
50
|
subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void;
|
|
51
|
+
private createDeviceFromCamera;
|
|
52
|
+
private getAdoptedDeviceIds;
|
|
53
|
+
private persistAdoptedDeviceId;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
interface FrigateConfig {
|
package/dist/index.d.ts
CHANGED
|
@@ -36,13 +36,21 @@ declare class FrigateProvider implements IDeviceProvider {
|
|
|
36
36
|
private devices;
|
|
37
37
|
private readonly liveEventListeners;
|
|
38
38
|
private mqttUnsubscribe?;
|
|
39
|
+
/** Cached Frigate config, refreshed on start() and discoverDevices() */
|
|
40
|
+
private frigateConfig;
|
|
41
|
+
private go2rtcStreams;
|
|
42
|
+
private frigateHost;
|
|
39
43
|
constructor(config: FrigateProviderConfig, ctx: CamstackContext);
|
|
40
44
|
start(): Promise<void>;
|
|
41
45
|
stop(): Promise<void>;
|
|
42
46
|
getStatus(): ProviderStatus;
|
|
43
47
|
discoverDevices(): Promise<DiscoveredDevice[]>;
|
|
48
|
+
adoptDevice(externalId: string, _config?: Record<string, unknown>): Promise<IDevice>;
|
|
44
49
|
getDevices(): IDevice[];
|
|
45
50
|
subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void;
|
|
51
|
+
private createDeviceFromCamera;
|
|
52
|
+
private getAdoptedDeviceIds;
|
|
53
|
+
private persistAdoptedDeviceId;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
interface FrigateConfig {
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
32
32
|
var FrigateApiClient = class {
|
|
33
33
|
constructor(config) {
|
|
34
34
|
this.config = config;
|
|
35
|
-
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
35
|
+
this.baseUrl = (config.baseUrl ?? "").replace(/\/+$/, "");
|
|
36
36
|
}
|
|
37
37
|
baseUrl;
|
|
38
38
|
authToken = null;
|
|
@@ -399,6 +399,7 @@ function normalizeBoundingBox(box, width, height) {
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
// src/frigate-device.ts
|
|
402
|
+
var import_types = require("@camstack/types");
|
|
402
403
|
var FrigateDevice = class {
|
|
403
404
|
constructor(config, api, ctx) {
|
|
404
405
|
this.config = config;
|
|
@@ -422,7 +423,7 @@ var FrigateDevice = class {
|
|
|
422
423
|
id;
|
|
423
424
|
name;
|
|
424
425
|
providerId;
|
|
425
|
-
type =
|
|
426
|
+
type = import_types.DeviceType.Camera;
|
|
426
427
|
capabilities;
|
|
427
428
|
capabilityMap = /* @__PURE__ */ new Map();
|
|
428
429
|
ctx;
|
|
@@ -729,6 +730,7 @@ function setNestedValue(obj, path, value) {
|
|
|
729
730
|
}
|
|
730
731
|
|
|
731
732
|
// src/frigate-provider.ts
|
|
733
|
+
var ADOPTED_DEVICES_KEY = "adoptedDevices";
|
|
732
734
|
var FrigateProvider = class {
|
|
733
735
|
constructor(config, ctx) {
|
|
734
736
|
this.config = config;
|
|
@@ -740,6 +742,7 @@ var FrigateProvider = class {
|
|
|
740
742
|
username: config.username,
|
|
741
743
|
password: config.password
|
|
742
744
|
});
|
|
745
|
+
this.frigateHost = new URL(config.url).hostname;
|
|
743
746
|
}
|
|
744
747
|
id;
|
|
745
748
|
type = "frigate";
|
|
@@ -751,49 +754,36 @@ var FrigateProvider = class {
|
|
|
751
754
|
devices = [];
|
|
752
755
|
liveEventListeners = /* @__PURE__ */ new Set();
|
|
753
756
|
mqttUnsubscribe;
|
|
757
|
+
/** Cached Frigate config, refreshed on start() and discoverDevices() */
|
|
758
|
+
frigateConfig = null;
|
|
759
|
+
go2rtcStreams = {};
|
|
760
|
+
frigateHost;
|
|
754
761
|
async start() {
|
|
755
762
|
this.ctx.logger.info(`Starting Frigate provider: ${this.config.url}`);
|
|
756
|
-
|
|
757
|
-
|
|
763
|
+
this.frigateConfig = await this.api.getConfig();
|
|
764
|
+
this.go2rtcStreams = await this.api.getGo2rtcStreams();
|
|
758
765
|
const resolutions = /* @__PURE__ */ new Map();
|
|
759
|
-
for (const [name, cam] of Object.entries(frigateConfig.cameras)) {
|
|
766
|
+
for (const [name, cam] of Object.entries(this.frigateConfig.cameras)) {
|
|
760
767
|
if (cam.detect) {
|
|
761
768
|
resolutions.set(name, { width: cam.detect.width, height: cam.detect.height });
|
|
762
769
|
}
|
|
763
770
|
}
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
{
|
|
780
|
-
cameraName: name,
|
|
781
|
-
providerId: this.id,
|
|
782
|
-
detectWidth: cam.detect?.width ?? 1920,
|
|
783
|
-
detectHeight: cam.detect?.height ?? 1080,
|
|
784
|
-
audioEnabled: cam.audio?.enabled ?? false,
|
|
785
|
-
recordEnabled: cam.record?.enabled ?? false,
|
|
786
|
-
ptzEnabled: false,
|
|
787
|
-
streams,
|
|
788
|
-
frigateHost
|
|
789
|
-
},
|
|
790
|
-
this.api,
|
|
791
|
-
deviceCtx
|
|
792
|
-
)
|
|
793
|
-
);
|
|
771
|
+
const adoptedIds = this.getAdoptedDeviceIds();
|
|
772
|
+
if (adoptedIds.length > 0) {
|
|
773
|
+
const devices = [];
|
|
774
|
+
for (const cameraName of adoptedIds) {
|
|
775
|
+
const cam = this.frigateConfig.cameras[cameraName];
|
|
776
|
+
if (!cam || cam.enabled === false) {
|
|
777
|
+
this.ctx.logger.warn(`Adopted camera "${cameraName}" no longer available in Frigate config, skipping`);
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
devices.push(this.createDeviceFromCamera(cameraName, cam));
|
|
781
|
+
}
|
|
782
|
+
this.devices = devices;
|
|
783
|
+
this.ctx.logger.info(`Loaded ${devices.length} adopted cameras`);
|
|
784
|
+
} else {
|
|
785
|
+
this.ctx.logger.info("No adopted cameras yet \u2014 use discoverDevices() + adoptDevice() to import cameras");
|
|
794
786
|
}
|
|
795
|
-
this.devices = devices;
|
|
796
|
-
this.ctx.logger.info(`Discovered ${devices.length} cameras`);
|
|
797
787
|
if (this.config.mqtt?.brokerUrl) {
|
|
798
788
|
this.mqtt = new FrigateMqttClient(
|
|
799
789
|
{
|
|
@@ -820,6 +810,8 @@ var FrigateProvider = class {
|
|
|
820
810
|
await this.mqtt?.disconnect();
|
|
821
811
|
this.mqtt = null;
|
|
822
812
|
this.devices = [];
|
|
813
|
+
this.frigateConfig = null;
|
|
814
|
+
this.go2rtcStreams = {};
|
|
823
815
|
}
|
|
824
816
|
getStatus() {
|
|
825
817
|
return {
|
|
@@ -828,13 +820,46 @@ var FrigateProvider = class {
|
|
|
828
820
|
};
|
|
829
821
|
}
|
|
830
822
|
async discoverDevices() {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
823
|
+
this.frigateConfig = await this.api.getConfig();
|
|
824
|
+
this.go2rtcStreams = await this.api.getGo2rtcStreams();
|
|
825
|
+
const adoptedIds = this.getAdoptedDeviceIds();
|
|
826
|
+
const discovered = [];
|
|
827
|
+
for (const [name, cam] of Object.entries(this.frigateConfig.cameras)) {
|
|
828
|
+
if (cam.enabled === false) continue;
|
|
829
|
+
const caps = ["camera", "events", "recording", "motionSensor", "objectDetector"];
|
|
830
|
+
if (cam.audio?.enabled) caps.push("audioDetector");
|
|
831
|
+
discovered.push({
|
|
832
|
+
externalId: name,
|
|
833
|
+
name,
|
|
834
|
+
type: "camera",
|
|
835
|
+
capabilities: caps,
|
|
836
|
+
metadata: { manufacturer: "Frigate NVR" }
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
return discovered;
|
|
840
|
+
}
|
|
841
|
+
async adoptDevice(externalId, _config) {
|
|
842
|
+
if (!this.frigateConfig) {
|
|
843
|
+
this.frigateConfig = await this.api.getConfig();
|
|
844
|
+
this.go2rtcStreams = await this.api.getGo2rtcStreams();
|
|
845
|
+
}
|
|
846
|
+
const cam = this.frigateConfig.cameras[externalId];
|
|
847
|
+
if (!cam) {
|
|
848
|
+
throw new Error(`Camera "${externalId}" not found in Frigate config`);
|
|
849
|
+
}
|
|
850
|
+
if (cam.enabled === false) {
|
|
851
|
+
throw new Error(`Camera "${externalId}" is disabled in Frigate config`);
|
|
852
|
+
}
|
|
853
|
+
const existing = this.devices.find((d) => d.name === externalId);
|
|
854
|
+
if (existing) {
|
|
855
|
+
this.ctx.logger.info(`Camera "${externalId}" is already adopted`);
|
|
856
|
+
return existing;
|
|
857
|
+
}
|
|
858
|
+
const device = this.createDeviceFromCamera(externalId, cam);
|
|
859
|
+
this.devices = [...this.devices, device];
|
|
860
|
+
await this.persistAdoptedDeviceId(externalId);
|
|
861
|
+
this.ctx.logger.info(`Adopted camera "${externalId}" (total: ${this.devices.length})`);
|
|
862
|
+
return device;
|
|
838
863
|
}
|
|
839
864
|
getDevices() {
|
|
840
865
|
return [...this.devices];
|
|
@@ -845,6 +870,41 @@ var FrigateProvider = class {
|
|
|
845
870
|
this.liveEventListeners.delete(callback);
|
|
846
871
|
};
|
|
847
872
|
}
|
|
873
|
+
// --- Private helpers ---
|
|
874
|
+
createDeviceFromCamera(cameraName, cam) {
|
|
875
|
+
const streams = buildStreamOptions(cameraName, this.go2rtcStreams);
|
|
876
|
+
const deviceId = `${this.id}/${cameraName}`;
|
|
877
|
+
const deviceCtx = {
|
|
878
|
+
id: `device:${deviceId}`,
|
|
879
|
+
logger: this.ctx.logger.child(cameraName),
|
|
880
|
+
eventBus: this.ctx.eventBus,
|
|
881
|
+
storage: this.ctx.storage,
|
|
882
|
+
config: new ElementConfigStore(`device:${deviceId}`, this.ctx.storage)
|
|
883
|
+
};
|
|
884
|
+
return new FrigateDevice(
|
|
885
|
+
{
|
|
886
|
+
cameraName,
|
|
887
|
+
providerId: this.id,
|
|
888
|
+
detectWidth: cam.detect?.width ?? 1920,
|
|
889
|
+
detectHeight: cam.detect?.height ?? 1080,
|
|
890
|
+
audioEnabled: cam.audio?.enabled ?? false,
|
|
891
|
+
recordEnabled: cam.record?.enabled ?? false,
|
|
892
|
+
ptzEnabled: false,
|
|
893
|
+
streams,
|
|
894
|
+
frigateHost: this.frigateHost
|
|
895
|
+
},
|
|
896
|
+
this.api,
|
|
897
|
+
deviceCtx
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
getAdoptedDeviceIds() {
|
|
901
|
+
return this.ctx.config.get(ADOPTED_DEVICES_KEY) ?? [];
|
|
902
|
+
}
|
|
903
|
+
async persistAdoptedDeviceId(cameraName) {
|
|
904
|
+
const current = this.getAdoptedDeviceIds();
|
|
905
|
+
if (current.includes(cameraName)) return;
|
|
906
|
+
await this.ctx.config.set(ADOPTED_DEVICES_KEY, [...current, cameraName]);
|
|
907
|
+
}
|
|
848
908
|
};
|
|
849
909
|
function buildStreamOptions(cameraName, go2rtcStreams) {
|
|
850
910
|
const streams = [];
|
|
@@ -862,6 +922,7 @@ var FrigateProviderAddon = class {
|
|
|
862
922
|
id: "provider-frigate",
|
|
863
923
|
name: "Frigate NVR Provider",
|
|
864
924
|
version: "0.1.0",
|
|
925
|
+
description: "Integrazione con Frigate NVR per camere e detection",
|
|
865
926
|
capabilities: ["device-provider"]
|
|
866
927
|
};
|
|
867
928
|
provider = null;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/frigate-api.ts","../src/frigate-mqtt.ts","../src/frigate-device.ts","../src/element-config-store.ts","../src/frigate-provider.ts","../src/addon.ts"],"sourcesContent":["export { FrigateProviderAddon } from './addon'\nexport { FrigateProvider } from './frigate-provider'\nexport type { FrigateProviderConfig } from './frigate-provider'\nexport { FrigateApiClient } from './frigate-api'\nexport type { FrigateApiConfig, EventsQuery, ReviewQuery, MotionQuery, TestConnectionResult } from './frigate-api'\nexport { FrigateMqttClient } from './frigate-mqtt'\nexport type { FrigateMqttConfig, MqttStatus } from './frigate-mqtt'\nexport { FrigateDevice } from './frigate-device'\nexport type { FrigateDeviceConfig } from './frigate-device'\nexport type {\n FrigateConfig,\n FrigateCameraConfig,\n FrigateRawEvent,\n FrigateReviewItem,\n FrigateRawRecording,\n FrigateMotionData,\n} from './frigate-types'\n","import type {\n FrigateConfig,\n FrigateMotionData,\n FrigateRawEvent,\n FrigateRawRecording,\n FrigateReviewItem,\n} from './frigate-types'\n\nexport interface FrigateApiConfig {\n baseUrl: string\n username?: string\n password?: string\n}\n\nexport interface EventsQuery {\n after?: number\n before?: number\n cameras?: string\n limit?: number\n labels?: string\n}\n\nexport interface ReviewQuery {\n after?: number\n before?: number\n cameras?: string\n limit?: number\n}\n\nexport interface MotionQuery {\n after: number\n before: number\n cameras?: string\n}\n\nexport interface TestConnectionResult {\n success: boolean\n version?: string\n cameraCount?: number\n error?: string\n}\n\nexport class FrigateApiClient {\n private readonly baseUrl: string\n private authToken: string | null = null\n private authHeader: string | null = null\n\n constructor(private readonly config: FrigateApiConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '')\n }\n\n private async authenticate(): Promise<void> {\n const { username, password } = this.config\n\n if (!username || !password) {\n return\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/api/login`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ user: username, password }),\n })\n\n if (response.ok) {\n const cookies = response.headers.get('set-cookie')\n if (cookies) {\n this.authToken = cookies\n return\n }\n }\n } catch {\n // Token login failed, fall through to Basic auth\n }\n\n this.authHeader = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {}\n\n if (this.authToken) {\n headers['Cookie'] = this.authToken\n } else if (this.authHeader) {\n headers['Authorization'] = this.authHeader\n }\n\n return headers\n }\n\n private async request<T>(path: string, options?: RequestInit): Promise<T> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.buildHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n return response.json() as Promise<T>\n }\n\n private async requestBuffer(path: string): Promise<Buffer> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n headers: this.buildHeaders(),\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n const arrayBuffer = await response.arrayBuffer()\n return Buffer.from(arrayBuffer)\n }\n\n private async requestText(path: string, options?: RequestInit): Promise<string> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.buildHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n return response.text()\n }\n\n // --- Config & Discovery ---\n\n async getConfig(): Promise<FrigateConfig> {\n return this.request<FrigateConfig>('/api/config')\n }\n\n async getGo2rtcStreams(): Promise<Record<string, unknown>> {\n return this.request<Record<string, unknown>>('/api/go2rtc/streams')\n }\n\n // --- Events ---\n\n async getEvents(params: EventsQuery): Promise<FrigateRawEvent[]> {\n const query = buildQueryString(params)\n return this.request<FrigateRawEvent[]>(`/api/events${query}`)\n }\n\n async getReviewItems(params: ReviewQuery): Promise<FrigateReviewItem[]> {\n const query = buildQueryString(params)\n return this.request<FrigateReviewItem[]>(`/api/review${query}`)\n }\n\n // --- Recordings ---\n\n async getRecordings(\n camera: string,\n after: number,\n before: number,\n ): Promise<FrigateRawRecording[]> {\n const query = buildQueryString({ after, before })\n return this.request<FrigateRawRecording[]>(`/api/${encodeURIComponent(camera)}/recordings${query}`)\n }\n\n async getMotionActivity(params: MotionQuery): Promise<FrigateMotionData[]> {\n const query = buildQueryString(params)\n return this.request<FrigateMotionData[]>(`/api/motion_activity${query}`)\n }\n\n // --- Media ---\n\n async getLatestSnapshot(camera: string): Promise<Buffer> {\n return this.requestBuffer(`/api/${encodeURIComponent(camera)}/latest.jpg`)\n }\n\n async getEventThumbnail(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/thumbnail.jpg`)\n }\n\n async getEventSnapshot(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/snapshot.jpg`)\n }\n\n async getEventClip(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/clip.mp4`)\n }\n\n async getRecordingThumbnail(camera: string, timestampSec: number): Promise<Buffer> {\n return this.requestBuffer(\n `/api/${encodeURIComponent(camera)}/recordings/thumbnail-${timestampSec}.jpg`,\n )\n }\n\n // --- WebRTC ---\n\n async proxyWhepSdp(streamName: string, sdpOffer: string): Promise<string> {\n return this.requestText(`/api/go2rtc/webrtc?src=${encodeURIComponent(streamName)}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: sdpOffer,\n })\n }\n\n // --- PTZ ---\n\n async getPtzInfo(\n camera: string,\n ): Promise<{ features?: string[]; presets?: string[] } | null> {\n try {\n return await this.request<{ features?: string[]; presets?: string[] }>(\n `/api/${encodeURIComponent(camera)}/ptz/info`,\n )\n } catch {\n return null\n }\n }\n\n async ptzCommand(\n camera: string,\n command: string,\n params?: Record<string, unknown>,\n ): Promise<void> {\n const query = buildQueryString({ ...params, command })\n await this.request<void>(`/api/${encodeURIComponent(camera)}/ptz${query}`)\n }\n\n // --- Test Connection ---\n\n async testConnection(): Promise<TestConnectionResult> {\n try {\n const config = await this.getConfig()\n const cameraNames = Object.keys(config.cameras ?? {})\n return {\n success: true,\n version: config.version,\n cameraCount: cameraNames.length,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n }\n}\n\n// --- Utility ---\n\nexport function buildQueryString(params: Record<string, unknown> | object): string {\n const entries = Object.entries(params).filter(\n ([, v]) => v !== undefined && v !== null,\n )\n if (entries.length === 0) {\n return ''\n }\n const searchParams = new URLSearchParams()\n for (const [key, value] of entries) {\n searchParams.set(key, String(value))\n }\n return `?${searchParams.toString()}`\n}\n","import type { MqttClient } from 'mqtt'\nimport { connect } from 'mqtt'\nimport type { LiveEvent } from '@camstack/types'\n\nexport interface FrigateMqttConfig {\n brokerUrl: string\n username?: string\n password?: string\n topicPrefix: string\n}\n\nexport interface MqttStatus {\n connected: boolean\n eventCount: number\n}\n\nexport class FrigateMqttClient {\n private client: MqttClient | null = null\n private readonly listeners: Set<(event: LiveEvent) => void> = new Set()\n private eventCount = 0\n\n constructor(\n private readonly config: FrigateMqttConfig,\n private readonly cameraResolutions: Map<string, { width: number; height: number }>,\n ) {}\n\n async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const { brokerUrl, username, password, topicPrefix } = this.config\n\n this.client = connect(brokerUrl, {\n username,\n password,\n reconnectPeriod: 5000,\n connectTimeout: 10000,\n })\n\n this.client.on('connect', () => {\n const prefix = topicPrefix\n this.client!.subscribe([\n `${prefix}/events`,\n `${prefix}/reviews`,\n `${prefix}/+/motion/state`,\n `${prefix}/+/audio/+`,\n ])\n resolve()\n })\n\n this.client.on('error', (err) => {\n reject(err)\n })\n\n this.client.on('message', (topic: string, payload: Buffer) => {\n const event = parseMqttMessage(\n topic,\n payload,\n topicPrefix,\n this.cameraResolutions,\n )\n if (event) {\n this.eventCount++\n for (const listener of this.listeners) {\n listener(event)\n }\n }\n })\n })\n }\n\n async disconnect(): Promise<void> {\n if (this.client) {\n await this.client.endAsync()\n this.client = null\n }\n this.listeners.clear()\n }\n\n subscribe(callback: (event: LiveEvent) => void): () => void {\n this.listeners.add(callback)\n return () => {\n this.listeners.delete(callback)\n }\n }\n\n get status(): MqttStatus {\n return {\n connected: this.client?.connected ?? false,\n eventCount: this.eventCount,\n }\n }\n}\n\n// --- Exported for testing ---\n\ninterface FrigateMqttEventPayload {\n type?: 'new' | 'update' | 'end'\n before?: FrigateMqttDetectionState\n after?: FrigateMqttDetectionState\n}\n\ninterface FrigateMqttDetectionState {\n id?: string\n camera?: string\n label?: string\n sub_label?: string\n top_score?: number\n start_time?: number\n end_time?: number | null\n current_zones?: string[]\n has_snapshot?: boolean\n has_clip?: boolean\n box?: [number, number, number, number]\n}\n\ninterface FrigateMqttReviewPayload {\n type?: 'new' | 'update' | 'end'\n before?: FrigateMqttReviewState\n after?: FrigateMqttReviewState\n}\n\ninterface FrigateMqttReviewState {\n id?: string\n camera?: string\n start_time?: number\n end_time?: number | null\n severity?: 'alert' | 'detection'\n data?: {\n objects?: string[]\n zones?: string[]\n sub_labels?: string[]\n }\n}\n\nexport function parseMqttMessage(\n topic: string,\n payload: Buffer,\n prefix: string,\n resolutions: Map<string, { width: number; height: number }>,\n): LiveEvent | null {\n const topicParts = topic.split('/')\n const prefixParts = prefix.split('/')\n const relative = topicParts.slice(prefixParts.length)\n\n // {prefix}/events\n if (relative.length === 1 && relative[0] === 'events') {\n return parseDetectionEvent(payload, resolutions)\n }\n\n // {prefix}/reviews\n if (relative.length === 1 && relative[0] === 'reviews') {\n return parseReviewEvent(payload)\n }\n\n // {prefix}/{camera}/motion/state\n if (relative.length === 3 && relative[1] === 'motion' && relative[2] === 'state') {\n const camera = relative[0]!\n return parseMotionEvent(camera, payload)\n }\n\n // {prefix}/{camera}/audio/{metric}\n if (relative.length === 3 && relative[1] === 'audio') {\n const camera = relative[0]!\n const metric = relative[2]!\n return parseAudioEvent(camera, metric, payload)\n }\n\n return null\n}\n\nfunction parseDetectionEvent(\n payload: Buffer,\n resolutions: Map<string, { width: number; height: number }>,\n): LiveEvent | null {\n try {\n const parsed: FrigateMqttEventPayload = JSON.parse(payload.toString())\n const state = parsed.after ?? parsed.before\n if (!state?.camera || !state?.label) {\n return null\n }\n\n const data: Record<string, unknown> = {\n eventType: parsed.type ?? 'update',\n id: state.id,\n label: state.label,\n subLabel: state.sub_label,\n score: state.top_score,\n startTime: state.start_time,\n endTime: state.end_time,\n zones: state.current_zones,\n hasSnapshot: state.has_snapshot,\n hasClip: state.has_clip,\n }\n\n if (state.box) {\n const resolution = resolutions.get(state.camera)\n if (resolution) {\n data.boundingBox = normalizeBoundingBox(state.box, resolution.width, resolution.height)\n } else {\n data.rawBox = state.box\n }\n }\n\n return {\n type: 'detection',\n camera: state.camera,\n timestamp: Date.now(),\n data,\n }\n } catch {\n return null\n }\n}\n\nfunction parseReviewEvent(payload: Buffer): LiveEvent | null {\n try {\n const parsed: FrigateMqttReviewPayload = JSON.parse(payload.toString())\n const state = parsed.after ?? parsed.before\n if (!state?.camera) {\n return null\n }\n\n return {\n type: 'review',\n camera: state.camera,\n timestamp: Date.now(),\n data: {\n eventType: parsed.type ?? 'update',\n id: state.id,\n severity: state.severity,\n startTime: state.start_time,\n endTime: state.end_time,\n objects: state.data?.objects,\n zones: state.data?.zones,\n subLabels: state.data?.sub_labels,\n },\n }\n } catch {\n return null\n }\n}\n\nfunction parseMotionEvent(camera: string, payload: Buffer): LiveEvent {\n const value = payload.toString().trim()\n const active = value === 'ON'\n\n return {\n type: 'motion',\n camera,\n timestamp: Date.now(),\n data: { active },\n }\n}\n\nfunction parseAudioEvent(camera: string, metric: string, payload: Buffer): LiveEvent | null {\n const raw = payload.toString().trim()\n const value = parseFloat(raw)\n\n if (isNaN(value)) {\n return null\n }\n\n return {\n type: 'audio',\n camera,\n timestamp: Date.now(),\n data: { metric, value },\n }\n}\n\n/**\n * Normalize Frigate bounding box from pixel coordinates to 0-1 range.\n * Frigate box format: [y_min, x_min, y_max, x_max] in pixels.\n * Output: { x, y, w, h } normalized to 0-1 range.\n */\nexport function normalizeBoundingBox(\n box: [number, number, number, number],\n width: number,\n height: number,\n): { x: number; y: number; w: number; h: number } {\n const [yMin, xMin, yMax, xMax] = box\n return {\n x: xMin / width,\n y: yMin / height,\n w: (xMax - xMin) / width,\n h: (yMax - yMin) / height,\n }\n}\n","import type {\n IDevice,\n DeviceType,\n DeviceState,\n DeviceMetadata,\n DeviceCapabilityName,\n IDeviceCapability,\n CamstackContext,\n ICamera,\n StreamOption,\n ConnectionMode,\n IMotionSensor,\n IObjectDetector,\n DetectionZone,\n DetectionLine,\n IAudioDetector,\n IEvents,\n EventQuery,\n EventQueryResult,\n DeviceStoredEvent,\n IRecording,\n RecordingSegment,\n TimeRange,\n} from '@camstack/types'\nimport type { FrigateApiClient } from './frigate-api'\n\nexport interface FrigateDeviceConfig {\n readonly cameraName: string\n readonly providerId: string\n readonly detectWidth: number\n readonly detectHeight: number\n readonly audioEnabled: boolean\n readonly recordEnabled: boolean\n readonly ptzEnabled: boolean\n readonly streams: StreamOption[]\n readonly frigateHost: string\n}\n\nexport class FrigateDevice implements IDevice {\n readonly id: string\n readonly name: string\n readonly providerId: string\n readonly type: DeviceType = 'camera'\n readonly capabilities: DeviceCapabilityName[]\n\n private readonly capabilityMap = new Map<DeviceCapabilityName, IDeviceCapability>()\n\n readonly ctx: CamstackContext\n\n constructor(\n private readonly config: FrigateDeviceConfig,\n private readonly api: FrigateApiClient,\n ctx: CamstackContext,\n ) {\n this.id = `${config.providerId}/${config.cameraName}`\n this.name = config.cameraName\n this.providerId = config.providerId\n this.ctx = ctx\n\n // Capabilities based on camera config\n const caps: DeviceCapabilityName[] = ['camera', 'events', 'recording', 'motionSensor', 'objectDetector']\n if (config.audioEnabled) caps.push('audioDetector')\n this.capabilities = caps\n\n this.capabilityMap.set('camera', this.createCamera())\n this.capabilityMap.set('motionSensor', this.createMotionSensor())\n this.capabilityMap.set('objectDetector', this.createObjectDetector())\n this.capabilityMap.set('events', this.createEvents())\n this.capabilityMap.set('recording', this.createRecording())\n if (config.audioEnabled) {\n this.capabilityMap.set('audioDetector', this.createAudioDetector())\n }\n }\n\n getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null {\n return (this.capabilityMap.get(cap) as T) ?? null\n }\n\n hasCapability(cap: DeviceCapabilityName): boolean {\n return this.capabilityMap.has(cap)\n }\n\n getState(): DeviceState {\n return { online: true }\n }\n\n getMetadata(): DeviceMetadata {\n return { manufacturer: 'Frigate NVR' }\n }\n\n // --- Capability Factories ---\n\n private createCamera(): ICamera {\n const { api, config } = this\n return {\n kind: 'camera',\n async getSnapshot() { return api.getLatestSnapshot(config.cameraName) },\n async getStreamOptions(): Promise<StreamOption[]> {\n return config.streams.map(s => ({\n ...s,\n url: s.url ?? `rtsp://${config.frigateHost}:8554/${s.id}`,\n }))\n },\n async getStreamUrl(option: StreamOption) {\n return `/api/go2rtc/webrtc?src=${encodeURIComponent(option.id)}`\n },\n getConnectionMode(): ConnectionMode { return 'on-demand' },\n async setConnectionMode() {},\n }\n }\n\n private createMotionSensor(): IMotionSensor {\n let motionActive = false\n return {\n kind: 'motionSensor',\n isMotionDetected() { return motionActive },\n subscribe() { return () => {} }, // real-time via MQTT through provider\n }\n }\n\n private createObjectDetector(): IObjectDetector {\n const zones: DetectionZone[] = []\n const lines: DetectionLine[] = []\n\n return {\n kind: 'objectDetector',\n getLabels() { return [] },\n onDetections() { return () => {} },\n\n // Zone management\n async getZones() { return zones },\n async addZone(zone) { zones.push(zone) },\n async removeZone(id) { const i = zones.findIndex(z => z.id === id); if (i >= 0) zones.splice(i, 1) },\n async updateZone(id, partial) { const z = zones.find(z => z.id === id); if (z) Object.assign(z, partial) },\n\n // Line management\n async getLines() { return lines },\n async addLine(line) { lines.push(line) },\n async removeLine(id) { const i = lines.findIndex(l => l.id === id); if (i >= 0) lines.splice(i, 1) },\n\n // Active tracking (populated by MQTT events in real-time)\n getActiveDetections() { return [] },\n getZoneDetections() { return [] },\n getStationaryDetections() { return [] },\n getMovingDetections() { return [] },\n }\n }\n\n private createEvents(): IEvents {\n const { api, config } = this\n return {\n kind: 'events',\n\n async getEvents(query: EventQuery): Promise<EventQueryResult> {\n const raw = await api.getEvents({\n cameras: config.cameraName,\n after: query.since,\n before: query.until,\n limit: query.limit,\n labels: query.detectionClasses?.join(','),\n })\n\n const events: DeviceStoredEvent[] = raw.map(e => ({\n id: e.id,\n type: 'detection',\n timestamp: e.start_time * 1000, // Frigate uses seconds, we use ms\n endTimestamp: e.end_time ? e.end_time * 1000 : undefined,\n label: e.label,\n score: e.top_score,\n hasClip: e.has_clip,\n hasSnapshot: e.has_snapshot,\n thumbnailUrl: `/api/events/${e.id}/thumbnail.jpg`,\n }))\n\n return { events, total: events.length }\n },\n\n async getEventThumbnail(eventId: string) {\n try { return await api.getEventThumbnail(eventId) }\n catch { return null }\n },\n\n async getEventSnapshot(eventId: string) {\n try { return await api.getEventSnapshot(eventId) }\n catch { return null }\n },\n\n async getEventClipUrl(eventId: string) {\n return `/api/events/${encodeURIComponent(eventId)}/clip.mp4`\n },\n }\n }\n\n private createRecording(): IRecording {\n const { api, config } = this\n return {\n kind: 'recording',\n\n async getSegments(range: TimeRange): Promise<RecordingSegment[]> {\n const recs = await api.getRecordings(config.cameraName, range.since, range.until)\n return recs.map(r => ({\n id: r.id ?? `${config.cameraName}-${r.start_time}`,\n startTime: r.start_time * 1000,\n endTime: r.end_time * 1000,\n duration: r.duration,\n }))\n },\n\n async getPlaybackUrl(startTime: number, endTime: number) {\n const startSec = Math.floor(startTime / 1000)\n const endSec = Math.floor(endTime / 1000)\n return `/api/${encodeURIComponent(config.cameraName)}/start/${startSec}/end/${endSec}/clip.mp4`\n },\n\n async getThumbnailAt(timestampMs: number) {\n try { return await api.getRecordingThumbnail(config.cameraName, Math.floor(timestampMs / 1000)) }\n catch { return null }\n },\n }\n }\n\n private createAudioDetector(): IAudioDetector {\n return {\n kind: 'audioDetector',\n getAudioLevel() { return 0 },\n subscribe() { return () => {} }, // real-time via MQTT through provider\n }\n }\n}\n","import type { IElementConfig } from '@camstack/types'\nimport type { IStorageLocation } from '@camstack/types'\n\n/**\n * Persisted config store for a single element.\n * Reads/writes to the element's scoped storage under the 'config' collection.\n * Notifies listeners on every change.\n */\nexport class ElementConfigStore implements IElementConfig {\n private cache: Record<string, unknown> = {}\n private listeners: Set<(config: Record<string, unknown>) => void> = new Set()\n private loaded = false\n\n constructor(\n private readonly elementId: string,\n private readonly storage: IStorageLocation,\n ) {}\n\n /** Load config from storage into cache. Called once on first access. */\n private async ensureLoaded(): Promise<void> {\n if (this.loaded) return\n if (!this.storage.structured) {\n this.loaded = true\n return\n }\n\n try {\n const records = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n if (records.length > 0) {\n this.cache = (records[0] as any).data ?? {}\n }\n } catch {\n // Storage might not be ready yet\n }\n this.loaded = true\n }\n\n getAll(): Record<string, unknown> {\n return { ...this.cache }\n }\n\n get<T = unknown>(key: string): T | undefined {\n const parts = key.split('.')\n let current: unknown = this.cache\n for (const part of parts) {\n if (current == null || typeof current !== 'object') return undefined\n current = (current as Record<string, unknown>)[part]\n }\n return current as T | undefined\n }\n\n async set(key: string, value: unknown): Promise<void> {\n await this.ensureLoaded()\n setNestedValue(this.cache, key, value)\n await this.persist()\n this.notifyListeners()\n }\n\n async setAll(config: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n this.cache = { ...config }\n await this.persist()\n this.notifyListeners()\n }\n\n onChange(callback: (config: Record<string, unknown>) => void): () => void {\n this.listeners.add(callback)\n return () => { this.listeners.delete(callback) }\n }\n\n /** Initialize from storage — called by ContextFactory after creation */\n async load(): Promise<void> {\n await this.ensureLoaded()\n }\n\n /** Initialize with default values (doesn't overwrite existing) */\n async loadDefaults(defaults: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n if (Object.keys(this.cache).length === 0) {\n this.cache = { ...defaults }\n await this.persist()\n }\n }\n\n private async persist(): Promise<void> {\n if (!this.storage.structured) return\n\n try {\n const existing = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n\n if (existing.length > 0) {\n await this.storage.structured.update('config', this.elementId, this.cache)\n } else {\n await this.storage.structured.insert({\n collection: 'config',\n id: this.elementId,\n data: this.cache,\n })\n }\n } catch {\n // Storage might not be ready\n }\n }\n\n private notifyListeners(): void {\n const snapshot = this.getAll()\n for (const listener of this.listeners) {\n try {\n listener(snapshot)\n } catch {\n // Don't let one bad listener kill others\n }\n }\n }\n}\n\nfunction setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.')\n let current: Record<string, unknown> = obj\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n current[parts[parts.length - 1]!] = value\n}\n","import { FrigateApiClient } from './frigate-api'\nimport { FrigateMqttClient } from './frigate-mqtt'\nimport { FrigateDevice } from './frigate-device'\nimport { ElementConfigStore } from './element-config-store'\nimport type { StreamOption } from '@camstack/types'\nimport type {\n IDeviceProvider,\n ProviderStatus,\n DiscoveredDevice,\n LiveEvent,\n} from '@camstack/types'\nimport type { IDevice } from '@camstack/types'\nimport type { ICamera } from '@camstack/types'\nimport type { CamstackContext } from '@camstack/types'\n\nexport interface FrigateProviderConfig {\n readonly id: string\n readonly name: string\n readonly url: string\n readonly username?: string\n readonly password?: string\n readonly mqtt?: {\n readonly brokerUrl: string\n readonly username?: string\n readonly password?: string\n readonly topicPrefix?: string\n }\n}\n\nexport class FrigateProvider implements IDeviceProvider {\n readonly id: string\n readonly type = 'frigate'\n readonly name: string\n readonly discoveryMode: 'auto' = 'auto'\n readonly ctx: CamstackContext\n\n private readonly api: FrigateApiClient\n private mqtt: FrigateMqttClient | null = null\n private devices: readonly FrigateDevice[] = []\n private readonly liveEventListeners: Set<(event: LiveEvent) => void> = new Set()\n private mqttUnsubscribe?: () => void\n\n constructor(\n private readonly config: FrigateProviderConfig,\n ctx: CamstackContext,\n ) {\n this.id = config.id\n this.name = config.name\n this.ctx = ctx\n this.api = new FrigateApiClient({\n baseUrl: config.url,\n username: config.username,\n password: config.password,\n })\n }\n\n async start(): Promise<void> {\n this.ctx.logger.info(`Starting Frigate provider: ${this.config.url}`)\n\n // 1. Fetch Frigate config to discover cameras\n const frigateConfig = await this.api.getConfig()\n const go2rtcStreams = await this.api.getGo2rtcStreams()\n\n // 2. Build camera resolutions map (for MQTT bbox normalization)\n const resolutions = new Map<string, { width: number; height: number }>()\n for (const [name, cam] of Object.entries(frigateConfig.cameras)) {\n if (cam.detect) {\n resolutions.set(name, { width: cam.detect.width, height: cam.detect.height })\n }\n }\n\n // 3. Create FrigateDevice per enabled camera\n const frigateHost = new URL(this.config.url).hostname\n const devices: FrigateDevice[] = []\n for (const [name, cam] of Object.entries(frigateConfig.cameras)) {\n if (cam.enabled === false) continue\n\n const streams = buildStreamOptions(name, go2rtcStreams)\n const deviceId = `${this.id}/${name}`\n\n // Create device context scoped under this provider\n const deviceCtx: CamstackContext = {\n id: `device:${deviceId}`,\n logger: this.ctx.logger.child(name),\n eventBus: this.ctx.eventBus,\n storage: this.ctx.storage,\n config: new ElementConfigStore(`device:${deviceId}`, this.ctx.storage),\n }\n\n devices.push(\n new FrigateDevice(\n {\n cameraName: name,\n providerId: this.id,\n detectWidth: cam.detect?.width ?? 1920,\n detectHeight: cam.detect?.height ?? 1080,\n audioEnabled: cam.audio?.enabled ?? false,\n recordEnabled: cam.record?.enabled ?? false,\n ptzEnabled: false,\n streams,\n frigateHost,\n },\n this.api,\n deviceCtx,\n ),\n )\n }\n this.devices = devices\n this.ctx.logger.info(`Discovered ${devices.length} cameras`)\n\n // 4. Connect MQTT if configured\n if (this.config.mqtt?.brokerUrl) {\n this.mqtt = new FrigateMqttClient(\n {\n brokerUrl: this.config.mqtt.brokerUrl,\n username: this.config.mqtt.username,\n password: this.config.mqtt.password,\n topicPrefix: this.config.mqtt.topicPrefix ?? 'frigate',\n },\n resolutions,\n )\n\n await this.mqtt.connect()\n this.mqttUnsubscribe = this.mqtt.subscribe((event) => {\n for (const listener of this.liveEventListeners) {\n listener(event)\n }\n })\n this.ctx.logger.info('MQTT connected')\n }\n }\n\n async stop(): Promise<void> {\n this.ctx.logger.info('Stopping Frigate provider')\n this.mqttUnsubscribe?.()\n this.mqttUnsubscribe = undefined\n await this.mqtt?.disconnect()\n this.mqtt = null\n this.devices = []\n }\n\n getStatus(): ProviderStatus {\n return {\n connected: this.mqtt?.status.connected ?? false,\n deviceCount: this.devices.length,\n }\n }\n\n async discoverDevices(): Promise<DiscoveredDevice[]> {\n return this.devices.map((d) => ({\n externalId: d.name,\n name: d.name,\n type: d.type,\n capabilities: d.capabilities,\n metadata: d.getMetadata(),\n }))\n }\n\n getDevices(): IDevice[] {\n return [...this.devices]\n }\n\n subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void {\n this.liveEventListeners.add(callback)\n return () => { this.liveEventListeners.delete(callback) }\n }\n}\n\n// --- Helpers ---\n\nfunction buildStreamOptions(\n cameraName: string,\n go2rtcStreams: Record<string, unknown>,\n): StreamOption[] {\n const streams: StreamOption[] = []\n for (const streamName of Object.keys(go2rtcStreams)) {\n if (!streamName.startsWith(cameraName)) continue\n const isSub = streamName.includes('sub') || streamName.includes('ext') || streamName.includes('medium')\n streams.push({ id: streamName, label: streamName, protocol: 'rtsp', quality: isSub ? 'sub' : 'main' })\n }\n return streams\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n ConfigUISchema,\n IConfigurable,\n} from '@camstack/types'\nimport { FrigateProvider } from './frigate-provider'\nimport type { FrigateProviderConfig } from './frigate-provider'\n\nexport class FrigateProviderAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'provider-frigate',\n name: 'Frigate NVR Provider',\n version: '0.1.0',\n capabilities: ['device-provider'],\n }\n\n private provider: FrigateProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n const config = context.addonConfig as unknown as FrigateProviderConfig\n if (!config.url) {\n context.logger.warn('Frigate provider: no URL configured, skipping initialization')\n return\n }\n\n const providerConfig: FrigateProviderConfig = {\n id: config.id ?? 'frigate-default',\n name: config.name ?? 'Frigate NVR',\n url: config.url,\n username: config.username,\n password: config.password,\n mqtt: config.mqtt,\n }\n\n this.provider = new FrigateProvider(providerConfig, {\n id: context.id,\n logger: context.logger,\n eventBus: context.eventBus,\n storage: context.storage,\n config: context.config,\n })\n\n context.logger.info('Frigate provider addon initialized')\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.stop()\n this.provider = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'device-provider' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'connection',\n title: 'Frigate Connection',\n description: 'Configure the connection to your Frigate NVR instance',\n columns: 2,\n fields: [\n { type: 'text', key: 'url', label: 'Frigate URL', required: true, placeholder: 'http://frigate:5000' },\n { type: 'text', key: 'name', label: 'Display Name', placeholder: 'Frigate NVR' },\n { type: 'text', key: 'username', label: 'Username' },\n { type: 'password', key: 'password', label: 'Password', showToggle: true },\n ],\n },\n {\n id: 'mqtt',\n title: 'MQTT Settings',\n description: 'Optional: Connect to Frigate MQTT for real-time events',\n style: 'accordion',\n defaultCollapsed: true,\n columns: 2,\n fields: [\n { type: 'text', key: 'mqtt.brokerUrl', label: 'MQTT Broker URL', placeholder: 'mqtt://broker:1883' },\n { type: 'text', key: 'mqtt.topicPrefix', label: 'Topic Prefix', placeholder: 'frigate' },\n { type: 'text', key: 'mqtt.username', label: 'MQTT Username' },\n { type: 'password', key: 'mqtt.password', label: 'MQTT Password', showToggle: true },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // Restart provider with new config\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC0CO,IAAM,mBAAN,MAAuB;AAAA,EAK5B,YAA6B,QAA0B;AAA1B;AAC3B,SAAK,UAAU,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AAAA,EAClD;AAAA,EANiB;AAAA,EACT,YAA2B;AAAA,EAC3B,aAA4B;AAAA,EAMpC,MAAc,eAA8B;AAC1C,UAAM,EAAE,UAAU,SAAS,IAAI,KAAK;AAEpC,QAAI,CAAC,YAAY,CAAC,UAAU;AAC1B;AAAA,IACF;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,cAAc;AAAA,QACxD,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,MAAM,UAAU,SAAS,CAAC;AAAA,MACnD,CAAC;AAED,UAAI,SAAS,IAAI;AACf,cAAM,UAAU,SAAS,QAAQ,IAAI,YAAY;AACjD,YAAI,SAAS;AACX,eAAK,YAAY;AACjB;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,SAAK,aAAa,SAAS,OAAO,KAAK,GAAG,QAAQ,IAAI,QAAQ,EAAE,EAAE,SAAS,QAAQ,CAAC;AAAA,EACtF;AAAA,EAEQ,eAAuC;AAC7C,UAAM,UAAkC,CAAC;AAEzC,QAAI,KAAK,WAAW;AAClB,cAAQ,QAAQ,IAAI,KAAK;AAAA,IAC3B,WAAW,KAAK,YAAY;AAC1B,cAAQ,eAAe,IAAI,KAAK;AAAA,IAClC;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,QAAW,MAAc,SAAmC;AACxE,SAAK,KAAK,OAAO,YAAY,KAAK,OAAO,aAAa,CAAC,KAAK,aAAa,CAAC,KAAK,YAAY;AACzF,YAAM,KAAK,aAAa;AAAA,IAC1B;AAEA,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAClC,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,GAAG,KAAK,aAAa;AAAA,QACrB,GAAG,SAAS;AAAA,MACd;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,sBAAsB,SAAS,MAAM,IAAI,SAAS,UAAU,QAAQ,IAAI,EAAE;AAAA,IAC5F;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEA,MAAc,cAAc,MAA+B;AACzD,SAAK,KAAK,OAAO,YAAY,KAAK,OAAO,aAAa,CAAC,KAAK,aAAa,CAAC,KAAK,YAAY;AACzF,YAAM,KAAK,aAAa;AAAA,IAC1B;AAEA,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAClC,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,SAAS,KAAK,aAAa;AAAA,IAC7B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,sBAAsB,SAAS,MAAM,IAAI,SAAS,UAAU,QAAQ,IAAI,EAAE;AAAA,IAC5F;AAEA,UAAM,cAAc,MAAM,SAAS,YAAY;AAC/C,WAAO,OAAO,KAAK,WAAW;AAAA,EAChC;AAAA,EAEA,MAAc,YAAY,MAAc,SAAwC;AAC9E,SAAK,KAAK,OAAO,YAAY,KAAK,OAAO,aAAa,CAAC,KAAK,aAAa,CAAC,KAAK,YAAY;AACzF,YAAM,KAAK,aAAa;AAAA,IAC1B;AAEA,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAClC,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,GAAG,KAAK,aAAa;AAAA,QACrB,GAAG,SAAS;AAAA,MACd;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,sBAAsB,SAAS,MAAM,IAAI,SAAS,UAAU,QAAQ,IAAI,EAAE;AAAA,IAC5F;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA,EAIA,MAAM,YAAoC;AACxC,WAAO,KAAK,QAAuB,aAAa;AAAA,EAClD;AAAA,EAEA,MAAM,mBAAqD;AACzD,WAAO,KAAK,QAAiC,qBAAqB;AAAA,EACpE;AAAA;AAAA,EAIA,MAAM,UAAU,QAAiD;AAC/D,UAAM,QAAQ,iBAAiB,MAAM;AACrC,WAAO,KAAK,QAA2B,cAAc,KAAK,EAAE;AAAA,EAC9D;AAAA,EAEA,MAAM,eAAe,QAAmD;AACtE,UAAM,QAAQ,iBAAiB,MAAM;AACrC,WAAO,KAAK,QAA6B,cAAc,KAAK,EAAE;AAAA,EAChE;AAAA;AAAA,EAIA,MAAM,cACJ,QACA,OACA,QACgC;AAChC,UAAM,QAAQ,iBAAiB,EAAE,OAAO,OAAO,CAAC;AAChD,WAAO,KAAK,QAA+B,QAAQ,mBAAmB,MAAM,CAAC,cAAc,KAAK,EAAE;AAAA,EACpG;AAAA,EAEA,MAAM,kBAAkB,QAAmD;AACzE,UAAM,QAAQ,iBAAiB,MAAM;AACrC,WAAO,KAAK,QAA6B,uBAAuB,KAAK,EAAE;AAAA,EACzE;AAAA;AAAA,EAIA,MAAM,kBAAkB,QAAiC;AACvD,WAAO,KAAK,cAAc,QAAQ,mBAAmB,MAAM,CAAC,aAAa;AAAA,EAC3E;AAAA,EAEA,MAAM,kBAAkB,SAAkC;AACxD,WAAO,KAAK,cAAc,eAAe,mBAAmB,OAAO,CAAC,gBAAgB;AAAA,EACtF;AAAA,EAEA,MAAM,iBAAiB,SAAkC;AACvD,WAAO,KAAK,cAAc,eAAe,mBAAmB,OAAO,CAAC,eAAe;AAAA,EACrF;AAAA,EAEA,MAAM,aAAa,SAAkC;AACnD,WAAO,KAAK,cAAc,eAAe,mBAAmB,OAAO,CAAC,WAAW;AAAA,EACjF;AAAA,EAEA,MAAM,sBAAsB,QAAgB,cAAuC;AACjF,WAAO,KAAK;AAAA,MACV,QAAQ,mBAAmB,MAAM,CAAC,yBAAyB,YAAY;AAAA,IACzE;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,aAAa,YAAoB,UAAmC;AACxE,WAAO,KAAK,YAAY,0BAA0B,mBAAmB,UAAU,CAAC,IAAI;AAAA,MAClF,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,kBAAkB;AAAA,MAC7C,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,WACJ,QAC6D;AAC7D,QAAI;AACF,aAAO,MAAM,KAAK;AAAA,QAChB,QAAQ,mBAAmB,MAAM,CAAC;AAAA,MACpC;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,WACJ,QACA,SACA,QACe;AACf,UAAM,QAAQ,iBAAiB,EAAE,GAAG,QAAQ,QAAQ,CAAC;AACrD,UAAM,KAAK,QAAc,QAAQ,mBAAmB,MAAM,CAAC,OAAO,KAAK,EAAE;AAAA,EAC3E;AAAA;AAAA,EAIA,MAAM,iBAAgD;AACpD,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,UAAU;AACpC,YAAM,cAAc,OAAO,KAAK,OAAO,WAAW,CAAC,CAAC;AACpD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS,OAAO;AAAA,QAChB,aAAa,YAAY;AAAA,MAC3B;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AACF;AAIO,SAAS,iBAAiB,QAAkD;AACjF,QAAM,UAAU,OAAO,QAAQ,MAAM,EAAE;AAAA,IACrC,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,UAAa,MAAM;AAAA,EACtC;AACA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AACA,QAAM,eAAe,IAAI,gBAAgB;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,iBAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,EACrC;AACA,SAAO,IAAI,aAAa,SAAS,CAAC;AACpC;;;ACvRA,kBAAwB;AAejB,IAAM,oBAAN,MAAwB;AAAA,EAK7B,YACmB,QACA,mBACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAPK,SAA4B;AAAA,EACnB,YAA6C,oBAAI,IAAI;AAAA,EAC9D,aAAa;AAAA,EAOrB,MAAM,UAAyB;AAC7B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,EAAE,WAAW,UAAU,UAAU,YAAY,IAAI,KAAK;AAE5D,WAAK,aAAS,qBAAQ,WAAW;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,QACjB,gBAAgB;AAAA,MAClB,CAAC;AAED,WAAK,OAAO,GAAG,WAAW,MAAM;AAC9B,cAAM,SAAS;AACf,aAAK,OAAQ,UAAU;AAAA,UACrB,GAAG,MAAM;AAAA,UACT,GAAG,MAAM;AAAA,UACT,GAAG,MAAM;AAAA,UACT,GAAG,MAAM;AAAA,QACX,CAAC;AACD,gBAAQ;AAAA,MACV,CAAC;AAED,WAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,eAAO,GAAG;AAAA,MACZ,CAAC;AAED,WAAK,OAAO,GAAG,WAAW,CAAC,OAAe,YAAoB;AAC5D,cAAM,QAAQ;AAAA,UACZ;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK;AAAA,QACP;AACA,YAAI,OAAO;AACT,eAAK;AACL,qBAAW,YAAY,KAAK,WAAW;AACrC,qBAAS,KAAK;AAAA,UAChB;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAA4B;AAChC,QAAI,KAAK,QAAQ;AACf,YAAM,KAAK,OAAO,SAAS;AAC3B,WAAK,SAAS;AAAA,IAChB;AACA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEA,UAAU,UAAkD;AAC1D,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,IAAI,SAAqB;AACvB,WAAO;AAAA,MACL,WAAW,KAAK,QAAQ,aAAa;AAAA,MACrC,YAAY,KAAK;AAAA,IACnB;AAAA,EACF;AACF;AA2CO,SAAS,iBACd,OACA,SACA,QACA,aACkB;AAClB,QAAM,aAAa,MAAM,MAAM,GAAG;AAClC,QAAM,cAAc,OAAO,MAAM,GAAG;AACpC,QAAM,WAAW,WAAW,MAAM,YAAY,MAAM;AAGpD,MAAI,SAAS,WAAW,KAAK,SAAS,CAAC,MAAM,UAAU;AACrD,WAAO,oBAAoB,SAAS,WAAW;AAAA,EACjD;AAGA,MAAI,SAAS,WAAW,KAAK,SAAS,CAAC,MAAM,WAAW;AACtD,WAAO,iBAAiB,OAAO;AAAA,EACjC;AAGA,MAAI,SAAS,WAAW,KAAK,SAAS,CAAC,MAAM,YAAY,SAAS,CAAC,MAAM,SAAS;AAChF,UAAM,SAAS,SAAS,CAAC;AACzB,WAAO,iBAAiB,QAAQ,OAAO;AAAA,EACzC;AAGA,MAAI,SAAS,WAAW,KAAK,SAAS,CAAC,MAAM,SAAS;AACpD,UAAM,SAAS,SAAS,CAAC;AACzB,UAAM,SAAS,SAAS,CAAC;AACzB,WAAO,gBAAgB,QAAQ,QAAQ,OAAO;AAAA,EAChD;AAEA,SAAO;AACT;AAEA,SAAS,oBACP,SACA,aACkB;AAClB,MAAI;AACF,UAAM,SAAkC,KAAK,MAAM,QAAQ,SAAS,CAAC;AACrE,UAAM,QAAQ,OAAO,SAAS,OAAO;AACrC,QAAI,CAAC,OAAO,UAAU,CAAC,OAAO,OAAO;AACnC,aAAO;AAAA,IACT;AAEA,UAAM,OAAgC;AAAA,MACpC,WAAW,OAAO,QAAQ;AAAA,MAC1B,IAAI,MAAM;AAAA,MACV,OAAO,MAAM;AAAA,MACb,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM;AAAA,MACb,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,MACb,aAAa,MAAM;AAAA,MACnB,SAAS,MAAM;AAAA,IACjB;AAEA,QAAI,MAAM,KAAK;AACb,YAAM,aAAa,YAAY,IAAI,MAAM,MAAM;AAC/C,UAAI,YAAY;AACd,aAAK,cAAc,qBAAqB,MAAM,KAAK,WAAW,OAAO,WAAW,MAAM;AAAA,MACxF,OAAO;AACL,aAAK,SAAS,MAAM;AAAA,MACtB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ,MAAM;AAAA,MACd,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,iBAAiB,SAAmC;AAC3D,MAAI;AACF,UAAM,SAAmC,KAAK,MAAM,QAAQ,SAAS,CAAC;AACtE,UAAM,QAAQ,OAAO,SAAS,OAAO;AACrC,QAAI,CAAC,OAAO,QAAQ;AAClB,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ,MAAM;AAAA,MACd,WAAW,KAAK,IAAI;AAAA,MACpB,MAAM;AAAA,QACJ,WAAW,OAAO,QAAQ;AAAA,QAC1B,IAAI,MAAM;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,WAAW,MAAM;AAAA,QACjB,SAAS,MAAM;AAAA,QACf,SAAS,MAAM,MAAM;AAAA,QACrB,OAAO,MAAM,MAAM;AAAA,QACnB,WAAW,MAAM,MAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,iBAAiB,QAAgB,SAA4B;AACpE,QAAM,QAAQ,QAAQ,SAAS,EAAE,KAAK;AACtC,QAAM,SAAS,UAAU;AAEzB,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,WAAW,KAAK,IAAI;AAAA,IACpB,MAAM,EAAE,OAAO;AAAA,EACjB;AACF;AAEA,SAAS,gBAAgB,QAAgB,QAAgB,SAAmC;AAC1F,QAAM,MAAM,QAAQ,SAAS,EAAE,KAAK;AACpC,QAAM,QAAQ,WAAW,GAAG;AAE5B,MAAI,MAAM,KAAK,GAAG;AAChB,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,WAAW,KAAK,IAAI;AAAA,IACpB,MAAM,EAAE,QAAQ,MAAM;AAAA,EACxB;AACF;AAOO,SAAS,qBACd,KACA,OACA,QACgD;AAChD,QAAM,CAAC,MAAM,MAAM,MAAM,IAAI,IAAI;AACjC,SAAO;AAAA,IACL,GAAG,OAAO;AAAA,IACV,GAAG,OAAO;AAAA,IACV,IAAI,OAAO,QAAQ;AAAA,IACnB,IAAI,OAAO,QAAQ;AAAA,EACrB;AACF;;;ACxPO,IAAM,gBAAN,MAAuC;AAAA,EAW5C,YACmB,QACA,KACjB,KACA;AAHiB;AACA;AAGjB,SAAK,KAAK,GAAG,OAAO,UAAU,IAAI,OAAO,UAAU;AACnD,SAAK,OAAO,OAAO;AACnB,SAAK,aAAa,OAAO;AACzB,SAAK,MAAM;AAGX,UAAM,OAA+B,CAAC,UAAU,UAAU,aAAa,gBAAgB,gBAAgB;AACvG,QAAI,OAAO,aAAc,MAAK,KAAK,eAAe;AAClD,SAAK,eAAe;AAEpB,SAAK,cAAc,IAAI,UAAU,KAAK,aAAa,CAAC;AACpD,SAAK,cAAc,IAAI,gBAAgB,KAAK,mBAAmB,CAAC;AAChE,SAAK,cAAc,IAAI,kBAAkB,KAAK,qBAAqB,CAAC;AACpE,SAAK,cAAc,IAAI,UAAU,KAAK,aAAa,CAAC;AACpD,SAAK,cAAc,IAAI,aAAa,KAAK,gBAAgB,CAAC;AAC1D,QAAI,OAAO,cAAc;AACvB,WAAK,cAAc,IAAI,iBAAiB,KAAK,oBAAoB,CAAC;AAAA,IACpE;AAAA,EACF;AAAA,EAjCS;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAmB;AAAA,EACnB;AAAA,EAEQ,gBAAgB,oBAAI,IAA6C;AAAA,EAEzE;AAAA,EA2BT,cAA2C,KAAqC;AAC9E,WAAQ,KAAK,cAAc,IAAI,GAAG,KAAW;AAAA,EAC/C;AAAA,EAEA,cAAc,KAAoC;AAChD,WAAO,KAAK,cAAc,IAAI,GAAG;AAAA,EACnC;AAAA,EAEA,WAAwB;AACtB,WAAO,EAAE,QAAQ,KAAK;AAAA,EACxB;AAAA,EAEA,cAA8B;AAC5B,WAAO,EAAE,cAAc,cAAc;AAAA,EACvC;AAAA;AAAA,EAIQ,eAAwB;AAC9B,UAAM,EAAE,KAAK,OAAO,IAAI;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,cAAc;AAAE,eAAO,IAAI,kBAAkB,OAAO,UAAU;AAAA,MAAE;AAAA,MACtE,MAAM,mBAA4C;AAChD,eAAO,OAAO,QAAQ,IAAI,QAAM;AAAA,UAC9B,GAAG;AAAA,UACH,KAAK,EAAE,OAAO,UAAU,OAAO,WAAW,SAAS,EAAE,EAAE;AAAA,QACzD,EAAE;AAAA,MACJ;AAAA,MACA,MAAM,aAAa,QAAsB;AACvC,eAAO,0BAA0B,mBAAmB,OAAO,EAAE,CAAC;AAAA,MAChE;AAAA,MACA,oBAAoC;AAAE,eAAO;AAAA,MAAY;AAAA,MACzD,MAAM,oBAAoB;AAAA,MAAC;AAAA,IAC7B;AAAA,EACF;AAAA,EAEQ,qBAAoC;AAC1C,QAAI,eAAe;AACnB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,mBAAmB;AAAE,eAAO;AAAA,MAAa;AAAA,MACzC,YAAY;AAAE,eAAO,MAAM;AAAA,QAAC;AAAA,MAAE;AAAA;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,uBAAwC;AAC9C,UAAM,QAAyB,CAAC;AAChC,UAAM,QAAyB,CAAC;AAEhC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,YAAY;AAAE,eAAO,CAAC;AAAA,MAAE;AAAA,MACxB,eAAe;AAAE,eAAO,MAAM;AAAA,QAAC;AAAA,MAAE;AAAA;AAAA,MAGjC,MAAM,WAAW;AAAE,eAAO;AAAA,MAAM;AAAA,MAChC,MAAM,QAAQ,MAAM;AAAE,cAAM,KAAK,IAAI;AAAA,MAAE;AAAA,MACvC,MAAM,WAAW,IAAI;AAAE,cAAM,IAAI,MAAM,UAAU,OAAK,EAAE,OAAO,EAAE;AAAG,YAAI,KAAK,EAAG,OAAM,OAAO,GAAG,CAAC;AAAA,MAAE;AAAA,MACnG,MAAM,WAAW,IAAI,SAAS;AAAE,cAAM,IAAI,MAAM,KAAK,CAAAA,OAAKA,GAAE,OAAO,EAAE;AAAG,YAAI,EAAG,QAAO,OAAO,GAAG,OAAO;AAAA,MAAE;AAAA;AAAA,MAGzG,MAAM,WAAW;AAAE,eAAO;AAAA,MAAM;AAAA,MAChC,MAAM,QAAQ,MAAM;AAAE,cAAM,KAAK,IAAI;AAAA,MAAE;AAAA,MACvC,MAAM,WAAW,IAAI;AAAE,cAAM,IAAI,MAAM,UAAU,OAAK,EAAE,OAAO,EAAE;AAAG,YAAI,KAAK,EAAG,OAAM,OAAO,GAAG,CAAC;AAAA,MAAE;AAAA;AAAA,MAGnG,sBAAsB;AAAE,eAAO,CAAC;AAAA,MAAE;AAAA,MAClC,oBAAoB;AAAE,eAAO,CAAC;AAAA,MAAE;AAAA,MAChC,0BAA0B;AAAE,eAAO,CAAC;AAAA,MAAE;AAAA,MACtC,sBAAsB;AAAE,eAAO,CAAC;AAAA,MAAE;AAAA,IACpC;AAAA,EACF;AAAA,EAEQ,eAAwB;AAC9B,UAAM,EAAE,KAAK,OAAO,IAAI;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MAEN,MAAM,UAAU,OAA8C;AAC5D,cAAM,MAAM,MAAM,IAAI,UAAU;AAAA,UAC9B,SAAS,OAAO;AAAA,UAChB,OAAO,MAAM;AAAA,UACb,QAAQ,MAAM;AAAA,UACd,OAAO,MAAM;AAAA,UACb,QAAQ,MAAM,kBAAkB,KAAK,GAAG;AAAA,QAC1C,CAAC;AAED,cAAM,SAA8B,IAAI,IAAI,QAAM;AAAA,UAChD,IAAI,EAAE;AAAA,UACN,MAAM;AAAA,UACN,WAAW,EAAE,aAAa;AAAA;AAAA,UAC1B,cAAc,EAAE,WAAW,EAAE,WAAW,MAAO;AAAA,UAC/C,OAAO,EAAE;AAAA,UACT,OAAO,EAAE;AAAA,UACT,SAAS,EAAE;AAAA,UACX,aAAa,EAAE;AAAA,UACf,cAAc,eAAe,EAAE,EAAE;AAAA,QACnC,EAAE;AAEF,eAAO,EAAE,QAAQ,OAAO,OAAO,OAAO;AAAA,MACxC;AAAA,MAEA,MAAM,kBAAkB,SAAiB;AACvC,YAAI;AAAE,iBAAO,MAAM,IAAI,kBAAkB,OAAO;AAAA,QAAE,QAC5C;AAAE,iBAAO;AAAA,QAAK;AAAA,MACtB;AAAA,MAEA,MAAM,iBAAiB,SAAiB;AACtC,YAAI;AAAE,iBAAO,MAAM,IAAI,iBAAiB,OAAO;AAAA,QAAE,QAC3C;AAAE,iBAAO;AAAA,QAAK;AAAA,MACtB;AAAA,MAEA,MAAM,gBAAgB,SAAiB;AACrC,eAAO,eAAe,mBAAmB,OAAO,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,kBAA8B;AACpC,UAAM,EAAE,KAAK,OAAO,IAAI;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MAEN,MAAM,YAAY,OAA+C;AAC/D,cAAM,OAAO,MAAM,IAAI,cAAc,OAAO,YAAY,MAAM,OAAO,MAAM,KAAK;AAChF,eAAO,KAAK,IAAI,QAAM;AAAA,UACpB,IAAI,EAAE,MAAM,GAAG,OAAO,UAAU,IAAI,EAAE,UAAU;AAAA,UAChD,WAAW,EAAE,aAAa;AAAA,UAC1B,SAAS,EAAE,WAAW;AAAA,UACtB,UAAU,EAAE;AAAA,QACd,EAAE;AAAA,MACJ;AAAA,MAEA,MAAM,eAAe,WAAmB,SAAiB;AACvD,cAAM,WAAW,KAAK,MAAM,YAAY,GAAI;AAC5C,cAAM,SAAS,KAAK,MAAM,UAAU,GAAI;AACxC,eAAO,QAAQ,mBAAmB,OAAO,UAAU,CAAC,UAAU,QAAQ,QAAQ,MAAM;AAAA,MACtF;AAAA,MAEA,MAAM,eAAe,aAAqB;AACxC,YAAI;AAAE,iBAAO,MAAM,IAAI,sBAAsB,OAAO,YAAY,KAAK,MAAM,cAAc,GAAI,CAAC;AAAA,QAAE,QAC1F;AAAE,iBAAO;AAAA,QAAK;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,sBAAsC;AAC5C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,gBAAgB;AAAE,eAAO;AAAA,MAAE;AAAA,MAC3B,YAAY;AAAE,eAAO,MAAM;AAAA,QAAC;AAAA,MAAE;AAAA;AAAA,IAChC;AAAA,EACF;AACF;;;AC5NO,IAAM,qBAAN,MAAmD;AAAA,EAKxD,YACmB,WACA,SACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAPK,QAAiC,CAAC;AAAA,EAClC,YAA4D,oBAAI,IAAI;AAAA,EACpE,SAAS;AAAA;AAAA,EAQjB,MAAc,eAA8B;AAC1C,QAAI,KAAK,OAAQ;AACjB,QAAI,CAAC,KAAK,QAAQ,YAAY;AAC5B,WAAK,SAAS;AACd;AAAA,IACF;AAEA,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,QAAQ,WAAW,MAAM,UAAU;AAAA,QAC5D,OAAO,EAAE,IAAI,KAAK,UAAU;AAAA,QAC5B,OAAO;AAAA,MACT,CAAC;AACD,UAAI,QAAQ,SAAS,GAAG;AACtB,aAAK,QAAS,QAAQ,CAAC,EAAU,QAAQ,CAAC;AAAA,MAC5C;AAAA,IACF,QAAQ;AAAA,IAER;AACA,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,SAAkC;AAChC,WAAO,EAAE,GAAG,KAAK,MAAM;AAAA,EACzB;AAAA,EAEA,IAAiB,KAA4B;AAC3C,UAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,QAAI,UAAmB,KAAK;AAC5B,eAAW,QAAQ,OAAO;AACxB,UAAI,WAAW,QAAQ,OAAO,YAAY,SAAU,QAAO;AAC3D,gBAAW,QAAoC,IAAI;AAAA,IACrD;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B;AACpD,UAAM,KAAK,aAAa;AACxB,mBAAe,KAAK,OAAO,KAAK,KAAK;AACrC,UAAM,KAAK,QAAQ;AACnB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,MAAM,OAAO,QAAgD;AAC3D,UAAM,KAAK,aAAa;AACxB,SAAK,QAAQ,EAAE,GAAG,OAAO;AACzB,UAAM,KAAK,QAAQ;AACnB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,SAAS,UAAiE;AACxE,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AAAE,WAAK,UAAU,OAAO,QAAQ;AAAA,IAAE;AAAA,EACjD;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,UAAM,KAAK,aAAa;AAAA,EAC1B;AAAA;AAAA,EAGA,MAAM,aAAa,UAAkD;AACnE,UAAM,KAAK,aAAa;AACxB,QAAI,OAAO,KAAK,KAAK,KAAK,EAAE,WAAW,GAAG;AACxC,WAAK,QAAQ,EAAE,GAAG,SAAS;AAC3B,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,MAAc,UAAyB;AACrC,QAAI,CAAC,KAAK,QAAQ,WAAY;AAE9B,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,QAAQ,WAAW,MAAM,UAAU;AAAA,QAC7D,OAAO,EAAE,IAAI,KAAK,UAAU;AAAA,QAC5B,OAAO;AAAA,MACT,CAAC;AAED,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,KAAK,QAAQ,WAAW,OAAO,UAAU,KAAK,WAAW,KAAK,KAAK;AAAA,MAC3E,OAAO;AACL,cAAM,KAAK,QAAQ,WAAW,OAAO;AAAA,UACnC,YAAY;AAAA,UACZ,IAAI,KAAK;AAAA,UACT,MAAM,KAAK;AAAA,QACb,CAAC;AAAA,MACH;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,kBAAwB;AAC9B,UAAM,WAAW,KAAK,OAAO;AAC7B,eAAW,YAAY,KAAK,WAAW;AACrC,UAAI;AACF,iBAAS,QAAQ;AAAA,MACnB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,eAAe,KAA8B,MAAc,OAAsB;AACxF,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,UAAmC;AACvC,WAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,EAAE,QAAQ,YAAY,OAAO,QAAQ,IAAI,MAAM,YAAY,QAAQ,IAAI,MAAM,MAAM;AACrF,cAAQ,IAAI,IAAI,CAAC;AAAA,IACnB;AACA,cAAU,QAAQ,IAAI;AAAA,EACxB;AACA,UAAQ,MAAM,MAAM,SAAS,CAAC,CAAE,IAAI;AACtC;;;ACxGO,IAAM,kBAAN,MAAiD;AAAA,EAatD,YACmB,QACjB,KACA;AAFiB;AAGjB,SAAK,KAAK,OAAO;AACjB,SAAK,OAAO,OAAO;AACnB,SAAK,MAAM;AACX,SAAK,MAAM,IAAI,iBAAiB;AAAA,MAC9B,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAxBS;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,gBAAwB;AAAA,EACxB;AAAA,EAEQ;AAAA,EACT,OAAiC;AAAA,EACjC,UAAoC,CAAC;AAAA,EAC5B,qBAAsD,oBAAI,IAAI;AAAA,EACvE;AAAA,EAgBR,MAAM,QAAuB;AAC3B,SAAK,IAAI,OAAO,KAAK,8BAA8B,KAAK,OAAO,GAAG,EAAE;AAGpE,UAAM,gBAAgB,MAAM,KAAK,IAAI,UAAU;AAC/C,UAAM,gBAAgB,MAAM,KAAK,IAAI,iBAAiB;AAGtD,UAAM,cAAc,oBAAI,IAA+C;AACvE,eAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,cAAc,OAAO,GAAG;AAC/D,UAAI,IAAI,QAAQ;AACd,oBAAY,IAAI,MAAM,EAAE,OAAO,IAAI,OAAO,OAAO,QAAQ,IAAI,OAAO,OAAO,CAAC;AAAA,MAC9E;AAAA,IACF;AAGA,UAAM,cAAc,IAAI,IAAI,KAAK,OAAO,GAAG,EAAE;AAC7C,UAAM,UAA2B,CAAC;AAClC,eAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,cAAc,OAAO,GAAG;AAC/D,UAAI,IAAI,YAAY,MAAO;AAE3B,YAAM,UAAU,mBAAmB,MAAM,aAAa;AACtD,YAAM,WAAW,GAAG,KAAK,EAAE,IAAI,IAAI;AAGnC,YAAM,YAA6B;AAAA,QACjC,IAAI,UAAU,QAAQ;AAAA,QACtB,QAAQ,KAAK,IAAI,OAAO,MAAM,IAAI;AAAA,QAClC,UAAU,KAAK,IAAI;AAAA,QACnB,SAAS,KAAK,IAAI;AAAA,QAClB,QAAQ,IAAI,mBAAmB,UAAU,QAAQ,IAAI,KAAK,IAAI,OAAO;AAAA,MACvE;AAEA,cAAQ;AAAA,QACN,IAAI;AAAA,UACF;AAAA,YACE,YAAY;AAAA,YACZ,YAAY,KAAK;AAAA,YACjB,aAAa,IAAI,QAAQ,SAAS;AAAA,YAClC,cAAc,IAAI,QAAQ,UAAU;AAAA,YACpC,cAAc,IAAI,OAAO,WAAW;AAAA,YACpC,eAAe,IAAI,QAAQ,WAAW;AAAA,YACtC,YAAY;AAAA,YACZ;AAAA,YACA;AAAA,UACF;AAAA,UACA,KAAK;AAAA,UACL;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,SAAK,UAAU;AACf,SAAK,IAAI,OAAO,KAAK,cAAc,QAAQ,MAAM,UAAU;AAG3D,QAAI,KAAK,OAAO,MAAM,WAAW;AAC/B,WAAK,OAAO,IAAI;AAAA,QACd;AAAA,UACE,WAAW,KAAK,OAAO,KAAK;AAAA,UAC5B,UAAU,KAAK,OAAO,KAAK;AAAA,UAC3B,UAAU,KAAK,OAAO,KAAK;AAAA,UAC3B,aAAa,KAAK,OAAO,KAAK,eAAe;AAAA,QAC/C;AAAA,QACA;AAAA,MACF;AAEA,YAAM,KAAK,KAAK,QAAQ;AACxB,WAAK,kBAAkB,KAAK,KAAK,UAAU,CAAC,UAAU;AACpD,mBAAW,YAAY,KAAK,oBAAoB;AAC9C,mBAAS,KAAK;AAAA,QAChB;AAAA,MACF,CAAC;AACD,WAAK,IAAI,OAAO,KAAK,gBAAgB;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,IAAI,OAAO,KAAK,2BAA2B;AAChD,SAAK,kBAAkB;AACvB,SAAK,kBAAkB;AACvB,UAAM,KAAK,MAAM,WAAW;AAC5B,SAAK,OAAO;AACZ,SAAK,UAAU,CAAC;AAAA,EAClB;AAAA,EAEA,YAA4B;AAC1B,WAAO;AAAA,MACL,WAAW,KAAK,MAAM,OAAO,aAAa;AAAA,MAC1C,aAAa,KAAK,QAAQ;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,kBAA+C;AACnD,WAAO,KAAK,QAAQ,IAAI,CAAC,OAAO;AAAA,MAC9B,YAAY,EAAE;AAAA,MACd,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,MACR,cAAc,EAAE;AAAA,MAChB,UAAU,EAAE,YAAY;AAAA,IAC1B,EAAE;AAAA,EACJ;AAAA,EAEA,aAAwB;AACtB,WAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EACzB;AAAA,EAEA,oBAAoB,UAAkD;AACpE,SAAK,mBAAmB,IAAI,QAAQ;AACpC,WAAO,MAAM;AAAE,WAAK,mBAAmB,OAAO,QAAQ;AAAA,IAAE;AAAA,EAC1D;AACF;AAIA,SAAS,mBACP,YACA,eACgB;AAChB,QAAM,UAA0B,CAAC;AACjC,aAAW,cAAc,OAAO,KAAK,aAAa,GAAG;AACnD,QAAI,CAAC,WAAW,WAAW,UAAU,EAAG;AACxC,UAAM,QAAQ,WAAW,SAAS,KAAK,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,SAAS,QAAQ;AACtG,YAAQ,KAAK,EAAE,IAAI,YAAY,OAAO,YAAY,UAAU,QAAQ,SAAS,QAAQ,QAAQ,OAAO,CAAC;AAAA,EACvG;AACA,SAAO;AACT;;;AC1KO,IAAM,uBAAN,MAAoE;AAAA,EAChE,WAA0B;AAAA,IACjC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,cAAc,CAAC,iBAAiB;AAAA,EAClC;AAAA,EAEQ,WAAmC;AAAA,EAE3C,MAAM,WAAW,SAAsC;AACrD,UAAM,SAAS,QAAQ;AACvB,QAAI,CAAC,OAAO,KAAK;AACf,cAAQ,OAAO,KAAK,8DAA8D;AAClF;AAAA,IACF;AAEA,UAAM,iBAAwC;AAAA,MAC5C,IAAI,OAAO,MAAM;AAAA,MACjB,MAAM,OAAO,QAAQ;AAAA,MACrB,KAAK,OAAO;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,MACjB,MAAM,OAAO;AAAA,IACf;AAEA,SAAK,WAAW,IAAI,gBAAgB,gBAAgB;AAAA,MAClD,IAAI,QAAQ;AAAA,MACZ,QAAQ,QAAQ;AAAA,MAChB,UAAU,QAAQ;AAAA,MAClB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAED,YAAQ,OAAO,KAAK,oCAAoC;AAAA,EAC1D;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,KAAK,UAAU,KAAK;AAC1B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,sBACE,MACiC;AACjC,QAAI,SAAS,qBAAqB,KAAK,UAAU;AAC/C,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT;AAAA,EAEA,kBAAkC;AAChC,WAAO;AAAA,MACL,UAAU;AAAA,QACR;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ;AAAA,YACN,EAAE,MAAM,QAAQ,KAAK,OAAO,OAAO,eAAe,UAAU,MAAM,aAAa,sBAAsB;AAAA,YACrG,EAAE,MAAM,QAAQ,KAAK,QAAQ,OAAO,gBAAgB,aAAa,cAAc;AAAA,YAC/E,EAAE,MAAM,QAAQ,KAAK,YAAY,OAAO,WAAW;AAAA,YACnD,EAAE,MAAM,YAAY,KAAK,YAAY,OAAO,YAAY,YAAY,KAAK;AAAA,UAC3E;AAAA,QACF;AAAA,QACA;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,UACb,OAAO;AAAA,UACP,kBAAkB;AAAA,UAClB,SAAS;AAAA,UACT,QAAQ;AAAA,YACN,EAAE,MAAM,QAAQ,KAAK,kBAAkB,OAAO,mBAAmB,aAAa,qBAAqB;AAAA,YACnG,EAAE,MAAM,QAAQ,KAAK,oBAAoB,OAAO,gBAAgB,aAAa,UAAU;AAAA,YACvF,EAAE,MAAM,QAAQ,KAAK,iBAAiB,OAAO,gBAAgB;AAAA,YAC7D,EAAE,MAAM,YAAY,KAAK,iBAAiB,OAAO,iBAAiB,YAAY,KAAK;AAAA,UACrF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAqC;AACnC,WAAO,CAAC;AAAA,EACV;AAAA,EAEA,MAAM,eAAe,SAAiD;AAAA,EAEtE;AACF;","names":["z"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/frigate-api.ts","../src/frigate-mqtt.ts","../src/frigate-device.ts","../src/element-config-store.ts","../src/frigate-provider.ts","../src/addon.ts"],"sourcesContent":["export { FrigateProviderAddon } from './addon'\nexport { FrigateProvider } from './frigate-provider'\nexport type { FrigateProviderConfig } from './frigate-provider'\nexport { FrigateApiClient } from './frigate-api'\nexport type { FrigateApiConfig, EventsQuery, ReviewQuery, MotionQuery, TestConnectionResult } from './frigate-api'\nexport { FrigateMqttClient } from './frigate-mqtt'\nexport type { FrigateMqttConfig, MqttStatus } from './frigate-mqtt'\nexport { FrigateDevice } from './frigate-device'\nexport type { FrigateDeviceConfig } from './frigate-device'\nexport type {\n FrigateConfig,\n FrigateCameraConfig,\n FrigateRawEvent,\n FrigateReviewItem,\n FrigateRawRecording,\n FrigateMotionData,\n} from './frigate-types'\n","import type {\n FrigateConfig,\n FrigateMotionData,\n FrigateRawEvent,\n FrigateRawRecording,\n FrigateReviewItem,\n} from './frigate-types'\n\nexport interface FrigateApiConfig {\n baseUrl: string\n username?: string\n password?: string\n}\n\nexport interface EventsQuery {\n after?: number\n before?: number\n cameras?: string\n limit?: number\n labels?: string\n}\n\nexport interface ReviewQuery {\n after?: number\n before?: number\n cameras?: string\n limit?: number\n}\n\nexport interface MotionQuery {\n after: number\n before: number\n cameras?: string\n}\n\nexport interface TestConnectionResult {\n success: boolean\n version?: string\n cameraCount?: number\n error?: string\n}\n\nexport class FrigateApiClient {\n private readonly baseUrl: string\n private authToken: string | null = null\n private authHeader: string | null = null\n\n constructor(private readonly config: FrigateApiConfig) {\n this.baseUrl = (config.baseUrl ?? '').replace(/\\/+$/, '')\n }\n\n private async authenticate(): Promise<void> {\n const { username, password } = this.config\n\n if (!username || !password) {\n return\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/api/login`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ user: username, password }),\n })\n\n if (response.ok) {\n const cookies = response.headers.get('set-cookie')\n if (cookies) {\n this.authToken = cookies\n return\n }\n }\n } catch {\n // Token login failed, fall through to Basic auth\n }\n\n this.authHeader = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {}\n\n if (this.authToken) {\n headers['Cookie'] = this.authToken\n } else if (this.authHeader) {\n headers['Authorization'] = this.authHeader\n }\n\n return headers\n }\n\n private async request<T>(path: string, options?: RequestInit): Promise<T> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.buildHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n return response.json() as Promise<T>\n }\n\n private async requestBuffer(path: string): Promise<Buffer> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n headers: this.buildHeaders(),\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n const arrayBuffer = await response.arrayBuffer()\n return Buffer.from(arrayBuffer)\n }\n\n private async requestText(path: string, options?: RequestInit): Promise<string> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.buildHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n return response.text()\n }\n\n // --- Config & Discovery ---\n\n async getConfig(): Promise<FrigateConfig> {\n return this.request<FrigateConfig>('/api/config')\n }\n\n async getGo2rtcStreams(): Promise<Record<string, unknown>> {\n return this.request<Record<string, unknown>>('/api/go2rtc/streams')\n }\n\n // --- Events ---\n\n async getEvents(params: EventsQuery): Promise<FrigateRawEvent[]> {\n const query = buildQueryString(params)\n return this.request<FrigateRawEvent[]>(`/api/events${query}`)\n }\n\n async getReviewItems(params: ReviewQuery): Promise<FrigateReviewItem[]> {\n const query = buildQueryString(params)\n return this.request<FrigateReviewItem[]>(`/api/review${query}`)\n }\n\n // --- Recordings ---\n\n async getRecordings(\n camera: string,\n after: number,\n before: number,\n ): Promise<FrigateRawRecording[]> {\n const query = buildQueryString({ after, before })\n return this.request<FrigateRawRecording[]>(`/api/${encodeURIComponent(camera)}/recordings${query}`)\n }\n\n async getMotionActivity(params: MotionQuery): Promise<FrigateMotionData[]> {\n const query = buildQueryString(params)\n return this.request<FrigateMotionData[]>(`/api/motion_activity${query}`)\n }\n\n // --- Media ---\n\n async getLatestSnapshot(camera: string): Promise<Buffer> {\n return this.requestBuffer(`/api/${encodeURIComponent(camera)}/latest.jpg`)\n }\n\n async getEventThumbnail(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/thumbnail.jpg`)\n }\n\n async getEventSnapshot(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/snapshot.jpg`)\n }\n\n async getEventClip(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/clip.mp4`)\n }\n\n async getRecordingThumbnail(camera: string, timestampSec: number): Promise<Buffer> {\n return this.requestBuffer(\n `/api/${encodeURIComponent(camera)}/recordings/thumbnail-${timestampSec}.jpg`,\n )\n }\n\n // --- WebRTC ---\n\n async proxyWhepSdp(streamName: string, sdpOffer: string): Promise<string> {\n return this.requestText(`/api/go2rtc/webrtc?src=${encodeURIComponent(streamName)}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: sdpOffer,\n })\n }\n\n // --- PTZ ---\n\n async getPtzInfo(\n camera: string,\n ): Promise<{ features?: string[]; presets?: string[] } | null> {\n try {\n return await this.request<{ features?: string[]; presets?: string[] }>(\n `/api/${encodeURIComponent(camera)}/ptz/info`,\n )\n } catch {\n return null\n }\n }\n\n async ptzCommand(\n camera: string,\n command: string,\n params?: Record<string, unknown>,\n ): Promise<void> {\n const query = buildQueryString({ ...params, command })\n await this.request<void>(`/api/${encodeURIComponent(camera)}/ptz${query}`)\n }\n\n // --- Test Connection ---\n\n async testConnection(): Promise<TestConnectionResult> {\n try {\n const config = await this.getConfig()\n const cameraNames = Object.keys(config.cameras ?? {})\n return {\n success: true,\n version: config.version,\n cameraCount: cameraNames.length,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n }\n}\n\n// --- Utility ---\n\nexport function buildQueryString(params: Record<string, unknown> | object): string {\n const entries = Object.entries(params).filter(\n ([, v]) => v !== undefined && v !== null,\n )\n if (entries.length === 0) {\n return ''\n }\n const searchParams = new URLSearchParams()\n for (const [key, value] of entries) {\n searchParams.set(key, String(value))\n }\n return `?${searchParams.toString()}`\n}\n","import type { MqttClient } from 'mqtt'\nimport { connect } from 'mqtt'\nimport type { LiveEvent } from '@camstack/types'\n\nexport interface FrigateMqttConfig {\n brokerUrl: string\n username?: string\n password?: string\n topicPrefix: string\n}\n\nexport interface MqttStatus {\n connected: boolean\n eventCount: number\n}\n\nexport class FrigateMqttClient {\n private client: MqttClient | null = null\n private readonly listeners: Set<(event: LiveEvent) => void> = new Set()\n private eventCount = 0\n\n constructor(\n private readonly config: FrigateMqttConfig,\n private readonly cameraResolutions: Map<string, { width: number; height: number }>,\n ) {}\n\n async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const { brokerUrl, username, password, topicPrefix } = this.config\n\n this.client = connect(brokerUrl, {\n username,\n password,\n reconnectPeriod: 5000,\n connectTimeout: 10000,\n })\n\n this.client.on('connect', () => {\n const prefix = topicPrefix\n this.client!.subscribe([\n `${prefix}/events`,\n `${prefix}/reviews`,\n `${prefix}/+/motion/state`,\n `${prefix}/+/audio/+`,\n ])\n resolve()\n })\n\n this.client.on('error', (err) => {\n reject(err)\n })\n\n this.client.on('message', (topic: string, payload: Buffer) => {\n const event = parseMqttMessage(\n topic,\n payload,\n topicPrefix,\n this.cameraResolutions,\n )\n if (event) {\n this.eventCount++\n for (const listener of this.listeners) {\n listener(event)\n }\n }\n })\n })\n }\n\n async disconnect(): Promise<void> {\n if (this.client) {\n await this.client.endAsync()\n this.client = null\n }\n this.listeners.clear()\n }\n\n subscribe(callback: (event: LiveEvent) => void): () => void {\n this.listeners.add(callback)\n return () => {\n this.listeners.delete(callback)\n }\n }\n\n get status(): MqttStatus {\n return {\n connected: this.client?.connected ?? false,\n eventCount: this.eventCount,\n }\n }\n}\n\n// --- Exported for testing ---\n\ninterface FrigateMqttEventPayload {\n type?: 'new' | 'update' | 'end'\n before?: FrigateMqttDetectionState\n after?: FrigateMqttDetectionState\n}\n\ninterface FrigateMqttDetectionState {\n id?: string\n camera?: string\n label?: string\n sub_label?: string\n top_score?: number\n start_time?: number\n end_time?: number | null\n current_zones?: string[]\n has_snapshot?: boolean\n has_clip?: boolean\n box?: [number, number, number, number]\n}\n\ninterface FrigateMqttReviewPayload {\n type?: 'new' | 'update' | 'end'\n before?: FrigateMqttReviewState\n after?: FrigateMqttReviewState\n}\n\ninterface FrigateMqttReviewState {\n id?: string\n camera?: string\n start_time?: number\n end_time?: number | null\n severity?: 'alert' | 'detection'\n data?: {\n objects?: string[]\n zones?: string[]\n sub_labels?: string[]\n }\n}\n\nexport function parseMqttMessage(\n topic: string,\n payload: Buffer,\n prefix: string,\n resolutions: Map<string, { width: number; height: number }>,\n): LiveEvent | null {\n const topicParts = topic.split('/')\n const prefixParts = prefix.split('/')\n const relative = topicParts.slice(prefixParts.length)\n\n // {prefix}/events\n if (relative.length === 1 && relative[0] === 'events') {\n return parseDetectionEvent(payload, resolutions)\n }\n\n // {prefix}/reviews\n if (relative.length === 1 && relative[0] === 'reviews') {\n return parseReviewEvent(payload)\n }\n\n // {prefix}/{camera}/motion/state\n if (relative.length === 3 && relative[1] === 'motion' && relative[2] === 'state') {\n const camera = relative[0]!\n return parseMotionEvent(camera, payload)\n }\n\n // {prefix}/{camera}/audio/{metric}\n if (relative.length === 3 && relative[1] === 'audio') {\n const camera = relative[0]!\n const metric = relative[2]!\n return parseAudioEvent(camera, metric, payload)\n }\n\n return null\n}\n\nfunction parseDetectionEvent(\n payload: Buffer,\n resolutions: Map<string, { width: number; height: number }>,\n): LiveEvent | null {\n try {\n const parsed: FrigateMqttEventPayload = JSON.parse(payload.toString())\n const state = parsed.after ?? parsed.before\n if (!state?.camera || !state?.label) {\n return null\n }\n\n const data: Record<string, unknown> = {\n eventType: parsed.type ?? 'update',\n id: state.id,\n label: state.label,\n subLabel: state.sub_label,\n score: state.top_score,\n startTime: state.start_time,\n endTime: state.end_time,\n zones: state.current_zones,\n hasSnapshot: state.has_snapshot,\n hasClip: state.has_clip,\n }\n\n if (state.box) {\n const resolution = resolutions.get(state.camera)\n if (resolution) {\n data.boundingBox = normalizeBoundingBox(state.box, resolution.width, resolution.height)\n } else {\n data.rawBox = state.box\n }\n }\n\n return {\n type: 'detection',\n camera: state.camera,\n timestamp: Date.now(),\n data,\n }\n } catch {\n return null\n }\n}\n\nfunction parseReviewEvent(payload: Buffer): LiveEvent | null {\n try {\n const parsed: FrigateMqttReviewPayload = JSON.parse(payload.toString())\n const state = parsed.after ?? parsed.before\n if (!state?.camera) {\n return null\n }\n\n return {\n type: 'review',\n camera: state.camera,\n timestamp: Date.now(),\n data: {\n eventType: parsed.type ?? 'update',\n id: state.id,\n severity: state.severity,\n startTime: state.start_time,\n endTime: state.end_time,\n objects: state.data?.objects,\n zones: state.data?.zones,\n subLabels: state.data?.sub_labels,\n },\n }\n } catch {\n return null\n }\n}\n\nfunction parseMotionEvent(camera: string, payload: Buffer): LiveEvent {\n const value = payload.toString().trim()\n const active = value === 'ON'\n\n return {\n type: 'motion',\n camera,\n timestamp: Date.now(),\n data: { active },\n }\n}\n\nfunction parseAudioEvent(camera: string, metric: string, payload: Buffer): LiveEvent | null {\n const raw = payload.toString().trim()\n const value = parseFloat(raw)\n\n if (isNaN(value)) {\n return null\n }\n\n return {\n type: 'audio',\n camera,\n timestamp: Date.now(),\n data: { metric, value },\n }\n}\n\n/**\n * Normalize Frigate bounding box from pixel coordinates to 0-1 range.\n * Frigate box format: [y_min, x_min, y_max, x_max] in pixels.\n * Output: { x, y, w, h } normalized to 0-1 range.\n */\nexport function normalizeBoundingBox(\n box: [number, number, number, number],\n width: number,\n height: number,\n): { x: number; y: number; w: number; h: number } {\n const [yMin, xMin, yMax, xMax] = box\n return {\n x: xMin / width,\n y: yMin / height,\n w: (xMax - xMin) / width,\n h: (yMax - yMin) / height,\n }\n}\n","import { DeviceType } from '@camstack/types'\nimport type {\n IDevice,\n DeviceState,\n DeviceMetadata,\n DeviceCapabilityName,\n IDeviceCapability,\n CamstackContext,\n ICamera,\n StreamOption,\n ConnectionMode,\n IMotionSensor,\n IObjectDetector,\n DetectionZone,\n DetectionLine,\n IAudioDetector,\n IEvents,\n EventQuery,\n EventQueryResult,\n DeviceStoredEvent,\n IRecording,\n RecordingSegment,\n TimeRange,\n} from '@camstack/types'\nimport type { FrigateApiClient } from './frigate-api'\n\nexport interface FrigateDeviceConfig {\n readonly cameraName: string\n readonly providerId: string\n readonly detectWidth: number\n readonly detectHeight: number\n readonly audioEnabled: boolean\n readonly recordEnabled: boolean\n readonly ptzEnabled: boolean\n readonly streams: StreamOption[]\n readonly frigateHost: string\n}\n\nexport class FrigateDevice implements IDevice {\n readonly id: string\n readonly name: string\n readonly providerId: string\n readonly type: DeviceType = DeviceType.Camera\n readonly capabilities: DeviceCapabilityName[]\n\n private readonly capabilityMap = new Map<DeviceCapabilityName, IDeviceCapability>()\n\n readonly ctx: CamstackContext\n\n constructor(\n private readonly config: FrigateDeviceConfig,\n private readonly api: FrigateApiClient,\n ctx: CamstackContext,\n ) {\n this.id = `${config.providerId}/${config.cameraName}`\n this.name = config.cameraName\n this.providerId = config.providerId\n this.ctx = ctx\n\n // Capabilities based on camera config\n const caps: DeviceCapabilityName[] = ['camera', 'events', 'recording', 'motionSensor', 'objectDetector']\n if (config.audioEnabled) caps.push('audioDetector')\n this.capabilities = caps\n\n this.capabilityMap.set('camera', this.createCamera())\n this.capabilityMap.set('motionSensor', this.createMotionSensor())\n this.capabilityMap.set('objectDetector', this.createObjectDetector())\n this.capabilityMap.set('events', this.createEvents())\n this.capabilityMap.set('recording', this.createRecording())\n if (config.audioEnabled) {\n this.capabilityMap.set('audioDetector', this.createAudioDetector())\n }\n }\n\n getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null {\n return (this.capabilityMap.get(cap) as T) ?? null\n }\n\n hasCapability(cap: DeviceCapabilityName): boolean {\n return this.capabilityMap.has(cap)\n }\n\n getState(): DeviceState {\n return { online: true }\n }\n\n getMetadata(): DeviceMetadata {\n return { manufacturer: 'Frigate NVR' }\n }\n\n // --- Capability Factories ---\n\n private createCamera(): ICamera {\n const { api, config } = this\n return {\n kind: 'camera',\n async getSnapshot() { return api.getLatestSnapshot(config.cameraName) },\n async getStreamOptions(): Promise<StreamOption[]> {\n return config.streams.map(s => ({\n ...s,\n url: s.url ?? `rtsp://${config.frigateHost}:8554/${s.id}`,\n }))\n },\n async getStreamUrl(option: StreamOption) {\n return `/api/go2rtc/webrtc?src=${encodeURIComponent(option.id)}`\n },\n getConnectionMode(): ConnectionMode { return 'on-demand' },\n async setConnectionMode() {},\n }\n }\n\n private createMotionSensor(): IMotionSensor {\n let motionActive = false\n return {\n kind: 'motionSensor',\n isMotionDetected() { return motionActive },\n subscribe() { return () => {} }, // real-time via MQTT through provider\n }\n }\n\n private createObjectDetector(): IObjectDetector {\n const zones: DetectionZone[] = []\n const lines: DetectionLine[] = []\n\n return {\n kind: 'objectDetector',\n getLabels() { return [] },\n onDetections() { return () => {} },\n\n // Zone management\n async getZones() { return zones },\n async addZone(zone) { zones.push(zone) },\n async removeZone(id) { const i = zones.findIndex(z => z.id === id); if (i >= 0) zones.splice(i, 1) },\n async updateZone(id, partial) { const z = zones.find(z => z.id === id); if (z) Object.assign(z, partial) },\n\n // Line management\n async getLines() { return lines },\n async addLine(line) { lines.push(line) },\n async removeLine(id) { const i = lines.findIndex(l => l.id === id); if (i >= 0) lines.splice(i, 1) },\n\n // Active tracking (populated by MQTT events in real-time)\n getActiveDetections() { return [] },\n getZoneDetections() { return [] },\n getStationaryDetections() { return [] },\n getMovingDetections() { return [] },\n }\n }\n\n private createEvents(): IEvents {\n const { api, config } = this\n return {\n kind: 'events',\n\n async getEvents(query: EventQuery): Promise<EventQueryResult> {\n const raw = await api.getEvents({\n cameras: config.cameraName,\n after: query.since,\n before: query.until,\n limit: query.limit,\n labels: query.detectionClasses?.join(','),\n })\n\n const events: DeviceStoredEvent[] = raw.map(e => ({\n id: e.id,\n type: 'detection',\n timestamp: e.start_time * 1000, // Frigate uses seconds, we use ms\n endTimestamp: e.end_time ? e.end_time * 1000 : undefined,\n label: e.label,\n score: e.top_score,\n hasClip: e.has_clip,\n hasSnapshot: e.has_snapshot,\n thumbnailUrl: `/api/events/${e.id}/thumbnail.jpg`,\n }))\n\n return { events, total: events.length }\n },\n\n async getEventThumbnail(eventId: string) {\n try { return await api.getEventThumbnail(eventId) }\n catch { return null }\n },\n\n async getEventSnapshot(eventId: string) {\n try { return await api.getEventSnapshot(eventId) }\n catch { return null }\n },\n\n async getEventClipUrl(eventId: string) {\n return `/api/events/${encodeURIComponent(eventId)}/clip.mp4`\n },\n }\n }\n\n private createRecording(): IRecording {\n const { api, config } = this\n return {\n kind: 'recording',\n\n async getSegments(range: TimeRange): Promise<RecordingSegment[]> {\n const recs = await api.getRecordings(config.cameraName, range.since, range.until)\n return recs.map(r => ({\n id: r.id ?? `${config.cameraName}-${r.start_time}`,\n startTime: r.start_time * 1000,\n endTime: r.end_time * 1000,\n duration: r.duration,\n }))\n },\n\n async getPlaybackUrl(startTime: number, endTime: number) {\n const startSec = Math.floor(startTime / 1000)\n const endSec = Math.floor(endTime / 1000)\n return `/api/${encodeURIComponent(config.cameraName)}/start/${startSec}/end/${endSec}/clip.mp4`\n },\n\n async getThumbnailAt(timestampMs: number) {\n try { return await api.getRecordingThumbnail(config.cameraName, Math.floor(timestampMs / 1000)) }\n catch { return null }\n },\n }\n }\n\n private createAudioDetector(): IAudioDetector {\n return {\n kind: 'audioDetector',\n getAudioLevel() { return 0 },\n subscribe() { return () => {} }, // real-time via MQTT through provider\n }\n }\n}\n","import type { IElementConfig } from '@camstack/types'\nimport type { IStorageLocation } from '@camstack/types'\n\n/**\n * Persisted config store for a single element.\n * Reads/writes to the element's scoped storage under the 'config' collection.\n * Notifies listeners on every change.\n */\nexport class ElementConfigStore implements IElementConfig {\n private cache: Record<string, unknown> = {}\n private listeners: Set<(config: Record<string, unknown>) => void> = new Set()\n private loaded = false\n\n constructor(\n private readonly elementId: string,\n private readonly storage: IStorageLocation,\n ) {}\n\n /** Load config from storage into cache. Called once on first access. */\n private async ensureLoaded(): Promise<void> {\n if (this.loaded) return\n if (!this.storage.structured) {\n this.loaded = true\n return\n }\n\n try {\n const records = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n if (records.length > 0) {\n this.cache = (records[0] as any).data ?? {}\n }\n } catch {\n // Storage might not be ready yet\n }\n this.loaded = true\n }\n\n getAll(): Record<string, unknown> {\n return { ...this.cache }\n }\n\n get<T = unknown>(key: string): T | undefined {\n const parts = key.split('.')\n let current: unknown = this.cache\n for (const part of parts) {\n if (current == null || typeof current !== 'object') return undefined\n current = (current as Record<string, unknown>)[part]\n }\n return current as T | undefined\n }\n\n async set(key: string, value: unknown): Promise<void> {\n await this.ensureLoaded()\n setNestedValue(this.cache, key, value)\n await this.persist()\n this.notifyListeners()\n }\n\n async setAll(config: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n this.cache = { ...config }\n await this.persist()\n this.notifyListeners()\n }\n\n onChange(callback: (config: Record<string, unknown>) => void): () => void {\n this.listeners.add(callback)\n return () => { this.listeners.delete(callback) }\n }\n\n /** Initialize from storage — called by ContextFactory after creation */\n async load(): Promise<void> {\n await this.ensureLoaded()\n }\n\n /** Initialize with default values (doesn't overwrite existing) */\n async loadDefaults(defaults: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n if (Object.keys(this.cache).length === 0) {\n this.cache = { ...defaults }\n await this.persist()\n }\n }\n\n private async persist(): Promise<void> {\n if (!this.storage.structured) return\n\n try {\n const existing = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n\n if (existing.length > 0) {\n await this.storage.structured.update('config', this.elementId, this.cache)\n } else {\n await this.storage.structured.insert({\n collection: 'config',\n id: this.elementId,\n data: this.cache,\n })\n }\n } catch {\n // Storage might not be ready\n }\n }\n\n private notifyListeners(): void {\n const snapshot = this.getAll()\n for (const listener of this.listeners) {\n try {\n listener(snapshot)\n } catch {\n // Don't let one bad listener kill others\n }\n }\n }\n}\n\nfunction setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.')\n let current: Record<string, unknown> = obj\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n current[parts[parts.length - 1]!] = value\n}\n","import { FrigateApiClient } from './frigate-api'\nimport { FrigateMqttClient } from './frigate-mqtt'\nimport { FrigateDevice } from './frigate-device'\nimport { ElementConfigStore } from './element-config-store'\nimport type { StreamOption } from '@camstack/types'\nimport type {\n IDeviceProvider,\n ProviderStatus,\n DiscoveredDevice,\n LiveEvent,\n} from '@camstack/types'\nimport type { IDevice } from '@camstack/types'\nimport type { CamstackContext } from '@camstack/types'\nimport type { FrigateConfig, FrigateCameraConfig } from './frigate-types'\n\nexport interface FrigateProviderConfig {\n readonly id: string\n readonly name: string\n readonly url: string\n readonly username?: string\n readonly password?: string\n readonly mqtt?: {\n readonly brokerUrl: string\n readonly username?: string\n readonly password?: string\n readonly topicPrefix?: string\n }\n}\n\nconst ADOPTED_DEVICES_KEY = 'adoptedDevices'\n\nexport class FrigateProvider implements IDeviceProvider {\n readonly id: string\n readonly type = 'frigate'\n readonly name: string\n readonly discoveryMode: 'auto' = 'auto'\n readonly ctx: CamstackContext\n\n private readonly api: FrigateApiClient\n private mqtt: FrigateMqttClient | null = null\n private devices: readonly FrigateDevice[] = []\n private readonly liveEventListeners: Set<(event: LiveEvent) => void> = new Set()\n private mqttUnsubscribe?: () => void\n\n /** Cached Frigate config, refreshed on start() and discoverDevices() */\n private frigateConfig: FrigateConfig | null = null\n private go2rtcStreams: Record<string, unknown> = {}\n private frigateHost: string\n\n constructor(\n private readonly config: FrigateProviderConfig,\n ctx: CamstackContext,\n ) {\n this.id = config.id\n this.name = config.name\n this.ctx = ctx\n this.api = new FrigateApiClient({\n baseUrl: config.url,\n username: config.username,\n password: config.password,\n })\n this.frigateHost = new URL(config.url).hostname\n }\n\n async start(): Promise<void> {\n this.ctx.logger.info(`Starting Frigate provider: ${this.config.url}`)\n\n // 1. Fetch Frigate config to discover cameras\n this.frigateConfig = await this.api.getConfig()\n this.go2rtcStreams = await this.api.getGo2rtcStreams()\n\n // 2. Build camera resolutions map (for MQTT bbox normalization)\n const resolutions = new Map<string, { width: number; height: number }>()\n for (const [name, cam] of Object.entries(this.frigateConfig.cameras)) {\n if (cam.detect) {\n resolutions.set(name, { width: cam.detect.width, height: cam.detect.height })\n }\n }\n\n // 3. Load only previously adopted cameras\n const adoptedIds = this.getAdoptedDeviceIds()\n if (adoptedIds.length > 0) {\n const devices: FrigateDevice[] = []\n for (const cameraName of adoptedIds) {\n const cam = this.frigateConfig.cameras[cameraName]\n if (!cam || cam.enabled === false) {\n this.ctx.logger.warn(`Adopted camera \"${cameraName}\" no longer available in Frigate config, skipping`)\n continue\n }\n devices.push(this.createDeviceFromCamera(cameraName, cam))\n }\n this.devices = devices\n this.ctx.logger.info(`Loaded ${devices.length} adopted cameras`)\n } else {\n this.ctx.logger.info('No adopted cameras yet — use discoverDevices() + adoptDevice() to import cameras')\n }\n\n // 4. Connect MQTT if configured\n if (this.config.mqtt?.brokerUrl) {\n this.mqtt = new FrigateMqttClient(\n {\n brokerUrl: this.config.mqtt.brokerUrl,\n username: this.config.mqtt.username,\n password: this.config.mqtt.password,\n topicPrefix: this.config.mqtt.topicPrefix ?? 'frigate',\n },\n resolutions,\n )\n\n await this.mqtt.connect()\n this.mqttUnsubscribe = this.mqtt.subscribe((event) => {\n for (const listener of this.liveEventListeners) {\n listener(event)\n }\n })\n this.ctx.logger.info('MQTT connected')\n }\n }\n\n async stop(): Promise<void> {\n this.ctx.logger.info('Stopping Frigate provider')\n this.mqttUnsubscribe?.()\n this.mqttUnsubscribe = undefined\n await this.mqtt?.disconnect()\n this.mqtt = null\n this.devices = []\n this.frigateConfig = null\n this.go2rtcStreams = {}\n }\n\n getStatus(): ProviderStatus {\n return {\n connected: this.mqtt?.status.connected ?? false,\n deviceCount: this.devices.length,\n }\n }\n\n async discoverDevices(): Promise<DiscoveredDevice[]> {\n // Refresh from Frigate API to get the latest camera list\n this.frigateConfig = await this.api.getConfig()\n this.go2rtcStreams = await this.api.getGo2rtcStreams()\n\n const adoptedIds = this.getAdoptedDeviceIds()\n const discovered: DiscoveredDevice[] = []\n\n for (const [name, cam] of Object.entries(this.frigateConfig.cameras)) {\n if (cam.enabled === false) continue\n\n // Build capabilities list to match what FrigateDevice would produce\n const caps: DiscoveredDevice['capabilities'] = ['camera', 'events', 'recording', 'motionSensor', 'objectDetector']\n if (cam.audio?.enabled) caps.push('audioDetector')\n\n discovered.push({\n externalId: name,\n name,\n type: 'camera',\n capabilities: caps,\n metadata: { manufacturer: 'Frigate NVR' },\n })\n }\n\n return discovered\n }\n\n async adoptDevice(externalId: string, _config?: Record<string, unknown>): Promise<IDevice> {\n // 1. Ensure we have a fresh Frigate config\n if (!this.frigateConfig) {\n this.frigateConfig = await this.api.getConfig()\n this.go2rtcStreams = await this.api.getGo2rtcStreams()\n }\n\n // 2. Find the camera in Frigate config\n const cam = this.frigateConfig.cameras[externalId]\n if (!cam) {\n throw new Error(`Camera \"${externalId}\" not found in Frigate config`)\n }\n if (cam.enabled === false) {\n throw new Error(`Camera \"${externalId}\" is disabled in Frigate config`)\n }\n\n // 3. Check if already adopted\n const existing = this.devices.find((d) => d.name === externalId)\n if (existing) {\n this.ctx.logger.info(`Camera \"${externalId}\" is already adopted`)\n return existing\n }\n\n // 4. Create the device\n const device = this.createDeviceFromCamera(externalId, cam)\n\n // 5. Add to the in-memory device list (immutable update)\n this.devices = [...this.devices, device]\n\n // 6. Persist adoption\n await this.persistAdoptedDeviceId(externalId)\n\n this.ctx.logger.info(`Adopted camera \"${externalId}\" (total: ${this.devices.length})`)\n return device\n }\n\n getDevices(): IDevice[] {\n return [...this.devices]\n }\n\n subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void {\n this.liveEventListeners.add(callback)\n return () => { this.liveEventListeners.delete(callback) }\n }\n\n // --- Private helpers ---\n\n private createDeviceFromCamera(cameraName: string, cam: FrigateCameraConfig): FrigateDevice {\n const streams = buildStreamOptions(cameraName, this.go2rtcStreams)\n const deviceId = `${this.id}/${cameraName}`\n\n const deviceCtx: CamstackContext = {\n id: `device:${deviceId}`,\n logger: this.ctx.logger.child(cameraName),\n eventBus: this.ctx.eventBus,\n storage: this.ctx.storage,\n config: new ElementConfigStore(`device:${deviceId}`, this.ctx.storage),\n }\n\n return new FrigateDevice(\n {\n cameraName,\n providerId: this.id,\n detectWidth: cam.detect?.width ?? 1920,\n detectHeight: cam.detect?.height ?? 1080,\n audioEnabled: cam.audio?.enabled ?? false,\n recordEnabled: cam.record?.enabled ?? false,\n ptzEnabled: false,\n streams,\n frigateHost: this.frigateHost,\n },\n this.api,\n deviceCtx,\n )\n }\n\n private getAdoptedDeviceIds(): readonly string[] {\n return this.ctx.config.get<string[]>(ADOPTED_DEVICES_KEY) ?? []\n }\n\n private async persistAdoptedDeviceId(cameraName: string): Promise<void> {\n const current = this.getAdoptedDeviceIds()\n if (current.includes(cameraName)) return\n await this.ctx.config.set(ADOPTED_DEVICES_KEY, [...current, cameraName])\n }\n}\n\n// --- Helpers ---\n\nfunction buildStreamOptions(\n cameraName: string,\n go2rtcStreams: Record<string, unknown>,\n): StreamOption[] {\n const streams: StreamOption[] = []\n for (const streamName of Object.keys(go2rtcStreams)) {\n if (!streamName.startsWith(cameraName)) continue\n const isSub = streamName.includes('sub') || streamName.includes('ext') || streamName.includes('medium')\n streams.push({ id: streamName, label: streamName, protocol: 'rtsp', quality: isSub ? 'sub' : 'main' })\n }\n return streams\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n ConfigUISchema,\n IConfigurable,\n} from '@camstack/types'\nimport { FrigateProvider } from './frigate-provider'\nimport type { FrigateProviderConfig } from './frigate-provider'\n\nexport class FrigateProviderAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'provider-frigate',\n name: 'Frigate NVR Provider',\n version: '0.1.0',\n description: 'Integrazione con Frigate NVR per camere e detection',\n capabilities: ['device-provider'],\n }\n\n private provider: FrigateProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n const config = context.addonConfig as unknown as FrigateProviderConfig\n if (!config.url) {\n context.logger.warn('Frigate provider: no URL configured, skipping initialization')\n return\n }\n\n const providerConfig: FrigateProviderConfig = {\n id: config.id ?? 'frigate-default',\n name: config.name ?? 'Frigate NVR',\n url: config.url,\n username: config.username,\n password: config.password,\n mqtt: config.mqtt,\n }\n\n this.provider = new FrigateProvider(providerConfig, {\n id: context.id,\n logger: context.logger,\n eventBus: context.eventBus,\n storage: context.storage,\n config: context.config,\n })\n\n context.logger.info('Frigate provider addon initialized')\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.stop()\n this.provider = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'device-provider' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'connection',\n title: 'Frigate Connection',\n description: 'Configure the connection to your Frigate NVR instance',\n columns: 2,\n fields: [\n { type: 'text', key: 'url', label: 'Frigate URL', required: true, placeholder: 'http://frigate:5000' },\n { type: 'text', key: 'name', label: 'Display Name', placeholder: 'Frigate NVR' },\n { type: 'text', key: 'username', label: 'Username' },\n { type: 'password', key: 'password', label: 'Password', showToggle: true },\n ],\n },\n {\n id: 'mqtt',\n title: 'MQTT Settings',\n description: 'Optional: Connect to Frigate MQTT for real-time events',\n style: 'accordion',\n defaultCollapsed: true,\n columns: 2,\n fields: [\n { type: 'text', key: 'mqtt.brokerUrl', label: 'MQTT Broker URL', placeholder: 'mqtt://broker:1883' },\n { type: 'text', key: 'mqtt.topicPrefix', label: 'Topic Prefix', placeholder: 'frigate' },\n { type: 'text', key: 'mqtt.username', label: 'MQTT Username' },\n { type: 'password', key: 'mqtt.password', label: 'MQTT Password', showToggle: true },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // Restart provider with new config\n }\n}\n"],"mappings":"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","names":["z"]}
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var FrigateApiClient = class {
|
|
3
3
|
constructor(config) {
|
|
4
4
|
this.config = config;
|
|
5
|
-
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
5
|
+
this.baseUrl = (config.baseUrl ?? "").replace(/\/+$/, "");
|
|
6
6
|
}
|
|
7
7
|
baseUrl;
|
|
8
8
|
authToken = null;
|
|
@@ -369,6 +369,7 @@ function normalizeBoundingBox(box, width, height) {
|
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
// src/frigate-device.ts
|
|
372
|
+
import { DeviceType } from "@camstack/types";
|
|
372
373
|
var FrigateDevice = class {
|
|
373
374
|
constructor(config, api, ctx) {
|
|
374
375
|
this.config = config;
|
|
@@ -392,7 +393,7 @@ var FrigateDevice = class {
|
|
|
392
393
|
id;
|
|
393
394
|
name;
|
|
394
395
|
providerId;
|
|
395
|
-
type =
|
|
396
|
+
type = DeviceType.Camera;
|
|
396
397
|
capabilities;
|
|
397
398
|
capabilityMap = /* @__PURE__ */ new Map();
|
|
398
399
|
ctx;
|
|
@@ -699,6 +700,7 @@ function setNestedValue(obj, path, value) {
|
|
|
699
700
|
}
|
|
700
701
|
|
|
701
702
|
// src/frigate-provider.ts
|
|
703
|
+
var ADOPTED_DEVICES_KEY = "adoptedDevices";
|
|
702
704
|
var FrigateProvider = class {
|
|
703
705
|
constructor(config, ctx) {
|
|
704
706
|
this.config = config;
|
|
@@ -710,6 +712,7 @@ var FrigateProvider = class {
|
|
|
710
712
|
username: config.username,
|
|
711
713
|
password: config.password
|
|
712
714
|
});
|
|
715
|
+
this.frigateHost = new URL(config.url).hostname;
|
|
713
716
|
}
|
|
714
717
|
id;
|
|
715
718
|
type = "frigate";
|
|
@@ -721,49 +724,36 @@ var FrigateProvider = class {
|
|
|
721
724
|
devices = [];
|
|
722
725
|
liveEventListeners = /* @__PURE__ */ new Set();
|
|
723
726
|
mqttUnsubscribe;
|
|
727
|
+
/** Cached Frigate config, refreshed on start() and discoverDevices() */
|
|
728
|
+
frigateConfig = null;
|
|
729
|
+
go2rtcStreams = {};
|
|
730
|
+
frigateHost;
|
|
724
731
|
async start() {
|
|
725
732
|
this.ctx.logger.info(`Starting Frigate provider: ${this.config.url}`);
|
|
726
|
-
|
|
727
|
-
|
|
733
|
+
this.frigateConfig = await this.api.getConfig();
|
|
734
|
+
this.go2rtcStreams = await this.api.getGo2rtcStreams();
|
|
728
735
|
const resolutions = /* @__PURE__ */ new Map();
|
|
729
|
-
for (const [name, cam] of Object.entries(frigateConfig.cameras)) {
|
|
736
|
+
for (const [name, cam] of Object.entries(this.frigateConfig.cameras)) {
|
|
730
737
|
if (cam.detect) {
|
|
731
738
|
resolutions.set(name, { width: cam.detect.width, height: cam.detect.height });
|
|
732
739
|
}
|
|
733
740
|
}
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
{
|
|
750
|
-
cameraName: name,
|
|
751
|
-
providerId: this.id,
|
|
752
|
-
detectWidth: cam.detect?.width ?? 1920,
|
|
753
|
-
detectHeight: cam.detect?.height ?? 1080,
|
|
754
|
-
audioEnabled: cam.audio?.enabled ?? false,
|
|
755
|
-
recordEnabled: cam.record?.enabled ?? false,
|
|
756
|
-
ptzEnabled: false,
|
|
757
|
-
streams,
|
|
758
|
-
frigateHost
|
|
759
|
-
},
|
|
760
|
-
this.api,
|
|
761
|
-
deviceCtx
|
|
762
|
-
)
|
|
763
|
-
);
|
|
741
|
+
const adoptedIds = this.getAdoptedDeviceIds();
|
|
742
|
+
if (adoptedIds.length > 0) {
|
|
743
|
+
const devices = [];
|
|
744
|
+
for (const cameraName of adoptedIds) {
|
|
745
|
+
const cam = this.frigateConfig.cameras[cameraName];
|
|
746
|
+
if (!cam || cam.enabled === false) {
|
|
747
|
+
this.ctx.logger.warn(`Adopted camera "${cameraName}" no longer available in Frigate config, skipping`);
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
devices.push(this.createDeviceFromCamera(cameraName, cam));
|
|
751
|
+
}
|
|
752
|
+
this.devices = devices;
|
|
753
|
+
this.ctx.logger.info(`Loaded ${devices.length} adopted cameras`);
|
|
754
|
+
} else {
|
|
755
|
+
this.ctx.logger.info("No adopted cameras yet \u2014 use discoverDevices() + adoptDevice() to import cameras");
|
|
764
756
|
}
|
|
765
|
-
this.devices = devices;
|
|
766
|
-
this.ctx.logger.info(`Discovered ${devices.length} cameras`);
|
|
767
757
|
if (this.config.mqtt?.brokerUrl) {
|
|
768
758
|
this.mqtt = new FrigateMqttClient(
|
|
769
759
|
{
|
|
@@ -790,6 +780,8 @@ var FrigateProvider = class {
|
|
|
790
780
|
await this.mqtt?.disconnect();
|
|
791
781
|
this.mqtt = null;
|
|
792
782
|
this.devices = [];
|
|
783
|
+
this.frigateConfig = null;
|
|
784
|
+
this.go2rtcStreams = {};
|
|
793
785
|
}
|
|
794
786
|
getStatus() {
|
|
795
787
|
return {
|
|
@@ -798,13 +790,46 @@ var FrigateProvider = class {
|
|
|
798
790
|
};
|
|
799
791
|
}
|
|
800
792
|
async discoverDevices() {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
793
|
+
this.frigateConfig = await this.api.getConfig();
|
|
794
|
+
this.go2rtcStreams = await this.api.getGo2rtcStreams();
|
|
795
|
+
const adoptedIds = this.getAdoptedDeviceIds();
|
|
796
|
+
const discovered = [];
|
|
797
|
+
for (const [name, cam] of Object.entries(this.frigateConfig.cameras)) {
|
|
798
|
+
if (cam.enabled === false) continue;
|
|
799
|
+
const caps = ["camera", "events", "recording", "motionSensor", "objectDetector"];
|
|
800
|
+
if (cam.audio?.enabled) caps.push("audioDetector");
|
|
801
|
+
discovered.push({
|
|
802
|
+
externalId: name,
|
|
803
|
+
name,
|
|
804
|
+
type: "camera",
|
|
805
|
+
capabilities: caps,
|
|
806
|
+
metadata: { manufacturer: "Frigate NVR" }
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
return discovered;
|
|
810
|
+
}
|
|
811
|
+
async adoptDevice(externalId, _config) {
|
|
812
|
+
if (!this.frigateConfig) {
|
|
813
|
+
this.frigateConfig = await this.api.getConfig();
|
|
814
|
+
this.go2rtcStreams = await this.api.getGo2rtcStreams();
|
|
815
|
+
}
|
|
816
|
+
const cam = this.frigateConfig.cameras[externalId];
|
|
817
|
+
if (!cam) {
|
|
818
|
+
throw new Error(`Camera "${externalId}" not found in Frigate config`);
|
|
819
|
+
}
|
|
820
|
+
if (cam.enabled === false) {
|
|
821
|
+
throw new Error(`Camera "${externalId}" is disabled in Frigate config`);
|
|
822
|
+
}
|
|
823
|
+
const existing = this.devices.find((d) => d.name === externalId);
|
|
824
|
+
if (existing) {
|
|
825
|
+
this.ctx.logger.info(`Camera "${externalId}" is already adopted`);
|
|
826
|
+
return existing;
|
|
827
|
+
}
|
|
828
|
+
const device = this.createDeviceFromCamera(externalId, cam);
|
|
829
|
+
this.devices = [...this.devices, device];
|
|
830
|
+
await this.persistAdoptedDeviceId(externalId);
|
|
831
|
+
this.ctx.logger.info(`Adopted camera "${externalId}" (total: ${this.devices.length})`);
|
|
832
|
+
return device;
|
|
808
833
|
}
|
|
809
834
|
getDevices() {
|
|
810
835
|
return [...this.devices];
|
|
@@ -815,6 +840,41 @@ var FrigateProvider = class {
|
|
|
815
840
|
this.liveEventListeners.delete(callback);
|
|
816
841
|
};
|
|
817
842
|
}
|
|
843
|
+
// --- Private helpers ---
|
|
844
|
+
createDeviceFromCamera(cameraName, cam) {
|
|
845
|
+
const streams = buildStreamOptions(cameraName, this.go2rtcStreams);
|
|
846
|
+
const deviceId = `${this.id}/${cameraName}`;
|
|
847
|
+
const deviceCtx = {
|
|
848
|
+
id: `device:${deviceId}`,
|
|
849
|
+
logger: this.ctx.logger.child(cameraName),
|
|
850
|
+
eventBus: this.ctx.eventBus,
|
|
851
|
+
storage: this.ctx.storage,
|
|
852
|
+
config: new ElementConfigStore(`device:${deviceId}`, this.ctx.storage)
|
|
853
|
+
};
|
|
854
|
+
return new FrigateDevice(
|
|
855
|
+
{
|
|
856
|
+
cameraName,
|
|
857
|
+
providerId: this.id,
|
|
858
|
+
detectWidth: cam.detect?.width ?? 1920,
|
|
859
|
+
detectHeight: cam.detect?.height ?? 1080,
|
|
860
|
+
audioEnabled: cam.audio?.enabled ?? false,
|
|
861
|
+
recordEnabled: cam.record?.enabled ?? false,
|
|
862
|
+
ptzEnabled: false,
|
|
863
|
+
streams,
|
|
864
|
+
frigateHost: this.frigateHost
|
|
865
|
+
},
|
|
866
|
+
this.api,
|
|
867
|
+
deviceCtx
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
getAdoptedDeviceIds() {
|
|
871
|
+
return this.ctx.config.get(ADOPTED_DEVICES_KEY) ?? [];
|
|
872
|
+
}
|
|
873
|
+
async persistAdoptedDeviceId(cameraName) {
|
|
874
|
+
const current = this.getAdoptedDeviceIds();
|
|
875
|
+
if (current.includes(cameraName)) return;
|
|
876
|
+
await this.ctx.config.set(ADOPTED_DEVICES_KEY, [...current, cameraName]);
|
|
877
|
+
}
|
|
818
878
|
};
|
|
819
879
|
function buildStreamOptions(cameraName, go2rtcStreams) {
|
|
820
880
|
const streams = [];
|
|
@@ -832,6 +892,7 @@ var FrigateProviderAddon = class {
|
|
|
832
892
|
id: "provider-frigate",
|
|
833
893
|
name: "Frigate NVR Provider",
|
|
834
894
|
version: "0.1.0",
|
|
895
|
+
description: "Integrazione con Frigate NVR per camere e detection",
|
|
835
896
|
capabilities: ["device-provider"]
|
|
836
897
|
};
|
|
837
898
|
provider = null;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/frigate-api.ts","../src/frigate-mqtt.ts","../src/frigate-device.ts","../src/element-config-store.ts","../src/frigate-provider.ts","../src/addon.ts"],"sourcesContent":["import type {\n FrigateConfig,\n FrigateMotionData,\n FrigateRawEvent,\n FrigateRawRecording,\n FrigateReviewItem,\n} from './frigate-types'\n\nexport interface FrigateApiConfig {\n baseUrl: string\n username?: string\n password?: string\n}\n\nexport interface EventsQuery {\n after?: number\n before?: number\n cameras?: string\n limit?: number\n labels?: string\n}\n\nexport interface ReviewQuery {\n after?: number\n before?: number\n cameras?: string\n limit?: number\n}\n\nexport interface MotionQuery {\n after: number\n before: number\n cameras?: string\n}\n\nexport interface TestConnectionResult {\n success: boolean\n version?: string\n cameraCount?: number\n error?: string\n}\n\nexport class FrigateApiClient {\n private readonly baseUrl: string\n private authToken: string | null = null\n private authHeader: string | null = null\n\n constructor(private readonly config: FrigateApiConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '')\n }\n\n private async authenticate(): Promise<void> {\n const { username, password } = this.config\n\n if (!username || !password) {\n return\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/api/login`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ user: username, password }),\n })\n\n if (response.ok) {\n const cookies = response.headers.get('set-cookie')\n if (cookies) {\n this.authToken = cookies\n return\n }\n }\n } catch {\n // Token login failed, fall through to Basic auth\n }\n\n this.authHeader = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {}\n\n if (this.authToken) {\n headers['Cookie'] = this.authToken\n } else if (this.authHeader) {\n headers['Authorization'] = this.authHeader\n }\n\n return headers\n }\n\n private async request<T>(path: string, options?: RequestInit): Promise<T> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.buildHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n return response.json() as Promise<T>\n }\n\n private async requestBuffer(path: string): Promise<Buffer> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n headers: this.buildHeaders(),\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n const arrayBuffer = await response.arrayBuffer()\n return Buffer.from(arrayBuffer)\n }\n\n private async requestText(path: string, options?: RequestInit): Promise<string> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.buildHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n return response.text()\n }\n\n // --- Config & Discovery ---\n\n async getConfig(): Promise<FrigateConfig> {\n return this.request<FrigateConfig>('/api/config')\n }\n\n async getGo2rtcStreams(): Promise<Record<string, unknown>> {\n return this.request<Record<string, unknown>>('/api/go2rtc/streams')\n }\n\n // --- Events ---\n\n async getEvents(params: EventsQuery): Promise<FrigateRawEvent[]> {\n const query = buildQueryString(params)\n return this.request<FrigateRawEvent[]>(`/api/events${query}`)\n }\n\n async getReviewItems(params: ReviewQuery): Promise<FrigateReviewItem[]> {\n const query = buildQueryString(params)\n return this.request<FrigateReviewItem[]>(`/api/review${query}`)\n }\n\n // --- Recordings ---\n\n async getRecordings(\n camera: string,\n after: number,\n before: number,\n ): Promise<FrigateRawRecording[]> {\n const query = buildQueryString({ after, before })\n return this.request<FrigateRawRecording[]>(`/api/${encodeURIComponent(camera)}/recordings${query}`)\n }\n\n async getMotionActivity(params: MotionQuery): Promise<FrigateMotionData[]> {\n const query = buildQueryString(params)\n return this.request<FrigateMotionData[]>(`/api/motion_activity${query}`)\n }\n\n // --- Media ---\n\n async getLatestSnapshot(camera: string): Promise<Buffer> {\n return this.requestBuffer(`/api/${encodeURIComponent(camera)}/latest.jpg`)\n }\n\n async getEventThumbnail(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/thumbnail.jpg`)\n }\n\n async getEventSnapshot(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/snapshot.jpg`)\n }\n\n async getEventClip(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/clip.mp4`)\n }\n\n async getRecordingThumbnail(camera: string, timestampSec: number): Promise<Buffer> {\n return this.requestBuffer(\n `/api/${encodeURIComponent(camera)}/recordings/thumbnail-${timestampSec}.jpg`,\n )\n }\n\n // --- WebRTC ---\n\n async proxyWhepSdp(streamName: string, sdpOffer: string): Promise<string> {\n return this.requestText(`/api/go2rtc/webrtc?src=${encodeURIComponent(streamName)}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: sdpOffer,\n })\n }\n\n // --- PTZ ---\n\n async getPtzInfo(\n camera: string,\n ): Promise<{ features?: string[]; presets?: string[] } | null> {\n try {\n return await this.request<{ features?: string[]; presets?: string[] }>(\n `/api/${encodeURIComponent(camera)}/ptz/info`,\n )\n } catch {\n return null\n }\n }\n\n async ptzCommand(\n camera: string,\n command: string,\n params?: Record<string, unknown>,\n ): Promise<void> {\n const query = buildQueryString({ ...params, command })\n await this.request<void>(`/api/${encodeURIComponent(camera)}/ptz${query}`)\n }\n\n // --- Test Connection ---\n\n async testConnection(): Promise<TestConnectionResult> {\n try {\n const config = await this.getConfig()\n const cameraNames = Object.keys(config.cameras ?? {})\n return {\n success: true,\n version: config.version,\n cameraCount: cameraNames.length,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n }\n}\n\n// --- Utility ---\n\nexport function buildQueryString(params: Record<string, unknown> | object): string {\n const entries = Object.entries(params).filter(\n ([, v]) => v !== undefined && v !== null,\n )\n if (entries.length === 0) {\n return ''\n }\n const searchParams = new URLSearchParams()\n for (const [key, value] of entries) {\n searchParams.set(key, String(value))\n }\n return `?${searchParams.toString()}`\n}\n","import type { MqttClient } from 'mqtt'\nimport { connect } from 'mqtt'\nimport type { LiveEvent } from '@camstack/types'\n\nexport interface FrigateMqttConfig {\n brokerUrl: string\n username?: string\n password?: string\n topicPrefix: string\n}\n\nexport interface MqttStatus {\n connected: boolean\n eventCount: number\n}\n\nexport class FrigateMqttClient {\n private client: MqttClient | null = null\n private readonly listeners: Set<(event: LiveEvent) => void> = new Set()\n private eventCount = 0\n\n constructor(\n private readonly config: FrigateMqttConfig,\n private readonly cameraResolutions: Map<string, { width: number; height: number }>,\n ) {}\n\n async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const { brokerUrl, username, password, topicPrefix } = this.config\n\n this.client = connect(brokerUrl, {\n username,\n password,\n reconnectPeriod: 5000,\n connectTimeout: 10000,\n })\n\n this.client.on('connect', () => {\n const prefix = topicPrefix\n this.client!.subscribe([\n `${prefix}/events`,\n `${prefix}/reviews`,\n `${prefix}/+/motion/state`,\n `${prefix}/+/audio/+`,\n ])\n resolve()\n })\n\n this.client.on('error', (err) => {\n reject(err)\n })\n\n this.client.on('message', (topic: string, payload: Buffer) => {\n const event = parseMqttMessage(\n topic,\n payload,\n topicPrefix,\n this.cameraResolutions,\n )\n if (event) {\n this.eventCount++\n for (const listener of this.listeners) {\n listener(event)\n }\n }\n })\n })\n }\n\n async disconnect(): Promise<void> {\n if (this.client) {\n await this.client.endAsync()\n this.client = null\n }\n this.listeners.clear()\n }\n\n subscribe(callback: (event: LiveEvent) => void): () => void {\n this.listeners.add(callback)\n return () => {\n this.listeners.delete(callback)\n }\n }\n\n get status(): MqttStatus {\n return {\n connected: this.client?.connected ?? false,\n eventCount: this.eventCount,\n }\n }\n}\n\n// --- Exported for testing ---\n\ninterface FrigateMqttEventPayload {\n type?: 'new' | 'update' | 'end'\n before?: FrigateMqttDetectionState\n after?: FrigateMqttDetectionState\n}\n\ninterface FrigateMqttDetectionState {\n id?: string\n camera?: string\n label?: string\n sub_label?: string\n top_score?: number\n start_time?: number\n end_time?: number | null\n current_zones?: string[]\n has_snapshot?: boolean\n has_clip?: boolean\n box?: [number, number, number, number]\n}\n\ninterface FrigateMqttReviewPayload {\n type?: 'new' | 'update' | 'end'\n before?: FrigateMqttReviewState\n after?: FrigateMqttReviewState\n}\n\ninterface FrigateMqttReviewState {\n id?: string\n camera?: string\n start_time?: number\n end_time?: number | null\n severity?: 'alert' | 'detection'\n data?: {\n objects?: string[]\n zones?: string[]\n sub_labels?: string[]\n }\n}\n\nexport function parseMqttMessage(\n topic: string,\n payload: Buffer,\n prefix: string,\n resolutions: Map<string, { width: number; height: number }>,\n): LiveEvent | null {\n const topicParts = topic.split('/')\n const prefixParts = prefix.split('/')\n const relative = topicParts.slice(prefixParts.length)\n\n // {prefix}/events\n if (relative.length === 1 && relative[0] === 'events') {\n return parseDetectionEvent(payload, resolutions)\n }\n\n // {prefix}/reviews\n if (relative.length === 1 && relative[0] === 'reviews') {\n return parseReviewEvent(payload)\n }\n\n // {prefix}/{camera}/motion/state\n if (relative.length === 3 && relative[1] === 'motion' && relative[2] === 'state') {\n const camera = relative[0]!\n return parseMotionEvent(camera, payload)\n }\n\n // {prefix}/{camera}/audio/{metric}\n if (relative.length === 3 && relative[1] === 'audio') {\n const camera = relative[0]!\n const metric = relative[2]!\n return parseAudioEvent(camera, metric, payload)\n }\n\n return null\n}\n\nfunction parseDetectionEvent(\n payload: Buffer,\n resolutions: Map<string, { width: number; height: number }>,\n): LiveEvent | null {\n try {\n const parsed: FrigateMqttEventPayload = JSON.parse(payload.toString())\n const state = parsed.after ?? parsed.before\n if (!state?.camera || !state?.label) {\n return null\n }\n\n const data: Record<string, unknown> = {\n eventType: parsed.type ?? 'update',\n id: state.id,\n label: state.label,\n subLabel: state.sub_label,\n score: state.top_score,\n startTime: state.start_time,\n endTime: state.end_time,\n zones: state.current_zones,\n hasSnapshot: state.has_snapshot,\n hasClip: state.has_clip,\n }\n\n if (state.box) {\n const resolution = resolutions.get(state.camera)\n if (resolution) {\n data.boundingBox = normalizeBoundingBox(state.box, resolution.width, resolution.height)\n } else {\n data.rawBox = state.box\n }\n }\n\n return {\n type: 'detection',\n camera: state.camera,\n timestamp: Date.now(),\n data,\n }\n } catch {\n return null\n }\n}\n\nfunction parseReviewEvent(payload: Buffer): LiveEvent | null {\n try {\n const parsed: FrigateMqttReviewPayload = JSON.parse(payload.toString())\n const state = parsed.after ?? parsed.before\n if (!state?.camera) {\n return null\n }\n\n return {\n type: 'review',\n camera: state.camera,\n timestamp: Date.now(),\n data: {\n eventType: parsed.type ?? 'update',\n id: state.id,\n severity: state.severity,\n startTime: state.start_time,\n endTime: state.end_time,\n objects: state.data?.objects,\n zones: state.data?.zones,\n subLabels: state.data?.sub_labels,\n },\n }\n } catch {\n return null\n }\n}\n\nfunction parseMotionEvent(camera: string, payload: Buffer): LiveEvent {\n const value = payload.toString().trim()\n const active = value === 'ON'\n\n return {\n type: 'motion',\n camera,\n timestamp: Date.now(),\n data: { active },\n }\n}\n\nfunction parseAudioEvent(camera: string, metric: string, payload: Buffer): LiveEvent | null {\n const raw = payload.toString().trim()\n const value = parseFloat(raw)\n\n if (isNaN(value)) {\n return null\n }\n\n return {\n type: 'audio',\n camera,\n timestamp: Date.now(),\n data: { metric, value },\n }\n}\n\n/**\n * Normalize Frigate bounding box from pixel coordinates to 0-1 range.\n * Frigate box format: [y_min, x_min, y_max, x_max] in pixels.\n * Output: { x, y, w, h } normalized to 0-1 range.\n */\nexport function normalizeBoundingBox(\n box: [number, number, number, number],\n width: number,\n height: number,\n): { x: number; y: number; w: number; h: number } {\n const [yMin, xMin, yMax, xMax] = box\n return {\n x: xMin / width,\n y: yMin / height,\n w: (xMax - xMin) / width,\n h: (yMax - yMin) / height,\n }\n}\n","import type {\n IDevice,\n DeviceType,\n DeviceState,\n DeviceMetadata,\n DeviceCapabilityName,\n IDeviceCapability,\n CamstackContext,\n ICamera,\n StreamOption,\n ConnectionMode,\n IMotionSensor,\n IObjectDetector,\n DetectionZone,\n DetectionLine,\n IAudioDetector,\n IEvents,\n EventQuery,\n EventQueryResult,\n DeviceStoredEvent,\n IRecording,\n RecordingSegment,\n TimeRange,\n} from '@camstack/types'\nimport type { FrigateApiClient } from './frigate-api'\n\nexport interface FrigateDeviceConfig {\n readonly cameraName: string\n readonly providerId: string\n readonly detectWidth: number\n readonly detectHeight: number\n readonly audioEnabled: boolean\n readonly recordEnabled: boolean\n readonly ptzEnabled: boolean\n readonly streams: StreamOption[]\n readonly frigateHost: string\n}\n\nexport class FrigateDevice implements IDevice {\n readonly id: string\n readonly name: string\n readonly providerId: string\n readonly type: DeviceType = 'camera'\n readonly capabilities: DeviceCapabilityName[]\n\n private readonly capabilityMap = new Map<DeviceCapabilityName, IDeviceCapability>()\n\n readonly ctx: CamstackContext\n\n constructor(\n private readonly config: FrigateDeviceConfig,\n private readonly api: FrigateApiClient,\n ctx: CamstackContext,\n ) {\n this.id = `${config.providerId}/${config.cameraName}`\n this.name = config.cameraName\n this.providerId = config.providerId\n this.ctx = ctx\n\n // Capabilities based on camera config\n const caps: DeviceCapabilityName[] = ['camera', 'events', 'recording', 'motionSensor', 'objectDetector']\n if (config.audioEnabled) caps.push('audioDetector')\n this.capabilities = caps\n\n this.capabilityMap.set('camera', this.createCamera())\n this.capabilityMap.set('motionSensor', this.createMotionSensor())\n this.capabilityMap.set('objectDetector', this.createObjectDetector())\n this.capabilityMap.set('events', this.createEvents())\n this.capabilityMap.set('recording', this.createRecording())\n if (config.audioEnabled) {\n this.capabilityMap.set('audioDetector', this.createAudioDetector())\n }\n }\n\n getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null {\n return (this.capabilityMap.get(cap) as T) ?? null\n }\n\n hasCapability(cap: DeviceCapabilityName): boolean {\n return this.capabilityMap.has(cap)\n }\n\n getState(): DeviceState {\n return { online: true }\n }\n\n getMetadata(): DeviceMetadata {\n return { manufacturer: 'Frigate NVR' }\n }\n\n // --- Capability Factories ---\n\n private createCamera(): ICamera {\n const { api, config } = this\n return {\n kind: 'camera',\n async getSnapshot() { return api.getLatestSnapshot(config.cameraName) },\n async getStreamOptions(): Promise<StreamOption[]> {\n return config.streams.map(s => ({\n ...s,\n url: s.url ?? `rtsp://${config.frigateHost}:8554/${s.id}`,\n }))\n },\n async getStreamUrl(option: StreamOption) {\n return `/api/go2rtc/webrtc?src=${encodeURIComponent(option.id)}`\n },\n getConnectionMode(): ConnectionMode { return 'on-demand' },\n async setConnectionMode() {},\n }\n }\n\n private createMotionSensor(): IMotionSensor {\n let motionActive = false\n return {\n kind: 'motionSensor',\n isMotionDetected() { return motionActive },\n subscribe() { return () => {} }, // real-time via MQTT through provider\n }\n }\n\n private createObjectDetector(): IObjectDetector {\n const zones: DetectionZone[] = []\n const lines: DetectionLine[] = []\n\n return {\n kind: 'objectDetector',\n getLabels() { return [] },\n onDetections() { return () => {} },\n\n // Zone management\n async getZones() { return zones },\n async addZone(zone) { zones.push(zone) },\n async removeZone(id) { const i = zones.findIndex(z => z.id === id); if (i >= 0) zones.splice(i, 1) },\n async updateZone(id, partial) { const z = zones.find(z => z.id === id); if (z) Object.assign(z, partial) },\n\n // Line management\n async getLines() { return lines },\n async addLine(line) { lines.push(line) },\n async removeLine(id) { const i = lines.findIndex(l => l.id === id); if (i >= 0) lines.splice(i, 1) },\n\n // Active tracking (populated by MQTT events in real-time)\n getActiveDetections() { return [] },\n getZoneDetections() { return [] },\n getStationaryDetections() { return [] },\n getMovingDetections() { return [] },\n }\n }\n\n private createEvents(): IEvents {\n const { api, config } = this\n return {\n kind: 'events',\n\n async getEvents(query: EventQuery): Promise<EventQueryResult> {\n const raw = await api.getEvents({\n cameras: config.cameraName,\n after: query.since,\n before: query.until,\n limit: query.limit,\n labels: query.detectionClasses?.join(','),\n })\n\n const events: DeviceStoredEvent[] = raw.map(e => ({\n id: e.id,\n type: 'detection',\n timestamp: e.start_time * 1000, // Frigate uses seconds, we use ms\n endTimestamp: e.end_time ? e.end_time * 1000 : undefined,\n label: e.label,\n score: e.top_score,\n hasClip: e.has_clip,\n hasSnapshot: e.has_snapshot,\n thumbnailUrl: `/api/events/${e.id}/thumbnail.jpg`,\n }))\n\n return { events, total: events.length }\n },\n\n async getEventThumbnail(eventId: string) {\n try { return await api.getEventThumbnail(eventId) }\n catch { return null }\n },\n\n async getEventSnapshot(eventId: string) {\n try { return await api.getEventSnapshot(eventId) }\n catch { return null }\n },\n\n async getEventClipUrl(eventId: string) {\n return `/api/events/${encodeURIComponent(eventId)}/clip.mp4`\n },\n }\n }\n\n private createRecording(): IRecording {\n const { api, config } = this\n return {\n kind: 'recording',\n\n async getSegments(range: TimeRange): Promise<RecordingSegment[]> {\n const recs = await api.getRecordings(config.cameraName, range.since, range.until)\n return recs.map(r => ({\n id: r.id ?? `${config.cameraName}-${r.start_time}`,\n startTime: r.start_time * 1000,\n endTime: r.end_time * 1000,\n duration: r.duration,\n }))\n },\n\n async getPlaybackUrl(startTime: number, endTime: number) {\n const startSec = Math.floor(startTime / 1000)\n const endSec = Math.floor(endTime / 1000)\n return `/api/${encodeURIComponent(config.cameraName)}/start/${startSec}/end/${endSec}/clip.mp4`\n },\n\n async getThumbnailAt(timestampMs: number) {\n try { return await api.getRecordingThumbnail(config.cameraName, Math.floor(timestampMs / 1000)) }\n catch { return null }\n },\n }\n }\n\n private createAudioDetector(): IAudioDetector {\n return {\n kind: 'audioDetector',\n getAudioLevel() { return 0 },\n subscribe() { return () => {} }, // real-time via MQTT through provider\n }\n }\n}\n","import type { IElementConfig } from '@camstack/types'\nimport type { IStorageLocation } from '@camstack/types'\n\n/**\n * Persisted config store for a single element.\n * Reads/writes to the element's scoped storage under the 'config' collection.\n * Notifies listeners on every change.\n */\nexport class ElementConfigStore implements IElementConfig {\n private cache: Record<string, unknown> = {}\n private listeners: Set<(config: Record<string, unknown>) => void> = new Set()\n private loaded = false\n\n constructor(\n private readonly elementId: string,\n private readonly storage: IStorageLocation,\n ) {}\n\n /** Load config from storage into cache. Called once on first access. */\n private async ensureLoaded(): Promise<void> {\n if (this.loaded) return\n if (!this.storage.structured) {\n this.loaded = true\n return\n }\n\n try {\n const records = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n if (records.length > 0) {\n this.cache = (records[0] as any).data ?? {}\n }\n } catch {\n // Storage might not be ready yet\n }\n this.loaded = true\n }\n\n getAll(): Record<string, unknown> {\n return { ...this.cache }\n }\n\n get<T = unknown>(key: string): T | undefined {\n const parts = key.split('.')\n let current: unknown = this.cache\n for (const part of parts) {\n if (current == null || typeof current !== 'object') return undefined\n current = (current as Record<string, unknown>)[part]\n }\n return current as T | undefined\n }\n\n async set(key: string, value: unknown): Promise<void> {\n await this.ensureLoaded()\n setNestedValue(this.cache, key, value)\n await this.persist()\n this.notifyListeners()\n }\n\n async setAll(config: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n this.cache = { ...config }\n await this.persist()\n this.notifyListeners()\n }\n\n onChange(callback: (config: Record<string, unknown>) => void): () => void {\n this.listeners.add(callback)\n return () => { this.listeners.delete(callback) }\n }\n\n /** Initialize from storage — called by ContextFactory after creation */\n async load(): Promise<void> {\n await this.ensureLoaded()\n }\n\n /** Initialize with default values (doesn't overwrite existing) */\n async loadDefaults(defaults: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n if (Object.keys(this.cache).length === 0) {\n this.cache = { ...defaults }\n await this.persist()\n }\n }\n\n private async persist(): Promise<void> {\n if (!this.storage.structured) return\n\n try {\n const existing = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n\n if (existing.length > 0) {\n await this.storage.structured.update('config', this.elementId, this.cache)\n } else {\n await this.storage.structured.insert({\n collection: 'config',\n id: this.elementId,\n data: this.cache,\n })\n }\n } catch {\n // Storage might not be ready\n }\n }\n\n private notifyListeners(): void {\n const snapshot = this.getAll()\n for (const listener of this.listeners) {\n try {\n listener(snapshot)\n } catch {\n // Don't let one bad listener kill others\n }\n }\n }\n}\n\nfunction setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.')\n let current: Record<string, unknown> = obj\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n current[parts[parts.length - 1]!] = value\n}\n","import { FrigateApiClient } from './frigate-api'\nimport { FrigateMqttClient } from './frigate-mqtt'\nimport { FrigateDevice } from './frigate-device'\nimport { ElementConfigStore } from './element-config-store'\nimport type { StreamOption } from '@camstack/types'\nimport type {\n IDeviceProvider,\n ProviderStatus,\n DiscoveredDevice,\n LiveEvent,\n} from '@camstack/types'\nimport type { IDevice } from '@camstack/types'\nimport type { ICamera } from '@camstack/types'\nimport type { CamstackContext } from '@camstack/types'\n\nexport interface FrigateProviderConfig {\n readonly id: string\n readonly name: string\n readonly url: string\n readonly username?: string\n readonly password?: string\n readonly mqtt?: {\n readonly brokerUrl: string\n readonly username?: string\n readonly password?: string\n readonly topicPrefix?: string\n }\n}\n\nexport class FrigateProvider implements IDeviceProvider {\n readonly id: string\n readonly type = 'frigate'\n readonly name: string\n readonly discoveryMode: 'auto' = 'auto'\n readonly ctx: CamstackContext\n\n private readonly api: FrigateApiClient\n private mqtt: FrigateMqttClient | null = null\n private devices: readonly FrigateDevice[] = []\n private readonly liveEventListeners: Set<(event: LiveEvent) => void> = new Set()\n private mqttUnsubscribe?: () => void\n\n constructor(\n private readonly config: FrigateProviderConfig,\n ctx: CamstackContext,\n ) {\n this.id = config.id\n this.name = config.name\n this.ctx = ctx\n this.api = new FrigateApiClient({\n baseUrl: config.url,\n username: config.username,\n password: config.password,\n })\n }\n\n async start(): Promise<void> {\n this.ctx.logger.info(`Starting Frigate provider: ${this.config.url}`)\n\n // 1. Fetch Frigate config to discover cameras\n const frigateConfig = await this.api.getConfig()\n const go2rtcStreams = await this.api.getGo2rtcStreams()\n\n // 2. Build camera resolutions map (for MQTT bbox normalization)\n const resolutions = new Map<string, { width: number; height: number }>()\n for (const [name, cam] of Object.entries(frigateConfig.cameras)) {\n if (cam.detect) {\n resolutions.set(name, { width: cam.detect.width, height: cam.detect.height })\n }\n }\n\n // 3. Create FrigateDevice per enabled camera\n const frigateHost = new URL(this.config.url).hostname\n const devices: FrigateDevice[] = []\n for (const [name, cam] of Object.entries(frigateConfig.cameras)) {\n if (cam.enabled === false) continue\n\n const streams = buildStreamOptions(name, go2rtcStreams)\n const deviceId = `${this.id}/${name}`\n\n // Create device context scoped under this provider\n const deviceCtx: CamstackContext = {\n id: `device:${deviceId}`,\n logger: this.ctx.logger.child(name),\n eventBus: this.ctx.eventBus,\n storage: this.ctx.storage,\n config: new ElementConfigStore(`device:${deviceId}`, this.ctx.storage),\n }\n\n devices.push(\n new FrigateDevice(\n {\n cameraName: name,\n providerId: this.id,\n detectWidth: cam.detect?.width ?? 1920,\n detectHeight: cam.detect?.height ?? 1080,\n audioEnabled: cam.audio?.enabled ?? false,\n recordEnabled: cam.record?.enabled ?? false,\n ptzEnabled: false,\n streams,\n frigateHost,\n },\n this.api,\n deviceCtx,\n ),\n )\n }\n this.devices = devices\n this.ctx.logger.info(`Discovered ${devices.length} cameras`)\n\n // 4. Connect MQTT if configured\n if (this.config.mqtt?.brokerUrl) {\n this.mqtt = new FrigateMqttClient(\n {\n brokerUrl: this.config.mqtt.brokerUrl,\n username: this.config.mqtt.username,\n password: this.config.mqtt.password,\n topicPrefix: this.config.mqtt.topicPrefix ?? 'frigate',\n },\n resolutions,\n )\n\n await this.mqtt.connect()\n this.mqttUnsubscribe = this.mqtt.subscribe((event) => {\n for (const listener of this.liveEventListeners) {\n listener(event)\n }\n })\n this.ctx.logger.info('MQTT connected')\n }\n }\n\n async stop(): Promise<void> {\n this.ctx.logger.info('Stopping Frigate provider')\n this.mqttUnsubscribe?.()\n this.mqttUnsubscribe = undefined\n await this.mqtt?.disconnect()\n this.mqtt = null\n this.devices = []\n }\n\n getStatus(): ProviderStatus {\n return {\n connected: this.mqtt?.status.connected ?? false,\n deviceCount: this.devices.length,\n }\n }\n\n async discoverDevices(): Promise<DiscoveredDevice[]> {\n return this.devices.map((d) => ({\n externalId: d.name,\n name: d.name,\n type: d.type,\n capabilities: d.capabilities,\n metadata: d.getMetadata(),\n }))\n }\n\n getDevices(): IDevice[] {\n return [...this.devices]\n }\n\n subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void {\n this.liveEventListeners.add(callback)\n return () => { this.liveEventListeners.delete(callback) }\n }\n}\n\n// --- Helpers ---\n\nfunction buildStreamOptions(\n cameraName: string,\n go2rtcStreams: Record<string, unknown>,\n): StreamOption[] {\n const streams: StreamOption[] = []\n for (const streamName of Object.keys(go2rtcStreams)) {\n if (!streamName.startsWith(cameraName)) continue\n const isSub = streamName.includes('sub') || streamName.includes('ext') || streamName.includes('medium')\n streams.push({ id: streamName, label: streamName, protocol: 'rtsp', quality: isSub ? 'sub' : 'main' })\n }\n return streams\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n ConfigUISchema,\n IConfigurable,\n} from '@camstack/types'\nimport { FrigateProvider } from './frigate-provider'\nimport type { FrigateProviderConfig } from './frigate-provider'\n\nexport class FrigateProviderAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'provider-frigate',\n name: 'Frigate NVR Provider',\n version: '0.1.0',\n capabilities: ['device-provider'],\n }\n\n private provider: FrigateProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n const config = context.addonConfig as unknown as FrigateProviderConfig\n if (!config.url) {\n context.logger.warn('Frigate provider: no URL configured, skipping initialization')\n return\n }\n\n const providerConfig: FrigateProviderConfig = {\n id: config.id ?? 'frigate-default',\n name: config.name ?? 'Frigate NVR',\n url: config.url,\n username: config.username,\n password: config.password,\n mqtt: config.mqtt,\n }\n\n this.provider = new FrigateProvider(providerConfig, {\n id: context.id,\n logger: context.logger,\n eventBus: context.eventBus,\n storage: context.storage,\n config: context.config,\n })\n\n context.logger.info('Frigate provider addon initialized')\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.stop()\n this.provider = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'device-provider' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'connection',\n title: 'Frigate Connection',\n description: 'Configure the connection to your Frigate NVR instance',\n columns: 2,\n fields: [\n { type: 'text', key: 'url', label: 'Frigate URL', required: true, placeholder: 'http://frigate:5000' },\n { type: 'text', key: 'name', label: 'Display Name', placeholder: 'Frigate NVR' },\n { type: 'text', key: 'username', label: 'Username' },\n { type: 'password', key: 'password', label: 'Password', showToggle: true },\n ],\n },\n {\n id: 'mqtt',\n title: 'MQTT Settings',\n description: 'Optional: Connect to Frigate MQTT for real-time events',\n style: 'accordion',\n defaultCollapsed: true,\n columns: 2,\n fields: [\n { type: 'text', key: 'mqtt.brokerUrl', label: 'MQTT Broker URL', placeholder: 'mqtt://broker:1883' },\n { type: 'text', key: 'mqtt.topicPrefix', label: 'Topic Prefix', placeholder: 'frigate' },\n { type: 'text', key: 'mqtt.username', label: 'MQTT Username' },\n { type: 'password', key: 'mqtt.password', label: 'MQTT Password', showToggle: true },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // Restart provider with new config\n }\n}\n"],"mappings":"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","names":["z"]}
|
|
1
|
+
{"version":3,"sources":["../src/frigate-api.ts","../src/frigate-mqtt.ts","../src/frigate-device.ts","../src/element-config-store.ts","../src/frigate-provider.ts","../src/addon.ts"],"sourcesContent":["import type {\n FrigateConfig,\n FrigateMotionData,\n FrigateRawEvent,\n FrigateRawRecording,\n FrigateReviewItem,\n} from './frigate-types'\n\nexport interface FrigateApiConfig {\n baseUrl: string\n username?: string\n password?: string\n}\n\nexport interface EventsQuery {\n after?: number\n before?: number\n cameras?: string\n limit?: number\n labels?: string\n}\n\nexport interface ReviewQuery {\n after?: number\n before?: number\n cameras?: string\n limit?: number\n}\n\nexport interface MotionQuery {\n after: number\n before: number\n cameras?: string\n}\n\nexport interface TestConnectionResult {\n success: boolean\n version?: string\n cameraCount?: number\n error?: string\n}\n\nexport class FrigateApiClient {\n private readonly baseUrl: string\n private authToken: string | null = null\n private authHeader: string | null = null\n\n constructor(private readonly config: FrigateApiConfig) {\n this.baseUrl = (config.baseUrl ?? '').replace(/\\/+$/, '')\n }\n\n private async authenticate(): Promise<void> {\n const { username, password } = this.config\n\n if (!username || !password) {\n return\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/api/login`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ user: username, password }),\n })\n\n if (response.ok) {\n const cookies = response.headers.get('set-cookie')\n if (cookies) {\n this.authToken = cookies\n return\n }\n }\n } catch {\n // Token login failed, fall through to Basic auth\n }\n\n this.authHeader = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {}\n\n if (this.authToken) {\n headers['Cookie'] = this.authToken\n } else if (this.authHeader) {\n headers['Authorization'] = this.authHeader\n }\n\n return headers\n }\n\n private async request<T>(path: string, options?: RequestInit): Promise<T> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.buildHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n return response.json() as Promise<T>\n }\n\n private async requestBuffer(path: string): Promise<Buffer> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n headers: this.buildHeaders(),\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n const arrayBuffer = await response.arrayBuffer()\n return Buffer.from(arrayBuffer)\n }\n\n private async requestText(path: string, options?: RequestInit): Promise<string> {\n if ((this.config.username || this.config.password) && !this.authToken && !this.authHeader) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const response = await fetch(url, {\n ...options,\n headers: {\n ...this.buildHeaders(),\n ...options?.headers,\n },\n })\n\n if (!response.ok) {\n throw new Error(`Frigate API error: ${response.status} ${response.statusText} for ${path}`)\n }\n\n return response.text()\n }\n\n // --- Config & Discovery ---\n\n async getConfig(): Promise<FrigateConfig> {\n return this.request<FrigateConfig>('/api/config')\n }\n\n async getGo2rtcStreams(): Promise<Record<string, unknown>> {\n return this.request<Record<string, unknown>>('/api/go2rtc/streams')\n }\n\n // --- Events ---\n\n async getEvents(params: EventsQuery): Promise<FrigateRawEvent[]> {\n const query = buildQueryString(params)\n return this.request<FrigateRawEvent[]>(`/api/events${query}`)\n }\n\n async getReviewItems(params: ReviewQuery): Promise<FrigateReviewItem[]> {\n const query = buildQueryString(params)\n return this.request<FrigateReviewItem[]>(`/api/review${query}`)\n }\n\n // --- Recordings ---\n\n async getRecordings(\n camera: string,\n after: number,\n before: number,\n ): Promise<FrigateRawRecording[]> {\n const query = buildQueryString({ after, before })\n return this.request<FrigateRawRecording[]>(`/api/${encodeURIComponent(camera)}/recordings${query}`)\n }\n\n async getMotionActivity(params: MotionQuery): Promise<FrigateMotionData[]> {\n const query = buildQueryString(params)\n return this.request<FrigateMotionData[]>(`/api/motion_activity${query}`)\n }\n\n // --- Media ---\n\n async getLatestSnapshot(camera: string): Promise<Buffer> {\n return this.requestBuffer(`/api/${encodeURIComponent(camera)}/latest.jpg`)\n }\n\n async getEventThumbnail(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/thumbnail.jpg`)\n }\n\n async getEventSnapshot(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/snapshot.jpg`)\n }\n\n async getEventClip(eventId: string): Promise<Buffer> {\n return this.requestBuffer(`/api/events/${encodeURIComponent(eventId)}/clip.mp4`)\n }\n\n async getRecordingThumbnail(camera: string, timestampSec: number): Promise<Buffer> {\n return this.requestBuffer(\n `/api/${encodeURIComponent(camera)}/recordings/thumbnail-${timestampSec}.jpg`,\n )\n }\n\n // --- WebRTC ---\n\n async proxyWhepSdp(streamName: string, sdpOffer: string): Promise<string> {\n return this.requestText(`/api/go2rtc/webrtc?src=${encodeURIComponent(streamName)}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/sdp' },\n body: sdpOffer,\n })\n }\n\n // --- PTZ ---\n\n async getPtzInfo(\n camera: string,\n ): Promise<{ features?: string[]; presets?: string[] } | null> {\n try {\n return await this.request<{ features?: string[]; presets?: string[] }>(\n `/api/${encodeURIComponent(camera)}/ptz/info`,\n )\n } catch {\n return null\n }\n }\n\n async ptzCommand(\n camera: string,\n command: string,\n params?: Record<string, unknown>,\n ): Promise<void> {\n const query = buildQueryString({ ...params, command })\n await this.request<void>(`/api/${encodeURIComponent(camera)}/ptz${query}`)\n }\n\n // --- Test Connection ---\n\n async testConnection(): Promise<TestConnectionResult> {\n try {\n const config = await this.getConfig()\n const cameraNames = Object.keys(config.cameras ?? {})\n return {\n success: true,\n version: config.version,\n cameraCount: cameraNames.length,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n }\n}\n\n// --- Utility ---\n\nexport function buildQueryString(params: Record<string, unknown> | object): string {\n const entries = Object.entries(params).filter(\n ([, v]) => v !== undefined && v !== null,\n )\n if (entries.length === 0) {\n return ''\n }\n const searchParams = new URLSearchParams()\n for (const [key, value] of entries) {\n searchParams.set(key, String(value))\n }\n return `?${searchParams.toString()}`\n}\n","import type { MqttClient } from 'mqtt'\nimport { connect } from 'mqtt'\nimport type { LiveEvent } from '@camstack/types'\n\nexport interface FrigateMqttConfig {\n brokerUrl: string\n username?: string\n password?: string\n topicPrefix: string\n}\n\nexport interface MqttStatus {\n connected: boolean\n eventCount: number\n}\n\nexport class FrigateMqttClient {\n private client: MqttClient | null = null\n private readonly listeners: Set<(event: LiveEvent) => void> = new Set()\n private eventCount = 0\n\n constructor(\n private readonly config: FrigateMqttConfig,\n private readonly cameraResolutions: Map<string, { width: number; height: number }>,\n ) {}\n\n async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const { brokerUrl, username, password, topicPrefix } = this.config\n\n this.client = connect(brokerUrl, {\n username,\n password,\n reconnectPeriod: 5000,\n connectTimeout: 10000,\n })\n\n this.client.on('connect', () => {\n const prefix = topicPrefix\n this.client!.subscribe([\n `${prefix}/events`,\n `${prefix}/reviews`,\n `${prefix}/+/motion/state`,\n `${prefix}/+/audio/+`,\n ])\n resolve()\n })\n\n this.client.on('error', (err) => {\n reject(err)\n })\n\n this.client.on('message', (topic: string, payload: Buffer) => {\n const event = parseMqttMessage(\n topic,\n payload,\n topicPrefix,\n this.cameraResolutions,\n )\n if (event) {\n this.eventCount++\n for (const listener of this.listeners) {\n listener(event)\n }\n }\n })\n })\n }\n\n async disconnect(): Promise<void> {\n if (this.client) {\n await this.client.endAsync()\n this.client = null\n }\n this.listeners.clear()\n }\n\n subscribe(callback: (event: LiveEvent) => void): () => void {\n this.listeners.add(callback)\n return () => {\n this.listeners.delete(callback)\n }\n }\n\n get status(): MqttStatus {\n return {\n connected: this.client?.connected ?? false,\n eventCount: this.eventCount,\n }\n }\n}\n\n// --- Exported for testing ---\n\ninterface FrigateMqttEventPayload {\n type?: 'new' | 'update' | 'end'\n before?: FrigateMqttDetectionState\n after?: FrigateMqttDetectionState\n}\n\ninterface FrigateMqttDetectionState {\n id?: string\n camera?: string\n label?: string\n sub_label?: string\n top_score?: number\n start_time?: number\n end_time?: number | null\n current_zones?: string[]\n has_snapshot?: boolean\n has_clip?: boolean\n box?: [number, number, number, number]\n}\n\ninterface FrigateMqttReviewPayload {\n type?: 'new' | 'update' | 'end'\n before?: FrigateMqttReviewState\n after?: FrigateMqttReviewState\n}\n\ninterface FrigateMqttReviewState {\n id?: string\n camera?: string\n start_time?: number\n end_time?: number | null\n severity?: 'alert' | 'detection'\n data?: {\n objects?: string[]\n zones?: string[]\n sub_labels?: string[]\n }\n}\n\nexport function parseMqttMessage(\n topic: string,\n payload: Buffer,\n prefix: string,\n resolutions: Map<string, { width: number; height: number }>,\n): LiveEvent | null {\n const topicParts = topic.split('/')\n const prefixParts = prefix.split('/')\n const relative = topicParts.slice(prefixParts.length)\n\n // {prefix}/events\n if (relative.length === 1 && relative[0] === 'events') {\n return parseDetectionEvent(payload, resolutions)\n }\n\n // {prefix}/reviews\n if (relative.length === 1 && relative[0] === 'reviews') {\n return parseReviewEvent(payload)\n }\n\n // {prefix}/{camera}/motion/state\n if (relative.length === 3 && relative[1] === 'motion' && relative[2] === 'state') {\n const camera = relative[0]!\n return parseMotionEvent(camera, payload)\n }\n\n // {prefix}/{camera}/audio/{metric}\n if (relative.length === 3 && relative[1] === 'audio') {\n const camera = relative[0]!\n const metric = relative[2]!\n return parseAudioEvent(camera, metric, payload)\n }\n\n return null\n}\n\nfunction parseDetectionEvent(\n payload: Buffer,\n resolutions: Map<string, { width: number; height: number }>,\n): LiveEvent | null {\n try {\n const parsed: FrigateMqttEventPayload = JSON.parse(payload.toString())\n const state = parsed.after ?? parsed.before\n if (!state?.camera || !state?.label) {\n return null\n }\n\n const data: Record<string, unknown> = {\n eventType: parsed.type ?? 'update',\n id: state.id,\n label: state.label,\n subLabel: state.sub_label,\n score: state.top_score,\n startTime: state.start_time,\n endTime: state.end_time,\n zones: state.current_zones,\n hasSnapshot: state.has_snapshot,\n hasClip: state.has_clip,\n }\n\n if (state.box) {\n const resolution = resolutions.get(state.camera)\n if (resolution) {\n data.boundingBox = normalizeBoundingBox(state.box, resolution.width, resolution.height)\n } else {\n data.rawBox = state.box\n }\n }\n\n return {\n type: 'detection',\n camera: state.camera,\n timestamp: Date.now(),\n data,\n }\n } catch {\n return null\n }\n}\n\nfunction parseReviewEvent(payload: Buffer): LiveEvent | null {\n try {\n const parsed: FrigateMqttReviewPayload = JSON.parse(payload.toString())\n const state = parsed.after ?? parsed.before\n if (!state?.camera) {\n return null\n }\n\n return {\n type: 'review',\n camera: state.camera,\n timestamp: Date.now(),\n data: {\n eventType: parsed.type ?? 'update',\n id: state.id,\n severity: state.severity,\n startTime: state.start_time,\n endTime: state.end_time,\n objects: state.data?.objects,\n zones: state.data?.zones,\n subLabels: state.data?.sub_labels,\n },\n }\n } catch {\n return null\n }\n}\n\nfunction parseMotionEvent(camera: string, payload: Buffer): LiveEvent {\n const value = payload.toString().trim()\n const active = value === 'ON'\n\n return {\n type: 'motion',\n camera,\n timestamp: Date.now(),\n data: { active },\n }\n}\n\nfunction parseAudioEvent(camera: string, metric: string, payload: Buffer): LiveEvent | null {\n const raw = payload.toString().trim()\n const value = parseFloat(raw)\n\n if (isNaN(value)) {\n return null\n }\n\n return {\n type: 'audio',\n camera,\n timestamp: Date.now(),\n data: { metric, value },\n }\n}\n\n/**\n * Normalize Frigate bounding box from pixel coordinates to 0-1 range.\n * Frigate box format: [y_min, x_min, y_max, x_max] in pixels.\n * Output: { x, y, w, h } normalized to 0-1 range.\n */\nexport function normalizeBoundingBox(\n box: [number, number, number, number],\n width: number,\n height: number,\n): { x: number; y: number; w: number; h: number } {\n const [yMin, xMin, yMax, xMax] = box\n return {\n x: xMin / width,\n y: yMin / height,\n w: (xMax - xMin) / width,\n h: (yMax - yMin) / height,\n }\n}\n","import { DeviceType } from '@camstack/types'\nimport type {\n IDevice,\n DeviceState,\n DeviceMetadata,\n DeviceCapabilityName,\n IDeviceCapability,\n CamstackContext,\n ICamera,\n StreamOption,\n ConnectionMode,\n IMotionSensor,\n IObjectDetector,\n DetectionZone,\n DetectionLine,\n IAudioDetector,\n IEvents,\n EventQuery,\n EventQueryResult,\n DeviceStoredEvent,\n IRecording,\n RecordingSegment,\n TimeRange,\n} from '@camstack/types'\nimport type { FrigateApiClient } from './frigate-api'\n\nexport interface FrigateDeviceConfig {\n readonly cameraName: string\n readonly providerId: string\n readonly detectWidth: number\n readonly detectHeight: number\n readonly audioEnabled: boolean\n readonly recordEnabled: boolean\n readonly ptzEnabled: boolean\n readonly streams: StreamOption[]\n readonly frigateHost: string\n}\n\nexport class FrigateDevice implements IDevice {\n readonly id: string\n readonly name: string\n readonly providerId: string\n readonly type: DeviceType = DeviceType.Camera\n readonly capabilities: DeviceCapabilityName[]\n\n private readonly capabilityMap = new Map<DeviceCapabilityName, IDeviceCapability>()\n\n readonly ctx: CamstackContext\n\n constructor(\n private readonly config: FrigateDeviceConfig,\n private readonly api: FrigateApiClient,\n ctx: CamstackContext,\n ) {\n this.id = `${config.providerId}/${config.cameraName}`\n this.name = config.cameraName\n this.providerId = config.providerId\n this.ctx = ctx\n\n // Capabilities based on camera config\n const caps: DeviceCapabilityName[] = ['camera', 'events', 'recording', 'motionSensor', 'objectDetector']\n if (config.audioEnabled) caps.push('audioDetector')\n this.capabilities = caps\n\n this.capabilityMap.set('camera', this.createCamera())\n this.capabilityMap.set('motionSensor', this.createMotionSensor())\n this.capabilityMap.set('objectDetector', this.createObjectDetector())\n this.capabilityMap.set('events', this.createEvents())\n this.capabilityMap.set('recording', this.createRecording())\n if (config.audioEnabled) {\n this.capabilityMap.set('audioDetector', this.createAudioDetector())\n }\n }\n\n getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null {\n return (this.capabilityMap.get(cap) as T) ?? null\n }\n\n hasCapability(cap: DeviceCapabilityName): boolean {\n return this.capabilityMap.has(cap)\n }\n\n getState(): DeviceState {\n return { online: true }\n }\n\n getMetadata(): DeviceMetadata {\n return { manufacturer: 'Frigate NVR' }\n }\n\n // --- Capability Factories ---\n\n private createCamera(): ICamera {\n const { api, config } = this\n return {\n kind: 'camera',\n async getSnapshot() { return api.getLatestSnapshot(config.cameraName) },\n async getStreamOptions(): Promise<StreamOption[]> {\n return config.streams.map(s => ({\n ...s,\n url: s.url ?? `rtsp://${config.frigateHost}:8554/${s.id}`,\n }))\n },\n async getStreamUrl(option: StreamOption) {\n return `/api/go2rtc/webrtc?src=${encodeURIComponent(option.id)}`\n },\n getConnectionMode(): ConnectionMode { return 'on-demand' },\n async setConnectionMode() {},\n }\n }\n\n private createMotionSensor(): IMotionSensor {\n let motionActive = false\n return {\n kind: 'motionSensor',\n isMotionDetected() { return motionActive },\n subscribe() { return () => {} }, // real-time via MQTT through provider\n }\n }\n\n private createObjectDetector(): IObjectDetector {\n const zones: DetectionZone[] = []\n const lines: DetectionLine[] = []\n\n return {\n kind: 'objectDetector',\n getLabels() { return [] },\n onDetections() { return () => {} },\n\n // Zone management\n async getZones() { return zones },\n async addZone(zone) { zones.push(zone) },\n async removeZone(id) { const i = zones.findIndex(z => z.id === id); if (i >= 0) zones.splice(i, 1) },\n async updateZone(id, partial) { const z = zones.find(z => z.id === id); if (z) Object.assign(z, partial) },\n\n // Line management\n async getLines() { return lines },\n async addLine(line) { lines.push(line) },\n async removeLine(id) { const i = lines.findIndex(l => l.id === id); if (i >= 0) lines.splice(i, 1) },\n\n // Active tracking (populated by MQTT events in real-time)\n getActiveDetections() { return [] },\n getZoneDetections() { return [] },\n getStationaryDetections() { return [] },\n getMovingDetections() { return [] },\n }\n }\n\n private createEvents(): IEvents {\n const { api, config } = this\n return {\n kind: 'events',\n\n async getEvents(query: EventQuery): Promise<EventQueryResult> {\n const raw = await api.getEvents({\n cameras: config.cameraName,\n after: query.since,\n before: query.until,\n limit: query.limit,\n labels: query.detectionClasses?.join(','),\n })\n\n const events: DeviceStoredEvent[] = raw.map(e => ({\n id: e.id,\n type: 'detection',\n timestamp: e.start_time * 1000, // Frigate uses seconds, we use ms\n endTimestamp: e.end_time ? e.end_time * 1000 : undefined,\n label: e.label,\n score: e.top_score,\n hasClip: e.has_clip,\n hasSnapshot: e.has_snapshot,\n thumbnailUrl: `/api/events/${e.id}/thumbnail.jpg`,\n }))\n\n return { events, total: events.length }\n },\n\n async getEventThumbnail(eventId: string) {\n try { return await api.getEventThumbnail(eventId) }\n catch { return null }\n },\n\n async getEventSnapshot(eventId: string) {\n try { return await api.getEventSnapshot(eventId) }\n catch { return null }\n },\n\n async getEventClipUrl(eventId: string) {\n return `/api/events/${encodeURIComponent(eventId)}/clip.mp4`\n },\n }\n }\n\n private createRecording(): IRecording {\n const { api, config } = this\n return {\n kind: 'recording',\n\n async getSegments(range: TimeRange): Promise<RecordingSegment[]> {\n const recs = await api.getRecordings(config.cameraName, range.since, range.until)\n return recs.map(r => ({\n id: r.id ?? `${config.cameraName}-${r.start_time}`,\n startTime: r.start_time * 1000,\n endTime: r.end_time * 1000,\n duration: r.duration,\n }))\n },\n\n async getPlaybackUrl(startTime: number, endTime: number) {\n const startSec = Math.floor(startTime / 1000)\n const endSec = Math.floor(endTime / 1000)\n return `/api/${encodeURIComponent(config.cameraName)}/start/${startSec}/end/${endSec}/clip.mp4`\n },\n\n async getThumbnailAt(timestampMs: number) {\n try { return await api.getRecordingThumbnail(config.cameraName, Math.floor(timestampMs / 1000)) }\n catch { return null }\n },\n }\n }\n\n private createAudioDetector(): IAudioDetector {\n return {\n kind: 'audioDetector',\n getAudioLevel() { return 0 },\n subscribe() { return () => {} }, // real-time via MQTT through provider\n }\n }\n}\n","import type { IElementConfig } from '@camstack/types'\nimport type { IStorageLocation } from '@camstack/types'\n\n/**\n * Persisted config store for a single element.\n * Reads/writes to the element's scoped storage under the 'config' collection.\n * Notifies listeners on every change.\n */\nexport class ElementConfigStore implements IElementConfig {\n private cache: Record<string, unknown> = {}\n private listeners: Set<(config: Record<string, unknown>) => void> = new Set()\n private loaded = false\n\n constructor(\n private readonly elementId: string,\n private readonly storage: IStorageLocation,\n ) {}\n\n /** Load config from storage into cache. Called once on first access. */\n private async ensureLoaded(): Promise<void> {\n if (this.loaded) return\n if (!this.storage.structured) {\n this.loaded = true\n return\n }\n\n try {\n const records = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n if (records.length > 0) {\n this.cache = (records[0] as any).data ?? {}\n }\n } catch {\n // Storage might not be ready yet\n }\n this.loaded = true\n }\n\n getAll(): Record<string, unknown> {\n return { ...this.cache }\n }\n\n get<T = unknown>(key: string): T | undefined {\n const parts = key.split('.')\n let current: unknown = this.cache\n for (const part of parts) {\n if (current == null || typeof current !== 'object') return undefined\n current = (current as Record<string, unknown>)[part]\n }\n return current as T | undefined\n }\n\n async set(key: string, value: unknown): Promise<void> {\n await this.ensureLoaded()\n setNestedValue(this.cache, key, value)\n await this.persist()\n this.notifyListeners()\n }\n\n async setAll(config: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n this.cache = { ...config }\n await this.persist()\n this.notifyListeners()\n }\n\n onChange(callback: (config: Record<string, unknown>) => void): () => void {\n this.listeners.add(callback)\n return () => { this.listeners.delete(callback) }\n }\n\n /** Initialize from storage — called by ContextFactory after creation */\n async load(): Promise<void> {\n await this.ensureLoaded()\n }\n\n /** Initialize with default values (doesn't overwrite existing) */\n async loadDefaults(defaults: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n if (Object.keys(this.cache).length === 0) {\n this.cache = { ...defaults }\n await this.persist()\n }\n }\n\n private async persist(): Promise<void> {\n if (!this.storage.structured) return\n\n try {\n const existing = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n\n if (existing.length > 0) {\n await this.storage.structured.update('config', this.elementId, this.cache)\n } else {\n await this.storage.structured.insert({\n collection: 'config',\n id: this.elementId,\n data: this.cache,\n })\n }\n } catch {\n // Storage might not be ready\n }\n }\n\n private notifyListeners(): void {\n const snapshot = this.getAll()\n for (const listener of this.listeners) {\n try {\n listener(snapshot)\n } catch {\n // Don't let one bad listener kill others\n }\n }\n }\n}\n\nfunction setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.')\n let current: Record<string, unknown> = obj\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n current[parts[parts.length - 1]!] = value\n}\n","import { FrigateApiClient } from './frigate-api'\nimport { FrigateMqttClient } from './frigate-mqtt'\nimport { FrigateDevice } from './frigate-device'\nimport { ElementConfigStore } from './element-config-store'\nimport type { StreamOption } from '@camstack/types'\nimport type {\n IDeviceProvider,\n ProviderStatus,\n DiscoveredDevice,\n LiveEvent,\n} from '@camstack/types'\nimport type { IDevice } from '@camstack/types'\nimport type { CamstackContext } from '@camstack/types'\nimport type { FrigateConfig, FrigateCameraConfig } from './frigate-types'\n\nexport interface FrigateProviderConfig {\n readonly id: string\n readonly name: string\n readonly url: string\n readonly username?: string\n readonly password?: string\n readonly mqtt?: {\n readonly brokerUrl: string\n readonly username?: string\n readonly password?: string\n readonly topicPrefix?: string\n }\n}\n\nconst ADOPTED_DEVICES_KEY = 'adoptedDevices'\n\nexport class FrigateProvider implements IDeviceProvider {\n readonly id: string\n readonly type = 'frigate'\n readonly name: string\n readonly discoveryMode: 'auto' = 'auto'\n readonly ctx: CamstackContext\n\n private readonly api: FrigateApiClient\n private mqtt: FrigateMqttClient | null = null\n private devices: readonly FrigateDevice[] = []\n private readonly liveEventListeners: Set<(event: LiveEvent) => void> = new Set()\n private mqttUnsubscribe?: () => void\n\n /** Cached Frigate config, refreshed on start() and discoverDevices() */\n private frigateConfig: FrigateConfig | null = null\n private go2rtcStreams: Record<string, unknown> = {}\n private frigateHost: string\n\n constructor(\n private readonly config: FrigateProviderConfig,\n ctx: CamstackContext,\n ) {\n this.id = config.id\n this.name = config.name\n this.ctx = ctx\n this.api = new FrigateApiClient({\n baseUrl: config.url,\n username: config.username,\n password: config.password,\n })\n this.frigateHost = new URL(config.url).hostname\n }\n\n async start(): Promise<void> {\n this.ctx.logger.info(`Starting Frigate provider: ${this.config.url}`)\n\n // 1. Fetch Frigate config to discover cameras\n this.frigateConfig = await this.api.getConfig()\n this.go2rtcStreams = await this.api.getGo2rtcStreams()\n\n // 2. Build camera resolutions map (for MQTT bbox normalization)\n const resolutions = new Map<string, { width: number; height: number }>()\n for (const [name, cam] of Object.entries(this.frigateConfig.cameras)) {\n if (cam.detect) {\n resolutions.set(name, { width: cam.detect.width, height: cam.detect.height })\n }\n }\n\n // 3. Load only previously adopted cameras\n const adoptedIds = this.getAdoptedDeviceIds()\n if (adoptedIds.length > 0) {\n const devices: FrigateDevice[] = []\n for (const cameraName of adoptedIds) {\n const cam = this.frigateConfig.cameras[cameraName]\n if (!cam || cam.enabled === false) {\n this.ctx.logger.warn(`Adopted camera \"${cameraName}\" no longer available in Frigate config, skipping`)\n continue\n }\n devices.push(this.createDeviceFromCamera(cameraName, cam))\n }\n this.devices = devices\n this.ctx.logger.info(`Loaded ${devices.length} adopted cameras`)\n } else {\n this.ctx.logger.info('No adopted cameras yet — use discoverDevices() + adoptDevice() to import cameras')\n }\n\n // 4. Connect MQTT if configured\n if (this.config.mqtt?.brokerUrl) {\n this.mqtt = new FrigateMqttClient(\n {\n brokerUrl: this.config.mqtt.brokerUrl,\n username: this.config.mqtt.username,\n password: this.config.mqtt.password,\n topicPrefix: this.config.mqtt.topicPrefix ?? 'frigate',\n },\n resolutions,\n )\n\n await this.mqtt.connect()\n this.mqttUnsubscribe = this.mqtt.subscribe((event) => {\n for (const listener of this.liveEventListeners) {\n listener(event)\n }\n })\n this.ctx.logger.info('MQTT connected')\n }\n }\n\n async stop(): Promise<void> {\n this.ctx.logger.info('Stopping Frigate provider')\n this.mqttUnsubscribe?.()\n this.mqttUnsubscribe = undefined\n await this.mqtt?.disconnect()\n this.mqtt = null\n this.devices = []\n this.frigateConfig = null\n this.go2rtcStreams = {}\n }\n\n getStatus(): ProviderStatus {\n return {\n connected: this.mqtt?.status.connected ?? false,\n deviceCount: this.devices.length,\n }\n }\n\n async discoverDevices(): Promise<DiscoveredDevice[]> {\n // Refresh from Frigate API to get the latest camera list\n this.frigateConfig = await this.api.getConfig()\n this.go2rtcStreams = await this.api.getGo2rtcStreams()\n\n const adoptedIds = this.getAdoptedDeviceIds()\n const discovered: DiscoveredDevice[] = []\n\n for (const [name, cam] of Object.entries(this.frigateConfig.cameras)) {\n if (cam.enabled === false) continue\n\n // Build capabilities list to match what FrigateDevice would produce\n const caps: DiscoveredDevice['capabilities'] = ['camera', 'events', 'recording', 'motionSensor', 'objectDetector']\n if (cam.audio?.enabled) caps.push('audioDetector')\n\n discovered.push({\n externalId: name,\n name,\n type: 'camera',\n capabilities: caps,\n metadata: { manufacturer: 'Frigate NVR' },\n })\n }\n\n return discovered\n }\n\n async adoptDevice(externalId: string, _config?: Record<string, unknown>): Promise<IDevice> {\n // 1. Ensure we have a fresh Frigate config\n if (!this.frigateConfig) {\n this.frigateConfig = await this.api.getConfig()\n this.go2rtcStreams = await this.api.getGo2rtcStreams()\n }\n\n // 2. Find the camera in Frigate config\n const cam = this.frigateConfig.cameras[externalId]\n if (!cam) {\n throw new Error(`Camera \"${externalId}\" not found in Frigate config`)\n }\n if (cam.enabled === false) {\n throw new Error(`Camera \"${externalId}\" is disabled in Frigate config`)\n }\n\n // 3. Check if already adopted\n const existing = this.devices.find((d) => d.name === externalId)\n if (existing) {\n this.ctx.logger.info(`Camera \"${externalId}\" is already adopted`)\n return existing\n }\n\n // 4. Create the device\n const device = this.createDeviceFromCamera(externalId, cam)\n\n // 5. Add to the in-memory device list (immutable update)\n this.devices = [...this.devices, device]\n\n // 6. Persist adoption\n await this.persistAdoptedDeviceId(externalId)\n\n this.ctx.logger.info(`Adopted camera \"${externalId}\" (total: ${this.devices.length})`)\n return device\n }\n\n getDevices(): IDevice[] {\n return [...this.devices]\n }\n\n subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void {\n this.liveEventListeners.add(callback)\n return () => { this.liveEventListeners.delete(callback) }\n }\n\n // --- Private helpers ---\n\n private createDeviceFromCamera(cameraName: string, cam: FrigateCameraConfig): FrigateDevice {\n const streams = buildStreamOptions(cameraName, this.go2rtcStreams)\n const deviceId = `${this.id}/${cameraName}`\n\n const deviceCtx: CamstackContext = {\n id: `device:${deviceId}`,\n logger: this.ctx.logger.child(cameraName),\n eventBus: this.ctx.eventBus,\n storage: this.ctx.storage,\n config: new ElementConfigStore(`device:${deviceId}`, this.ctx.storage),\n }\n\n return new FrigateDevice(\n {\n cameraName,\n providerId: this.id,\n detectWidth: cam.detect?.width ?? 1920,\n detectHeight: cam.detect?.height ?? 1080,\n audioEnabled: cam.audio?.enabled ?? false,\n recordEnabled: cam.record?.enabled ?? false,\n ptzEnabled: false,\n streams,\n frigateHost: this.frigateHost,\n },\n this.api,\n deviceCtx,\n )\n }\n\n private getAdoptedDeviceIds(): readonly string[] {\n return this.ctx.config.get<string[]>(ADOPTED_DEVICES_KEY) ?? []\n }\n\n private async persistAdoptedDeviceId(cameraName: string): Promise<void> {\n const current = this.getAdoptedDeviceIds()\n if (current.includes(cameraName)) return\n await this.ctx.config.set(ADOPTED_DEVICES_KEY, [...current, cameraName])\n }\n}\n\n// --- Helpers ---\n\nfunction buildStreamOptions(\n cameraName: string,\n go2rtcStreams: Record<string, unknown>,\n): StreamOption[] {\n const streams: StreamOption[] = []\n for (const streamName of Object.keys(go2rtcStreams)) {\n if (!streamName.startsWith(cameraName)) continue\n const isSub = streamName.includes('sub') || streamName.includes('ext') || streamName.includes('medium')\n streams.push({ id: streamName, label: streamName, protocol: 'rtsp', quality: isSub ? 'sub' : 'main' })\n }\n return streams\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n ConfigUISchema,\n IConfigurable,\n} from '@camstack/types'\nimport { FrigateProvider } from './frigate-provider'\nimport type { FrigateProviderConfig } from './frigate-provider'\n\nexport class FrigateProviderAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'provider-frigate',\n name: 'Frigate NVR Provider',\n version: '0.1.0',\n description: 'Integrazione con Frigate NVR per camere e detection',\n capabilities: ['device-provider'],\n }\n\n private provider: FrigateProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n const config = context.addonConfig as unknown as FrigateProviderConfig\n if (!config.url) {\n context.logger.warn('Frigate provider: no URL configured, skipping initialization')\n return\n }\n\n const providerConfig: FrigateProviderConfig = {\n id: config.id ?? 'frigate-default',\n name: config.name ?? 'Frigate NVR',\n url: config.url,\n username: config.username,\n password: config.password,\n mqtt: config.mqtt,\n }\n\n this.provider = new FrigateProvider(providerConfig, {\n id: context.id,\n logger: context.logger,\n eventBus: context.eventBus,\n storage: context.storage,\n config: context.config,\n })\n\n context.logger.info('Frigate provider addon initialized')\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.stop()\n this.provider = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'device-provider' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'connection',\n title: 'Frigate Connection',\n description: 'Configure the connection to your Frigate NVR instance',\n columns: 2,\n fields: [\n { type: 'text', key: 'url', label: 'Frigate URL', required: true, placeholder: 'http://frigate:5000' },\n { type: 'text', key: 'name', label: 'Display Name', placeholder: 'Frigate NVR' },\n { type: 'text', key: 'username', label: 'Username' },\n { type: 'password', key: 'password', label: 'Password', showToggle: true },\n ],\n },\n {\n id: 'mqtt',\n title: 'MQTT Settings',\n description: 'Optional: Connect to Frigate MQTT for real-time events',\n style: 'accordion',\n defaultCollapsed: true,\n columns: 2,\n fields: [\n { type: 'text', key: 'mqtt.brokerUrl', label: 'MQTT Broker URL', placeholder: 'mqtt://broker:1883' },\n { type: 'text', key: 'mqtt.topicPrefix', label: 'Topic Prefix', placeholder: 'frigate' },\n { type: 'text', key: 'mqtt.username', label: 'MQTT Username' },\n { type: 'password', key: 'mqtt.password', label: 'MQTT Password', showToggle: true },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // Restart provider with new config\n }\n}\n"],"mappings":"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","names":["z"]}
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-frigate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Frigate NVR device provider addon for CamStack",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"camstack",
|
|
7
|
+
"addon",
|
|
8
|
+
"camstack-addon",
|
|
9
|
+
"frigate",
|
|
10
|
+
"nvr",
|
|
11
|
+
"provider",
|
|
12
|
+
"camera"
|
|
13
|
+
],
|
|
6
14
|
"license": "MIT",
|
|
7
15
|
"repository": {
|
|
8
16
|
"type": "git",
|
|
@@ -12,7 +20,12 @@
|
|
|
12
20
|
"module": "./dist/index.mjs",
|
|
13
21
|
"types": "./dist/index.d.ts",
|
|
14
22
|
"exports": {
|
|
15
|
-
".": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./dist/index.mjs",
|
|
25
|
+
"require": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
16
29
|
},
|
|
17
30
|
"camstack": {
|
|
18
31
|
"addons": [
|
|
@@ -20,13 +33,22 @@
|
|
|
20
33
|
"id": "provider-frigate",
|
|
21
34
|
"entry": "./dist/addon.js",
|
|
22
35
|
"slot": "provider",
|
|
36
|
+
"icon": "assets/icon.svg",
|
|
37
|
+
"color": "#3b82f6",
|
|
38
|
+
"instanceMode": "multiple",
|
|
23
39
|
"capabilities": [
|
|
24
|
-
{
|
|
40
|
+
{
|
|
41
|
+
"name": "device-provider",
|
|
42
|
+
"mode": "collection"
|
|
43
|
+
}
|
|
25
44
|
]
|
|
26
45
|
}
|
|
27
46
|
]
|
|
28
47
|
},
|
|
29
|
-
"files": [
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"assets"
|
|
51
|
+
],
|
|
30
52
|
"scripts": {
|
|
31
53
|
"build": "tsup",
|
|
32
54
|
"dev": "tsup --watch",
|