@camstack/sdk 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adaptive-stream.d.ts +91 -0
- package/dist/backend-client.d.ts +315 -0
- package/dist/backend-router.d.ts +5 -0
- package/dist/camera.d.ts +37 -0
- package/dist/detection.d.ts +58 -0
- package/dist/devices.d.ts +147 -0
- package/dist/features.d.ts +31 -0
- package/dist/index.d.ts +15 -0
- package/dist/nvr.d.ts +285 -0
- package/dist/timeline.d.ts +177 -0
- package/dist/types.d.ts +44 -0
- package/package.json +5 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Stream Session — negotiates optimal stream quality with the proxy.
|
|
3
|
+
*
|
|
4
|
+
* The client reports viewport size and network conditions periodically.
|
|
5
|
+
* The proxy selects the best stream from the provider's available streams
|
|
6
|
+
* and instructs the client to switch via go2rtc stream renegotiation.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. Client opens session → sends initial constraints (viewport, network)
|
|
10
|
+
* 2. Server evaluates available streams → picks optimal → responds with stream assignment
|
|
11
|
+
* 3. Client connects to assigned go2rtc stream via WebRTC WHEP
|
|
12
|
+
* 4. Periodically, client reports updated stats → server may switch stream
|
|
13
|
+
* 5. On switch: client tears down old PC, creates new PC to new go2rtc stream
|
|
14
|
+
*/
|
|
15
|
+
/** Client-reported constraints for stream selection. */
|
|
16
|
+
export interface StreamConstraints {
|
|
17
|
+
/** Viewport width in CSS pixels. */
|
|
18
|
+
viewportWidth: number;
|
|
19
|
+
/** Viewport height in CSS pixels. */
|
|
20
|
+
viewportHeight: number;
|
|
21
|
+
/** Device pixel ratio (retina = 2+). */
|
|
22
|
+
pixelRatio?: number;
|
|
23
|
+
/** Estimated downlink bandwidth in Mbps (from navigator.connection or WebRTC stats). */
|
|
24
|
+
bandwidthMbps?: number;
|
|
25
|
+
/** Round-trip time in ms (from WebRTC stats). */
|
|
26
|
+
rttMs?: number;
|
|
27
|
+
/** Packet loss ratio 0-1 (from WebRTC stats). */
|
|
28
|
+
packetLoss?: number;
|
|
29
|
+
/** Whether the client is on a cellular network. */
|
|
30
|
+
isCellular?: boolean;
|
|
31
|
+
/** Whether the client prefers low latency over quality. */
|
|
32
|
+
preferLowLatency?: boolean;
|
|
33
|
+
/** Maximum resolution the client wants (e.g., from a user setting). */
|
|
34
|
+
maxResolution?: "auto" | "high" | "medium" | "low";
|
|
35
|
+
}
|
|
36
|
+
/** Server-assigned stream for the client. */
|
|
37
|
+
export interface StreamAssignment {
|
|
38
|
+
/** go2rtc stream name to connect to. */
|
|
39
|
+
streamName: string;
|
|
40
|
+
/** Stream profile (main/sub/ext). */
|
|
41
|
+
profile: string;
|
|
42
|
+
/** Transport type (native/rtsp/rtmp). */
|
|
43
|
+
transport: string;
|
|
44
|
+
/** Human-readable label. */
|
|
45
|
+
label: string;
|
|
46
|
+
/** Stream metadata (if known). */
|
|
47
|
+
metadata?: {
|
|
48
|
+
width?: number;
|
|
49
|
+
height?: number;
|
|
50
|
+
fps?: number;
|
|
51
|
+
bitrate?: number;
|
|
52
|
+
codec?: string;
|
|
53
|
+
};
|
|
54
|
+
/** Reason for this selection. */
|
|
55
|
+
reason: string;
|
|
56
|
+
}
|
|
57
|
+
/** Stream selection request from client → server. */
|
|
58
|
+
export interface StreamSelectionRequest {
|
|
59
|
+
providerId: string;
|
|
60
|
+
camera: string;
|
|
61
|
+
constraints: StreamConstraints;
|
|
62
|
+
/** Current stream (for comparison — only switch if significantly better). */
|
|
63
|
+
currentStreamName?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Stream selection response from server → client. */
|
|
66
|
+
export interface StreamSelectionResponse {
|
|
67
|
+
assignment: StreamAssignment;
|
|
68
|
+
/** Available alternatives the client can manually pick. */
|
|
69
|
+
alternatives: StreamAssignment[];
|
|
70
|
+
/** Seconds until next automatic re-evaluation. */
|
|
71
|
+
nextEvalSeconds: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Select the optimal stream from available options based on client constraints.
|
|
75
|
+
* This is a pure function — no side effects.
|
|
76
|
+
*/
|
|
77
|
+
export declare function selectOptimalStream(streams: Array<{
|
|
78
|
+
streamName: string;
|
|
79
|
+
profile: string;
|
|
80
|
+
transport: string;
|
|
81
|
+
label: string;
|
|
82
|
+
metadata?: {
|
|
83
|
+
width?: number;
|
|
84
|
+
height?: number;
|
|
85
|
+
fps?: number;
|
|
86
|
+
bitrate?: number;
|
|
87
|
+
codec?: string;
|
|
88
|
+
};
|
|
89
|
+
}>, constraints: StreamConstraints, defaultTransport?: string): StreamAssignment | null;
|
|
90
|
+
/** Determine seconds until next re-evaluation based on stability. */
|
|
91
|
+
export declare function getNextEvalInterval(constraints: StreamConstraints, wasSwitch: boolean): number;
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend client — connects directly to the CamStack NestJS backend via tRPC.
|
|
3
|
+
*
|
|
4
|
+
* Use this when the client needs to talk to the backend server directly
|
|
5
|
+
* (admin UI, local agent, embedded mode) rather than going through the proxy.
|
|
6
|
+
*
|
|
7
|
+
* Features: full typed access to all backend tRPC routes (auth, devices, providers,
|
|
8
|
+
* pipeline, agents, addons, bridge pipeline, recording, events, live subscriptions).
|
|
9
|
+
*/
|
|
10
|
+
import { createTRPCClient } from "@trpc/client";
|
|
11
|
+
import type { BackendAppRouter } from "./backend-router.js";
|
|
12
|
+
export type SettingsSection = 'server' | 'auth' | 'features' | 'storage' | 'logging' | 'addons' | 'recording' | 'streaming' | 'ffmpeg' | 'detection' | 'backup' | 'retention';
|
|
13
|
+
export interface BackendClientConfig {
|
|
14
|
+
/** Backend server URL (e.g. "http://localhost:4443") */
|
|
15
|
+
readonly serverUrl: string;
|
|
16
|
+
/** JWT token for authentication */
|
|
17
|
+
readonly token?: string;
|
|
18
|
+
/** Connection timeout in ms (default: 10000) */
|
|
19
|
+
readonly connectTimeoutMs?: number;
|
|
20
|
+
/** Use WebSocket for all queries/mutations (default: true in browser, false in Node) */
|
|
21
|
+
readonly useWebSocket?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare class BackendClient {
|
|
24
|
+
/** Raw tRPC client — for advanced usage / direct path access */
|
|
25
|
+
readonly trpc: ReturnType<typeof createTRPCClient<BackendAppRouter>>;
|
|
26
|
+
readonly serverUrl: string;
|
|
27
|
+
private token;
|
|
28
|
+
private wsClient;
|
|
29
|
+
constructor(config: BackendClientConfig);
|
|
30
|
+
/** Update the auth token (e.g. after login) */
|
|
31
|
+
setToken(token: string): void;
|
|
32
|
+
/** Close the WebSocket connection (if using WS transport) */
|
|
33
|
+
close(): void;
|
|
34
|
+
login(username: string, password: string): Promise<{
|
|
35
|
+
token: string;
|
|
36
|
+
user: {
|
|
37
|
+
id: string;
|
|
38
|
+
username: string;
|
|
39
|
+
role: import("@camstack/types").UserRole;
|
|
40
|
+
};
|
|
41
|
+
}>;
|
|
42
|
+
getMe(): Promise<import("@camstack/types").AuthenticatedUser>;
|
|
43
|
+
logout(): Promise<{
|
|
44
|
+
success: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
getSystemInfo(): Promise<{
|
|
47
|
+
version: string;
|
|
48
|
+
uptime: number;
|
|
49
|
+
nodeVersion: string;
|
|
50
|
+
platform: NodeJS.Platform;
|
|
51
|
+
features: import("@camstack/types").FeatureManifest;
|
|
52
|
+
}>;
|
|
53
|
+
getFeatureFlags(): Promise<import("@camstack/types").FeatureManifest>;
|
|
54
|
+
listProviders(): Promise<readonly import("@camstack/types").ProviderListItem[]>;
|
|
55
|
+
getProvider(providerId: string): Promise<{
|
|
56
|
+
id: string;
|
|
57
|
+
type: string;
|
|
58
|
+
name: string;
|
|
59
|
+
discoveryMode: "auto" | "manual" | "both";
|
|
60
|
+
status: import("@camstack/types").ProviderStatus;
|
|
61
|
+
deviceCount: number;
|
|
62
|
+
}>;
|
|
63
|
+
startProvider(providerId: string): Promise<{
|
|
64
|
+
success: boolean;
|
|
65
|
+
}>;
|
|
66
|
+
stopProvider(providerId: string): Promise<{
|
|
67
|
+
success: boolean;
|
|
68
|
+
}>;
|
|
69
|
+
listProviderTypes(): Promise<{
|
|
70
|
+
addonId: string;
|
|
71
|
+
name: string;
|
|
72
|
+
description: string;
|
|
73
|
+
iconUrl: string | null;
|
|
74
|
+
color: string;
|
|
75
|
+
instanceMode: "unique" | "multiple";
|
|
76
|
+
discoveryMode: "auto" | "manual" | "both";
|
|
77
|
+
existingInstances: {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
}[];
|
|
81
|
+
canAdd: boolean;
|
|
82
|
+
}[]>;
|
|
83
|
+
listDevices(): Promise<{
|
|
84
|
+
id: string;
|
|
85
|
+
name: string;
|
|
86
|
+
providerId: string;
|
|
87
|
+
type: import("@camstack/types").DeviceType;
|
|
88
|
+
capabilities: import("@camstack/types").DeviceCapabilityName[];
|
|
89
|
+
state: import("@camstack/types").DeviceState;
|
|
90
|
+
}[]>;
|
|
91
|
+
getDevice(deviceId: string): Promise<{
|
|
92
|
+
id: string;
|
|
93
|
+
name: string;
|
|
94
|
+
providerId: string;
|
|
95
|
+
type: import("@camstack/types").DeviceType;
|
|
96
|
+
capabilities: import("@camstack/types").DeviceCapabilityName[];
|
|
97
|
+
state: import("@camstack/types").DeviceState;
|
|
98
|
+
metadata: import("@camstack/types").DeviceMetadata;
|
|
99
|
+
}>;
|
|
100
|
+
discoverDevices(providerId: string): Promise<import("@camstack/types").DiscoveredDevice[]>;
|
|
101
|
+
adoptDevice(providerId: string, externalId: string): Promise<import("@camstack/types").IDevice>;
|
|
102
|
+
createDevice(providerId: string, config: Record<string, unknown>): Promise<import("@camstack/types").IDevice>;
|
|
103
|
+
/** Returns the URL path for a static asset served by an addon. */
|
|
104
|
+
getAddonAssetUrl(addonId: string, assetPath: string): string;
|
|
105
|
+
listAddons(): Promise<{
|
|
106
|
+
manifest: import("@camstack/types").AddonManifest & {
|
|
107
|
+
packageName: string;
|
|
108
|
+
packageVersion: string;
|
|
109
|
+
packageDisplayName?: string;
|
|
110
|
+
protected?: boolean;
|
|
111
|
+
removable?: boolean;
|
|
112
|
+
};
|
|
113
|
+
declaration?: import("@camstack/types").AddonDeclaration;
|
|
114
|
+
hasConfigSchema: boolean;
|
|
115
|
+
source: "core" | "installed" | "workspace";
|
|
116
|
+
installSource?: "npm" | "workspace";
|
|
117
|
+
}[]>;
|
|
118
|
+
getAddonConfigSchema(addonId: string): Promise<import("@camstack/types").ConfigUISchema | null>;
|
|
119
|
+
getAddonConfig(addonId: string): Promise<Record<string, unknown>>;
|
|
120
|
+
updateAddonConfig(addonId: string, config: Record<string, unknown>): Promise<{
|
|
121
|
+
success: boolean;
|
|
122
|
+
}>;
|
|
123
|
+
getAddonLogs(addonId: string, options?: {
|
|
124
|
+
limit?: number;
|
|
125
|
+
level?: 'debug' | 'info' | 'warn' | 'error';
|
|
126
|
+
}): Promise<import("@camstack/types").LogEntry[]>;
|
|
127
|
+
listKnownFaces(): Promise<{
|
|
128
|
+
id: string;
|
|
129
|
+
label: string;
|
|
130
|
+
group?: string;
|
|
131
|
+
cropBase64: string;
|
|
132
|
+
createdAt: number;
|
|
133
|
+
updatedAt: number;
|
|
134
|
+
source?: string;
|
|
135
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
136
|
+
}[]>;
|
|
137
|
+
registerFace(label: string, cropBase64: string, group?: string): Promise<never>;
|
|
138
|
+
listPipelines(): Promise<{
|
|
139
|
+
id: string;
|
|
140
|
+
packageName: string;
|
|
141
|
+
slot: import("@camstack/types").PipelineSlot | null;
|
|
142
|
+
}[]>;
|
|
143
|
+
getPipelineStatus(deviceId: string): Promise<import("@camstack/types").PipelineConfig | null>;
|
|
144
|
+
listAgents(): Promise<readonly import("@camstack/types").AgentListItem[]>;
|
|
145
|
+
dispatchTask(agentId: string, task: Record<string, unknown>): Promise<import("@camstack/types").AgentTaskResult>;
|
|
146
|
+
getProcessTree(): Promise<{
|
|
147
|
+
id: string;
|
|
148
|
+
name: string;
|
|
149
|
+
pid: number;
|
|
150
|
+
state: "running";
|
|
151
|
+
cpuPercent: number;
|
|
152
|
+
memoryPercent: number;
|
|
153
|
+
memoryMB: number;
|
|
154
|
+
isHub: boolean;
|
|
155
|
+
platform: string;
|
|
156
|
+
arch: string;
|
|
157
|
+
host: string;
|
|
158
|
+
capabilities: string[];
|
|
159
|
+
installedAddons: string[];
|
|
160
|
+
pythonRuntimes: string[];
|
|
161
|
+
connectedSince: number;
|
|
162
|
+
subProcesses: unknown[];
|
|
163
|
+
}[]>;
|
|
164
|
+
bridgeListAddons(): Promise<{
|
|
165
|
+
id: string;
|
|
166
|
+
packageName: string;
|
|
167
|
+
slot: import("@camstack/types").PipelineSlot | null;
|
|
168
|
+
}[]>;
|
|
169
|
+
bridgeGetPipeline(deviceId: string): Promise<import("@camstack/types").PipelineConfig | null>;
|
|
170
|
+
bridgeSetPipeline(deviceId: string, config: {
|
|
171
|
+
video: unknown[];
|
|
172
|
+
audio?: unknown;
|
|
173
|
+
}): Promise<{
|
|
174
|
+
success: boolean;
|
|
175
|
+
}>;
|
|
176
|
+
bridgeValidatePipeline(config: {
|
|
177
|
+
video: unknown[];
|
|
178
|
+
audio?: unknown;
|
|
179
|
+
}): Promise<import("@camstack/types").ValidationResult>;
|
|
180
|
+
bridgeGetAddonConfig(addonId: string): Promise<Record<string, unknown>>;
|
|
181
|
+
bridgeSetAddonConfig(addonId: string, config: Record<string, unknown>): Promise<{
|
|
182
|
+
success: boolean;
|
|
183
|
+
}>;
|
|
184
|
+
bridgeListPackages(): Promise<import("@camstack/types").InstalledPackage[]>;
|
|
185
|
+
bridgeListAddonsPackages(): Promise<{
|
|
186
|
+
manifest: import("@camstack/types").AddonManifest & {
|
|
187
|
+
packageName: string;
|
|
188
|
+
packageVersion: string;
|
|
189
|
+
packageDisplayName?: string;
|
|
190
|
+
protected?: boolean;
|
|
191
|
+
removable?: boolean;
|
|
192
|
+
};
|
|
193
|
+
declaration?: import("@camstack/types").AddonDeclaration;
|
|
194
|
+
hasConfigSchema: boolean;
|
|
195
|
+
source: "core" | "installed" | "workspace";
|
|
196
|
+
installSource?: "npm" | "workspace";
|
|
197
|
+
}[]>;
|
|
198
|
+
bridgeInstallPackage(packageName: string, version?: string): Promise<{
|
|
199
|
+
success: boolean;
|
|
200
|
+
loaded: string[];
|
|
201
|
+
failed: string[];
|
|
202
|
+
}>;
|
|
203
|
+
bridgeUninstallPackage(packageName: string): Promise<{
|
|
204
|
+
success: boolean;
|
|
205
|
+
}>;
|
|
206
|
+
getRecordingConfig(deviceId: string): Promise<unknown>;
|
|
207
|
+
getRecordingPolicy(deviceId: string): Promise<unknown>;
|
|
208
|
+
getRecordingPolicyStatus(deviceId: string): Promise<{
|
|
209
|
+
deviceId: string;
|
|
210
|
+
policyExists: boolean;
|
|
211
|
+
enabled: any;
|
|
212
|
+
mode: any;
|
|
213
|
+
isRecording: boolean;
|
|
214
|
+
}>;
|
|
215
|
+
getRecordingSegments(deviceId: string, streamId: string, startTime: number, endTime: number): Promise<unknown>;
|
|
216
|
+
getEvents(deviceId: string, options?: {
|
|
217
|
+
limit?: number;
|
|
218
|
+
offset?: number;
|
|
219
|
+
}): Promise<import("@camstack/types").EventQueryResult>;
|
|
220
|
+
getLogs(options?: {
|
|
221
|
+
level?: 'debug' | 'info' | 'warn' | 'error';
|
|
222
|
+
limit?: number;
|
|
223
|
+
since?: number;
|
|
224
|
+
until?: number;
|
|
225
|
+
scope?: string[];
|
|
226
|
+
}): Promise<import("@camstack/types").LogEntry[]>;
|
|
227
|
+
replEval(code: string, scope?: {
|
|
228
|
+
type: 'system';
|
|
229
|
+
} | {
|
|
230
|
+
type: 'device';
|
|
231
|
+
deviceId: string;
|
|
232
|
+
} | {
|
|
233
|
+
type: 'provider';
|
|
234
|
+
providerId: string;
|
|
235
|
+
} | {
|
|
236
|
+
type: 'addon';
|
|
237
|
+
addonId: string;
|
|
238
|
+
}): Promise<import("@camstack/types").ReplResult>;
|
|
239
|
+
listUsers(): Promise<Omit<import("@camstack/types").UserRecord, "passwordHash">[]>;
|
|
240
|
+
createUser(username: string, password: string, role: 'super_admin' | 'admin' | 'viewer'): Promise<{
|
|
241
|
+
id: string;
|
|
242
|
+
username: string;
|
|
243
|
+
role: import("@camstack/types").UserRole;
|
|
244
|
+
allowedProviders: string[] | "*";
|
|
245
|
+
allowedDevices: Record<string, string[] | "*">;
|
|
246
|
+
createdAt: number;
|
|
247
|
+
updatedAt: number;
|
|
248
|
+
}>;
|
|
249
|
+
getTrackingSessions(deviceId?: string): Promise<{
|
|
250
|
+
trackId: string;
|
|
251
|
+
deviceId: string;
|
|
252
|
+
className: string;
|
|
253
|
+
label: string | undefined;
|
|
254
|
+
firstSeen: number;
|
|
255
|
+
lastSeen: number;
|
|
256
|
+
totalFrames: number;
|
|
257
|
+
state: string;
|
|
258
|
+
globalId: string | undefined;
|
|
259
|
+
positions: readonly {
|
|
260
|
+
readonly x: number;
|
|
261
|
+
readonly y: number;
|
|
262
|
+
readonly t: number;
|
|
263
|
+
}[];
|
|
264
|
+
hasEmbedding: boolean;
|
|
265
|
+
hasCrop: boolean;
|
|
266
|
+
}[]>;
|
|
267
|
+
listProcesses(): Promise<(import("@camstack/types").ManagedProcessStatus | {
|
|
268
|
+
id: string;
|
|
269
|
+
label: string;
|
|
270
|
+
state: "running" | "stopped" | "crashed" | "starting";
|
|
271
|
+
pid?: number;
|
|
272
|
+
stats?: {
|
|
273
|
+
pid: number;
|
|
274
|
+
cpu: number;
|
|
275
|
+
memory: number;
|
|
276
|
+
uptime: number;
|
|
277
|
+
restartCount: number;
|
|
278
|
+
};
|
|
279
|
+
restartCount: number;
|
|
280
|
+
mode: "forked" | "in-process";
|
|
281
|
+
})[]>;
|
|
282
|
+
enableProvider(providerId: string): Promise<{
|
|
283
|
+
success: boolean;
|
|
284
|
+
}>;
|
|
285
|
+
disableProvider(providerId: string): Promise<{
|
|
286
|
+
success: boolean;
|
|
287
|
+
}>;
|
|
288
|
+
/** Fetch the UI schema for a single section, or all sections if omitted. */
|
|
289
|
+
getSettingsSchema(section?: SettingsSection): Promise<{
|
|
290
|
+
section: "auth" | "features" | "server" | "storage" | "logging" | "addons" | "recording" | "streaming" | "ffmpeg" | "detection" | "backup" | "retention";
|
|
291
|
+
schema: import("@camstack/types").ConfigUISchema | null;
|
|
292
|
+
readOnly: boolean;
|
|
293
|
+
tabs?: undefined;
|
|
294
|
+
schemas?: undefined;
|
|
295
|
+
} | {
|
|
296
|
+
tabs: readonly import("@camstack/types").SettingsTabDef[];
|
|
297
|
+
schemas: Record<string, unknown>;
|
|
298
|
+
section?: undefined;
|
|
299
|
+
schema?: undefined;
|
|
300
|
+
readOnly?: undefined;
|
|
301
|
+
}>;
|
|
302
|
+
getSettings(section: SettingsSection): Promise<{
|
|
303
|
+
section: "auth" | "features" | "server" | "storage" | "logging" | "addons" | "recording" | "streaming" | "ffmpeg" | "detection" | "backup" | "retention";
|
|
304
|
+
data: Record<string, unknown>;
|
|
305
|
+
}>;
|
|
306
|
+
updateSettings(section: SettingsSection, data: Record<string, unknown>): Promise<{
|
|
307
|
+
success: boolean;
|
|
308
|
+
section: "auth" | "features" | "server" | "storage" | "logging" | "addons" | "recording" | "streaming" | "ffmpeg" | "detection" | "backup" | "retention";
|
|
309
|
+
}>;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Create a BackendClient instance with the given config.
|
|
313
|
+
* Convenience factory function.
|
|
314
|
+
*/
|
|
315
|
+
export declare function createBackendClient(config: BackendClientConfig): BackendClient;
|
package/dist/camera.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Camera & PTZ types — shared between CamStack app and proxy.
|
|
3
|
+
*/
|
|
4
|
+
/** Camera source type discriminator. */
|
|
5
|
+
export type CameraSourceType = "scrypted" | "frigate" | "onvif" | "rtsp" | "camstack";
|
|
6
|
+
/** Streaming method. */
|
|
7
|
+
export type StreamMethod = "webrtc" | "mjpeg";
|
|
8
|
+
/** Scrypted-compatible PTZ command. */
|
|
9
|
+
export interface PanTiltZoomCommand {
|
|
10
|
+
movement?: "Relative" | "Absolute" | "Continuous" | "Home" | "Preset";
|
|
11
|
+
pan?: number;
|
|
12
|
+
tilt?: number;
|
|
13
|
+
zoom?: number;
|
|
14
|
+
preset?: string;
|
|
15
|
+
timeout?: number;
|
|
16
|
+
}
|
|
17
|
+
/** PTZ capabilities reported by the device. */
|
|
18
|
+
export interface PanTiltZoomCapabilities {
|
|
19
|
+
pan?: boolean;
|
|
20
|
+
tilt?: boolean;
|
|
21
|
+
zoom?: boolean;
|
|
22
|
+
presets?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
/** Accessory switch kinds exposed by the Advanced Notifier plugin. */
|
|
25
|
+
export type CameraAccessorySwitchKind = "siren_on_motion" | "siren" | "light_on_motion" | "light" | "pir" | "autotracking";
|
|
26
|
+
/** Extended camera status with accessory switches (from AN plugin). */
|
|
27
|
+
export interface CameraStatusEntry {
|
|
28
|
+
notificationsEnabled: boolean;
|
|
29
|
+
isRecording: boolean;
|
|
30
|
+
isSnapshotsEnabled: boolean;
|
|
31
|
+
isRebroadcastEnabled: boolean;
|
|
32
|
+
accessorySwitchStates: Partial<Record<CameraAccessorySwitchKind, boolean>>;
|
|
33
|
+
}
|
|
34
|
+
/** Batch camera status response. */
|
|
35
|
+
export interface CamerasStatusResult {
|
|
36
|
+
cameras: Record<string, CameraStatusEntry>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection classes — shared between CamStack app and proxy.
|
|
3
|
+
* Aligned with scrypted-advanced-notifier/src/detectionClasses.ts.
|
|
4
|
+
*/
|
|
5
|
+
export declare enum DetectionClass {
|
|
6
|
+
Motion = "motion",
|
|
7
|
+
Person = "person",
|
|
8
|
+
Vehicle = "vehicle",
|
|
9
|
+
Animal = "animal",
|
|
10
|
+
Audio = "audio",
|
|
11
|
+
Face = "face",
|
|
12
|
+
Plate = "plate",
|
|
13
|
+
Package = "package",
|
|
14
|
+
Doorbell = "doorbell",
|
|
15
|
+
Sensor = "sensor"
|
|
16
|
+
}
|
|
17
|
+
export declare const animalClasses: string[];
|
|
18
|
+
export declare const personClasses: string[];
|
|
19
|
+
export declare const vehicleClasses: string[];
|
|
20
|
+
export declare const faceClasses: string[];
|
|
21
|
+
export declare const licensePlateClasses: string[];
|
|
22
|
+
export declare const motionClasses: string[];
|
|
23
|
+
export declare const packageClasses: string[];
|
|
24
|
+
export declare const audioClasses: string[];
|
|
25
|
+
/** Common YAMNet audio labels (advanced-notifier). Parent: Audio. */
|
|
26
|
+
export declare const audioLabelClasses: string[];
|
|
27
|
+
export declare const doorbellClasses: string[];
|
|
28
|
+
/** Sensor types (advanced-notifier SupportedSensorType). Parent: Sensor. */
|
|
29
|
+
export declare const sensorLabelClasses: string[];
|
|
30
|
+
export declare const detectionClassesDefaultMap: Record<string, DetectionClass>;
|
|
31
|
+
export declare const isFaceClassname: (c: string) => boolean;
|
|
32
|
+
export declare const isPlateClassname: (c: string) => boolean;
|
|
33
|
+
export declare const isAnimalClassname: (c: string) => boolean;
|
|
34
|
+
export declare const isPersonClassname: (c: string) => boolean;
|
|
35
|
+
export declare const isVehicleClassname: (c: string) => boolean;
|
|
36
|
+
export declare const isMotionClassname: (c: string) => boolean;
|
|
37
|
+
export declare const isDoorbellClassname: (c: string) => boolean;
|
|
38
|
+
export declare const isPackageClassname: (c: string) => boolean;
|
|
39
|
+
export declare const isAudioClassname: (c: string) => boolean;
|
|
40
|
+
export declare const isSensorLabelClassname: (c: string) => boolean;
|
|
41
|
+
export declare const isLabelDetection: (c: string) => boolean;
|
|
42
|
+
export declare const getParentClass: (className: string) => DetectionClass | undefined;
|
|
43
|
+
export declare const getParentDetectionClass: (det: {
|
|
44
|
+
label?: string;
|
|
45
|
+
className: string;
|
|
46
|
+
}) => DetectionClass | undefined;
|
|
47
|
+
export declare const defaultDetectionClasses: DetectionClass[];
|
|
48
|
+
/** Default enabled classes: all except Motion. */
|
|
49
|
+
export declare const DEFAULT_ENABLED_CLASSES: (DetectionClass.Person | DetectionClass.Vehicle | DetectionClass.Animal | DetectionClass.Audio | DetectionClass.Face | DetectionClass.Plate | DetectionClass.Package | DetectionClass.Doorbell | DetectionClass.Sensor)[];
|
|
50
|
+
export type TimelineEventPreset = "all" | "important" | "critical" | "custom";
|
|
51
|
+
/** Classes for "critical" preset: security-relevant only (person, doorbell, package). */
|
|
52
|
+
export declare const TIMELINE_PRESET_CRITICAL: string[];
|
|
53
|
+
/** Classes for "important" preset: critical + vehicle, animal, audio, face, plate. */
|
|
54
|
+
export declare const TIMELINE_PRESET_IMPORTANT: string[];
|
|
55
|
+
/** Classes for "all" preset: same as DEFAULT_ENABLED_CLASSES. */
|
|
56
|
+
export declare const TIMELINE_PRESET_ALL: string[];
|
|
57
|
+
/** Get enabled classes for a preset. For "custom", pass customClasses. */
|
|
58
|
+
export declare function getClassesForTimelinePreset(preset: TimelineEventPreset, customClasses?: string[]): string[];
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device types, mappings, and filtering constants — shared between CamStack app and proxy.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Canonical device type normalization (Scrypted PascalCase + HA domains → canonical)
|
|
6
|
+
* - Eligible device type lists for Scrypted and Home Assistant
|
|
7
|
+
* - Unified Device / DeviceCommand / CommandResult interfaces
|
|
8
|
+
* - AssociatedDeviceState shape
|
|
9
|
+
*/
|
|
10
|
+
export type CanonicalDeviceType = "light" | "switch" | "cover" | "lock" | "alarm" | "button" | "select" | "siren" | "sensor" | "entry" | "media_player" | "script";
|
|
11
|
+
/**
|
|
12
|
+
* Maps raw device types from Scrypted (PascalCase) and Home Assistant (lowercase domains)
|
|
13
|
+
* to canonical CamStack device types.
|
|
14
|
+
*/
|
|
15
|
+
export declare const RAW_TO_CANONICAL: Record<string, CanonicalDeviceType>;
|
|
16
|
+
/**
|
|
17
|
+
* Extended HA domain → type map (includes non-eligible domains for display/categorization).
|
|
18
|
+
* Superset of RAW_TO_CANONICAL for HA-specific domains.
|
|
19
|
+
*/
|
|
20
|
+
export declare const HA_DOMAIN_TYPE_MAP: Record<string, string>;
|
|
21
|
+
/**
|
|
22
|
+
* Extended Scrypted type → canonical map (includes non-eligible types for display).
|
|
23
|
+
* Superset of RAW_TO_CANONICAL for Scrypted-specific types.
|
|
24
|
+
*/
|
|
25
|
+
export declare const SCRYPTED_TYPE_TO_CANONICAL: Record<string, string>;
|
|
26
|
+
/** Normalize raw type (Scrypted or HA) to canonical key for filtering and display. */
|
|
27
|
+
export declare function getCanonicalDeviceType(rawType: string): CanonicalDeviceType | null;
|
|
28
|
+
/** Scrypted device types eligible for device control in CamStack. PascalCase. */
|
|
29
|
+
export declare const ELIGIBLE_SCRYPTED_DEVICE_TYPES: readonly ["Entry", "Light", "Switch", "Lock", "SecuritySystem", "Buttons", "WindowCovering", "Siren", "Sensor", "Select", "Program"];
|
|
30
|
+
/** Set version for O(1) lookup. */
|
|
31
|
+
export declare const ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET: Set<string>;
|
|
32
|
+
/** Home Assistant domains eligible for device control in CamStack. */
|
|
33
|
+
export declare const ELIGIBLE_HA_DOMAINS: readonly ["light", "switch", "input_boolean", "cover", "lock", "alarm_control_panel", "input_button", "button", "input_select", "select", "siren", "media_player", "script"];
|
|
34
|
+
/** Set version for O(1) lookup. */
|
|
35
|
+
export declare const ELIGIBLE_HA_DOMAINS_SET: Set<string>;
|
|
36
|
+
/** All device command actions supported by CamStack. */
|
|
37
|
+
export type DeviceCommandAction = "turnOn" | "turnOff" | "openEntry" | "closeEntry" | "stopEntry" | "lock" | "unlock" | "disarm" | "arm" | "pressButton" | "selectOption" | "volumeUp" | "volumeDown" | "volumeMute";
|
|
38
|
+
/**
|
|
39
|
+
* State snapshot shape for an associated device.
|
|
40
|
+
* Used by both app (AssociatedDeviceStateSnapshot) and proxy (Device.state).
|
|
41
|
+
*/
|
|
42
|
+
export interface AssociatedDeviceState {
|
|
43
|
+
name?: string;
|
|
44
|
+
type?: string;
|
|
45
|
+
interfaces?: string[];
|
|
46
|
+
on?: boolean;
|
|
47
|
+
hasBrightnessControl?: boolean;
|
|
48
|
+
brightness?: number;
|
|
49
|
+
lockState?: string;
|
|
50
|
+
securitySystemState?: {
|
|
51
|
+
mode?: string;
|
|
52
|
+
supportedModes?: string[];
|
|
53
|
+
};
|
|
54
|
+
buttons?: string[];
|
|
55
|
+
entryOpen?: boolean | "jammed";
|
|
56
|
+
value?: string;
|
|
57
|
+
options?: string[];
|
|
58
|
+
volumeLevel?: number;
|
|
59
|
+
isVolumeMuted?: boolean;
|
|
60
|
+
coverSupportsStop?: boolean;
|
|
61
|
+
coverPosition?: number;
|
|
62
|
+
}
|
|
63
|
+
/** A device from an automation system (Scrypted, Home Assistant, etc.). */
|
|
64
|
+
export interface Device {
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
/**
|
|
68
|
+
* Canonical device type (lowercase): "light", "switch", "cover", "lock",
|
|
69
|
+
* "alarm", "button", "select", "siren", "sensor", "entry", "media_player",
|
|
70
|
+
* "script", "camera", "nvr".
|
|
71
|
+
*/
|
|
72
|
+
type: string;
|
|
73
|
+
/** Original type from the source system (e.g. Scrypted "WindowCovering", HA domain "cover"). */
|
|
74
|
+
rawType?: string;
|
|
75
|
+
/** Provider that owns this device. */
|
|
76
|
+
providerId?: string;
|
|
77
|
+
model?: string;
|
|
78
|
+
manufacturer?: string;
|
|
79
|
+
firmware?: string;
|
|
80
|
+
online: boolean;
|
|
81
|
+
interfaces: string[];
|
|
82
|
+
/** Current state (key-value pairs, device-type-dependent). See AssociatedDeviceState. */
|
|
83
|
+
state: Record<string, unknown>;
|
|
84
|
+
/** Available actions for this device (defined by the provider). */
|
|
85
|
+
actions?: DeviceAction[];
|
|
86
|
+
/** Child devices (e.g. NVR → camera channels). */
|
|
87
|
+
children?: Device[];
|
|
88
|
+
}
|
|
89
|
+
/** A command to send to a device. */
|
|
90
|
+
export interface DeviceCommand {
|
|
91
|
+
deviceId: string;
|
|
92
|
+
command: string;
|
|
93
|
+
params?: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
/** Result of a command execution. */
|
|
96
|
+
export interface CommandResult {
|
|
97
|
+
success: boolean;
|
|
98
|
+
error?: string;
|
|
99
|
+
state?: Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
/** Device source type (integration that provides the device). */
|
|
102
|
+
export type AssociatedDeviceSource = "scrypted" | "ha";
|
|
103
|
+
/** Eligible device summary (for device picker UI). */
|
|
104
|
+
export interface EligibleDevice {
|
|
105
|
+
id: string;
|
|
106
|
+
name: string;
|
|
107
|
+
type: string;
|
|
108
|
+
source: AssociatedDeviceSource;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* An action that can be performed on a device.
|
|
112
|
+
* Actions are declared by the provider and rendered dynamically in the UI.
|
|
113
|
+
*/
|
|
114
|
+
export interface DeviceAction {
|
|
115
|
+
/** Unique action ID (e.g. "toggle", "setBrightness", "ptzMove", "reboot"). */
|
|
116
|
+
id: string;
|
|
117
|
+
/** Display label (e.g. "Toggle", "Set Brightness", "Move Camera"). */
|
|
118
|
+
label: string;
|
|
119
|
+
/** Action category for UI grouping. */
|
|
120
|
+
category?: "power" | "control" | "ptz" | "media" | "system" | "custom";
|
|
121
|
+
/** Icon hint (lucide icon name or emoji). */
|
|
122
|
+
icon?: string;
|
|
123
|
+
/** Parameters this action accepts. */
|
|
124
|
+
params?: DeviceActionParam[];
|
|
125
|
+
/** Whether this action is destructive (shown with warning). */
|
|
126
|
+
destructive?: boolean;
|
|
127
|
+
}
|
|
128
|
+
/** A parameter for a device action. */
|
|
129
|
+
export interface DeviceActionParam {
|
|
130
|
+
/** Parameter key. */
|
|
131
|
+
key: string;
|
|
132
|
+
/** Display label. */
|
|
133
|
+
label: string;
|
|
134
|
+
/** Type for UI rendering. */
|
|
135
|
+
type: "boolean" | "number" | "string" | "select" | "slider";
|
|
136
|
+
/** Default value. */
|
|
137
|
+
default?: unknown;
|
|
138
|
+
/** For select type: available options. */
|
|
139
|
+
options?: {
|
|
140
|
+
value: string;
|
|
141
|
+
label: string;
|
|
142
|
+
}[];
|
|
143
|
+
/** For slider/number: min/max/step. */
|
|
144
|
+
min?: number;
|
|
145
|
+
max?: number;
|
|
146
|
+
step?: number;
|
|
147
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature capability matrix — shared between CamStack app and proxy.
|
|
3
|
+
*
|
|
4
|
+
* Maps each app feature to:
|
|
5
|
+
* - Which source types (Frigate, Scrypted, RTSP) support it
|
|
6
|
+
* - Which platforms (iOS, Android, Web, Scrypted embed) it works on
|
|
7
|
+
* - Whether the camstack-server backend is required
|
|
8
|
+
* - The ICameraSource method that enables the feature
|
|
9
|
+
*/
|
|
10
|
+
export type FeatureId = "liveStream" | "multiResolution" | "motion" | "objectDetection" | "audioVolume" | "audioVolumes" | "ptz" | "intercom" | "deviceStatus" | "timeline" | "clusteredTimeline" | "detectionClasses" | "videoClips" | "nvrPlayback" | "nvrScrub" | "recordingThumbnail" | "nvrSeekToLive";
|
|
11
|
+
export type PlatformId = "ios" | "android" | "web" | "scryptedEmbed";
|
|
12
|
+
export type SourceType = "frigate" | "scrypted" | "rtsp";
|
|
13
|
+
export interface FeatureEntry {
|
|
14
|
+
id: FeatureId;
|
|
15
|
+
label: string;
|
|
16
|
+
/** Which source types support this feature. */
|
|
17
|
+
sources: Partial<Record<SourceType, boolean>>;
|
|
18
|
+
/** Platform availability overrides. When omitted, all platforms are supported. */
|
|
19
|
+
platforms?: Partial<Record<PlatformId, boolean>>;
|
|
20
|
+
/** True when this feature needs the camstack-server backend. */
|
|
21
|
+
requiresBackend?: boolean;
|
|
22
|
+
/** The ICameraSource method name that enables this feature at runtime. */
|
|
23
|
+
adapterMethod?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare const FEATURE_MATRIX: FeatureEntry[];
|
|
26
|
+
/** Check if a feature is available for a given source type and platform. */
|
|
27
|
+
export declare function isFeatureAvailable(featureId: FeatureId, source: SourceType, platform: PlatformId): boolean;
|
|
28
|
+
/** Get all features supported by a source type. */
|
|
29
|
+
export declare function getSourceFeatures(source: SourceType): FeatureEntry[];
|
|
30
|
+
/** Get all features that require the backend proxy. */
|
|
31
|
+
export declare function getBackendRequiredFeatures(): FeatureEntry[];
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { BackendClient, createBackendClient } from "./backend-client.js";
|
|
2
|
+
export type { BackendAppRouter } from "./backend-router.js";
|
|
3
|
+
export type { BackendAppRouter as AppRouter } from "./backend-router.js";
|
|
4
|
+
export type { BackendClientConfig } from "./types.js";
|
|
5
|
+
export { DetectionClass, animalClasses, personClasses, vehicleClasses, faceClasses, licensePlateClasses, motionClasses, packageClasses, audioClasses, audioLabelClasses, doorbellClasses, sensorLabelClasses, detectionClassesDefaultMap, isFaceClassname, isPlateClassname, isAnimalClassname, isPersonClassname, isVehicleClassname, isMotionClassname, isDoorbellClassname, isPackageClassname, isAudioClassname, isSensorLabelClassname, isLabelDetection, getParentClass, getParentDetectionClass, defaultDetectionClasses, DEFAULT_ENABLED_CLASSES, TIMELINE_PRESET_CRITICAL, TIMELINE_PRESET_IMPORTANT, TIMELINE_PRESET_ALL, getClassesForTimelinePreset, } from "./detection.js";
|
|
6
|
+
export type { TimelineEventPreset } from "./detection.js";
|
|
7
|
+
export { RAW_TO_CANONICAL, HA_DOMAIN_TYPE_MAP, SCRYPTED_TYPE_TO_CANONICAL, getCanonicalDeviceType, ELIGIBLE_SCRYPTED_DEVICE_TYPES, ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET, ELIGIBLE_HA_DOMAINS, ELIGIBLE_HA_DOMAINS_SET, } from "./devices.js";
|
|
8
|
+
export type { CanonicalDeviceType, DeviceCommandAction, AssociatedDeviceState, Device, DeviceAction, DeviceActionParam, DeviceCommand, CommandResult, AssociatedDeviceSource, EligibleDevice, } from "./devices.js";
|
|
9
|
+
export type { TimelineEventSeverity, MotionFragmentType, DetectionEvent, MotionItem, TimelineArtifact, AudioLevelSegment, TimelineRecordingSegment, CameraDayDataResponse, ClusteredDayDataResponse, TimelineCluster, DetectionGroup, ClusteredDayDataQuery, GroupedEventsQuery, GroupedEventsResult, ClusterEventsQuery, ClusterEventsResult, ReelEvent, ReelEventsQuery, ReelEventsResult, TimelineDetectionEvent, TimelineMotionItem, TimelineArtifactItem, TimelineClusterFromServer, } from "./timeline.js";
|
|
10
|
+
export type { CameraSourceType, StreamMethod, PanTiltZoomCommand, PanTiltZoomCapabilities, CameraAccessorySwitchKind, CameraStatusEntry, CamerasStatusResult, } from "./camera.js";
|
|
11
|
+
export { FEATURE_MATRIX, isFeatureAvailable, getSourceFeatures, getBackendRequiredFeatures, } from "./features.js";
|
|
12
|
+
export type { FeatureId, PlatformId, SourceType, FeatureEntry, } from "./features.js";
|
|
13
|
+
export { selectOptimalStream, getNextEvalInterval, } from "./adaptive-stream.js";
|
|
14
|
+
export type { StreamConstraints, StreamAssignment, StreamSelectionRequest, StreamSelectionResponse, } from "./adaptive-stream.js";
|
|
15
|
+
export type { StreamOption, NvrCamera, CameraAccessory, StreamInfo, NormalizedBoundingBox, DetectionData, EventSnapshotData, NvrEvent, RecordingSegment, MotionBucket, LiveDetection, LiveMotionData, LiveDetectionData, LiveAudioData, LiveMotionEvent, LiveDetectionEvent, LiveAudioEvent, LiveGenericEvent, LiveReviewEvent, LiveEvent, LiveEventType, VideoClipsQuery, NvrVideoClip, VideoClipsResult, VideoClipUrlResult, CameraDayData, CameraStatus, EventsQuery, MotionQuery, RecordingsQuery, NvrConfig, ProviderConfigs, } from "./nvr.js";
|
package/dist/nvr.d.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NVR types — system-agnostic types used by providers and the SDK client.
|
|
3
|
+
*
|
|
4
|
+
* These are the canonical types from the proxy's providers/types.ts,
|
|
5
|
+
* now owned by the SDK so both app and proxy can share them.
|
|
6
|
+
*/
|
|
7
|
+
export interface StreamOption {
|
|
8
|
+
/** Unique identifier for this option (e.g. "auto", "cam_main", "mjpeg"). */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Display label (e.g. "Auto", "High (WebRTC)", "MJPEG"). */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Streaming technology. */
|
|
13
|
+
method: "webrtc" | "mjpeg" | "hls" | "rtsp";
|
|
14
|
+
/** go2rtc stream name (required for WebRTC). */
|
|
15
|
+
streamName?: string;
|
|
16
|
+
/** Raw source URL (RTSP, etc.) — for display/copy in admin UI. */
|
|
17
|
+
sourceUrl?: string;
|
|
18
|
+
/** Group label for UI separators (e.g. "go2rtc", "Frigate", "Scrypted"). */
|
|
19
|
+
group?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface NvrCamera {
|
|
22
|
+
name: string;
|
|
23
|
+
/** Display label (may differ from internal name). */
|
|
24
|
+
label?: string;
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
hasAudio: boolean;
|
|
27
|
+
hasPtz: boolean;
|
|
28
|
+
/** Available stream names for this camera (e.g. ["cam_main", "cam_sub"]). */
|
|
29
|
+
streams: string[];
|
|
30
|
+
/** Available streaming options with method and quality info. Always includes "Auto". */
|
|
31
|
+
streamOptions: StreamOption[];
|
|
32
|
+
/** Accessories/actions available on this camera (siren, floodlight, PTZ, etc.). */
|
|
33
|
+
accessories?: CameraAccessory[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A camera accessory — a controllable feature attached to a camera.
|
|
37
|
+
* Declared by the provider, rendered dynamically in the UI.
|
|
38
|
+
*/
|
|
39
|
+
export interface CameraAccessory {
|
|
40
|
+
/** Unique accessory ID (e.g. "siren", "floodlight", "ptz", "ir_led"). */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Display label (e.g. "Siren", "Floodlight"). */
|
|
43
|
+
label: string;
|
|
44
|
+
/** Accessory type determines UI rendering. */
|
|
45
|
+
type: "toggle" | "button" | "ptz" | "slider";
|
|
46
|
+
/** Lucide icon name (e.g. "siren", "lightbulb", "move", "sun"). */
|
|
47
|
+
icon: string;
|
|
48
|
+
/** Current state (for toggles: boolean, for sliders: number). */
|
|
49
|
+
state?: unknown;
|
|
50
|
+
/** For slider type: min/max/step. */
|
|
51
|
+
min?: number;
|
|
52
|
+
max?: number;
|
|
53
|
+
step?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface StreamInfo {
|
|
56
|
+
name: string;
|
|
57
|
+
/** Camera this stream belongs to (if determinable). */
|
|
58
|
+
camera?: string;
|
|
59
|
+
}
|
|
60
|
+
/** Normalized bounding box in 0–1 range (provider-agnostic). */
|
|
61
|
+
export interface NormalizedBoundingBox {
|
|
62
|
+
x1: number;
|
|
63
|
+
y1: number;
|
|
64
|
+
x2: number;
|
|
65
|
+
y2: number;
|
|
66
|
+
}
|
|
67
|
+
/** Detection data attached to an event. */
|
|
68
|
+
export interface DetectionData {
|
|
69
|
+
label: string;
|
|
70
|
+
subLabel?: string;
|
|
71
|
+
/** Confidence score 0–1. */
|
|
72
|
+
score: number;
|
|
73
|
+
/** Best confidence score observed during the event. */
|
|
74
|
+
topScore?: number;
|
|
75
|
+
/** Bounding box of the detected object, normalized 0–1. */
|
|
76
|
+
boundingBox?: NormalizedBoundingBox;
|
|
77
|
+
/** Detection region, normalized 0–1. */
|
|
78
|
+
region?: NormalizedBoundingBox;
|
|
79
|
+
/** Area as fraction of frame area (0–1). */
|
|
80
|
+
area?: number;
|
|
81
|
+
/** Named zones the object is currently in. */
|
|
82
|
+
currentZones?: string[];
|
|
83
|
+
/** Named zones the object has entered during this event. */
|
|
84
|
+
enteredZones?: string[];
|
|
85
|
+
/** Attributes detected (e.g., { face: 0.64, glasses: 0.82 }). */
|
|
86
|
+
attributes?: Record<string, number>;
|
|
87
|
+
/** Current estimated speed. */
|
|
88
|
+
currentSpeed?: number;
|
|
89
|
+
/** Average estimated speed over the event. */
|
|
90
|
+
averageSpeed?: number;
|
|
91
|
+
/** Velocity angle in degrees. */
|
|
92
|
+
velocityAngle?: number;
|
|
93
|
+
/** License plate recognition result. */
|
|
94
|
+
licensePlate?: {
|
|
95
|
+
text: string;
|
|
96
|
+
score: number;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Snapshot metadata associated with an event. */
|
|
100
|
+
export interface EventSnapshotData {
|
|
101
|
+
boundingBox?: NormalizedBoundingBox;
|
|
102
|
+
score?: number;
|
|
103
|
+
/** Area as fraction of frame area (0–1). */
|
|
104
|
+
area?: number;
|
|
105
|
+
attributes?: Record<string, number>;
|
|
106
|
+
}
|
|
107
|
+
export interface NvrEvent {
|
|
108
|
+
id: string;
|
|
109
|
+
camera: string;
|
|
110
|
+
label: string;
|
|
111
|
+
startTime: number;
|
|
112
|
+
endTime: number | null;
|
|
113
|
+
severity?: string;
|
|
114
|
+
hasClip: boolean;
|
|
115
|
+
hasSnapshot: boolean;
|
|
116
|
+
thumbnailPath?: string;
|
|
117
|
+
snapshotPath?: string;
|
|
118
|
+
clipPath?: string;
|
|
119
|
+
detection?: DetectionData;
|
|
120
|
+
snapshot?: EventSnapshotData;
|
|
121
|
+
falsePositive?: boolean;
|
|
122
|
+
retainIndefinitely?: boolean;
|
|
123
|
+
raw?: Record<string, unknown>;
|
|
124
|
+
}
|
|
125
|
+
export interface RecordingSegment {
|
|
126
|
+
id: string;
|
|
127
|
+
camera: string;
|
|
128
|
+
startTime: number;
|
|
129
|
+
endTime: number;
|
|
130
|
+
duration: number;
|
|
131
|
+
motion?: number;
|
|
132
|
+
objects?: number;
|
|
133
|
+
dBFS?: number;
|
|
134
|
+
}
|
|
135
|
+
export interface MotionBucket {
|
|
136
|
+
camera: string;
|
|
137
|
+
startTime: number;
|
|
138
|
+
motion?: number;
|
|
139
|
+
audio?: number;
|
|
140
|
+
}
|
|
141
|
+
/** Single object detection from a live frame. */
|
|
142
|
+
export interface LiveDetection {
|
|
143
|
+
/** Detection class — the object type (person, car, dog, face, plate, etc.) */
|
|
144
|
+
className: string;
|
|
145
|
+
/** Recognized identity — face name, license plate text, sub-label. */
|
|
146
|
+
label?: string;
|
|
147
|
+
/** Detection confidence 0–1. */
|
|
148
|
+
score: number;
|
|
149
|
+
/** Label/recognition confidence 0–1 — takes precedence over score when available. */
|
|
150
|
+
labelScore?: number;
|
|
151
|
+
/** Named zones where the object currently is. */
|
|
152
|
+
zones?: string[];
|
|
153
|
+
/** Tracking/correlation ID across frames. */
|
|
154
|
+
id?: string;
|
|
155
|
+
/** Bounding box as fractions 0–1 of frame dimensions. */
|
|
156
|
+
boundingBox?: [number, number, number, number] | {
|
|
157
|
+
x: number;
|
|
158
|
+
y: number;
|
|
159
|
+
w: number;
|
|
160
|
+
h: number;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export interface LiveMotionData {
|
|
164
|
+
active: boolean;
|
|
165
|
+
}
|
|
166
|
+
export interface LiveDetectionData {
|
|
167
|
+
detections: LiveDetection[];
|
|
168
|
+
/** Input frame dimensions [width, height] in pixels (for reference). */
|
|
169
|
+
inputDimensions?: [number, number];
|
|
170
|
+
/** Event ID from the NVR (e.g. Frigate event ID). */
|
|
171
|
+
eventId?: string;
|
|
172
|
+
/** Provider-specific extra data. */
|
|
173
|
+
[key: string]: unknown;
|
|
174
|
+
}
|
|
175
|
+
export interface LiveAudioData {
|
|
176
|
+
/** Audio metric type (e.g. "dBFS", "rms", "audioDetected", "audioLevel"). */
|
|
177
|
+
metric: string;
|
|
178
|
+
/** Numeric value. */
|
|
179
|
+
value?: number;
|
|
180
|
+
/** dBFS level. */
|
|
181
|
+
dBFS?: number;
|
|
182
|
+
/** RMS level. */
|
|
183
|
+
rms?: number;
|
|
184
|
+
}
|
|
185
|
+
export type LiveEventType = "motion" | "detection" | "audio" | "event" | "review";
|
|
186
|
+
interface LiveEventBase {
|
|
187
|
+
camera: string;
|
|
188
|
+
timestamp: number;
|
|
189
|
+
/** Provider info — populated by onAllEvents subscription. */
|
|
190
|
+
provider?: {
|
|
191
|
+
id: string;
|
|
192
|
+
name: string;
|
|
193
|
+
type: string;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export interface LiveMotionEvent extends LiveEventBase {
|
|
197
|
+
type: "motion";
|
|
198
|
+
data: LiveMotionData;
|
|
199
|
+
}
|
|
200
|
+
export interface LiveDetectionEvent extends LiveEventBase {
|
|
201
|
+
type: "detection";
|
|
202
|
+
data: LiveDetectionData;
|
|
203
|
+
}
|
|
204
|
+
export interface LiveAudioEvent extends LiveEventBase {
|
|
205
|
+
type: "audio";
|
|
206
|
+
data: LiveAudioData;
|
|
207
|
+
}
|
|
208
|
+
export interface LiveGenericEvent extends LiveEventBase {
|
|
209
|
+
type: "event";
|
|
210
|
+
data: Record<string, unknown>;
|
|
211
|
+
}
|
|
212
|
+
export interface LiveReviewEvent extends LiveEventBase {
|
|
213
|
+
type: "review";
|
|
214
|
+
data: Record<string, unknown>;
|
|
215
|
+
}
|
|
216
|
+
export type LiveEvent = LiveMotionEvent | LiveDetectionEvent | LiveAudioEvent | LiveGenericEvent | LiveReviewEvent;
|
|
217
|
+
export interface VideoClipsQuery {
|
|
218
|
+
camera: string;
|
|
219
|
+
after: number;
|
|
220
|
+
before: number;
|
|
221
|
+
limit?: number;
|
|
222
|
+
offset?: number;
|
|
223
|
+
}
|
|
224
|
+
export interface NvrVideoClip {
|
|
225
|
+
id: string;
|
|
226
|
+
camera: string;
|
|
227
|
+
startTime: number;
|
|
228
|
+
endTime: number;
|
|
229
|
+
duration: number;
|
|
230
|
+
label?: string;
|
|
231
|
+
hasClip: boolean;
|
|
232
|
+
hasSnapshot: boolean;
|
|
233
|
+
source: "event" | "recording";
|
|
234
|
+
}
|
|
235
|
+
export interface VideoClipsResult {
|
|
236
|
+
clips: NvrVideoClip[];
|
|
237
|
+
total: number;
|
|
238
|
+
}
|
|
239
|
+
export interface VideoClipUrlResult {
|
|
240
|
+
url: string;
|
|
241
|
+
mimeType: string;
|
|
242
|
+
}
|
|
243
|
+
export interface CameraDayData {
|
|
244
|
+
events: NvrEvent[];
|
|
245
|
+
motion: MotionBucket[];
|
|
246
|
+
recordings: RecordingSegment[];
|
|
247
|
+
}
|
|
248
|
+
export interface CameraStatus {
|
|
249
|
+
camera: string;
|
|
250
|
+
online: boolean;
|
|
251
|
+
recording: boolean;
|
|
252
|
+
detecting: boolean;
|
|
253
|
+
motionActive: boolean;
|
|
254
|
+
}
|
|
255
|
+
export interface EventsQuery {
|
|
256
|
+
after?: number;
|
|
257
|
+
before?: number;
|
|
258
|
+
cameras?: string[];
|
|
259
|
+
limit?: number;
|
|
260
|
+
labels?: string[];
|
|
261
|
+
}
|
|
262
|
+
export interface MotionQuery {
|
|
263
|
+
after: number;
|
|
264
|
+
before: number;
|
|
265
|
+
scale?: number;
|
|
266
|
+
cameras?: string[];
|
|
267
|
+
}
|
|
268
|
+
export interface RecordingsQuery {
|
|
269
|
+
camera: string;
|
|
270
|
+
after: number;
|
|
271
|
+
before: number;
|
|
272
|
+
}
|
|
273
|
+
export interface NvrConfig {
|
|
274
|
+
type: string;
|
|
275
|
+
version?: string;
|
|
276
|
+
raw?: unknown;
|
|
277
|
+
}
|
|
278
|
+
export interface ProviderConfigs {
|
|
279
|
+
cameras: Array<{
|
|
280
|
+
id: string;
|
|
281
|
+
name: string;
|
|
282
|
+
}>;
|
|
283
|
+
enabledDetectionSources?: string[];
|
|
284
|
+
}
|
|
285
|
+
export {};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline & event types — shared between CamStack app and proxy.
|
|
3
|
+
*
|
|
4
|
+
* Names are unified: the proxy's naming convention is canonical.
|
|
5
|
+
* The app re-exports aliases for backward compatibility where needed.
|
|
6
|
+
*/
|
|
7
|
+
/** Frigate severity: alert = notable, detection = regular. */
|
|
8
|
+
export type TimelineEventSeverity = "alert" | "detection";
|
|
9
|
+
/** Motion fragment type: start/end from event boundaries, update from motion API. */
|
|
10
|
+
export type MotionFragmentType = "start" | "end" | "update";
|
|
11
|
+
/**
|
|
12
|
+
* A detection event for timeline display and grouping.
|
|
13
|
+
* Unified from app's TimelineDetectionEvent and proxy's DetectionEvent.
|
|
14
|
+
*/
|
|
15
|
+
export interface DetectionEvent {
|
|
16
|
+
id?: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
source?: string;
|
|
19
|
+
classes?: string[];
|
|
20
|
+
/** @deprecated Use `classes` instead. Kept for backward compat with some app code. */
|
|
21
|
+
detectionClasses?: string[];
|
|
22
|
+
label?: string;
|
|
23
|
+
thumbnailUrl?: string;
|
|
24
|
+
imageUrl?: string;
|
|
25
|
+
videoUrl?: string;
|
|
26
|
+
gifUrl?: string;
|
|
27
|
+
severity?: TimelineEventSeverity;
|
|
28
|
+
deviceName?: string;
|
|
29
|
+
deviceId?: string;
|
|
30
|
+
ruleType?: string;
|
|
31
|
+
croppedThumbnailUrl?: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Motion on/off item for timeline bar.
|
|
36
|
+
* Unified from app's TimelineMotionItem and proxy's MotionItem.
|
|
37
|
+
*/
|
|
38
|
+
export interface MotionItem {
|
|
39
|
+
timestamp: number;
|
|
40
|
+
deviceId?: string;
|
|
41
|
+
motion: "on" | "off";
|
|
42
|
+
/** Fragment type or "motion" | "audio" (AN audio thresholds). */
|
|
43
|
+
type?: MotionFragmentType | "motion" | "audio" | string;
|
|
44
|
+
/** For type "audio": level 1–5 (threshold crossed). */
|
|
45
|
+
level?: number;
|
|
46
|
+
/** For type "audio": dBFS at threshold crossing. */
|
|
47
|
+
dBFS?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Rule artifact for timeline (from Scrypted AN plugin rules).
|
|
51
|
+
* Unified from app's TimelineArtifactItem and proxy's TimelineArtifact.
|
|
52
|
+
*/
|
|
53
|
+
export interface TimelineArtifact {
|
|
54
|
+
ruleName: string;
|
|
55
|
+
ruleType?: string;
|
|
56
|
+
timestamp: number;
|
|
57
|
+
imageUrl?: string;
|
|
58
|
+
gifUrl?: string;
|
|
59
|
+
videoUrl?: string;
|
|
60
|
+
}
|
|
61
|
+
/** Audio level segment for timeline bar. */
|
|
62
|
+
export interface AudioLevelSegment {
|
|
63
|
+
start: number;
|
|
64
|
+
end: number;
|
|
65
|
+
level: number;
|
|
66
|
+
dBFS?: number;
|
|
67
|
+
}
|
|
68
|
+
/** Recording segment for timeline bar (where recording exists). */
|
|
69
|
+
export interface TimelineRecordingSegment {
|
|
70
|
+
start: number;
|
|
71
|
+
end: number;
|
|
72
|
+
}
|
|
73
|
+
/** Day data for timeline (events, motion, artifacts). */
|
|
74
|
+
export interface CameraDayDataResponse {
|
|
75
|
+
events: DetectionEvent[];
|
|
76
|
+
motion: MotionItem[];
|
|
77
|
+
artifacts: TimelineArtifact[];
|
|
78
|
+
recordings?: TimelineRecordingSegment[];
|
|
79
|
+
}
|
|
80
|
+
/** Clustered day data response (server-side bucketing). */
|
|
81
|
+
export interface ClusteredDayDataResponse {
|
|
82
|
+
clusters: TimelineCluster[];
|
|
83
|
+
motion: MotionItem[];
|
|
84
|
+
artifacts: TimelineArtifact[];
|
|
85
|
+
motionSegments?: TimelineRecordingSegment[];
|
|
86
|
+
audioLevelSegments?: AudioLevelSegment[];
|
|
87
|
+
recordings?: TimelineRecordingSegment[];
|
|
88
|
+
}
|
|
89
|
+
/** A time-bucketed cluster of detection events. */
|
|
90
|
+
export interface TimelineCluster {
|
|
91
|
+
events: DetectionEvent[];
|
|
92
|
+
representative: DetectionEvent;
|
|
93
|
+
classes: string[];
|
|
94
|
+
labels: string[];
|
|
95
|
+
startMs: number;
|
|
96
|
+
endMs: number;
|
|
97
|
+
}
|
|
98
|
+
/** A group of related detection events with a representative. */
|
|
99
|
+
export interface DetectionGroup {
|
|
100
|
+
events: DetectionEvent[];
|
|
101
|
+
representative: DetectionEvent;
|
|
102
|
+
classes: string[];
|
|
103
|
+
labels: string[];
|
|
104
|
+
}
|
|
105
|
+
/** Query for clustered day data. */
|
|
106
|
+
export interface ClusteredDayDataQuery {
|
|
107
|
+
camera: string;
|
|
108
|
+
days: string[];
|
|
109
|
+
bucketMs?: number;
|
|
110
|
+
enabledClasses?: string[];
|
|
111
|
+
classFilter?: string;
|
|
112
|
+
}
|
|
113
|
+
/** Query for grouped events. */
|
|
114
|
+
export interface GroupedEventsQuery {
|
|
115
|
+
after?: number;
|
|
116
|
+
before?: number;
|
|
117
|
+
limit?: number;
|
|
118
|
+
offset?: number;
|
|
119
|
+
cameras?: string[];
|
|
120
|
+
detectionClasses?: string[];
|
|
121
|
+
eventSource?: string;
|
|
122
|
+
filter?: string;
|
|
123
|
+
groupingRange?: number;
|
|
124
|
+
}
|
|
125
|
+
/** Result of grouped events query. */
|
|
126
|
+
export interface GroupedEventsResult {
|
|
127
|
+
groups: DetectionGroup[];
|
|
128
|
+
total: number;
|
|
129
|
+
ruleArtifacts?: DetectionGroup[];
|
|
130
|
+
}
|
|
131
|
+
/** Query for events within a cluster time range. */
|
|
132
|
+
export interface ClusterEventsQuery {
|
|
133
|
+
camera: string;
|
|
134
|
+
startMs: number;
|
|
135
|
+
endMs: number;
|
|
136
|
+
}
|
|
137
|
+
/** Result of cluster events query. */
|
|
138
|
+
export interface ClusterEventsResult {
|
|
139
|
+
events: DetectionEvent[];
|
|
140
|
+
}
|
|
141
|
+
/** A reel event (carousel-friendly). All timestamps are in **seconds** (Unix epoch). */
|
|
142
|
+
export interface ReelEvent {
|
|
143
|
+
id: string;
|
|
144
|
+
camera: string;
|
|
145
|
+
cameraName: string;
|
|
146
|
+
label: string;
|
|
147
|
+
/** Unix timestamp in **seconds** (not milliseconds). */
|
|
148
|
+
startTime: number;
|
|
149
|
+
thumbnail?: string;
|
|
150
|
+
croppedThumbnailUrl?: string;
|
|
151
|
+
hasClip?: boolean;
|
|
152
|
+
gifUrl?: string;
|
|
153
|
+
videoUrl?: string;
|
|
154
|
+
imageUrl?: string;
|
|
155
|
+
classes?: string[];
|
|
156
|
+
}
|
|
157
|
+
/** Query for reel events. */
|
|
158
|
+
export interface ReelEventsQuery {
|
|
159
|
+
limit?: number;
|
|
160
|
+
offset?: number;
|
|
161
|
+
cameras?: string[];
|
|
162
|
+
eventSource?: string;
|
|
163
|
+
detectionClasses?: string[];
|
|
164
|
+
}
|
|
165
|
+
/** Result of reel events query. */
|
|
166
|
+
export interface ReelEventsResult {
|
|
167
|
+
events: ReelEvent[];
|
|
168
|
+
total: number;
|
|
169
|
+
}
|
|
170
|
+
/** @deprecated Use DetectionEvent instead. */
|
|
171
|
+
export type TimelineDetectionEvent = DetectionEvent;
|
|
172
|
+
/** @deprecated Use MotionItem instead. */
|
|
173
|
+
export type TimelineMotionItem = MotionItem;
|
|
174
|
+
/** @deprecated Use TimelineArtifact instead. */
|
|
175
|
+
export type TimelineArtifactItem = TimelineArtifact;
|
|
176
|
+
/** @deprecated Use TimelineCluster instead. */
|
|
177
|
+
export type TimelineClusterFromServer = TimelineCluster;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK client configuration types.
|
|
3
|
+
*
|
|
4
|
+
* NVR types are now in nvr.ts (shared across app and proxy).
|
|
5
|
+
*/
|
|
6
|
+
export interface HlsInfo {
|
|
7
|
+
hlsUrl: string;
|
|
8
|
+
}
|
|
9
|
+
/** Public provider info (returned by providers.list). */
|
|
10
|
+
export interface ProviderInfo {
|
|
11
|
+
id: string;
|
|
12
|
+
type: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ProxyClientConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Primary proxy URL (e.g. "http://192.168.1.100:4000").
|
|
18
|
+
* Can also be an array of URLs to race (like Scrypted SDK).
|
|
19
|
+
* The first successful connection wins.
|
|
20
|
+
*/
|
|
21
|
+
proxyUrl: string | string[];
|
|
22
|
+
/** Optional auth token for admin endpoints. */
|
|
23
|
+
token?: string;
|
|
24
|
+
/** Transport: "websocket" (default, like Scrypted SDK) or "http" (REST fallback). */
|
|
25
|
+
transport?: "websocket" | "http";
|
|
26
|
+
/** Connection timeout per URL in ms (default: 10000). */
|
|
27
|
+
connectTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface DirectClientConfig {
|
|
30
|
+
directUrl: string;
|
|
31
|
+
type: "frigate";
|
|
32
|
+
username?: string;
|
|
33
|
+
password?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface BackendClientConfig {
|
|
36
|
+
/** Backend server URL (e.g. "http://localhost:4443") */
|
|
37
|
+
readonly serverUrl: string;
|
|
38
|
+
/** JWT token for authentication */
|
|
39
|
+
readonly token?: string;
|
|
40
|
+
/** Connection timeout in ms (default: 10000) */
|
|
41
|
+
readonly connectTimeoutMs?: number;
|
|
42
|
+
}
|
|
43
|
+
export type CamStackClientConfig = ProxyClientConfig | DirectClientConfig;
|
|
44
|
+
export declare function isProxyConfig(config: CamStackClientConfig): config is ProxyClientConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -23,7 +23,11 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"superjson": "^2.2.6"
|
|
25
25
|
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@camstack/types": "^0.1.0"
|
|
28
|
+
},
|
|
26
29
|
"devDependencies": {
|
|
30
|
+
"@camstack/types": "*",
|
|
27
31
|
"@trpc/client": "^11.16.0",
|
|
28
32
|
"@trpc/server": "^11.16.0",
|
|
29
33
|
"@types/node": "^22.19.13",
|