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