@camstack/addon-provider-frigate 0.1.0 → 0.1.2

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.
@@ -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 = "camera";
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
- const frigateConfig = await this.api.getConfig();
757
- const go2rtcStreams = await this.api.getGo2rtcStreams();
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 frigateHost = new URL(this.config.url).hostname;
765
- const devices = [];
766
- for (const [name, cam] of Object.entries(frigateConfig.cameras)) {
767
- if (cam.enabled === false) continue;
768
- const streams = buildStreamOptions(name, go2rtcStreams);
769
- const deviceId = `${this.id}/${name}`;
770
- const deviceCtx = {
771
- id: `device:${deviceId}`,
772
- logger: this.ctx.logger.child(name),
773
- eventBus: this.ctx.eventBus,
774
- storage: this.ctx.storage,
775
- config: new ElementConfigStore(`device:${deviceId}`, this.ctx.storage)
776
- };
777
- devices.push(
778
- new FrigateDevice(
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
- return this.devices.map((d) => ({
832
- externalId: d.name,
833
- name: d.name,
834
- type: d.type,
835
- capabilities: d.capabilities,
836
- metadata: d.getMetadata()
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":"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","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 = "camera";
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
- const frigateConfig = await this.api.getConfig();
727
- const go2rtcStreams = await this.api.getGo2rtcStreams();
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 frigateHost = new URL(this.config.url).hostname;
735
- const devices = [];
736
- for (const [name, cam] of Object.entries(frigateConfig.cameras)) {
737
- if (cam.enabled === false) continue;
738
- const streams = buildStreamOptions(name, go2rtcStreams);
739
- const deviceId = `${this.id}/${name}`;
740
- const deviceCtx = {
741
- id: `device:${deviceId}`,
742
- logger: this.ctx.logger.child(name),
743
- eventBus: this.ctx.eventBus,
744
- storage: this.ctx.storage,
745
- config: new ElementConfigStore(`device:${deviceId}`, this.ctx.storage)
746
- };
747
- devices.push(
748
- new FrigateDevice(
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
- return this.devices.map((d) => ({
802
- externalId: d.name,
803
- name: d.name,
804
- type: d.type,
805
- capabilities: d.capabilities,
806
- metadata: d.getMetadata()
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;
@@ -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":";AA0CO,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,SAAS,eAAe;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,SAAS,QAAQ,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/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.0",
3
+ "version": "0.1.2",
4
4
  "description": "Frigate NVR device provider addon for CamStack",
5
- "keywords": ["camstack", "addon", "camstack-addon", "frigate", "nvr", "provider", "camera"],
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,21 +20,36 @@
12
20
  "module": "./dist/index.mjs",
13
21
  "types": "./dist/index.d.ts",
14
22
  "exports": {
15
- ".": { "import": "./dist/index.mjs", "require": "./dist/index.js", "types": "./dist/index.d.ts" }
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": {
31
+ "displayName": "Frigate Provider",
18
32
  "addons": [
19
33
  {
20
34
  "id": "provider-frigate",
21
35
  "entry": "./dist/addon.js",
22
36
  "slot": "provider",
37
+ "icon": "assets/icon.svg",
38
+ "color": "#3b82f6",
39
+ "instanceMode": "multiple",
23
40
  "capabilities": [
24
- { "name": "device-provider", "mode": "collection" }
41
+ {
42
+ "name": "device-provider",
43
+ "mode": "collection"
44
+ }
25
45
  ]
26
46
  }
27
47
  ]
28
48
  },
29
- "files": ["dist"],
49
+ "files": [
50
+ "dist",
51
+ "assets"
52
+ ],
30
53
  "scripts": {
31
54
  "build": "tsup",
32
55
  "dev": "tsup --watch",