@camera.ui/sdk 0.0.2 → 0.0.4
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/LICENSE.md +1 -1
- package/README.md +3 -3
- package/dist/camera/events.js +2 -0
- package/dist/camera/index.js +3 -1
- package/dist/external.js +7 -0
- package/dist/index.d.ts +7132 -4248
- package/dist/index.js +3 -11
- package/dist/internal/contract-validators.js +21 -0
- package/dist/internal/index.d.ts +915 -0
- package/dist/internal/index.js +9 -0
- package/dist/internal/sensor-triggers.js +2 -0
- package/dist/internal/shared-utils.js +86 -0
- package/dist/internal/streaming-internal.js +1 -0
- package/dist/manager/index.js +1 -1
- package/dist/observable/index.js +419 -0
- package/dist/plugin/api.js +21 -0
- package/dist/plugin/contract.js +101 -114
- package/dist/plugin/helper.js +277 -0
- package/dist/plugin/index.js +4 -1
- package/dist/plugin/interfaces.js +51 -1
- package/dist/plugin/notifier.js +23 -0
- package/dist/plugin/oauth.js +1 -0
- package/dist/sensor/audio.js +103 -81
- package/dist/sensor/base.js +350 -318
- package/dist/sensor/battery.js +73 -59
- package/dist/sensor/classifier.js +117 -0
- package/dist/sensor/clip.js +30 -0
- package/dist/sensor/contact.js +37 -18
- package/dist/sensor/detection.js +4 -0
- package/dist/sensor/doorbell.js +52 -38
- package/dist/sensor/face.js +71 -86
- package/dist/sensor/garage.js +121 -0
- package/dist/sensor/humidity.js +52 -0
- package/dist/sensor/index.js +17 -11
- package/dist/sensor/leak.js +52 -0
- package/dist/sensor/licensePlate.js +70 -79
- package/dist/sensor/light.js +82 -38
- package/dist/sensor/lock.js +99 -0
- package/dist/sensor/motion.js +85 -70
- package/dist/sensor/object.js +73 -94
- package/dist/sensor/occupancy.js +52 -0
- package/dist/sensor/ptz.js +114 -100
- package/dist/sensor/securitySystem.js +98 -0
- package/dist/sensor/siren.js +75 -43
- package/dist/sensor/smoke.js +52 -0
- package/dist/sensor/spec.js +1 -0
- package/dist/sensor/switch.js +72 -0
- package/dist/sensor/temperature.js +52 -0
- package/dist/storage/index.js +1 -2
- package/dist/types.js +1 -0
- package/docs/.vitepress/config.ts +77 -0
- package/docs/.vitepress/theme/index.ts +5 -0
- package/docs/.vitepress/theme/style.css +117 -0
- package/docs/index.md +16 -0
- package/docs/logo.png +0 -0
- package/docs/public/apple-touch-icon.png +0 -0
- package/docs/public/favicon-16.ico +0 -0
- package/docs/public/favicon.ico +0 -0
- package/docs/public/logo.svg +1 -0
- package/examples/README.md +7 -0
- package/examples/getting-started.md +535 -0
- package/package.json +36 -23
- package/scripts/build-example-docs.mjs +62 -0
- package/tsconfig.node.json +3 -2
- package/typedoc.json +42 -0
- package/dist/sensor/guards.js +0 -133
- package/dist/sensor/types.js +0 -46
- package/dist/service/base.js +0 -96
- package/dist/service/index.js +0 -3
- /package/dist/camera/{types.js → enums.js} +0 -0
- /package/dist/{manager/types.js → camera/frames.js} +0 -0
- /package/dist/{plugin/types.js → internal/camera-config-internal.js} +0 -0
- /package/dist/{service/services.js → internal/camera-enums.js} +0 -0
- /package/dist/{service/types.js → internal/camera-wire.js} +0 -0
- /package/dist/{storage/schema.js → internal/manager-rpc.js} +0 -0
- /package/dist/{storage/storages.js → internal/sensor-rpc.js} +0 -0
package/dist/plugin/contract.js
CHANGED
|
@@ -1,120 +1,107 @@
|
|
|
1
|
-
import { SensorType } from '../sensor/types.js';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @param contract - The contract to validate
|
|
6
|
-
*
|
|
7
|
-
* @returns Whether the contract is valid
|
|
2
|
+
* Role a plugin plays in the system. The role decides which lifecycle hooks
|
|
3
|
+
* the host invokes and which contract validations apply (see helper.ts).
|
|
8
4
|
*/
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
if (c.dependencies !== undefined && !Array.isArray(c.dependencies)) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
if (c.cameraController !== undefined && typeof c.cameraController !== 'boolean') {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
5
|
+
export var PluginRole;
|
|
6
|
+
(function (PluginRole) {
|
|
7
|
+
/**
|
|
8
|
+
* Cloud-service integration that manages its own cameras end-to-end via a
|
|
9
|
+
* vendor account (e.g. a vendor SDK / cloud API). The hub owns camera
|
|
10
|
+
* creation, streaming and sensors; it cannot expose sensors for cameras
|
|
11
|
+
* owned by other plugins.
|
|
12
|
+
*/
|
|
13
|
+
PluginRole["Hub"] = "hub";
|
|
14
|
+
/**
|
|
15
|
+
* Adds sensors to existing cameras without owning the camera itself.
|
|
16
|
+
* Typical use: a detection plugin that consumes another plugin's video
|
|
17
|
+
* frames and emits motion / object / face detections back into the system.
|
|
18
|
+
*/
|
|
19
|
+
PluginRole["SensorProvider"] = "sensorProvider";
|
|
20
|
+
/**
|
|
21
|
+
* Manages cameras and their media streams (ONVIF, RTSP, generic IP, ...).
|
|
22
|
+
* The plugin is responsible for stream URLs, PTZ, snapshots, and the
|
|
23
|
+
* lifecycle hooks in BasePlugin. It does not produce sensors for foreign
|
|
24
|
+
* cameras.
|
|
25
|
+
*/
|
|
26
|
+
PluginRole["CameraController"] = "cameraController";
|
|
27
|
+
/**
|
|
28
|
+
* Combined role: plugin both manages cameras and exposes sensors (its own
|
|
29
|
+
* cameras and, when consumes is set, also foreign cameras). Used by
|
|
30
|
+
* integrations that ship a complete camera + detection stack.
|
|
31
|
+
*/
|
|
32
|
+
PluginRole["CameraAndSensorProvider"] = "cameraAndSensorProvider";
|
|
33
|
+
})(PluginRole || (PluginRole = {}));
|
|
47
34
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* @param contract - The plugin contract to check
|
|
51
|
-
*
|
|
52
|
-
* @returns Whether the plugin is a provider
|
|
35
|
+
* Capability flags a plugin advertises in its contract. The host uses these
|
|
36
|
+
* to decide which RPC handlers to wire up and which UI affordances to show.
|
|
53
37
|
*/
|
|
54
|
-
export
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
export var PluginInterface;
|
|
39
|
+
(function (PluginInterface) {
|
|
40
|
+
/** Implements MotionDetectionInterface (video-based motion detection). */
|
|
41
|
+
PluginInterface["MotionDetection"] = "MotionDetection";
|
|
42
|
+
/** Implements ObjectDetectionInterface (e.g. person, vehicle, animal). */
|
|
43
|
+
PluginInterface["ObjectDetection"] = "ObjectDetection";
|
|
44
|
+
/** Implements AudioDetectionInterface (event/keyword audio detection). */
|
|
45
|
+
PluginInterface["AudioDetection"] = "AudioDetection";
|
|
46
|
+
/**
|
|
47
|
+
* Implements FaceDetectionInterface (face localisation + embeddings). The
|
|
48
|
+
* NVR owns matching against enrolled faces; the plugin only emits
|
|
49
|
+
* detections + embeddings.
|
|
50
|
+
*/
|
|
51
|
+
PluginInterface["FaceDetection"] = "FaceDetection";
|
|
52
|
+
/** Implements LicensePlateDetectionInterface (plate localisation + OCR). */
|
|
53
|
+
PluginInterface["LicensePlateDetection"] = "LicensePlateDetection";
|
|
54
|
+
/**
|
|
55
|
+
* Implements ClassifierDetectionInterface (generic image classification
|
|
56
|
+
* emitting attribute/label pairs).
|
|
57
|
+
*/
|
|
58
|
+
PluginInterface["ClassifierDetection"] = "ClassifierDetection";
|
|
59
|
+
/**
|
|
60
|
+
* Implements ClipDetectionInterface (CLIP image and text embeddings used
|
|
61
|
+
* for semantic search).
|
|
62
|
+
*/
|
|
63
|
+
PluginInterface["ClipDetection"] = "ClipDetection";
|
|
64
|
+
/**
|
|
65
|
+
* Implements DiscoveryProvider — plugin can scan the network for new
|
|
66
|
+
* cameras and adopt them. Only valid for camera-controlling roles.
|
|
67
|
+
*/
|
|
68
|
+
PluginInterface["DiscoveryProvider"] = "DiscoveryProvider";
|
|
69
|
+
/**
|
|
70
|
+
* Implements NVRInterface — persists events and recordings, and serves
|
|
71
|
+
* them back to the UI / mobile clients. Exactly one plugin per host
|
|
72
|
+
* fills this role at runtime.
|
|
73
|
+
*/
|
|
74
|
+
PluginInterface["NVR"] = "NVR";
|
|
75
|
+
/**
|
|
76
|
+
* Implements NotifierInterface (getDevices, sendNotification, ...). Lets
|
|
77
|
+
* the central NotificationManager dispatch notifications to this plugin
|
|
78
|
+
* regardless of role — see notifier.ts.
|
|
79
|
+
*/
|
|
80
|
+
PluginInterface["Notifier"] = "Notifier";
|
|
81
|
+
/**
|
|
82
|
+
* Implements the OAuthCapable base interface (getOAuthMetadata,
|
|
83
|
+
* getOAuthState, disconnect) plus at least one flow sub-interface below —
|
|
84
|
+
* see oauth.ts.
|
|
85
|
+
*/
|
|
86
|
+
PluginInterface["OAuthCapable"] = "OAuthCapable";
|
|
87
|
+
/** Implements OAuthDeviceFlowCapable (RFC 8628 Device Authorization Grant). */
|
|
88
|
+
PluginInterface["OAuthDeviceFlow"] = "OAuthDeviceFlow";
|
|
89
|
+
/** Implements OAuthAuthCodeFlowCapable (Authorization Code Flow + PKCE). */
|
|
90
|
+
PluginInterface["OAuthAuthCodeFlow"] = "OAuthAuthCodeFlow";
|
|
91
|
+
/** Implements OAuthClientCredentialsCapable (user-supplied client_id + client_secret). */
|
|
92
|
+
PluginInterface["OAuthClientCredentials"] = "OAuthClientCredentials";
|
|
93
|
+
})(PluginInterface || (PluginInterface = {}));
|
|
57
94
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @returns Whether the plugin is a consumer
|
|
95
|
+
* Permission a plugin requests so it can call a host-provided system feature.
|
|
96
|
+
* Each capability gates one outgoing SDK call — calls without the matching
|
|
97
|
+
* capability are rejected by the host.
|
|
63
98
|
*/
|
|
64
|
-
export
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*/
|
|
74
|
-
export function isHub(contract) {
|
|
75
|
-
return contract.provides.length === 0 && contract.consumes.length > 0;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Check if a plugin provides a specific sensor type
|
|
79
|
-
*
|
|
80
|
-
* @param contract - The plugin contract to check
|
|
81
|
-
*
|
|
82
|
-
* @param type - The sensor type to check
|
|
83
|
-
*
|
|
84
|
-
* @returns Whether the plugin provides the sensor type
|
|
85
|
-
*/
|
|
86
|
-
export function providesSensor(contract, type) {
|
|
87
|
-
return contract.provides.includes(type);
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Check if a plugin consumes a specific sensor type
|
|
91
|
-
*
|
|
92
|
-
* @param contract - The plugin contract to check
|
|
93
|
-
*
|
|
94
|
-
* @param type - The sensor type to check
|
|
95
|
-
*
|
|
96
|
-
* @returns Whether the plugin consumes the sensor type
|
|
97
|
-
*/
|
|
98
|
-
export function consumesSensor(contract, type) {
|
|
99
|
-
return contract.consumes.includes(type);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Check if a plugin is a camera controller (can create/manage cameras)
|
|
103
|
-
*
|
|
104
|
-
* @param contract - The plugin contract to check
|
|
105
|
-
*
|
|
106
|
-
* @returns Whether the plugin is a camera controller
|
|
107
|
-
*/
|
|
108
|
-
export function isCameraController(contract) {
|
|
109
|
-
return contract.cameraController === true;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Check if a plugin is qualified (provides, consumes, or is camera controller)
|
|
113
|
-
*
|
|
114
|
-
* @param contract - The plugin contract to check
|
|
115
|
-
*
|
|
116
|
-
* @returns Whether the plugin is qualified
|
|
117
|
-
*/
|
|
118
|
-
export function isQualifiedContract(contract) {
|
|
119
|
-
return contract.provides.length > 0 || contract.consumes.length > 0 || contract.cameraController === true;
|
|
120
|
-
}
|
|
99
|
+
export var PluginCapability;
|
|
100
|
+
(function (PluginCapability) {
|
|
101
|
+
/**
|
|
102
|
+
* Grants the plugin permission to call `api.notificationManager.publish`.
|
|
103
|
+
* Without this capability the host silently drops published notifications
|
|
104
|
+
* and logs an error.
|
|
105
|
+
*/
|
|
106
|
+
PluginCapability["PublishNotifications"] = "publishNotifications";
|
|
107
|
+
})(PluginCapability || (PluginCapability = {}));
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { PluginCapability, PluginInterface, PluginRole } from './contract.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check the structural validity of an unknown contract object — required
|
|
4
|
+
* fields present, enum values inside the accepted sets — and return one
|
|
5
|
+
* human-readable error per problem found. Returns an empty array when the
|
|
6
|
+
* contract is valid.
|
|
7
|
+
*
|
|
8
|
+
* @param contract - Untrusted candidate contract (e.g. from manifest JSON).
|
|
9
|
+
*
|
|
10
|
+
* @returns Error messages, empty if the contract is valid.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { getContractValidationErrors } from '@camera.ui/sdk';
|
|
15
|
+
*
|
|
16
|
+
* const errors = getContractValidationErrors(rawManifest);
|
|
17
|
+
* if (errors.length) throw new Error(errors.join('\n'));
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function getContractValidationErrors(contract) {
|
|
21
|
+
const errors = [];
|
|
22
|
+
if (!contract || typeof contract !== 'object') {
|
|
23
|
+
errors.push('Contract must be an object. Got: ' + (contract === null ? 'null' : typeof contract));
|
|
24
|
+
return errors;
|
|
25
|
+
}
|
|
26
|
+
const c = contract;
|
|
27
|
+
const validRoles = Object.values(PluginRole);
|
|
28
|
+
// Import SensorType values dynamically to avoid circular dependency
|
|
29
|
+
const validSensorTypes = [
|
|
30
|
+
'motion',
|
|
31
|
+
'object',
|
|
32
|
+
'audio',
|
|
33
|
+
'face',
|
|
34
|
+
'licensePlate',
|
|
35
|
+
'classifier',
|
|
36
|
+
'contact',
|
|
37
|
+
'temperature',
|
|
38
|
+
'humidity',
|
|
39
|
+
'occupancy',
|
|
40
|
+
'smoke',
|
|
41
|
+
'leak',
|
|
42
|
+
'light',
|
|
43
|
+
'siren',
|
|
44
|
+
'switch',
|
|
45
|
+
'lock',
|
|
46
|
+
'garage',
|
|
47
|
+
'ptz',
|
|
48
|
+
'securitySystem',
|
|
49
|
+
'doorbell',
|
|
50
|
+
'battery',
|
|
51
|
+
'clip',
|
|
52
|
+
];
|
|
53
|
+
// Check role
|
|
54
|
+
if (c.role === undefined) {
|
|
55
|
+
errors.push('Missing required field: "role"');
|
|
56
|
+
}
|
|
57
|
+
else if (typeof c.role !== 'string') {
|
|
58
|
+
errors.push(`Field "role" must be a string. Got: ${typeof c.role}`);
|
|
59
|
+
}
|
|
60
|
+
else if (!validRoles.includes(c.role)) {
|
|
61
|
+
errors.push(`Invalid role "${c.role}". Valid roles: ${validRoles.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
// Check name
|
|
64
|
+
if (c.name === undefined) {
|
|
65
|
+
errors.push('Missing required field: "name"');
|
|
66
|
+
}
|
|
67
|
+
else if (typeof c.name !== 'string') {
|
|
68
|
+
errors.push(`Field "name" must be a string. Got: ${typeof c.name}`);
|
|
69
|
+
}
|
|
70
|
+
else if (c.name.length === 0) {
|
|
71
|
+
errors.push('Field "name" cannot be empty');
|
|
72
|
+
}
|
|
73
|
+
// Check provides
|
|
74
|
+
if (c.provides === undefined) {
|
|
75
|
+
errors.push('Missing required field: "provides"');
|
|
76
|
+
}
|
|
77
|
+
else if (!Array.isArray(c.provides)) {
|
|
78
|
+
errors.push(`Field "provides" must be an array. Got: ${typeof c.provides}`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
for (const type of c.provides) {
|
|
82
|
+
if (!validSensorTypes.includes(type)) {
|
|
83
|
+
errors.push(`Invalid sensor type in "provides": "${type}". Valid types: ${validSensorTypes.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Check consumes
|
|
88
|
+
if (c.consumes === undefined) {
|
|
89
|
+
errors.push('Missing required field: "consumes"');
|
|
90
|
+
}
|
|
91
|
+
else if (!Array.isArray(c.consumes)) {
|
|
92
|
+
errors.push(`Field "consumes" must be an array. Got: ${typeof c.consumes}`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
for (const type of c.consumes) {
|
|
96
|
+
if (!validSensorTypes.includes(type)) {
|
|
97
|
+
errors.push(`Invalid sensor type in "consumes": "${type}". Valid types: ${validSensorTypes.join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Check interfaces
|
|
102
|
+
const validInterfaces = Object.values(PluginInterface);
|
|
103
|
+
if (c.interfaces === undefined) {
|
|
104
|
+
errors.push('Missing required field: "interfaces"');
|
|
105
|
+
}
|
|
106
|
+
else if (!Array.isArray(c.interfaces)) {
|
|
107
|
+
errors.push(`Field "interfaces" must be an array. Got: ${typeof c.interfaces}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
for (const iface of c.interfaces) {
|
|
111
|
+
if (!validInterfaces.includes(iface)) {
|
|
112
|
+
errors.push(`Invalid interface in "interfaces": "${iface}". Valid interfaces: ${validInterfaces.join(', ')}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Check optional capabilities
|
|
117
|
+
const validCapabilities = Object.values(PluginCapability);
|
|
118
|
+
if (c.capabilities !== undefined) {
|
|
119
|
+
if (!Array.isArray(c.capabilities)) {
|
|
120
|
+
errors.push(`Field "capabilities" must be an array. Got: ${typeof c.capabilities}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
for (const cap of c.capabilities) {
|
|
124
|
+
if (!validCapabilities.includes(cap)) {
|
|
125
|
+
errors.push(`Invalid capability in "capabilities": "${cap}". Valid capabilities: ${validCapabilities.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Check optional pythonVersion
|
|
131
|
+
if (c.pythonVersion !== undefined) {
|
|
132
|
+
if (!['3.11', '3.12'].includes(c.pythonVersion)) {
|
|
133
|
+
errors.push(`Invalid pythonVersion "${c.pythonVersion}". Valid versions: 3.11, 3.12`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Check optional dependencies
|
|
137
|
+
if (c.dependencies !== undefined && !Array.isArray(c.dependencies)) {
|
|
138
|
+
errors.push(`Field "dependencies" must be an array. Got: ${typeof c.dependencies}`);
|
|
139
|
+
}
|
|
140
|
+
return errors;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Enforce role-specific consistency rules on top of the structural check
|
|
144
|
+
* (e.g. SensorProvider plugins must declare at least one provided sensor;
|
|
145
|
+
* Hub plugins cannot expose sensors). Throws on the first violation.
|
|
146
|
+
*
|
|
147
|
+
* @param contract - Already-structurally-valid contract.
|
|
148
|
+
*
|
|
149
|
+
* @param pluginName - Optional plugin name; used to prefix error messages.
|
|
150
|
+
*
|
|
151
|
+
* @throws {Error} When the contract violates a role-specific rule.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* import { validateContractConsistency } from '@camera.ui/sdk';
|
|
156
|
+
*
|
|
157
|
+
* validateContractConsistency(contract, 'my-plugin');
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export function validateContractConsistency(contract, pluginName) {
|
|
161
|
+
const prefix = pluginName ? `Plugin "${pluginName}": ` : '';
|
|
162
|
+
switch (contract.role) {
|
|
163
|
+
case PluginRole.Hub:
|
|
164
|
+
if (contract.provides.length > 0) {
|
|
165
|
+
throw new Error(`${prefix}Hub plugins cannot provide sensors.`);
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case PluginRole.SensorProvider:
|
|
169
|
+
if (contract.provides.length === 0) {
|
|
170
|
+
throw new Error(`${prefix}SensorProvider plugins must provide at least one sensor type.`);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case PluginRole.CameraAndSensorProvider:
|
|
174
|
+
if (contract.provides.length === 0) {
|
|
175
|
+
throw new Error(`${prefix}CameraAndSensorProvider plugins must provide at least one sensor type.`);
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
case PluginRole.CameraController:
|
|
179
|
+
// CameraController can have empty or filled provides array
|
|
180
|
+
// (sensors are only for its own cameras)
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Reports whether the plugin's role is Hub (vendor cloud integration that
|
|
186
|
+
* manages its own cameras end-to-end).
|
|
187
|
+
*
|
|
188
|
+
* @param contract - Plugin contract to inspect.
|
|
189
|
+
*
|
|
190
|
+
* @returns `true` if the role is {@link PluginRole.Hub}.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* import { isHub } from '@camera.ui/sdk';
|
|
195
|
+
*
|
|
196
|
+
* if (isHub(contract)) skipLocalDiscovery();
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export function isHub(contract) {
|
|
200
|
+
return contract.role === PluginRole.Hub;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Reports whether the plugin is allowed to add sensors to cameras owned by
|
|
204
|
+
* other plugins (true for SensorProvider and CameraAndSensorProvider).
|
|
205
|
+
* Hub and pure CameraController plugins only see their own cameras.
|
|
206
|
+
*
|
|
207
|
+
* @param contract - Plugin contract to inspect.
|
|
208
|
+
*
|
|
209
|
+
* @returns `true` if the plugin may attach sensors to any camera.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* import { canProvideSensorsToAnyCameras } from '@camera.ui/sdk';
|
|
214
|
+
*
|
|
215
|
+
* if (canProvideSensorsToAnyCameras(contract)) listAllCameras();
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export function canProvideSensorsToAnyCameras(contract) {
|
|
219
|
+
return contract.role === PluginRole.SensorProvider || contract.role === PluginRole.CameraAndSensorProvider;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Reports whether the plugin can create cameras (role is CameraController
|
|
223
|
+
* or CameraAndSensorProvider). Used to gate camera-creating operations such
|
|
224
|
+
* as DiscoveryProvider adoption.
|
|
225
|
+
*
|
|
226
|
+
* @param contract - Plugin contract to inspect.
|
|
227
|
+
*
|
|
228
|
+
* @returns `true` if the plugin may create cameras.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* import { canCreateCameras } from '@camera.ui/sdk';
|
|
233
|
+
*
|
|
234
|
+
* if (canCreateCameras(contract)) enableAdoption();
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
export function canCreateCameras(contract) {
|
|
238
|
+
return contract.role === PluginRole.CameraController || contract.role === PluginRole.CameraAndSensorProvider;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Reports whether the plugin implements the given capability.
|
|
242
|
+
*
|
|
243
|
+
* @param contract - Plugin contract to inspect.
|
|
244
|
+
*
|
|
245
|
+
* @param iface - Interface to check (e.g. `PluginInterface.DiscoveryProvider`).
|
|
246
|
+
*
|
|
247
|
+
* @returns `true` if `iface` is listed in the contract's `interfaces`.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* import { hasInterface, PluginInterface } from '@camera.ui/sdk';
|
|
252
|
+
*
|
|
253
|
+
* if (hasInterface(contract, PluginInterface.DiscoveryProvider)) startScan();
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
export function hasInterface(contract, iface) {
|
|
257
|
+
return contract.interfaces.includes(iface);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Reports whether the plugin requested the given capability.
|
|
261
|
+
*
|
|
262
|
+
* @param contract - Plugin contract to inspect.
|
|
263
|
+
*
|
|
264
|
+
* @param cap - Capability to check (e.g. `PluginCapability.PublishNotifications`).
|
|
265
|
+
*
|
|
266
|
+
* @returns `true` if `cap` is listed in the contract's `capabilities`.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* import { hasCapability, PluginCapability } from '@camera.ui/sdk';
|
|
271
|
+
*
|
|
272
|
+
* if (hasCapability(contract, PluginCapability.PublishNotifications)) allowPublish();
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
export function hasCapability(contract, cap) {
|
|
276
|
+
return contract.capabilities?.includes(cap) ?? false;
|
|
277
|
+
}
|
package/dist/plugin/index.js
CHANGED
|
@@ -1 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Base class every plugin extends. It wires up the three dependencies the
|
|
3
|
+
* host injects (logger, PluginAPI, DeviceStorage) and declares the lifecycle
|
|
4
|
+
* methods the host calls on the plugin.
|
|
5
|
+
*
|
|
6
|
+
* Lifecycle order: the host calls `configureCameras()` once at startup with
|
|
7
|
+
* every camera already assigned to this plugin, then calls `onCameraAdded()`
|
|
8
|
+
* / `onCameraReleased()` as the user adds or removes cameras at runtime.
|
|
9
|
+
*
|
|
10
|
+
* The generic `T` types `storage.values` so plugin code gets autocompletion
|
|
11
|
+
* for its own settings shape.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* export default class MyPlugin extends BasePlugin<MyStorage> {
|
|
16
|
+
* private state = new Map<string, MyState>();
|
|
17
|
+
*
|
|
18
|
+
* async configureCameras(cameras: CameraDevice[]): Promise<void> {
|
|
19
|
+
* for (const camera of cameras) await this.onCameraAdded(camera);
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* async onCameraAdded(camera: CameraDevice): Promise<void> {
|
|
23
|
+
* this.state.set(camera.id, await this.attach(camera));
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* async onCameraReleased(cameraId: string): Promise<void> {
|
|
27
|
+
* this.state.get(cameraId)?.dispose();
|
|
28
|
+
* this.state.delete(cameraId);
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @see /examples/getting-started — end-to-end walkthrough.
|
|
34
|
+
*/
|
|
35
|
+
export class BasePlugin {
|
|
36
|
+
logger;
|
|
37
|
+
api;
|
|
38
|
+
storage;
|
|
39
|
+
constructor(logger, api, storage) {
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
this.api = api;
|
|
42
|
+
this.storage = storage;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Override to register a JSON schema for the plugin-level settings form
|
|
46
|
+
* rendered in the UI. Default: no schema.
|
|
47
|
+
*/
|
|
48
|
+
get storageSchema() {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic notification types — domain-agnostic. The NotificationManager and
|
|
3
|
+
* notifier plugins talk over RPC and JSON-encode these types directly.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Severity classifies how urgent a Notification is. Notifiers map this to
|
|
7
|
+
* platform-specific delivery characteristics; the host bypasses user-configured
|
|
8
|
+
* Quiet Hours for `critical`.
|
|
9
|
+
*/
|
|
10
|
+
export var Severity;
|
|
11
|
+
(function (Severity) {
|
|
12
|
+
/** Standard notification — default delivery (sound + banner). */
|
|
13
|
+
Severity["Info"] = "info";
|
|
14
|
+
/** Heightened attention; notifiers may use a different sound/colour. */
|
|
15
|
+
Severity["Warn"] = "warn";
|
|
16
|
+
/** Failure or action-required notification. */
|
|
17
|
+
Severity["Error"] = "error";
|
|
18
|
+
/**
|
|
19
|
+
* Highest-priority delivery on supporting notifiers; bypasses user-configured
|
|
20
|
+
* Quiet Hours on the host.
|
|
21
|
+
*/
|
|
22
|
+
Severity["Critical"] = "critical";
|
|
23
|
+
})(Severity || (Severity = {}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|