@capturebridge/sdk 0.7.0
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/Camera.js +68 -0
- package/CanonCamera.js +25 -0
- package/CaptureBridge.js +162 -0
- package/CapturePlugin.js +79 -0
- package/Common.js +98 -0
- package/Device.js +451 -0
- package/ICAO.js +70 -0
- package/IFace.js +274 -0
- package/Logs.js +129 -0
- package/MockCamera.js +10 -0
- package/Plugin.js +298 -0
- package/Scanner.js +64 -0
- package/SignatureTablet.js +109 -0
- package/SignatureTabletWidgets.js +153 -0
- package/StreamingCapturePlugin.js +121 -0
- package/TopazSigGem.js +25 -0
- package/package.json +7 -0
package/IFace.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import ICAO from './ICAO.js'
|
|
2
|
+
import { BASE_URL, blobToBase64 } from './Common.js'
|
|
3
|
+
|
|
4
|
+
// This is not exported as we may eventually detect this from the plugin
|
|
5
|
+
// See: Iface.matchModes()
|
|
6
|
+
const VALID_MODES = ['accurate', 'balanced', 'fast', 'accurate_server']
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Probe
|
|
10
|
+
* @classdesc Probe Class for Innovatrics IFace SDK
|
|
11
|
+
*
|
|
12
|
+
* @see {@link IFace}
|
|
13
|
+
*/
|
|
14
|
+
export class Probe {
|
|
15
|
+
source
|
|
16
|
+
url
|
|
17
|
+
imageData
|
|
18
|
+
/**
|
|
19
|
+
* Instantiate an IFace Probe
|
|
20
|
+
* @constructor
|
|
21
|
+
* @param {string} source - The image source for the probe.
|
|
22
|
+
* @param {string} [type] - The type of data in the image source, valid values
|
|
23
|
+
* are 'url', 'base64', and 'dataurl'. If not provided 'base64' is the default.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const candidate = new Candidate('/person/123.jpg', '123', 'url')
|
|
27
|
+
*/
|
|
28
|
+
constructor (source, type = 'base64') {
|
|
29
|
+
// TODO:
|
|
30
|
+
// If the source is typeof object and/or type=object we should do some duck
|
|
31
|
+
// typing and accept any of...
|
|
32
|
+
// - The result of an ICAO check that contains a cropped image
|
|
33
|
+
// - An Image tag (fetch the source)
|
|
34
|
+
// - A Blob
|
|
35
|
+
// - An Array Buffer
|
|
36
|
+
// - An object that has a fullCapture method (call function in .data())
|
|
37
|
+
switch (type) {
|
|
38
|
+
case 'url':
|
|
39
|
+
this.url = source
|
|
40
|
+
break
|
|
41
|
+
case 'base64':
|
|
42
|
+
this.imageData = source
|
|
43
|
+
break
|
|
44
|
+
case 'dataurl':
|
|
45
|
+
this.imageData = source.split(',')[1]
|
|
46
|
+
break
|
|
47
|
+
case undefined:
|
|
48
|
+
case null:
|
|
49
|
+
default:
|
|
50
|
+
throw new Error('Invalid image source type provided')
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fetch the image data and base64 encode it
|
|
56
|
+
* @async
|
|
57
|
+
* @example
|
|
58
|
+
* const probe = new Probe("/img/photo.jpg")
|
|
59
|
+
* await probe.data()
|
|
60
|
+
* console.log(probe.imageData)
|
|
61
|
+
*/
|
|
62
|
+
async data () {
|
|
63
|
+
if (!this.imageData) {
|
|
64
|
+
const response = await fetch(this.url)
|
|
65
|
+
const blob = await response.blob()
|
|
66
|
+
this.imageData = await blobToBase64(blob)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
toJSON () {
|
|
71
|
+
return this.imageData
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Candidate
|
|
77
|
+
* @classdesc Candidate Class for Innovatrics IFace SDK
|
|
78
|
+
*
|
|
79
|
+
* @see {@link IFace}
|
|
80
|
+
*/
|
|
81
|
+
export class Candidate extends Probe {
|
|
82
|
+
id
|
|
83
|
+
|
|
84
|
+
// TODO: Support setting different modes on individual candidates
|
|
85
|
+
// an example usecase for this is to set 'accurate_server' the most recent
|
|
86
|
+
// photo, and 'fast' for the remaining set (since they likely have already
|
|
87
|
+
// been verified at some point) In the plugin we would have to re-template the
|
|
88
|
+
// probe for each additional mode and it doesn't make sense to do so until
|
|
89
|
+
// the plugin is checking each photo against the probe in a separate thread.
|
|
90
|
+
mode
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Instantiate an IFace Candidate
|
|
94
|
+
* @constructor
|
|
95
|
+
* @param {string} source - The image source for the candidate.
|
|
96
|
+
* @param {string} id - The ID for the candidate when the match results are
|
|
97
|
+
* returned this Id will be returned with it's respective results.
|
|
98
|
+
* @param {string} [type] - The type of data in the image source, valid values
|
|
99
|
+
* are 'url', 'base64', and 'dataurl'. If not provided 'base64' is the default.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const candidate = new Candidate('/person/123.jpg', '123', 'url')
|
|
103
|
+
*/
|
|
104
|
+
constructor (source, id, type = 'base64') {
|
|
105
|
+
super(source, type)
|
|
106
|
+
if (id) {
|
|
107
|
+
this.id = id
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error('Candidate ID not provided')
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
toJSON () {
|
|
114
|
+
return {
|
|
115
|
+
id: this.id,
|
|
116
|
+
base64: this.imageData
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* IFace
|
|
123
|
+
* @classdesc Class for Innovatrics IFace SDK
|
|
124
|
+
*
|
|
125
|
+
*/
|
|
126
|
+
export class IFace extends ICAO {
|
|
127
|
+
/**
|
|
128
|
+
* Instantiate an IFace plugin
|
|
129
|
+
* @constructor
|
|
130
|
+
* @param {string} [baseURL] - Protocol, domain, and port for the service.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* const iface = new IFace()
|
|
134
|
+
*/
|
|
135
|
+
constructor (baseUrl = BASE_URL) {
|
|
136
|
+
super('capture_verify_iface', baseUrl)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Return list of available face matching modes
|
|
141
|
+
*
|
|
142
|
+
* @returns {string[]} List of available face matching modes
|
|
143
|
+
*
|
|
144
|
+
* @async
|
|
145
|
+
* @example
|
|
146
|
+
* const iface = new IFace()
|
|
147
|
+
* const modes = await iface.matchModes()
|
|
148
|
+
* // do something with modes, such as build a <select> element
|
|
149
|
+
* const select = document.createElement('select')
|
|
150
|
+
* modes.forEach(mode => {
|
|
151
|
+
* const option = document.createElement('option')
|
|
152
|
+
* option.value = mode
|
|
153
|
+
* select.appendChild(mode)
|
|
154
|
+
* })
|
|
155
|
+
* document.body.appendChild(select)
|
|
156
|
+
*/
|
|
157
|
+
async matchModes () {
|
|
158
|
+
// This is async as we may later get this from the plugin at runtime
|
|
159
|
+
return VALID_MODES
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
matchModesHandler (callback) {
|
|
163
|
+
callback(null, VALID_MODES)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check the ICAO compliance of an image
|
|
168
|
+
* @param {string} image - A base64 encoded image.
|
|
169
|
+
*
|
|
170
|
+
* @param {string|bool} [crop] - If falsy image will not be cropped, otherwise
|
|
171
|
+
* the value will be interpreted as the preferred crop method which will vary
|
|
172
|
+
* by plugin implementation.
|
|
173
|
+
*
|
|
174
|
+
* @returns {object} Results of the ICAO check, this may vary by plugin
|
|
175
|
+
* implementation.
|
|
176
|
+
*
|
|
177
|
+
* @async
|
|
178
|
+
* @example
|
|
179
|
+
* // Take a photo with a Canon Camera and perform ICAO checks on the
|
|
180
|
+
* // returned image
|
|
181
|
+
* const camera = new CanonCamera()
|
|
182
|
+
* const photo = await camera.takePhoto('base64')
|
|
183
|
+
* const iface = new IFace()
|
|
184
|
+
* const results = await iface.icao(photo)
|
|
185
|
+
* const img = document.createElement('img')
|
|
186
|
+
* img.src = 'data:image/jpeg;base64,' + results.cropped
|
|
187
|
+
* console.log(`found ${result.faces_found} faces in the image`)
|
|
188
|
+
* document.body.appendChild(img)
|
|
189
|
+
*/
|
|
190
|
+
async icao (image, cropMethod = false) {
|
|
191
|
+
return await this.check(image, cropMethod)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Perform a facial match against one or more photos.
|
|
196
|
+
*
|
|
197
|
+
* @param {object|string} probe - Either a Probe object or a base64 encoded
|
|
198
|
+
* image that is being compared or searched against one or more candidates
|
|
199
|
+
*
|
|
200
|
+
* @param {object[]} candidates - An array of candidate objects against which
|
|
201
|
+
* the probe photo is compared.
|
|
202
|
+
* @param {string} candidates[].id - Id of the photo, when the results of the
|
|
203
|
+
* matched are returned, this id will be returned so results can be matched
|
|
204
|
+
* back to their original image. Must be less than 16 characters.
|
|
205
|
+
* @param {string} candidates[].base64 - Base64 encoded string containing the
|
|
206
|
+
* photo.
|
|
207
|
+
* @param {string} [candidates[].mode] - Matching mode to use for this photo.
|
|
208
|
+
* If left unspecified will use the mode provided in the mode parameter.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} [mode=fast] - Matching mode to use for all images, can be
|
|
211
|
+
* overriden on each candidate if desired. Valid values are: 'accurate',
|
|
212
|
+
* 'balanced', 'fast', and 'accurate_server'.
|
|
213
|
+
*
|
|
214
|
+
* @async
|
|
215
|
+
* @example
|
|
216
|
+
* // Create an iface object
|
|
217
|
+
* const iface = new IFace()
|
|
218
|
+
*
|
|
219
|
+
* // Obtain a photo for the probe photo
|
|
220
|
+
* const probe = await camera.takePhoto('base64')
|
|
221
|
+
*
|
|
222
|
+
* // Create a candidate set from the person's previous photos
|
|
223
|
+
* const candidates = [
|
|
224
|
+
* new Candidate('/person/1/photo/2.jpg', '3', 'url'),
|
|
225
|
+
* new Candidate('/person/1/photo/1.jpg', '2', 'url')
|
|
226
|
+
* ]
|
|
227
|
+
*
|
|
228
|
+
* // Match the probe to all the candidates using the 'balanced' mode
|
|
229
|
+
* const results = await iface.match(probe, candidates, 'balanced')
|
|
230
|
+
*
|
|
231
|
+
* // use the results
|
|
232
|
+
* console.log(results)
|
|
233
|
+
*/
|
|
234
|
+
async match (probe, candidates, mode = 'fast') {
|
|
235
|
+
if (VALID_MODES.indexOf(mode) === -1) {
|
|
236
|
+
throw new Error('Invalid mode provided')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// If this is a Probe object fetch it's image data
|
|
240
|
+
if (typeof probe === 'object' && probe.constructor.name === 'Probe') {
|
|
241
|
+
await probe.data()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// And fetch data for candidates
|
|
245
|
+
const asyncOperations = candidates.map(async candidate => {
|
|
246
|
+
if (typeof candidate === 'object' &&
|
|
247
|
+
candidate.constructor.name === 'Candidate') {
|
|
248
|
+
await candidate.data()
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// Wait for all async operations to complete
|
|
253
|
+
await Promise.all(asyncOperations)
|
|
254
|
+
|
|
255
|
+
const body = {
|
|
256
|
+
probe,
|
|
257
|
+
mode,
|
|
258
|
+
candidates
|
|
259
|
+
}
|
|
260
|
+
const options = {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
mode: 'cors',
|
|
263
|
+
headers: {
|
|
264
|
+
'Content-Type': 'application/json'
|
|
265
|
+
},
|
|
266
|
+
body: JSON.stringify(body)
|
|
267
|
+
}
|
|
268
|
+
const url = `${this.baseUrl}/plugin/${this.id}/face/match`
|
|
269
|
+
const response = await fetch(url, options)
|
|
270
|
+
return await response.json()
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export default IFace
|
package/Logs.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { BASE_URL } from './Common.js'
|
|
2
|
+
|
|
3
|
+
export const TRACE = 10
|
|
4
|
+
export const DEBUG = 20
|
|
5
|
+
export const INFO = 30
|
|
6
|
+
export const WARN = 40
|
|
7
|
+
export const ERROR = 50
|
|
8
|
+
export const FATAL = 60
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @constant
|
|
12
|
+
* @type {Object.<string, number>}
|
|
13
|
+
*
|
|
14
|
+
*/
|
|
15
|
+
export const LOG_LEVELS = {
|
|
16
|
+
trace: TRACE,
|
|
17
|
+
debug: DEBUG,
|
|
18
|
+
info: INFO,
|
|
19
|
+
warn: WARN,
|
|
20
|
+
error: ERROR,
|
|
21
|
+
fatal: FATAL
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @classdesc Plugins write
|
|
26
|
+
* {@link https://github.com/trentm/node-bunyan?tab=readme-ov-file#log-record-fields|Bunyan}
|
|
27
|
+
* formated logs to separate log files. This class provides
|
|
28
|
+
* utilities for viewing and following those logs as they're written.
|
|
29
|
+
*/
|
|
30
|
+
export class Logs {
|
|
31
|
+
baseUrl
|
|
32
|
+
#reader
|
|
33
|
+
#controller
|
|
34
|
+
/**
|
|
35
|
+
* Instantiate a Log Object
|
|
36
|
+
* @constructor
|
|
37
|
+
* @param {string} [baseURL] - Protocol, domain, and port for the service.
|
|
38
|
+
*/
|
|
39
|
+
constructor (baseUrl = BASE_URL) {
|
|
40
|
+
this.baseUrl = baseUrl
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* End the log stream and stop following logs.
|
|
45
|
+
*/
|
|
46
|
+
end () {
|
|
47
|
+
this.#reader?.cancel()
|
|
48
|
+
this.#controller?.abort()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Add a handler for following logs. Note that each invocation of this method
|
|
53
|
+
* opens a connection to the server and holds it open. Web browsers limit the
|
|
54
|
+
* number of connections to a single domain. Avoid using this method more than
|
|
55
|
+
* once per client.
|
|
56
|
+
* @param {string[]} plugins - Follow logs for the provided Plugin IDs.
|
|
57
|
+
* @param {string|number} level - Log Level.
|
|
58
|
+
* @param {function} callback - Invoked with two arguments. The first argument
|
|
59
|
+
* is an Error object (if an error occurred) or null. The second argument is
|
|
60
|
+
* an log object.
|
|
61
|
+
*/
|
|
62
|
+
follow (plugins, level, callback) {
|
|
63
|
+
let pluginList
|
|
64
|
+
if (typeof plugins === 'object' && plugins.length > 0) {
|
|
65
|
+
pluginList = plugins.join(',')
|
|
66
|
+
} else {
|
|
67
|
+
throw new Error('Invalid value provided for plugins argument')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const logLevel = (typeof level === 'number') ? level : LOG_LEVELS[level]
|
|
71
|
+
|
|
72
|
+
if (!logLevel) {
|
|
73
|
+
throw new Error('Invalid value provided for log level argument')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof callback !== 'function') {
|
|
77
|
+
throw new Error('Invalid value provided for callback argument')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const url = `${this.baseUrl}/plugin/logs/follow?plugins=${pluginList}&level=${logLevel}`
|
|
81
|
+
|
|
82
|
+
this.#controller = new AbortController()
|
|
83
|
+
const signal = this.#controller.signal
|
|
84
|
+
|
|
85
|
+
fetch(url, { signal })
|
|
86
|
+
.then(({ body }) => {
|
|
87
|
+
let buffer = ''
|
|
88
|
+
if (!body) {
|
|
89
|
+
return callback(new Error('No response body'))
|
|
90
|
+
}
|
|
91
|
+
const readData = data => {
|
|
92
|
+
if (!data.done) {
|
|
93
|
+
callback(null, data.value)
|
|
94
|
+
this.#reader.read().then(readData).catch(e => callback(e))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.#reader = body
|
|
98
|
+
.pipeThrough(new TextDecoderStream())
|
|
99
|
+
.pipeThrough(new TransformStream({
|
|
100
|
+
transform (chunk, controller) {
|
|
101
|
+
buffer += chunk
|
|
102
|
+
const parts = buffer.split('\n')
|
|
103
|
+
parts.slice(0, -1).forEach(part => controller.enqueue(part))
|
|
104
|
+
buffer = parts[parts.length - 1]
|
|
105
|
+
},
|
|
106
|
+
flush (controller) {
|
|
107
|
+
if (buffer) {
|
|
108
|
+
controller.enqueue(buffer)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}))
|
|
112
|
+
.pipeThrough(new TransformStream({
|
|
113
|
+
transform (chunk, controller) {
|
|
114
|
+
controller.enqueue(JSON.parse(chunk))
|
|
115
|
+
}
|
|
116
|
+
}))
|
|
117
|
+
.getReader()
|
|
118
|
+
this.#reader
|
|
119
|
+
.read()
|
|
120
|
+
.then(readData)
|
|
121
|
+
.catch(e => callback(e))
|
|
122
|
+
}).catch(e => {
|
|
123
|
+
// Don't emit an error when aborting the fetch operation
|
|
124
|
+
if (e.name !== 'AbortError') {
|
|
125
|
+
callback(e)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
package/MockCamera.js
ADDED
package/Plugin.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import Device from './Device.js'
|
|
2
|
+
|
|
3
|
+
import { BASE_URL, DELETE, asyncToCallback } from './Common.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @classdesc CaptureBridge utilizes a plugin system for interacting with
|
|
7
|
+
* hardware devices and vendor SDKs. Clients will interact with plugins via the
|
|
8
|
+
* REST API using their unique plugin ID. This class provides basic methods
|
|
9
|
+
* for working with these plugins such as obtaining a list of compatible devices
|
|
10
|
+
* and managing the plugin's configuration.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link CaptureBridge#plugins} Get all installed plugin.
|
|
13
|
+
* @see {@link CaptureBridge#plugin} Get a single plugin by ID.
|
|
14
|
+
*/
|
|
15
|
+
class Plugin {
|
|
16
|
+
/**
|
|
17
|
+
* @property {string} id - ID of the plugin, used when building REST endpoint paths.
|
|
18
|
+
* @property {string} name - Human friendly name for the plugin.
|
|
19
|
+
* @property {string} description - Human friendly description of plugin.
|
|
20
|
+
* @property {string} version - {@link https://semver.org/|Semantic version} version of the plugin.
|
|
21
|
+
* @property {string[]} methods - A plugin's list of supported RPC methods.
|
|
22
|
+
* @property {boolean} ready - True if plugin has been loaded into memory and is ready to receive requests.
|
|
23
|
+
* @property {object} defaultDevice - Default device to use for the plugin.
|
|
24
|
+
* @property {string[]} configMethods - Methods required to implement configuration.
|
|
25
|
+
*/
|
|
26
|
+
baseUrl
|
|
27
|
+
id
|
|
28
|
+
name
|
|
29
|
+
description
|
|
30
|
+
version
|
|
31
|
+
methods = []
|
|
32
|
+
ready = false
|
|
33
|
+
defaultDevice = null
|
|
34
|
+
configMethods = ['config_schema', 'get_config', 'set_config']
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Instantiate a plugin.
|
|
38
|
+
* @constructor
|
|
39
|
+
* @param {object|string} plugin - plugin object as received from the API or a plugin ID string.
|
|
40
|
+
* @param {string} plugin.id - ID of the plugin, used when building endpoint paths to call methods on the plugin.
|
|
41
|
+
* @param {string} plugin.name - Human friendly name of plugin.
|
|
42
|
+
* @param {string} plugin.description - Human friendly description of plugin.
|
|
43
|
+
* @param {string} plugin.version - {@link https://semver.org/|Semantic version} version of the plugin.
|
|
44
|
+
* @param {string[]} plugin.methods - plugin's capabilities.
|
|
45
|
+
* @param {boolean} plugin.ready - True if plugin has been loaded into memory and is ready.
|
|
46
|
+
* @param {string} [baseURL=BASE_URL] - Override the default protocol, domain, and port for the service.
|
|
47
|
+
* @throws Will throw an Error the plugin argument is not a plugin ID (String) or an object.
|
|
48
|
+
*/
|
|
49
|
+
constructor (plugin, baseUrl = BASE_URL) {
|
|
50
|
+
this.baseUrl = baseUrl
|
|
51
|
+
if (typeof plugin === 'string') {
|
|
52
|
+
this.id = plugin
|
|
53
|
+
} else if (typeof plugin === 'object') {
|
|
54
|
+
Object.assign(this, {}, plugin)
|
|
55
|
+
} else {
|
|
56
|
+
throw new Error('Invalid properties supplied to Plugin constructor')
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get all devices for this plugin
|
|
62
|
+
*
|
|
63
|
+
* @method
|
|
64
|
+
* @async
|
|
65
|
+
* @see {@link https://capture.local.valididcloud.com:9001/doc/#api-Plugin-GetPluginDevices|API Endpoint (/plugin/:pluginId/device)}
|
|
66
|
+
* @returns {object[]} Array of {@link Device} objects.
|
|
67
|
+
* @example
|
|
68
|
+
* const plugin = await captureBridge.plugin('capture_camera_canon')
|
|
69
|
+
* const devices = await plugin.devices()
|
|
70
|
+
*/
|
|
71
|
+
async devices () {
|
|
72
|
+
const response = await fetch(`${this.baseUrl}/plugin/${this.id}/device`)
|
|
73
|
+
const devices = await response.json()
|
|
74
|
+
return devices.map(d => new Device(d, this, this.baseUrl))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get all Devices for this plugin
|
|
79
|
+
*
|
|
80
|
+
* @method
|
|
81
|
+
* @see {@link https://capture.local.valididcloud.com:9001/doc/#api-Plugin-GetPluginDevices|API Endpoint (/plugin/:pluginId/device)}
|
|
82
|
+
* @param {function} callback - Invoked with two arguments. The first argument
|
|
83
|
+
* is an Error object (if an error occurred) or null. The second argument is
|
|
84
|
+
* an Array of {@link Device} objects. If no devices are available the second
|
|
85
|
+
* argument will be an empty Array.
|
|
86
|
+
* @example
|
|
87
|
+
* captureBridge.pluginHandler('capture_camera_canon', (error, plugin) => {
|
|
88
|
+
* plugin.devicesHandler((error, devices) => console.log(devices))
|
|
89
|
+
* })
|
|
90
|
+
*/
|
|
91
|
+
devicesHandler (callback) {
|
|
92
|
+
asyncToCallback(this, this.devices, callback)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get a device by ID for this plugin
|
|
97
|
+
*
|
|
98
|
+
* @method
|
|
99
|
+
* @async
|
|
100
|
+
* @see {@link https://capture.local.valididcloud.com:9001/doc/#api-Plugin-GetPluginDevices|API Endpoint (/plugin/:pluginId/device)}
|
|
101
|
+
* @param {string} id - Device ID
|
|
102
|
+
* @returns {object?} Requested {@link Device} or null
|
|
103
|
+
* @example
|
|
104
|
+
* const plugin = await captureBridge.plugin('capture_camera_canon')
|
|
105
|
+
* const device = await plugin.device('AWOOO56709')
|
|
106
|
+
*/
|
|
107
|
+
async device (id) {
|
|
108
|
+
const response = await fetch(`${this.baseUrl}/plugin/${this.id}/device`)
|
|
109
|
+
const devices = await response.json()
|
|
110
|
+
const device = devices.find(d => d.id === id)
|
|
111
|
+
return device ? new Device(device, this, this.baseUrl) : null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get a Device by ID for this plugin
|
|
116
|
+
* @method
|
|
117
|
+
* @see {@link https://capture.local.valididcloud.com:9001/doc/#api-Plugin-GetPluginDevices|API Endpoint (/plugin/:pluginId/device)}
|
|
118
|
+
* @param {string} id - Device ID
|
|
119
|
+
* @param {function} callback - Invoked with two arguments. The first argument
|
|
120
|
+
* is an Error object (if an error occurred) or null. The second argument is
|
|
121
|
+
* a {@link Device} object. If the requested device is not available the
|
|
122
|
+
* second argument will be null.
|
|
123
|
+
* @example
|
|
124
|
+
* captureBridge.pluginHandler('capture_camera_canon', (error, plugin) => {
|
|
125
|
+
* plugin.deviceHandler('AWOOO56709', (error, device) => console.log(device))
|
|
126
|
+
* })
|
|
127
|
+
*/
|
|
128
|
+
deviceHandler (id, callback) {
|
|
129
|
+
asyncToCallback(this, this.device, callback, id)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async configSchema () {
|
|
133
|
+
if (!await this.supportsConfig()) {
|
|
134
|
+
throw new Error('Plugin does not support config')
|
|
135
|
+
}
|
|
136
|
+
const response = await fetch(`${this.baseUrl}/plugin/${this.id}/config/schema`)
|
|
137
|
+
return await response.json()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
configSchemaHandler (callback) {
|
|
141
|
+
asyncToCallback(this, this.configSchema, callback)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async config () {
|
|
145
|
+
if (!await this.supportsConfig()) {
|
|
146
|
+
throw new Error('Plugin does not support config')
|
|
147
|
+
}
|
|
148
|
+
const response = await fetch(`${this.baseUrl}/plugin/${this.id}/config`)
|
|
149
|
+
return await response.json()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
configHandler (callback) {
|
|
153
|
+
asyncToCallback(this, this.config, callback)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async saveConfig (config) {
|
|
157
|
+
if (!await this.supportsConfig()) {
|
|
158
|
+
throw new Error('Plugin does not support config')
|
|
159
|
+
}
|
|
160
|
+
const options = {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
mode: 'cors',
|
|
163
|
+
headers: {
|
|
164
|
+
'Content-Type': 'application/json'
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify(config)
|
|
167
|
+
}
|
|
168
|
+
const url = `${this.baseUrl}/plugin/${this.id}/config`
|
|
169
|
+
const response = await fetch(url, options)
|
|
170
|
+
return await response.json()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async saveConfigHandler (config, callback) {
|
|
174
|
+
asyncToCallback(this, this.saveConfig, callback, config)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async supportsConfig () {
|
|
178
|
+
await this.update()
|
|
179
|
+
return this.configMethods.every(m => this.methods.includes(m))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
supportsConfigHandler (callback) {
|
|
183
|
+
asyncToCallback(this, this.configHandler, callback)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async shutdown () {
|
|
187
|
+
const url = `${this.baseUrl}/plugin/${this.id}`
|
|
188
|
+
const response = await fetch(url, DELETE)
|
|
189
|
+
return await response.json()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
shutdownHandler (callback) {
|
|
193
|
+
asyncToCallback(this, this.shutdown, callback)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async startup () {
|
|
197
|
+
// See: ValidRD/wa_capture_bridge/issues/48
|
|
198
|
+
// We need an explicit endpoint for this instead
|
|
199
|
+
const devices = await this.devices()
|
|
200
|
+
if (devices) {
|
|
201
|
+
await this.update(true)
|
|
202
|
+
return { status: 'starting' }
|
|
203
|
+
}
|
|
204
|
+
return { status: 'failed' }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
startupHandler (callback) {
|
|
208
|
+
asyncToCallback(this, this.startup, callback)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* This class can be instantiated from either a plugin ID or a full plugin
|
|
213
|
+
* object from an API request.
|
|
214
|
+
* Because constructors cannot be async we use this method to hydrate the
|
|
215
|
+
* plugin properties if needed.
|
|
216
|
+
*
|
|
217
|
+
* @private
|
|
218
|
+
* @method
|
|
219
|
+
*/
|
|
220
|
+
async update (force = false) {
|
|
221
|
+
if (force || this.methods.length === 0) {
|
|
222
|
+
const response = await fetch(`${this.baseUrl}/plugin`)
|
|
223
|
+
const plugins = await response.json()
|
|
224
|
+
const plugin = plugins.find(p => p.id === this.id)
|
|
225
|
+
Object.assign(this, plugin)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
updateHandler (force = false, callback) {
|
|
230
|
+
asyncToCallback(this, this.update, callback, force)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async rpc (request) {
|
|
234
|
+
const options = {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
mode: 'cors',
|
|
237
|
+
headers: {
|
|
238
|
+
'Content-Type': 'application/json'
|
|
239
|
+
},
|
|
240
|
+
body: JSON.stringify(request)
|
|
241
|
+
}
|
|
242
|
+
const url = `${this.baseUrl}/plugin/${this.id}/rpc`
|
|
243
|
+
const response = await fetch(url, options)
|
|
244
|
+
return await response.json()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
rpcHandler (request, callback) {
|
|
248
|
+
asyncToCallback(this, this.rpc, callback, request)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async ping () {
|
|
252
|
+
const response = await fetch(`${this.baseUrl}/plugin/${this.id}/ping`)
|
|
253
|
+
return await response.json()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
pingHandler (callback) {
|
|
257
|
+
asyncToCallback(this, this.ping, callback)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Classes that extend this one will add their own methods to perform
|
|
262
|
+
* operations on a device. They will call this method first to get a device
|
|
263
|
+
* object to use for making calls to the appropriate endpoint.
|
|
264
|
+
*
|
|
265
|
+
* This method really should be "protected" in classical OOP terms but
|
|
266
|
+
* Javascript does not currently have support for such scopes, and it's not
|
|
267
|
+
* marked using the ES6 private modifier so that it can be inherited by it's
|
|
268
|
+
* children.
|
|
269
|
+
* @private
|
|
270
|
+
* @method
|
|
271
|
+
*/
|
|
272
|
+
async setupDevice (deviceOpt) {
|
|
273
|
+
await this.update()
|
|
274
|
+
|
|
275
|
+
if (deviceOpt instanceof Device) {
|
|
276
|
+
this.defaultDevice = deviceOpt
|
|
277
|
+
return this.defaultDevice
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (typeof deviceOpt === 'string') {
|
|
281
|
+
this.defaultDevice = this.device(deviceOpt)
|
|
282
|
+
return this.defaultDevice
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!this.defaultDevice) {
|
|
286
|
+
const devices = await this.devices()
|
|
287
|
+
if (!devices.length) {
|
|
288
|
+
throw new Error('No devices found for the plugin')
|
|
289
|
+
}
|
|
290
|
+
this.defaultDevice = devices[0]
|
|
291
|
+
return this.defaultDevice
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return this.defaultDevice
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export default Plugin
|