@capturebridge/sdk 0.12.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Device.js CHANGED
@@ -1,595 +1,588 @@
1
- import {
2
- BASE_URL,
3
- POST,
4
- DELETE,
5
- DEFAULT_STREAM_OPT,
6
- DEFAULT_CAPTURE_OPT,
7
- checkResponse,
8
- timeout,
9
- asyncToCallback,
10
- urlParamsToString
11
- } from './Common.js'
12
-
13
- /**
14
- * Device
15
- * @classdesc Class for interacting with a Device returned from a plugin.
16
- */
17
- class Device {
18
- /*
19
- * @property {Object} device - Device object as received from the API
20
- * @property {string} device.id - URL safe device identified, this can often be hashed or encoded and should be considered opaque.
21
- * @property {string?} device.raw_id - When possible, if the id is of some encoded or hashed type, this is the underlying value and can be shown to users.
22
- * @property {string} device.name - Human friendly name of the device.
23
- */
24
- baseUrl
25
- plugin
26
- id
27
- raw_id
28
- name
29
-
30
- /**
31
- * Instantiate a device.
32
- * @constructor
33
- * @param {object} device - Device object as received from the API
34
- * @param {string} device.id - URL safe device identified, this can often be hashed or encoded and should be considered opaque.
35
- * @param {string?} device.raw_id - When possible, if the id is of some encoded or hashed type, this is the underlying value and can be shown to users.
36
- * @param {string} device.name - Human friendly name of the device.
37
- * @param {object} plugin - Plugin serviced by this device.
38
- * @param {string} [baseURL] - Protocol, domain, and port for the service.
39
- */
40
- constructor (properties, plugin, baseUrl = BASE_URL) {
41
- this.baseUrl = baseUrl
42
- this.plugin = plugin
43
- Object.assign(this, {}, properties)
44
- }
45
-
46
- /**
47
- * Will throw an exception if a plugin cannot perform the provided RPC command
48
- * @param {string} method - The JSON RPC method to check.
49
- */
50
- can (method) {
51
- if (this.plugin.methods.indexOf(method) === -1) {
52
- throw new Error(`${method} not supported for this device`)
53
- }
54
- }
55
-
56
- /**
57
- * Will throw an exception if the plugin cannot performa at least one of the
58
- * provided JSON RPC commandss
59
- * @param {string[]} methods - The JSON RPC methods to check.
60
- */
61
- canDoOne (methods) {
62
- if (!methods.some(m => this.plugin.methods.includes(m))) {
63
- throw new Error(`Device does not support any of the methods: ${methods.join(',')}`)
64
- }
65
- }
66
-
67
- /**
68
- * Get the stream endpoint URL.
69
- *
70
- * @description the URL returned from this endpoint can be attached to an
71
- * img tag's src attribute. The device's live stream will be started and
72
- * begin streaming to the img tag as a
73
- * {@link https://en.wikipedia.org/wiki/Motion_JPEG|Motion JPEG} stream.
74
- * If the src is changed, the img removed from the DOM, or client disconnected
75
- * for any reason, the live feed will automatically be stopped.
76
- *
77
- * @method
78
- * @param {StreamOptions} [streamOpt] - Additional options for streaming.
79
- * @returns {string} stream endpoint URL
80
- * @example
81
- * const img = document.createElement('img')
82
- * img.src = device.streamUrl()
83
- * document.body.appendChild(img)
84
- */
85
- streamUrl (streamOpt = {}) {
86
- this.can('start_feed')
87
- this.can('stop_feed')
88
- const mergedOpts = Object.assign({}, DEFAULT_STREAM_OPT, streamOpt)
89
- const params = new URLSearchParams(mergedOpts).toString()
90
- return `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/stream?${params}`
91
- }
92
-
93
- /**
94
- * Take full capture from the specified device and return an
95
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static|ObjectURL}
96
- * that can be set directly on an img tag's src attribute. Note that URLs
97
- * created by this method should be
98
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL_static|revoked}
99
- * when they are no longer needed.
100
- * (Async/Await version)
101
- *
102
- * @method
103
- * @async
104
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
105
- * frame.
106
- * @returns {string}
107
- * @example
108
- * // Load the blob into FileReader and append to the document's body
109
- * const blob = await device.captureAsBlob()
110
- * const reader = new FileReader()
111
- * reader.onload = event => {
112
- * const img = document.createElement('img')
113
- * img.src = event.target.result
114
- * document.body.appendChild(img)
115
- * }
116
- * reader.readAsDataURL(blob)
117
- */
118
- async captureAsObjectURL (captureOpt = {}) {
119
- this.can('capture')
120
- const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
121
-
122
- // Method ignores kind, always returns an object URL
123
- delete captureParams.kind
124
-
125
- const params = urlParamsToString(captureParams)
126
- const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/capture?${params}`
127
- const response = await fetch(url, POST)
128
- return await response.blob()
129
- }
130
-
131
- /**
132
- * Take full capture from the specified device and return a
133
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob} that can
134
- * be used with a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader|FileReader}
135
- * (Async/Await version)
136
- *
137
- * @method
138
- * @async
139
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
140
- * frame.
141
- * @returns {object} {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob}
142
- * @example
143
- * // Load the blob into FileReader and append to the document's body
144
- * const blob = await device.captureAsBlob()
145
- * const reader = new FileReader()
146
- * reader.onload = event => {
147
- * const img = document.createElement('img')
148
- * img.src = event.target.result
149
- * document.body.appendChild(img)
150
- * }
151
- * reader.readAsDataURL(blob)
152
- */
153
- async captureAsBlob (captureOpt = {}) {
154
- this.can('capture')
155
- const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
156
-
157
- // Method ignores kind, always returns binary data (blob)
158
- delete captureParams.kind
159
-
160
- const params = urlParamsToString(captureParams)
161
- const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/capture?${params}`
162
- const response = await fetch(url, POST)
163
- return await response.blob()
164
- }
165
-
166
- /**
167
- * Take full capture from the specified device and return a
168
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob} that can
169
- * be used with a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader|FileReader}
170
- * (Callback version)
171
- *
172
- * @method
173
- * @param {function} callback
174
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
175
- * frame.
176
- * @example
177
- * // Load the blob into FileReader and append to the document's body
178
- * device.captureAsBlobHandler((error, blob) => {
179
- * const reader = new FileReader()
180
- * reader.onload = event => {
181
- * const img = document.createElement('img')
182
- * img.src = event.target.result
183
- * document.body.appendChild(img)
184
- * }
185
- * reader.readAsDataURL(blob)
186
- * })
187
- */
188
- captureAsBlobHandler (callback, captureOpt = {}) {
189
- asyncToCallback(this, this.captureAsBlob, captureOpt, callback)
190
- }
191
-
192
- /**
193
- * Take full capture from the specified device and return a
194
- * base64 encoded string of the image that can be used with a
195
- * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs|Data URL}
196
- * (Async/Await version)
197
- *
198
- * @method
199
- * @async
200
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
201
- * frame.
202
- * @returns {string} Base64 Encoded image
203
- * @example
204
- * // Add the base64 string as a Data URL to an img tag and append to
205
- * // the document's body
206
- * const base64 = await device.captureAsBase64()
207
- * const img = document.createElement('img')
208
- * img.src = 'data:image/jpeg;base64,' + base64
209
- * document.body.appendChild(img)
210
- */
211
- async captureAsBase64 (captureOpt = {}) {
212
- this.can('capture')
213
- const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
214
-
215
- // Method ignores kind, always returns base64 data
216
- delete captureParams.kind
217
- captureParams.base64 = true
218
-
219
- const params = urlParamsToString(captureParams)
220
- const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/capture?${params}`
221
- const response = await fetch(url, POST)
222
- await checkResponse(response)
223
- const { image } = await response.json()
224
- return image
225
- }
226
-
227
- /**
228
- * Take full capture from the specified device and return a
229
- * base64 encoded string of the image that can be used with a
230
- * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs|Data URL}
231
- * (Callback version)
232
- * @method
233
- * @param {function} callback
234
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
235
- * frame.
236
- */
237
- captureAsBase64Handler (callback, captureOpt = {}) {
238
- asyncToCallback(this, this.captureAsBase64, callback)
239
- }
240
-
241
- /**
242
- * Get a base64 encoded frame from a device's live feed.
243
- * @method
244
- *
245
- * @description This method will startup the live
246
- * feed if necessary and wait for it to initialize before returning a frame.
247
- * Subsequent calls will return the next available frame.
248
- * You must manually stop the live feed if you are using this method.
249
- *
250
- * The frame returned will be a base64 encoded string of the image that can
251
- * be used with a
252
- * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs|Data URL}
253
- *
254
- * If implementing a real-time preview, it is highly recommended to use the
255
- * stream endpoint which will stream a Motion JPEG.
256
- *
257
- * @see {@link streamUrl}
258
- * @see {@link stopFeed}
259
- *
260
- * @async
261
- * @param {number} [millis] - Milliseconds to wait if feed is not ready
262
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
263
- * frame.
264
- * @returns {string} Base64 Encoded image
265
- * @example
266
- * // Add the base64 string as a Data URL to an img tag and append to
267
- * // the document's body
268
- * const base64 = await device.frameAsBase64()
269
- * const img = document.createElement('img')
270
- * img.src = 'data:image/jpeg;base64,' + base64
271
- * document.body.appendChild(img)
272
- */
273
- async frameAsBase64 (captureOpt = {}, millis = 500) {
274
- this.can('start_feed')
275
- const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
276
-
277
- // Method ignores kind, always returns base64 data
278
- delete captureParams.kind
279
- captureParams.base64 = true
280
-
281
- const params = urlParamsToString(captureParams)
282
- const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/livefeed?${params}`
283
- const response = await fetch(url)
284
- const result = await response.json()
285
- if (result.error && (result.code === 425 || result.code === 400)) {
286
- await timeout(millis)
287
- return await this.frameAsBase64(captureOpt, millis)
288
- } else {
289
- return result.image
290
- }
291
- }
292
-
293
- /**
294
- * Get a base64 encoded frame from a device's live feed. (Callback version)
295
- * @method
296
- *
297
- * @description This method will startup the live
298
- * feed if necessary and wait for it to initialize before returning a frame.
299
- * Subsequent calls will return the next available frame.
300
- * You must manually stop the live feed if you are using this method.
301
- *
302
- * The frame returned will be a base64 encoded string of the image that can
303
- * be used with a
304
- * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs|Data URL}
305
- *
306
- * If implementing a real-time preview, it is highly recommended to use the
307
- * stream endpoint which will stream a Motion JPEG.
308
- *
309
- * @see {@link streamUrl}
310
- * @see {@link setStopFeed}
311
- *
312
- * @param {function} callback
313
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
314
- * frame.
315
- * @param {number} [millis] - Milliseconds to wait if feed is not ready
316
- * @example
317
- * // Add the base64 string as a Data URL to an img tag and append to
318
- * // the document's body
319
- * device.frameAsBase64Handler(base64 => {
320
- * const img = document.createElement('img')
321
- * img.src = 'data:image/jpeg;base64,' + base64
322
- * document.body.appendChild(img)
323
- * })
324
- */
325
- frameAsBase64Handler (callback, captureOpt = {}, millis = 500) {
326
- asyncToCallback(this, this.frameAsBase64, callback, captureOpt, millis)
327
- }
328
-
329
- /**
330
- * Get a binary frame from a device's live feed. (Async/await version)
331
- * @method
332
- *
333
- * @description This method will startup the live
334
- * feed if necessary and wait for it to initialize before returning a frame.
335
- * Subsequent calls will return the next available frame.
336
- * You must manually stop the live feed if you are using this method.
337
- *
338
- * The frame returned will be a
339
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob} that can
340
- * be used with a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader|FileReader}
341
- *
342
- * If implementing a real-time preview, it is highly recommended to use the
343
- * stream endpoint which will stream a Motion JPEG.
344
- *
345
- * @see {@link streamUrl}
346
- * @see {@link stopFeed}
347
- *
348
- * @async
349
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
350
- * frame.
351
- * @param {number} [millis] - Milliseconds to wait if feed is not ready
352
- * @returns {object} {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob}
353
- * @example
354
- * // Load the blob into FileReader and append to the document's body
355
- * const blob = await device.frameAsBlob()
356
- * const reader = new FileReader()
357
- * reader.onload = event => {
358
- * const img = document.createElement('img')
359
- * img.src = event.target.result
360
- * document.body.appendChild(img)
361
- * }
362
- * reader.readAsDataURL(blob)
363
- */
364
- async frameAsBlob (captureOpt = {}, millis = 500) {
365
- this.can('start_feed')
366
- const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
367
-
368
- // Method ignores kind, always returns binary data (blob)
369
- delete captureParams.kind
370
-
371
- const params = urlParamsToString(captureParams)
372
- const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/livefeed?${params}`
373
- const result = await this.frameAsBase64(captureOpt, millis)
374
- if (typeof result === 'string') {
375
- timeout(millis)
376
- const response = await fetch(url)
377
- return await response.blob()
378
- }
379
- throw new Error(result || 'Failed to get frame')
380
- }
381
-
382
- /**
383
- * Get a binary frame from a device's live feed. (Callback version)
384
- * @method
385
- *
386
- * @description This method will startup the live
387
- * feed if necessary and wait for it to initialize before returning a frame.
388
- * Subsequent calls will return the next available frame.
389
- * You must manually stop the live feed if you are using this method.
390
- *
391
- * The frame returned will be a
392
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob} that can
393
- * be used with a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader|FileReader}
394
- *
395
- * If implementing a real-time preview, it is highly recommended to use the
396
- * stream endpoint which will stream a Motion JPEG.
397
- *
398
- * @see {@link streamUrl}
399
- * @see {@link setStopFeed}
400
- *
401
- * @param {function} callback
402
- *
403
- * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
404
- * frame.
405
- *
406
- * @param {number} [millis] - Milliseconds to wait if feed is not ready
407
- *
408
- * @example
409
- * // Load the blob into FileReader and append to the document's body
410
- * device.frameAsBlobHandler((error, blob) => {
411
- * const reader = new FileReader()
412
- * reader.onload = event => {
413
- * const img = document.createElement('img')
414
- * img.src = event.target.result
415
- * document.body.appendChild(img)
416
- * }
417
- * reader.readAsDataURL(blob)
418
- * })
419
- */
420
- frameAsBlobHandler (callback, captureOpt = {}, millis = 500) {
421
- asyncToCallback(this, this.frameAsBlob, callback, captureOpt, millis)
422
- }
423
-
424
- /**
425
- * Stop the live feed if it is running.
426
- * (Async/Await version)
427
- *
428
- * @method
429
- * @async
430
- * @returns {object} Status of the stop operation
431
- * @example
432
- * // Plugin is now running it's live feed in a background thread
433
- * const blob = await device.frameAsBlob()
434
- * // Live feed is now stopping
435
- * console.log(await device.stopFeed())
436
- */
437
- async stopFeed () {
438
- this.can('stop_feed')
439
- const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/livefeed`
440
- const response = await fetch(url, DELETE)
441
- return await response.json()
442
- }
443
-
444
- /**
445
- * Stop the live feed if it is running.
446
- * (Callback version)
447
- *
448
- * @method
449
- * @param {number} [millis] - Milliseconds to wait if feed is not ready
450
- * @param {function} callback with the status of the stop operation
451
- * @example
452
- * device.stopFeedHandler((error, blob) => {
453
- * // Plugin is now running it's live feed in a background thread
454
- * device.setStopFeed(result => {
455
- * // Live feed is now stopping
456
- * console.log(result)
457
- * })
458
- * })
459
- */
460
- stopFeedHandler (callback) {
461
- asyncToCallback(this, this.frameAsBlob, callback)
462
- }
463
-
464
- /**
465
- * Clear a device's display.
466
- * (Async/Await version)
467
- *
468
- * @method
469
- * @async
470
- * @param {boolean} [backlight] If provided and set to true, will enable
471
- * backlight. If false, it will be disabled. If unspecified no action will be
472
- * taken on the backlight.
473
- * @returns {object} Status of the clear operation
474
- * @example
475
- * console.log(await await device.clear())
476
- */
477
- async clear (backlight) {
478
- this.canDoOne(['draw', 'clear'])
479
- let url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/display`
480
- if (typeof backlight === 'boolean') {
481
- url += `?backlight=${backlight}`
482
- }
483
- const response = await fetch(url, DELETE)
484
- return await response.json()
485
- }
486
-
487
- /**
488
- * Clear a device's display.
489
- * (Callback version)
490
- *
491
- * @method
492
- * @param {function} callback with the status of the clear operation
493
- * @param {boolean} [backlight] If provided and set to true, will enable
494
- * backlight. If false, it will be disabled. If unspecified no action will be
495
- * taken on the backlight.
496
- * @example
497
- * device.clearHandler((error, result) => {
498
- * console.log(result)
499
- * })
500
- */
501
- clearHandler (callback, backlight) {
502
- asyncToCallback(this, this.clear, callback, backlight)
503
- }
504
-
505
- /**
506
- * Display an Array of objects on the display.
507
- *
508
- * @description If any objects are Button types, the method will wait for one
509
- * to be clicked and will then return the ID of the clicked object.
510
- *
511
- * @param {object[]} objects An array of Signature Tablet Widgets to display.
512
- *
513
- * @param {boolean} [clear] If true (the default), the display will be cleared
514
- * before adding the objects.
515
- *
516
- *
517
- * @async
518
- * @method
519
- * @returns {string?} ID of the clicked object if any
520
- * @example
521
- * const result = await tablet.displayObjects([
522
- * new TextButton("OK", 20, 200),
523
- * new TextButton("Cancel", 80, 200)
524
- * ])
525
- *
526
- * console.log(`${result} was clicked`)
527
- */
528
- async displayObjects (objects, clear = true) {
529
- this.can('draw')
530
- const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/display`
531
-
532
- const asyncOperations = objects.map(async obj => {
533
- if (obj.constructor.name === 'Image') {
534
- await obj.data()
535
- }
536
- })
537
-
538
- // Wait for all async operations to complete
539
- await Promise.all(asyncOperations)
540
-
541
- const options = {
542
- method: 'POST',
543
- mode: 'cors',
544
- headers: {
545
- 'Content-Type': 'application/json'
546
- },
547
- body: JSON.stringify({
548
- clear,
549
- backlight: true,
550
- objects
551
- })
552
- }
553
- const response = await fetch(url, options)
554
- const { clicked } = await response.json()
555
- return clicked
556
- }
557
-
558
- async displayObjectsHandler (objects, clear = true, callback) {
559
- asyncToCallback(this, this.displayObjects, callback, objects, clear)
560
- }
561
-
562
- /**
563
- * Get detailed information about a device
564
- *
565
- * @description Returns detailed information about a device.
566
- *
567
- * @async
568
- * @method
569
- * @returns {object} Device info
570
- * @example
571
- * const info = await device.info()
572
- */
573
- async info () {
574
- this.can('device_info')
575
- const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/info`
576
- const response = await fetch(url)
577
- return await response.json()
578
- }
579
-
580
- /**
581
- * Get detailed information about a device
582
- *
583
- * @description Returns detailed information about a device.
584
- *
585
- * @method
586
- * @returns {object} Device info
587
- * @example
588
- * const info = await device.info()
589
- */
590
- async infoHandler (callback) {
591
- asyncToCallback(this, this.info, callback)
592
- }
593
- }
594
-
595
- export default Device
1
+ import {
2
+ BASE_URL,
3
+ POST,
4
+ DELETE,
5
+ DEFAULT_STREAM_OPT,
6
+ DEFAULT_CAPTURE_OPT,
7
+ checkResponse,
8
+ timeout,
9
+ asyncToCallback,
10
+ urlParamsToString
11
+ } from './Common.js'
12
+
13
+ /**
14
+ * Device
15
+ * @classdesc Class for interacting with a Device returned from a plugin.
16
+ */
17
+ class Device {
18
+ /*
19
+ * @property {Object} device - Device object as received from the API
20
+ * @property {string} device.id - URL safe device identified, this can often be hashed or encoded and should be considered opaque.
21
+ * @property {string?} device.raw_id - When possible, if the id is of some encoded or hashed type, this is the underlying value and can be shown to users.
22
+ * @property {string} device.name - Human friendly name of the device.
23
+ */
24
+ baseUrl
25
+ plugin
26
+ id
27
+ raw_id
28
+ name
29
+
30
+ /**
31
+ * Instantiate a device.
32
+ * @constructor
33
+ * @param {object} device - Device object as received from the API
34
+ * @param {string} device.id - URL safe device identified, this can often be hashed or encoded and should be considered opaque.
35
+ * @param {string?} device.raw_id - When possible, if the id is of some encoded or hashed type, this is the underlying value and can be shown to users.
36
+ * @param {string} device.name - Human friendly name of the device.
37
+ * @param {object} plugin - Plugin serviced by this device.
38
+ * @param {string} [baseURL] - Protocol, domain, and port for the service.
39
+ */
40
+ constructor (properties, plugin, baseUrl = BASE_URL) {
41
+ this.baseUrl = baseUrl
42
+ this.plugin = plugin
43
+ Object.assign(this, {}, properties)
44
+ }
45
+
46
+ /**
47
+ * Will throw an exception if a plugin cannot perform the provided RPC command
48
+ * @param {string} method - The JSON RPC method to check.
49
+ */
50
+ can (method) {
51
+ if (this.plugin.methods.indexOf(method) === -1) {
52
+ throw new Error(`${method} not supported for this device`)
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Will throw an exception if the plugin cannot performa at least one of the
58
+ * provided JSON RPC commandss
59
+ * @param {string[]} methods - The JSON RPC methods to check.
60
+ */
61
+ canDoOne (methods) {
62
+ if (!methods.some(m => this.plugin.methods.includes(m))) {
63
+ throw new Error(`Device does not support any of the methods: ${methods.join(',')}`)
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Get the stream endpoint URL.
69
+ *
70
+ * @description the URL returned from this endpoint can be attached to an
71
+ * img tag's src attribute. The device's live stream will be started and
72
+ * begin streaming to the img tag as a
73
+ * {@link https://en.wikipedia.org/wiki/Motion_JPEG|Motion JPEG} stream.
74
+ * If the src is changed, the img removed from the DOM, or client disconnected
75
+ * for any reason, the live feed will automatically be stopped.
76
+ *
77
+ * @method
78
+ * @param {StreamOptions} [streamOpt] - Additional options for streaming.
79
+ * @returns {string} stream endpoint URL
80
+ * @example
81
+ * const img = document.createElement('img')
82
+ * img.src = device.streamUrl()
83
+ * document.body.appendChild(img)
84
+ */
85
+ streamUrl (streamOpt = {}) {
86
+ this.can('start_feed')
87
+ this.can('stop_feed')
88
+ const mergedOpts = Object.assign({}, DEFAULT_STREAM_OPT, streamOpt)
89
+ const params = new URLSearchParams(mergedOpts).toString()
90
+ return `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/stream?${params}`
91
+ }
92
+
93
+ /**
94
+ * Take full capture from the specified device and return an
95
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static|ObjectURL}
96
+ * that can be set directly on an img tag's src attribute. Note that URLs
97
+ * created by this method should be
98
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL_static|revoked}
99
+ * when they are no longer needed.
100
+ * (Async/Await version)
101
+ *
102
+ * @method
103
+ * @async
104
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
105
+ * frame.
106
+ * @returns {string}
107
+ * @example
108
+ * // Load the blob into FileReader and append to the document's body
109
+ * const blob = await device.captureAsBlob()
110
+ * const reader = new FileReader()
111
+ * reader.onload = event => {
112
+ * const img = document.createElement('img')
113
+ * img.src = event.target.result
114
+ * document.body.appendChild(img)
115
+ * }
116
+ * reader.readAsDataURL(blob)
117
+ */
118
+ async captureAsObjectURL (captureOpt = {}) {
119
+ this.can('capture')
120
+ const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
121
+
122
+ // Method ignores kind, always returns an object URL
123
+ delete captureParams.kind
124
+
125
+ const params = urlParamsToString(captureParams)
126
+ const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/capture?${params}`
127
+ const response = await fetch(url, POST)
128
+ return await response.blob()
129
+ }
130
+
131
+ /**
132
+ * Take full capture from the specified device and return a
133
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob} that can
134
+ * be used with a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader|FileReader}
135
+ * (Async/Await version)
136
+ *
137
+ * @method
138
+ * @async
139
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
140
+ * frame.
141
+ * @returns {object} {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob}
142
+ * @example
143
+ * // Load the blob into FileReader and append to the document's body
144
+ * const blob = await device.captureAsBlob()
145
+ * const reader = new FileReader()
146
+ * reader.onload = event => {
147
+ * const img = document.createElement('img')
148
+ * img.src = event.target.result
149
+ * document.body.appendChild(img)
150
+ * }
151
+ * reader.readAsDataURL(blob)
152
+ */
153
+ async captureAsBlob (captureOpt = {}) {
154
+ this.can('capture')
155
+ const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
156
+
157
+ // Method ignores kind, always returns binary data (blob)
158
+ delete captureParams.kind
159
+
160
+ const params = urlParamsToString(captureParams)
161
+ const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/capture?${params}`
162
+ const response = await fetch(url, POST)
163
+ return await response.blob()
164
+ }
165
+
166
+ /**
167
+ * Take full capture from the specified device and return a
168
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob} that can
169
+ * be used with a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader|FileReader}
170
+ * (Callback version)
171
+ *
172
+ * @method
173
+ * @param {function} callback
174
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
175
+ * frame.
176
+ * @example
177
+ * // Load the blob into FileReader and append to the document's body
178
+ * device.captureAsBlobHandler((error, blob) => {
179
+ * const reader = new FileReader()
180
+ * reader.onload = event => {
181
+ * const img = document.createElement('img')
182
+ * img.src = event.target.result
183
+ * document.body.appendChild(img)
184
+ * }
185
+ * reader.readAsDataURL(blob)
186
+ * })
187
+ */
188
+ captureAsBlobHandler (callback, captureOpt = {}) {
189
+ asyncToCallback(this, this.captureAsBlob, captureOpt, callback)
190
+ }
191
+
192
+ /**
193
+ * Take full capture from the specified device and return a
194
+ * base64 encoded string of the image that can be used with a
195
+ * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs|Data URL}
196
+ * (Async/Await version)
197
+ *
198
+ * @method
199
+ * @async
200
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
201
+ * frame.
202
+ * @returns {string} Base64 Encoded image
203
+ * @example
204
+ * // Add the base64 string as a Data URL to an img tag and append to
205
+ * // the document's body
206
+ * const base64 = await device.captureAsBase64()
207
+ * const img = document.createElement('img')
208
+ * img.src = 'data:image/jpeg;base64,' + base64
209
+ * document.body.appendChild(img)
210
+ */
211
+ async captureAsBase64 (captureOpt = {}) {
212
+ this.can('capture')
213
+ const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
214
+
215
+ // Method ignores kind, always returns base64 data
216
+ delete captureParams.kind
217
+ captureParams.base64 = true
218
+
219
+ const params = urlParamsToString(captureParams)
220
+ const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/capture?${params}`
221
+ const response = await fetch(url, POST)
222
+ await checkResponse(response)
223
+ const { image } = await response.json()
224
+ return image
225
+ }
226
+
227
+ /**
228
+ * Take full capture from the specified device and return a
229
+ * base64 encoded string of the image that can be used with a
230
+ * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs|Data URL}
231
+ * (Callback version)
232
+ * @method
233
+ * @param {function} callback
234
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
235
+ * frame.
236
+ */
237
+ captureAsBase64Handler (callback, captureOpt = {}) {
238
+ asyncToCallback(this, this.captureAsBase64, callback)
239
+ }
240
+
241
+ /**
242
+ * Get a base64 encoded frame from a device's live feed.
243
+ * @method
244
+ *
245
+ * @description This method will startup the live
246
+ * feed if necessary and wait for it to initialize before returning a frame.
247
+ * Subsequent calls will return the next available frame.
248
+ * You must manually stop the live feed if you are using this method.
249
+ *
250
+ * The frame returned will be a base64 encoded string of the image that can
251
+ * be used with a
252
+ * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs|Data URL}
253
+ *
254
+ * If implementing a real-time preview, it is highly recommended to use the
255
+ * stream endpoint which will stream a Motion JPEG.
256
+ *
257
+ * @see {@link streamUrl}
258
+ * @see {@link stopFeed}
259
+ *
260
+ * @async
261
+ * @param {number} [millis] - Milliseconds to wait if feed is not ready
262
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
263
+ * frame.
264
+ * @returns {string} Base64 Encoded image
265
+ * @example
266
+ * // Add the base64 string as a Data URL to an img tag and append to
267
+ * // the document's body
268
+ * const base64 = await device.frameAsBase64()
269
+ * const img = document.createElement('img')
270
+ * img.src = 'data:image/jpeg;base64,' + base64
271
+ * document.body.appendChild(img)
272
+ */
273
+ async frameAsBase64 (captureOpt = {}, millis = 500) {
274
+ this.can('start_feed')
275
+ const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
276
+
277
+ // Method ignores kind, always returns base64 data
278
+ delete captureParams.kind
279
+ captureParams.base64 = true
280
+
281
+ const params = urlParamsToString(captureParams)
282
+ const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/livefeed?${params}`
283
+ const response = await fetch(url)
284
+ const result = await response.json()
285
+ if (result.error && (result.code === 425 || result.code === 400)) {
286
+ await timeout(millis)
287
+ return await this.frameAsBase64(captureOpt, millis)
288
+ } else {
289
+ return result.image
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Get a base64 encoded frame from a device's live feed. (Callback version)
295
+ * @method
296
+ *
297
+ * @description This method will startup the live
298
+ * feed if necessary and wait for it to initialize before returning a frame.
299
+ * Subsequent calls will return the next available frame.
300
+ * You must manually stop the live feed if you are using this method.
301
+ *
302
+ * The frame returned will be a base64 encoded string of the image that can
303
+ * be used with a
304
+ * {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs|Data URL}
305
+ *
306
+ * If implementing a real-time preview, it is highly recommended to use the
307
+ * stream endpoint which will stream a Motion JPEG.
308
+ *
309
+ * @see {@link streamUrl}
310
+ * @see {@link setStopFeed}
311
+ *
312
+ * @param {function} callback
313
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
314
+ * frame.
315
+ * @param {number} [millis] - Milliseconds to wait if feed is not ready
316
+ * @example
317
+ * // Add the base64 string as a Data URL to an img tag and append to
318
+ * // the document's body
319
+ * device.frameAsBase64Handler(base64 => {
320
+ * const img = document.createElement('img')
321
+ * img.src = 'data:image/jpeg;base64,' + base64
322
+ * document.body.appendChild(img)
323
+ * })
324
+ */
325
+ frameAsBase64Handler (callback, captureOpt = {}, millis = 500) {
326
+ asyncToCallback(this, this.frameAsBase64, callback, captureOpt, millis)
327
+ }
328
+
329
+ /**
330
+ * Get a binary frame from a device's live feed. (Async/await version)
331
+ * @method
332
+ *
333
+ * @description This method will startup the live
334
+ * feed if necessary and wait for it to initialize before returning a frame.
335
+ * Subsequent calls will return the next available frame.
336
+ * You must manually stop the live feed if you are using this method.
337
+ *
338
+ * The frame returned will be a
339
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob} that can
340
+ * be used with a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader|FileReader}
341
+ *
342
+ * If implementing a real-time preview, it is highly recommended to use the
343
+ * stream endpoint which will stream a Motion JPEG.
344
+ *
345
+ * @see {@link streamUrl}
346
+ * @see {@link stopFeed}
347
+ *
348
+ * @async
349
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
350
+ * frame.
351
+ * @param {number} [millis] - Milliseconds to wait if feed is not ready
352
+ * @returns {object} {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob}
353
+ * @example
354
+ * // Load the blob into FileReader and append to the document's body
355
+ * const blob = await device.frameAsBlob()
356
+ * const reader = new FileReader()
357
+ * reader.onload = event => {
358
+ * const img = document.createElement('img')
359
+ * img.src = event.target.result
360
+ * document.body.appendChild(img)
361
+ * }
362
+ * reader.readAsDataURL(blob)
363
+ */
364
+ async frameAsBlob (captureOpt = {}, millis = 500) {
365
+ this.can('start_feed')
366
+ const captureParams = Object.assign({}, DEFAULT_CAPTURE_OPT, captureOpt)
367
+
368
+ // Method ignores kind, always returns binary data (blob)
369
+ delete captureParams.kind
370
+
371
+ const params = urlParamsToString(captureParams)
372
+ const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/livefeed?${params}`
373
+ const result = await this.frameAsBase64(captureOpt, millis)
374
+ if (typeof result === 'string') {
375
+ timeout(millis)
376
+ const response = await fetch(url)
377
+ return await response.blob()
378
+ }
379
+ throw new Error(result || 'Failed to get frame')
380
+ }
381
+
382
+ /**
383
+ * Get a binary frame from a device's live feed. (Callback version)
384
+ * @method
385
+ *
386
+ * @description This method will startup the live
387
+ * feed if necessary and wait for it to initialize before returning a frame.
388
+ * Subsequent calls will return the next available frame.
389
+ * You must manually stop the live feed if you are using this method.
390
+ *
391
+ * The frame returned will be a
392
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob|Blob} that can
393
+ * be used with a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader|FileReader}
394
+ *
395
+ * If implementing a real-time preview, it is highly recommended to use the
396
+ * stream endpoint which will stream a Motion JPEG.
397
+ *
398
+ * @see {@link streamUrl}
399
+ * @see {@link setStopFeed}
400
+ *
401
+ * @param {function} callback
402
+ *
403
+ * @param {CaptureOptions} [captureOpt] - Additional options for capturing a
404
+ * frame.
405
+ *
406
+ * @param {number} [millis] - Milliseconds to wait if feed is not ready
407
+ *
408
+ * @example
409
+ * // Load the blob into FileReader and append to the document's body
410
+ * device.frameAsBlobHandler((error, blob) => {
411
+ * const reader = new FileReader()
412
+ * reader.onload = event => {
413
+ * const img = document.createElement('img')
414
+ * img.src = event.target.result
415
+ * document.body.appendChild(img)
416
+ * }
417
+ * reader.readAsDataURL(blob)
418
+ * })
419
+ */
420
+ frameAsBlobHandler (callback, captureOpt = {}, millis = 500) {
421
+ asyncToCallback(this, this.frameAsBlob, callback, captureOpt, millis)
422
+ }
423
+
424
+ /**
425
+ * Stop the live feed if it is running.
426
+ * (Async/Await version)
427
+ *
428
+ * @method
429
+ * @async
430
+ * @returns {object} Status of the stop operation
431
+ * @example
432
+ * // Plugin is now running it's live feed in a background thread
433
+ * const blob = await device.frameAsBlob()
434
+ * // Live feed is now stopping
435
+ * console.log(await device.stopFeed())
436
+ */
437
+ async stopFeed () {
438
+ this.can('stop_feed')
439
+ const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/livefeed`
440
+ const response = await fetch(url, DELETE)
441
+ return await response.json()
442
+ }
443
+
444
+ /**
445
+ * Stop the live feed if it is running.
446
+ * (Callback version)
447
+ *
448
+ * @method
449
+ * @param {number} [millis] - Milliseconds to wait if feed is not ready
450
+ * @param {function} callback with the status of the stop operation
451
+ * @example
452
+ * device.stopFeedHandler((error, blob) => {
453
+ * // Plugin is now running it's live feed in a background thread
454
+ * device.setStopFeed(result => {
455
+ * // Live feed is now stopping
456
+ * console.log(result)
457
+ * })
458
+ * })
459
+ */
460
+ stopFeedHandler (callback) {
461
+ asyncToCallback(this, this.frameAsBlob, callback)
462
+ }
463
+
464
+ /**
465
+ * Clear a device's display.
466
+ * (Async/Await version)
467
+ *
468
+ * @method
469
+ * @async
470
+ * @param {boolean} [backlight] If provided and set to true, will enable
471
+ * backlight. If false, it will be disabled. If unspecified no action will be
472
+ * taken on the backlight.
473
+ * @returns {object} Status of the clear operation
474
+ * @example
475
+ * console.log(await await device.clear())
476
+ */
477
+ async clear (backlight) {
478
+ this.canDoOne(['draw', 'clear'])
479
+ let url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/display`
480
+ if (typeof backlight === 'boolean') {
481
+ url += `?backlight=${backlight}`
482
+ }
483
+ const response = await fetch(url, DELETE)
484
+ return await response.json()
485
+ }
486
+
487
+ /**
488
+ * Clear a device's display.
489
+ * (Callback version)
490
+ *
491
+ * @method
492
+ * @param {function} callback with the status of the clear operation
493
+ * @param {boolean} [backlight] If provided and set to true, will enable
494
+ * backlight. If false, it will be disabled. If unspecified no action will be
495
+ * taken on the backlight.
496
+ * @example
497
+ * device.clearHandler((error, result) => {
498
+ * console.log(result)
499
+ * })
500
+ */
501
+ clearHandler (callback, backlight) {
502
+ asyncToCallback(this, this.clear, callback, backlight)
503
+ }
504
+
505
+ /**
506
+ * Render an Array of Objects on the display and wait for user input.
507
+ *
508
+ * @description If any Objects are Buttons, the method will wait for one
509
+ * to be clicked and will then return results. The format of the Array of
510
+ * Objects, the response structure, and the scene options are plugin specific.
511
+ *
512
+ * @param {Object[]} objects An array of drawable objects to display.
513
+ * @param {Object} [sceneOpt] Options for configuring the scene.
514
+ * @param {boolean} [sceneOpt.clear] If `true`, the display will be cleared
515
+ * before adding the Objects.
516
+ * @param {Object} [sceneOpt.backlight] If `true`, the backlight will be
517
+ * enabled before adding the Objects.
518
+ *
519
+ * @async
520
+ * @method
521
+ */
522
+ async displayObjects (objects, sceneOpt = {}) {
523
+ this.can('draw')
524
+ const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/display`
525
+
526
+ const asyncOperations = objects.map(async obj => {
527
+ if (obj.constructor.name === 'Image') {
528
+ await obj.data()
529
+ }
530
+ })
531
+
532
+ // Wait for all async operations to complete
533
+ await Promise.all(asyncOperations)
534
+
535
+ sceneOpt.objects = objects
536
+
537
+ const options = {
538
+ method: 'POST',
539
+ mode: 'cors',
540
+ headers: {
541
+ 'Content-Type': 'application/json'
542
+ },
543
+ body: JSON.stringify(sceneOpt)
544
+ }
545
+
546
+ const response = await fetch(url, options)
547
+ await checkResponse(response)
548
+ return await response.json()
549
+ }
550
+
551
+ async displayObjectsHandler (objects, clear = true, callback) {
552
+ asyncToCallback(this, this.displayObjects, callback, objects, clear)
553
+ }
554
+
555
+ /**
556
+ * Get detailed information about a device
557
+ *
558
+ * @description Returns detailed information about a device.
559
+ *
560
+ * @async
561
+ * @method
562
+ * @returns {object} Device info
563
+ * @example
564
+ * const info = await device.info()
565
+ */
566
+ async info () {
567
+ this.can('device_info')
568
+ const url = `${this.baseUrl}/plugin/${this.plugin.id}/device/${this.id}/info`
569
+ const response = await fetch(url)
570
+ return await response.json()
571
+ }
572
+
573
+ /**
574
+ * Get detailed information about a device
575
+ *
576
+ * @description Returns detailed information about a device.
577
+ *
578
+ * @method
579
+ * @returns {object} Device info
580
+ * @example
581
+ * const info = await device.info()
582
+ */
583
+ async infoHandler (callback) {
584
+ asyncToCallback(this, this.info, callback)
585
+ }
586
+ }
587
+
588
+ export default Device