From f0ade467a900500fdeaf55603ae729f136316746 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Thu, 5 Dec 2024 08:41:22 +0100 Subject: [PATCH] feat: add flush() method, resolves #106 --- packages/core/src/genericHIDDevice.ts | 3 + packages/core/src/xkeys.ts | 6 ++ packages/node/src/__tests__/xkeys.spec.ts | 91 +++++++++++++++++++++++ packages/node/src/node-hid-wrapper.ts | 3 + packages/webhid/src/web-hid-wrapper.ts | 3 + 5 files changed, 106 insertions(+) diff --git a/packages/core/src/genericHIDDevice.ts b/packages/core/src/genericHIDDevice.ts index 9067f14..b8407ee 100644 --- a/packages/core/src/genericHIDDevice.ts +++ b/packages/core/src/genericHIDDevice.ts @@ -8,5 +8,8 @@ export interface HIDDevice { write(data: number[]): void + /** Returns a promise which settles when all writes has completed */ + flush(): Promise + close(): Promise } diff --git a/packages/core/src/xkeys.ts b/packages/core/src/xkeys.ts index 2197650..d1f6cae 100644 --- a/packages/core/src/xkeys.ts +++ b/packages/core/src/xkeys.ts @@ -647,6 +647,12 @@ export class XKeys extends EventEmitter { public writeData(message: HIDMessage): void { this._write(message) } + /** + * Returns a Promise that settles when all writes have been completed + */ + public async flush(): Promise { + await this.device.flush() + } /** (Internal function) Called when there has been detected that the device has been disconnected */ public async _handleDeviceDisconnected(): Promise { diff --git a/packages/node/src/__tests__/xkeys.spec.ts b/packages/node/src/__tests__/xkeys.spec.ts index ab36b3b..ab2b26f 100644 --- a/packages/node/src/__tests__/xkeys.spec.ts +++ b/packages/node/src/__tests__/xkeys.spec.ts @@ -137,4 +137,95 @@ describe('Unit tests', () => { expect(onError).toHaveBeenCalledTimes(0) }) + test('flush()', async () => { + const hidDevice = { + vendorId: XKeys.vendorId, + productId: 1029, + interface: 0, + path: 'mockPath', + } as HID.Device + + const mockWriteStart = jest.fn() + const mockWriteEnd = jest.fn() + HIDMock.setMockWriteHandler(async (hid, message) => { + mockWriteStart() + await sleep(10) + mockWriteEnd() + handleXkeysMessages(hid, message) + }) + + const myXkeysPanel = await setupXkeysPanel(hidDevice) + + const errorListener = jest.fn(console.error) + myXkeysPanel.on('error', errorListener) + + mockWriteStart.mockClear() + mockWriteEnd.mockClear() + + myXkeysPanel.toggleAllBacklights() + + expect(mockWriteStart).toBeCalledTimes(1) + expect(mockWriteEnd).toBeCalledTimes(0) // Should not have been called yet + + // cleanup: + await myXkeysPanel.flush() // waits for all writes to finish + + expect(mockWriteEnd).toBeCalledTimes(1) + + await myXkeysPanel.close() // close the device. + myXkeysPanel.off('error', errorListener) + + expect(errorListener).toHaveBeenCalledTimes(0) + }) + test('flush() with error', async () => { + const hidDevice = { + vendorId: XKeys.vendorId, + productId: 1029, + interface: 0, + path: 'mockPath', + } as HID.Device + + const mockWriteStart = jest.fn() + const mockWriteEnd = jest.fn() + HIDMock.setMockWriteHandler(async (hid, message) => { + mockWriteStart() + await sleep(10) + mockWriteEnd() + // console.log('message', message) + + if (message[0] === 0 && message[1] === 184) { + // toggleAllBacklights + throw new Error('Mock error') + } + + handleXkeysMessages(hid, message) + }) + + const myXkeysPanel = await setupXkeysPanel(hidDevice) + + const errorListener = jest.fn((e) => { + if (`${e}`.includes('Mock error')) return // ignore + console.error(e) + }) + myXkeysPanel.on('error', errorListener) + + mockWriteStart.mockClear() + mockWriteEnd.mockClear() + + myXkeysPanel.toggleAllBacklights() + + expect(mockWriteStart).toBeCalledTimes(1) + expect(errorListener).toBeCalledTimes(0) // Should not have been called yet + + // cleanup: + await myXkeysPanel.flush() // waits for all writes to finish + + expect(errorListener).toBeCalledTimes(1) + errorListener.mockClear() + + await myXkeysPanel.close() // close the device. + myXkeysPanel.off('error', errorListener) + + expect(errorListener).toHaveBeenCalledTimes(0) + }) }) diff --git a/packages/node/src/node-hid-wrapper.ts b/packages/node/src/node-hid-wrapper.ts index 0312d3d..d69d71c 100644 --- a/packages/node/src/node-hid-wrapper.ts +++ b/packages/node/src/node-hid-wrapper.ts @@ -41,6 +41,9 @@ export class NodeHIDDevice extends EventEmitter implements HIDDevice { this.device.removeListener('error', this._handleError) this.device.removeListener('data', this._handleData) } + public async flush(): Promise { + await this.writeQueue.onIdle() + } private _handleData = (data: Buffer) => { this.emit('data', data) diff --git a/packages/webhid/src/web-hid-wrapper.ts b/packages/webhid/src/web-hid-wrapper.ts index f740c46..2e91eb4 100644 --- a/packages/webhid/src/web-hid-wrapper.ts +++ b/packages/webhid/src/web-hid-wrapper.ts @@ -30,6 +30,9 @@ export class WebHIDDevice extends EventEmitter implements CoreHIDDevice { this.emit('error', err) }) } + public async flush(): Promise { + await this.reportQueue.onIdle() + } public async close(): Promise { await this.device.close()