diff --git a/example/index.html b/example/index.html
index a6d3a71..737fa23 100644
--- a/example/index.html
+++ b/example/index.html
@@ -29,8 +29,14 @@
WebUSB
To seed the window.keepkeyManager
with WebUSB devices, first pair a device, then click start.
+
+
+
+
+
+
-
+
diff --git a/example/js/main.js b/example/js/main.js
index 80629b8..7da70ba 100644
--- a/example/js/main.js
+++ b/example/js/main.js
@@ -126,6 +126,9 @@ window.connectWebUSB = function () {
const k = manager.get()
console.log('Putting first keepkey on window as window.keepkey: ', k)
window.keepkey = k
+ k.device.events.on('write', data => console.log('Event: write', data))
+ k.device.events.on('read', data => console.log('Event: read', data))
+ k.device.events.on('reading', () => console.log('Event: reading'))
}
return manager.exec('getFeatures')
})
@@ -147,3 +150,40 @@ window.connectWebUSB = function () {
console.error(String(e))
})
}
+
+// So we can see which commands aren't resolving
+window.allPings = []
+
+const log = (name, p) => {
+ console.log('sending', name)
+ const details = { name, state: 'pending' }
+ details.p = p
+ .then(res => {
+ console.log(`${name} response:`, res)
+ details.state = 'resolved'
+ })
+ .catch(e => {
+ console.error(`${name} error:`, e)
+ details.state = 'rejected'
+ })
+ allPings.push(details)
+ return p
+}
+
+let pingCount = 0
+
+window.pingWithButton = function () {
+ log(`ping ${++pingCount} with button`, manager.exec('ping', { message: 'ping' + pingCount, buttonProtection: true }))
+}
+
+window.pingWithPIN = function () {
+ log(`ping ${++pingCount} with PIN`, manager.exec('ping', { message: 'ping' + pingCount, pinProtection: true }))
+}
+
+window.ping = function () {
+ log(`ping ${++pingCount}`, manager.exec('ping', { message: 'ping' + pingCount }))
+}
+
+window.cancelPending = function () {
+ log('cancelPending', window.keepkey.cancel())
+}
diff --git a/src/device.ts b/src/device.ts
index 6384ad4..1220a14 100644
--- a/src/device.ts
+++ b/src/device.ts
@@ -84,6 +84,8 @@ export default abstract class Device {
public abstract sendRaw (buffer: ByteBuffer): Promise
+ public abstract cancelPending (): Promise
+
protected toMessageBuffer (msgTypeEnum: number, msg: jspb.Message): ByteBuffer {
const messageBuffer = msg.serializeBinary()
@@ -112,4 +114,15 @@ export default abstract class Device {
const reader = new jspb.BinaryReader(dataView.slice(9), 0, buff.limit - (9 + 2))
return [typeID, MessageType.deserializeBinaryFromReader(msg, reader)]
}
+
+ protected static failureMessageFactory (e?: Error | string) {
+ const msg = new Messages.Failure()
+ msg.setCode(Types.FailureType.FAILURE_UNEXPECTEDMESSAGE)
+ if (typeof e === 'string') {
+ msg.setMessage(e)
+ } else {
+ msg.setMessage(String(e))
+ }
+ return ByteBuffer.wrap(msg.serializeBinary())
+ }
}
diff --git a/src/keepkey.ts b/src/keepkey.ts
index 040e74c..9868b51 100644
--- a/src/keepkey.ts
+++ b/src/keepkey.ts
@@ -138,11 +138,7 @@ export default class KeepKey {
// Cancel aborts the last device action that required user interaction
// It can follow a button request, passphrase request, or pin request
public async cancel (): Promise {
- const cancel = new Messages.Cancel()
- // send
- await this.device.exchange(Messages.MessageType.MESSAGETYPE_CANCEL, cancel)
-
- // Emit event to notify clients that an action has been cancelled
+ await this.device.cancelPending()
this.device.events.emit('CANCEL_ACTION')
}
diff --git a/src/webUSBDevice.test.ts b/src/webUSBDevice.test.ts
index 24358d6..29e2f19 100644
--- a/src/webUSBDevice.test.ts
+++ b/src/webUSBDevice.test.ts
@@ -7,7 +7,7 @@ describe('WebUSBDevice', () => {
test('should return a Failure message if `read` throws an error', async () => {
const config = { usbDevice: {
transferIn: jest.fn().mockRejectedValueOnce(new Error('TEST'))
- }, events: {} }
+ }, events: { emit: jest.fn() } }
// @ts-ignore
const device = new WebUSBDevice(config)
const msg = new Messages.Cancel()
diff --git a/src/webUSBDevice.ts b/src/webUSBDevice.ts
index f80be1f..bad4165 100644
--- a/src/webUSBDevice.ts
+++ b/src/webUSBDevice.ts
@@ -4,7 +4,6 @@ import { default as PQueue } from 'p-queue'
import Device from './device'
import Messages from './kkProto/messages_pb'
-import Types from './kkProto/types_pb'
export interface WebUSBDeviceConfig {
usbDevice: USBDevice,
@@ -43,13 +42,32 @@ export default class WebUSBDevice extends Device {
}
}
+ public async cancelPending () {
+ console.log('pending', this.queue.pending)
+ try {
+ // If there are no pending commands, we should wait for a read back from the cancel command
+ // Otherwise the pending promise will read the error
+ if (this.queue.pending === 0) {
+ this.queue.add(() => this.read(), { priority: 1000 })
+ .then(() => console.log('cancenPending read done'))
+ .catch(e => console.log('cancenPending read failed', e))
+ }
+
+ const cancelMsg = new Messages.Cancel()
+ const buffer = this.toMessageBuffer(Messages.MessageType.MESSAGETYPE_CANCEL, cancelMsg)
+ await this.write(buffer)
+ } catch (e) {
+ console.error('Cancel Pending Error', e)
+ }
+ }
+
public async disconnect (): Promise {
if (!this.usbDevice.opened) return
try {
// If the device is disconnected, this will fail and throw, which is fine.
await this.usbDevice.releaseInterface(0)
} catch (e) {
- console.log(e)
+ console.log('Disconnect Error (Ignored):', e)
}
}
@@ -66,16 +84,14 @@ export default class WebUSBDevice extends Device {
await this.write(buffer)
return await this.read()
} catch (e) {
- const msg = new Messages.Failure()
- msg.setCode(Types.FailureType.FAILURE_UNEXPECTEDMESSAGE)
- msg.setMessage(String(e))
- return ByteBuffer.wrap(msg.serializeBinary())
+ return Device.failureMessageFactory(e)
}
})
}
protected async write (buff: ByteBuffer): Promise {
// break frame into segments
+ this.events.emit('write', buff)
for (let i = 0; i < buff.limit; i += SEGMENT_SIZE) {
let segment = buff.toArrayBuffer().slice(i, i + SEGMENT_SIZE)
let padding = new Array(SEGMENT_SIZE - segment.byteLength + 1).join('\0')
@@ -89,6 +105,7 @@ export default class WebUSBDevice extends Device {
}
protected async read (): Promise {
+ this.events.emit('reading')
let first = await this.readChunk()
// Check that buffer starts with: "?##" [ 0x3f, 0x23, 0x23 ]
// "?" = USB marker, "##" = KeepKey magic bytes
@@ -112,7 +129,9 @@ export default class WebUSBDevice extends Device {
}
}
- return ByteBuffer.wrap(buffer)
+ const res = ByteBuffer.wrap(buffer)
+ this.events.emit('read', res)
+ return res
} else {
console.error('Invalid message', { msgLength, valid, first })
throw new Error('Invalid message')