@onekeyfe/hd-transport-electron 1.2.2-alpha.101 → 1.2.2-alpha.103
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/dist/{index-d51c7da6.js → index-052b4b5b.js} +33 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/{noble-ble-handler-0b455c4f.js → noble-ble-handler-eff26dd3.js} +309 -68
- package/dist/noble-ble-handler.d.ts +3 -0
- package/dist/noble-ble-handler.d.ts.map +1 -1
- package/dist/types/desktop-api.d.ts +2 -0
- package/dist/types/desktop-api.d.ts.map +1 -1
- package/dist/types/noble-extended.d.ts +2 -0
- package/dist/types/noble-extended.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/noble-ble-handler.test.ts +517 -0
- package/src/index.ts +7 -0
- package/src/noble-ble-handler.ts +352 -80
- package/src/types/desktop-api.ts +31 -0
- package/src/types/noble-extended.ts +2 -0
|
@@ -8,5 +8,8 @@ export declare function createNobleBleConnectionError(error: NobleBleNativeError
|
|
|
8
8
|
export declare function createNobleBleIpcErrorResponse(error: unknown): NobleBleIpcErrorResponse;
|
|
9
9
|
export declare function resolveNobleBleWritePacingDelay(options?: NobleBleWriteOptions): number;
|
|
10
10
|
export declare function setupNobleBleHandlers(webContents: WebContents): void;
|
|
11
|
+
export declare function disposeNobleBleSupport(releaseNoble?: (instance: {
|
|
12
|
+
stop(): void;
|
|
13
|
+
}) => void): Promise<void>;
|
|
11
14
|
export {};
|
|
12
15
|
//# sourceMappingURL=noble-ble-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAA+B,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzE,OAAO,KAAK,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"noble-ble-handler.d.ts","sourceRoot":"","sources":["../src/noble-ble-handler.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAA+B,WAAW,EAAE,MAAM,UAAU,CAAC;AAEzE,OAAO,KAAK,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAgC1F,KAAK,mBAAmB,GAAG,KAAK,GAAG;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,mBAAmB,EAAE,aAAa,SAAK,+CAiB3F;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAgCvF;AA+KD,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,oBAAoB,UAI7E;AA4xDD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CA+NpE;AAOD,wBAAgB,sBAAsB,CACpC,YAAY,GAAE,CAAC,QAAQ,EAAE;IAAE,IAAI,IAAI,IAAI,CAAA;CAAE,KAAK,IAAkC,GAC/E,OAAO,CAAC,IAAI,CAAC,CAiEf"}
|
|
@@ -13,6 +13,8 @@ export type NobleBleIpcErrorResponse = {
|
|
|
13
13
|
success: false;
|
|
14
14
|
error: NobleBleIpcErrorPayload;
|
|
15
15
|
};
|
|
16
|
+
export declare function isNobleBleIpcErrorResponse(response: unknown): response is NobleBleIpcErrorResponse;
|
|
17
|
+
export declare function invokeNobleBleIpc<T>(request: Promise<T | NobleBleIpcErrorResponse>): Promise<T>;
|
|
16
18
|
export interface NobleBleAPI {
|
|
17
19
|
enumerate: () => Promise<{
|
|
18
20
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,uBAAuB,CAAC;CAChC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACxF,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAGzC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACnF,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAOvF,oBAAoB,EAAE,CACpB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,oBAAoB,CAAA;KAAE,KAAK,IAAI,KACpF,MAAM,IAAI,CAAC;IAChB,iBAAiB,EAAE,MAAM,OAAO,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC,CAAC;CACJ;AAGD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB"}
|
|
1
|
+
{"version":3,"file":"desktop-api.d.ts","sourceRoot":"","sources":["../../src/types/desktop-api.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,uBAAuB,CAAC;CAChC,CAAC;AAEF,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,OAAO,GAChB,QAAQ,IAAI,wBAAwB,CAatC;AAMD,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,OAAO,CAAC,CAAC,GAAG,wBAAwB,CAAC,GAC7C,OAAO,CAAC,CAAC,CAAC,CAMZ;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACzD,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACxF,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAGzC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,cAAc,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACnF,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAOvF,oBAAoB,EAAE,CACpB,QAAQ,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,oBAAoB,CAAA;KAAE,KAAK,IAAI,KACpF,MAAM,IAAI,CAAC;IAChB,iBAAiB,EAAE,MAAM,OAAO,CAAC;QAC/B,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC,CAAC;CACJ;AAGD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB"}
|
|
@@ -14,6 +14,8 @@ export interface NobleModule {
|
|
|
14
14
|
state: string;
|
|
15
15
|
startScanning(serviceUUIDs: string[], allowDuplicates: boolean, callback?: (error?: Error) => void): void;
|
|
16
16
|
stopScanning(callback?: () => void): void;
|
|
17
|
+
stop(): void;
|
|
18
|
+
cancelConnect?(id: string): void;
|
|
17
19
|
on(event: 'stateChange', listener: (state: string) => void): void;
|
|
18
20
|
on(event: 'discover', listener: (peripheral: Peripheral) => void): void;
|
|
19
21
|
removeListener(event: 'stateChange', listener: (state: string) => void): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noble-extended.d.ts","sourceRoot":"","sources":["../../src/types/noble-extended.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAGD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CACX,YAAY,EAAE,MAAM,EAAE,EACtB,eAAe,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GACjC,IAAI,CAAC;IACR,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1C,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9E,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAGD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CAC9C;AAGD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,GAAG,EAAE,GACb,IAAI,CAMN"}
|
|
1
|
+
{"version":3,"file":"noble-extended.d.ts","sourceRoot":"","sources":["../../src/types/noble-extended.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,MAAM,WAAW,UAAW,SAAQ,oBAAoB;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAGD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CACX,YAAY,EAAE,MAAM,EAAE,EACtB,eAAe,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,GACjC,IAAI,CAAC;IACR,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC1C,IAAI,IAAI,IAAI,CAAC;IACb,aAAa,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC9E,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAGD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CAC9C;AAGD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,GAAG,IAAI,EAAE,GAAG,EAAE,GACb,IAAI,CAMN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-transport-electron",
|
|
3
|
-
"version": "1.2.2-alpha.
|
|
3
|
+
"version": "1.2.2-alpha.103",
|
|
4
4
|
"author": "OneKey",
|
|
5
5
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"electron-log": ">=4.0.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onekeyfe/hd-core": "1.2.2-alpha.
|
|
29
|
-
"@onekeyfe/hd-shared": "1.2.2-alpha.
|
|
30
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
28
|
+
"@onekeyfe/hd-core": "1.2.2-alpha.103",
|
|
29
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.103",
|
|
30
|
+
"@onekeyfe/hd-transport": "1.2.2-alpha.103",
|
|
31
31
|
"@stoprocent/noble": "2.3.16",
|
|
32
32
|
"p-retry": "^4.6.2"
|
|
33
33
|
},
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"electron": "^25.0.0",
|
|
37
37
|
"typescript": "^5.3.3"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "cd5aaa8a42a61c93f28b7d4931081fb7cd652e26"
|
|
40
40
|
}
|
|
@@ -60,6 +60,16 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
60
60
|
).toMatchObject({
|
|
61
61
|
errorCode: HardwareErrorCode.BleConnectedError,
|
|
62
62
|
});
|
|
63
|
+
expect(
|
|
64
|
+
createNobleBleConnectionError(
|
|
65
|
+
Object.assign(new Error('Encryption is insufficient'), {
|
|
66
|
+
nativeErrorCode: 15,
|
|
67
|
+
nativeErrorDomain: 'CBATTErrorDomain',
|
|
68
|
+
})
|
|
69
|
+
)
|
|
70
|
+
).toMatchObject({
|
|
71
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
72
|
+
});
|
|
63
73
|
expect(createNobleBleConnectionError(new Error('connection failed'))).toMatchObject({
|
|
64
74
|
errorCode: HardwareErrorCode.BleConnectedError,
|
|
65
75
|
});
|
|
@@ -115,6 +125,29 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
115
125
|
});
|
|
116
126
|
});
|
|
117
127
|
|
|
128
|
+
test('rejects a structured Noble IPC failure at the preload boundary', async () => {
|
|
129
|
+
const { invokeNobleBleIpc } = await import('../types/desktop-api');
|
|
130
|
+
|
|
131
|
+
await expect(
|
|
132
|
+
invokeNobleBleIpc(
|
|
133
|
+
Promise.resolve({
|
|
134
|
+
type: 'NobleBleIpcError' as const,
|
|
135
|
+
success: false as const,
|
|
136
|
+
error: {
|
|
137
|
+
name: 'HardwareError',
|
|
138
|
+
message: 'Bluetooth pairing information is no longer valid',
|
|
139
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
)
|
|
143
|
+
).rejects.toMatchObject({
|
|
144
|
+
name: 'HardwareError',
|
|
145
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await expect(invokeNobleBleIpc(Promise.resolve('connected'))).resolves.toBe('connected');
|
|
149
|
+
});
|
|
150
|
+
|
|
118
151
|
test('keeps safe pacing by default and allows an explicit high-throughput bypass', async () => {
|
|
119
152
|
const { resolveNobleBleWritePacingDelay } = await import('../noble-ble-handler');
|
|
120
153
|
|
|
@@ -272,6 +305,290 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
272
305
|
expect(peripheral.connect).toHaveBeenCalledTimes(1);
|
|
273
306
|
});
|
|
274
307
|
|
|
308
|
+
test('disconnects and settles a pending Noble connect before a late callback arrives', async () => {
|
|
309
|
+
const handlers = new Map<string, IpcHandler>();
|
|
310
|
+
const ipcMain = {
|
|
311
|
+
handle: jest.fn((channel: string, handler: IpcHandler) => {
|
|
312
|
+
handlers.set(channel, handler);
|
|
313
|
+
}),
|
|
314
|
+
removeHandler: jest.fn((channel: string) => {
|
|
315
|
+
handlers.delete(channel);
|
|
316
|
+
}),
|
|
317
|
+
};
|
|
318
|
+
const noble = new EventEmitter() as EventEmitter & {
|
|
319
|
+
state: string;
|
|
320
|
+
startScanning: jest.Mock;
|
|
321
|
+
stopScanning: jest.Mock;
|
|
322
|
+
};
|
|
323
|
+
let stopScanningCallback: (() => void) | undefined;
|
|
324
|
+
let connectCallback: ((error?: Error) => void) | undefined;
|
|
325
|
+
let resolveConnectStarted = () => undefined;
|
|
326
|
+
const connectStarted = new Promise<void>(resolve => {
|
|
327
|
+
resolveConnectStarted = resolve;
|
|
328
|
+
});
|
|
329
|
+
const peripheral = Object.assign(
|
|
330
|
+
new EventEmitter(),
|
|
331
|
+
createPeripheral('pending-device', 'Pro2 C3D4'),
|
|
332
|
+
{
|
|
333
|
+
connect: jest.fn((callback: (error?: Error) => void) => {
|
|
334
|
+
connectCallback = callback;
|
|
335
|
+
resolveConnectStarted();
|
|
336
|
+
}),
|
|
337
|
+
disconnect: jest.fn((callback: () => void) => {
|
|
338
|
+
peripheral.state = 'disconnected';
|
|
339
|
+
callback();
|
|
340
|
+
}),
|
|
341
|
+
discoverServices: jest.fn(),
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
noble.state = 'poweredOn';
|
|
345
|
+
noble.startScanning = jest.fn((_services, _duplicates, callback) => {
|
|
346
|
+
callback?.();
|
|
347
|
+
noble.emit('discover', peripheral);
|
|
348
|
+
});
|
|
349
|
+
noble.stopScanning = jest.fn(callback => {
|
|
350
|
+
stopScanningCallback = callback;
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
jest.doMock('@stoprocent/noble', () => noble);
|
|
354
|
+
jest.doMock('electron', () => ({ ipcMain }));
|
|
355
|
+
jest.doMock('electron-log', () => ({
|
|
356
|
+
info: jest.fn(),
|
|
357
|
+
debug: jest.fn(),
|
|
358
|
+
error: jest.fn(),
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
const { setupNobleBleHandlers } = await import('../noble-ble-handler');
|
|
362
|
+
setupNobleBleHandlers({
|
|
363
|
+
on: jest.fn(),
|
|
364
|
+
send: jest.fn(),
|
|
365
|
+
} as unknown as WebContents);
|
|
366
|
+
|
|
367
|
+
const connect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT);
|
|
368
|
+
const disconnect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT);
|
|
369
|
+
if (!connect || !disconnect) {
|
|
370
|
+
throw new Error('Electron Noble BLE handlers were not registered');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const connectPromise = Promise.resolve(connect(undefined, peripheral.id));
|
|
374
|
+
await Promise.resolve();
|
|
375
|
+
stopScanningCallback?.();
|
|
376
|
+
await connectStarted;
|
|
377
|
+
|
|
378
|
+
await expect(Promise.resolve(disconnect(undefined, peripheral.id))).resolves.toBeUndefined();
|
|
379
|
+
await expect(connectPromise).resolves.toMatchObject({
|
|
380
|
+
type: 'NobleBleIpcError',
|
|
381
|
+
success: false,
|
|
382
|
+
error: {
|
|
383
|
+
errorCode: HardwareErrorCode.BleDeviceDisconnected,
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
expect(peripheral.disconnect).toHaveBeenCalledTimes(1);
|
|
387
|
+
|
|
388
|
+
peripheral.state = 'connected';
|
|
389
|
+
connectCallback?.();
|
|
390
|
+
await Promise.resolve();
|
|
391
|
+
|
|
392
|
+
expect(peripheral.discoverServices).not.toHaveBeenCalled();
|
|
393
|
+
expect(peripheral.disconnect).toHaveBeenCalledTimes(2);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test('disconnects and settles a pending forced reconnect before a late callback arrives', async () => {
|
|
397
|
+
const handlers = new Map<string, IpcHandler>();
|
|
398
|
+
const ipcMain = {
|
|
399
|
+
handle: jest.fn((channel: string, handler: IpcHandler) => {
|
|
400
|
+
handlers.set(channel, handler);
|
|
401
|
+
}),
|
|
402
|
+
removeHandler: jest.fn((channel: string) => {
|
|
403
|
+
handlers.delete(channel);
|
|
404
|
+
}),
|
|
405
|
+
};
|
|
406
|
+
const noble = new EventEmitter() as EventEmitter & {
|
|
407
|
+
state: string;
|
|
408
|
+
startScanning: jest.Mock;
|
|
409
|
+
stopScanning: jest.Mock;
|
|
410
|
+
};
|
|
411
|
+
let stopScanningCallback: (() => void) | undefined;
|
|
412
|
+
const connectCallbacks: Array<(error?: Error) => void> = [];
|
|
413
|
+
let resolveInitialConnectStarted = () => undefined;
|
|
414
|
+
const initialConnectStarted = new Promise<void>(resolve => {
|
|
415
|
+
resolveInitialConnectStarted = resolve;
|
|
416
|
+
});
|
|
417
|
+
let resolveForcedReconnectStarted = () => undefined;
|
|
418
|
+
const forcedReconnectStarted = new Promise<void>(resolve => {
|
|
419
|
+
resolveForcedReconnectStarted = resolve;
|
|
420
|
+
});
|
|
421
|
+
const peripheral = Object.assign(
|
|
422
|
+
new EventEmitter(),
|
|
423
|
+
createPeripheral('forced-reconnect-device', 'Pro2 C3D4'),
|
|
424
|
+
{
|
|
425
|
+
connect: jest.fn((callback: (error?: Error) => void) => {
|
|
426
|
+
connectCallbacks.push(callback);
|
|
427
|
+
if (connectCallbacks.length === 1) {
|
|
428
|
+
resolveInitialConnectStarted();
|
|
429
|
+
} else if (connectCallbacks.length === 2) {
|
|
430
|
+
resolveForcedReconnectStarted();
|
|
431
|
+
}
|
|
432
|
+
}),
|
|
433
|
+
disconnect: jest.fn((callback: () => void) => {
|
|
434
|
+
peripheral.state = 'disconnected';
|
|
435
|
+
callback();
|
|
436
|
+
}),
|
|
437
|
+
discoverServices: jest.fn(),
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
noble.state = 'poweredOn';
|
|
441
|
+
noble.startScanning = jest.fn((_services, _duplicates, callback) => {
|
|
442
|
+
callback?.();
|
|
443
|
+
noble.emit('discover', peripheral);
|
|
444
|
+
});
|
|
445
|
+
noble.stopScanning = jest.fn(callback => {
|
|
446
|
+
stopScanningCallback = callback;
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
jest.doMock('@stoprocent/noble', () => noble);
|
|
450
|
+
jest.doMock('electron', () => ({ ipcMain }));
|
|
451
|
+
jest.doMock('electron-log', () => ({
|
|
452
|
+
info: jest.fn(),
|
|
453
|
+
debug: jest.fn(),
|
|
454
|
+
error: jest.fn(),
|
|
455
|
+
}));
|
|
456
|
+
|
|
457
|
+
const { setupNobleBleHandlers } = await import('../noble-ble-handler');
|
|
458
|
+
setupNobleBleHandlers({
|
|
459
|
+
on: jest.fn(),
|
|
460
|
+
send: jest.fn(),
|
|
461
|
+
} as unknown as WebContents);
|
|
462
|
+
|
|
463
|
+
const connect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT);
|
|
464
|
+
const disconnect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT);
|
|
465
|
+
if (!connect || !disconnect) {
|
|
466
|
+
throw new Error('Electron Noble BLE handlers were not registered');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const connectPromise = Promise.resolve(connect(undefined, peripheral.id));
|
|
470
|
+
await Promise.resolve();
|
|
471
|
+
stopScanningCallback?.();
|
|
472
|
+
await initialConnectStarted;
|
|
473
|
+
|
|
474
|
+
peripheral.state = 'connected';
|
|
475
|
+
connectCallbacks[0]?.();
|
|
476
|
+
await forcedReconnectStarted;
|
|
477
|
+
|
|
478
|
+
await expect(Promise.resolve(disconnect(undefined, peripheral.id))).resolves.toBeUndefined();
|
|
479
|
+
await expect(connectPromise).resolves.toMatchObject({
|
|
480
|
+
type: 'NobleBleIpcError',
|
|
481
|
+
success: false,
|
|
482
|
+
error: {
|
|
483
|
+
errorCode: HardwareErrorCode.BleDeviceDisconnected,
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
expect(peripheral.disconnect).toHaveBeenCalledTimes(2);
|
|
487
|
+
|
|
488
|
+
peripheral.state = 'connected';
|
|
489
|
+
connectCallbacks[1]?.();
|
|
490
|
+
await Promise.resolve();
|
|
491
|
+
|
|
492
|
+
expect(peripheral.discoverServices).not.toHaveBeenCalled();
|
|
493
|
+
expect(peripheral.disconnect).toHaveBeenCalledTimes(3);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test('maps a structured native subscription failure before returning it over IPC', async () => {
|
|
497
|
+
const handlers = new Map<string, IpcHandler>();
|
|
498
|
+
const ipcMain = {
|
|
499
|
+
handle: jest.fn((channel: string, handler: IpcHandler) => {
|
|
500
|
+
handlers.set(channel, handler);
|
|
501
|
+
}),
|
|
502
|
+
removeHandler: jest.fn((channel: string) => {
|
|
503
|
+
handlers.delete(channel);
|
|
504
|
+
}),
|
|
505
|
+
};
|
|
506
|
+
const nativeError = Object.assign(new Error('Encryption is insufficient'), {
|
|
507
|
+
nativeErrorCode: 15,
|
|
508
|
+
nativeErrorDomain: 'CBATTErrorDomain',
|
|
509
|
+
});
|
|
510
|
+
const notifyCharacteristic = Object.assign(new EventEmitter(), {
|
|
511
|
+
uuid: '0003',
|
|
512
|
+
unsubscribe: jest.fn((callback: (error?: Error) => void) => callback()),
|
|
513
|
+
subscribe: jest.fn((callback: (error?: Error) => void) => callback(nativeError)),
|
|
514
|
+
});
|
|
515
|
+
const writeCharacteristic = Object.assign(new EventEmitter(), {
|
|
516
|
+
uuid: '0002',
|
|
517
|
+
});
|
|
518
|
+
const service = {
|
|
519
|
+
uuid: '0001',
|
|
520
|
+
discoverCharacteristics: jest.fn(
|
|
521
|
+
(
|
|
522
|
+
_characteristicUuids: string[],
|
|
523
|
+
callback: (error: Error | null, value: unknown[]) => void
|
|
524
|
+
) => callback(null, [writeCharacteristic, notifyCharacteristic])
|
|
525
|
+
),
|
|
526
|
+
};
|
|
527
|
+
const peripheral = Object.assign(
|
|
528
|
+
new EventEmitter(),
|
|
529
|
+
createPeripheral('subscription-device', 'Pro2 E5F6'),
|
|
530
|
+
{
|
|
531
|
+
connect: jest.fn((callback: (error?: Error) => void) => {
|
|
532
|
+
peripheral.state = 'connected';
|
|
533
|
+
callback();
|
|
534
|
+
}),
|
|
535
|
+
disconnect: jest.fn((callback: () => void) => {
|
|
536
|
+
peripheral.state = 'disconnected';
|
|
537
|
+
callback();
|
|
538
|
+
}),
|
|
539
|
+
discoverServices: jest.fn(
|
|
540
|
+
(_serviceUuids: string[], callback: (error: Error | null, value: unknown[]) => void) =>
|
|
541
|
+
callback(null, [service])
|
|
542
|
+
),
|
|
543
|
+
}
|
|
544
|
+
);
|
|
545
|
+
const noble = Object.assign(new EventEmitter(), {
|
|
546
|
+
state: 'poweredOn',
|
|
547
|
+
startScanning: jest.fn((_services, _duplicates, callback) => {
|
|
548
|
+
callback?.();
|
|
549
|
+
noble.emit('discover', peripheral);
|
|
550
|
+
}),
|
|
551
|
+
stopScanning: jest.fn(callback => callback?.()),
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
jest.doMock('@stoprocent/noble', () => noble);
|
|
555
|
+
jest.doMock('electron', () => ({ ipcMain }));
|
|
556
|
+
jest.doMock('electron-log', () => ({
|
|
557
|
+
info: jest.fn(),
|
|
558
|
+
debug: jest.fn(),
|
|
559
|
+
error: jest.fn(),
|
|
560
|
+
}));
|
|
561
|
+
|
|
562
|
+
const { setupNobleBleHandlers } = await import('../noble-ble-handler');
|
|
563
|
+
setupNobleBleHandlers({
|
|
564
|
+
on: jest.fn(),
|
|
565
|
+
send: jest.fn(),
|
|
566
|
+
} as unknown as WebContents);
|
|
567
|
+
|
|
568
|
+
const connect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT);
|
|
569
|
+
const subscribe = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_SUBSCRIBE);
|
|
570
|
+
const disconnect = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_DISCONNECT);
|
|
571
|
+
if (!connect || !subscribe || !disconnect) {
|
|
572
|
+
throw new Error('Electron Noble BLE handlers were not registered');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
await expect(Promise.resolve(connect(undefined, peripheral.id))).resolves.toBeUndefined();
|
|
576
|
+
try {
|
|
577
|
+
await expect(Promise.resolve(subscribe(undefined, peripheral.id))).resolves.toMatchObject({
|
|
578
|
+
type: 'NobleBleIpcError',
|
|
579
|
+
success: false,
|
|
580
|
+
error: {
|
|
581
|
+
errorCode: HardwareErrorCode.BleBondInvalid,
|
|
582
|
+
params: {
|
|
583
|
+
nativeErrorMessage: 'Notification subscription failed: Encryption is insufficient',
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
} finally {
|
|
588
|
+
await disconnect(undefined, peripheral.id);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
275
592
|
test('settles a pending Noble connect with the native disconnect error', () => {
|
|
276
593
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
277
594
|
const Noble = require('@stoprocent/noble/lib/noble');
|
|
@@ -431,3 +748,203 @@ describe('Electron Noble BLE device discovery', () => {
|
|
|
431
748
|
]);
|
|
432
749
|
});
|
|
433
750
|
});
|
|
751
|
+
|
|
752
|
+
describe('Noble BLE process shutdown', () => {
|
|
753
|
+
const flushCallbacks = () =>
|
|
754
|
+
new Promise<void>(resolve => {
|
|
755
|
+
setImmediate(resolve);
|
|
756
|
+
});
|
|
757
|
+
afterEach(() => {
|
|
758
|
+
jest.useRealTimers();
|
|
759
|
+
jest.resetModules();
|
|
760
|
+
jest.clearAllMocks();
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
const setup = async (state = 'poweredOn') => {
|
|
764
|
+
const handlers = new Map<string, IpcHandler>();
|
|
765
|
+
const native = Object.assign(new EventEmitter(), {
|
|
766
|
+
state,
|
|
767
|
+
startScanning: jest.fn((_uuids, _duplicates, callback) => callback?.()),
|
|
768
|
+
stopScanning: jest.fn(callback => callback?.()),
|
|
769
|
+
stop: jest.fn(),
|
|
770
|
+
});
|
|
771
|
+
jest.doMock('@stoprocent/noble', () => native);
|
|
772
|
+
jest.doMock('electron', () => ({
|
|
773
|
+
ipcMain: {
|
|
774
|
+
handle: (channel: string, listener: IpcHandler) => handlers.set(channel, listener),
|
|
775
|
+
removeHandler: (channel: string) => handlers.delete(channel),
|
|
776
|
+
},
|
|
777
|
+
}));
|
|
778
|
+
jest.doMock(
|
|
779
|
+
'electron-log',
|
|
780
|
+
() => ({
|
|
781
|
+
info: jest.fn(),
|
|
782
|
+
debug: jest.fn(),
|
|
783
|
+
warn: jest.fn(),
|
|
784
|
+
error: jest.fn(),
|
|
785
|
+
}),
|
|
786
|
+
{ virtual: true }
|
|
787
|
+
);
|
|
788
|
+
const sdk = await import('../noble-ble-handler');
|
|
789
|
+
const window = new EventEmitter();
|
|
790
|
+
sdk.setupNobleBleHandlers(window as unknown as WebContents);
|
|
791
|
+
return { sdk, native, window, handlers };
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
test('does not initialize native BLE when quitting before first use', async () => {
|
|
795
|
+
const { sdk, native, handlers } = await setup();
|
|
796
|
+
await sdk.disposeNobleBleSupport();
|
|
797
|
+
expect(native.stop).not.toHaveBeenCalled();
|
|
798
|
+
expect(handlers.size).toBe(0);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test('cancels an active scan, ignores its late callback and releases native once', async () => {
|
|
802
|
+
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
803
|
+
const { sdk, native, handlers } = await setup();
|
|
804
|
+
let completeScan: (() => void) | undefined;
|
|
805
|
+
native.startScanning.mockImplementation((_uuids, _duplicates, callback) => {
|
|
806
|
+
completeScan = callback;
|
|
807
|
+
});
|
|
808
|
+
const scan = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE)?.({});
|
|
809
|
+
await Promise.resolve();
|
|
810
|
+
await Promise.resolve();
|
|
811
|
+
await sdk.disposeNobleBleSupport();
|
|
812
|
+
await sdk.disposeNobleBleSupport();
|
|
813
|
+
await scan;
|
|
814
|
+
completeScan?.();
|
|
815
|
+
expect(native.stop).toHaveBeenCalledTimes(1);
|
|
816
|
+
expect(native.listenerCount('discover')).toBe(0);
|
|
817
|
+
expect(native.listenerCount('stateChange')).toBe(0);
|
|
818
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test('cancels power-on waits before releasing native', async () => {
|
|
822
|
+
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
823
|
+
const { sdk, native, handlers } = await setup('unknown');
|
|
824
|
+
const availability = handlers.get(EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK)?.({});
|
|
825
|
+
await sdk.disposeNobleBleSupport();
|
|
826
|
+
await availability;
|
|
827
|
+
expect(native.stop).toHaveBeenCalledTimes(1);
|
|
828
|
+
expect(native.listenerCount('stateChange')).toBe(0);
|
|
829
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test('cancels native connects and waits for disconnect before release', async () => {
|
|
833
|
+
const { sdk, native, handlers } = await setup();
|
|
834
|
+
await handlers.get(EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK)?.({});
|
|
835
|
+
let connectCallback: ((error?: Error) => void) | undefined;
|
|
836
|
+
let disconnectCallback: (() => void) | undefined;
|
|
837
|
+
const peripheral = Object.assign(new EventEmitter(), {
|
|
838
|
+
...createPeripheral('pending-device', 'OneKey Pro'),
|
|
839
|
+
connect: jest.fn(callback => {
|
|
840
|
+
connectCallback = callback;
|
|
841
|
+
}),
|
|
842
|
+
cancelConnect: jest.fn(() => connectCallback?.(new Error('connection canceled'))),
|
|
843
|
+
disconnect: jest.fn(callback => {
|
|
844
|
+
disconnectCallback = callback;
|
|
845
|
+
}),
|
|
846
|
+
discoverServices: jest.fn(),
|
|
847
|
+
});
|
|
848
|
+
native.emit('discover', peripheral);
|
|
849
|
+
const connecting = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT)?.({}, peripheral.id);
|
|
850
|
+
await Promise.resolve();
|
|
851
|
+
await Promise.resolve();
|
|
852
|
+
expect(peripheral.connect).toHaveBeenCalledTimes(1);
|
|
853
|
+
const disposing = sdk.disposeNobleBleSupport();
|
|
854
|
+
await flushCallbacks();
|
|
855
|
+
expect(peripheral.cancelConnect).toHaveBeenCalledTimes(1);
|
|
856
|
+
expect(peripheral.disconnect).toHaveBeenCalled();
|
|
857
|
+
expect(native.stop).not.toHaveBeenCalled();
|
|
858
|
+
disconnectCallback?.();
|
|
859
|
+
await disposing;
|
|
860
|
+
expect(native.stop).toHaveBeenCalledTimes(1);
|
|
861
|
+
expect(await connecting).toMatchObject({ success: false });
|
|
862
|
+
expect(peripheral.discoverServices).not.toHaveBeenCalled();
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
test.each(['peripheral', 'direct'])(
|
|
866
|
+
'keeps late %s connects inert after the disposal timeout',
|
|
867
|
+
async route => {
|
|
868
|
+
jest.useFakeTimers({ doNotFake: ['performance', 'setImmediate'] });
|
|
869
|
+
const { sdk, native, handlers } = await setup();
|
|
870
|
+
await handlers.get(EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK)?.({});
|
|
871
|
+
let finishConnect: () => void = () => undefined;
|
|
872
|
+
const peripheral = Object.assign(new EventEmitter(), {
|
|
873
|
+
...createPeripheral('late-device', 'OneKey Pro'),
|
|
874
|
+
connect: jest.fn(callback => {
|
|
875
|
+
finishConnect = () => callback();
|
|
876
|
+
}),
|
|
877
|
+
cancelConnect: jest.fn(),
|
|
878
|
+
disconnect: jest.fn(callback => callback?.()),
|
|
879
|
+
discoverServices: jest.fn(),
|
|
880
|
+
});
|
|
881
|
+
const connectAsync = jest.fn(
|
|
882
|
+
() =>
|
|
883
|
+
new Promise(resolve => {
|
|
884
|
+
finishConnect = () => resolve(peripheral);
|
|
885
|
+
})
|
|
886
|
+
);
|
|
887
|
+
const cancelConnect = jest.fn();
|
|
888
|
+
if (route === 'direct') Object.assign(native, { connectAsync, cancelConnect });
|
|
889
|
+
else native.emit('discover', peripheral);
|
|
890
|
+
const connecting = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_CONNECT)?.({}, peripheral.id);
|
|
891
|
+
await flushCallbacks();
|
|
892
|
+
if (route === 'direct') {
|
|
893
|
+
jest.advanceTimersByTime(1500);
|
|
894
|
+
await flushCallbacks();
|
|
895
|
+
}
|
|
896
|
+
expect(route === 'direct' ? connectAsync : peripheral.connect).toHaveBeenCalledTimes(1);
|
|
897
|
+
const disposing = sdk.disposeNobleBleSupport();
|
|
898
|
+
await flushCallbacks();
|
|
899
|
+
expect(route === 'direct' ? cancelConnect : peripheral.cancelConnect).toHaveBeenCalledTimes(
|
|
900
|
+
1
|
|
901
|
+
);
|
|
902
|
+
expect(native.stop).not.toHaveBeenCalled();
|
|
903
|
+
jest.advanceTimersByTime(3500);
|
|
904
|
+
await disposing;
|
|
905
|
+
const disconnects = peripheral.disconnect.mock.calls.length;
|
|
906
|
+
peripheral.state = 'connected';
|
|
907
|
+
finishConnect();
|
|
908
|
+
await flushCallbacks();
|
|
909
|
+
expect(peripheral.disconnect).toHaveBeenCalledTimes(disconnects);
|
|
910
|
+
expect(peripheral.discoverServices).not.toHaveBeenCalled();
|
|
911
|
+
expect(native.stop).toHaveBeenCalledTimes(1);
|
|
912
|
+
expect(await connecting).toMatchObject({ success: false });
|
|
913
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
914
|
+
}
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
test('allows a host to defer shared native release until both transports are idle', async () => {
|
|
918
|
+
const { sdk, native, handlers } = await setup();
|
|
919
|
+
await handlers.get(EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK)?.({});
|
|
920
|
+
const releaseNoble = jest.fn();
|
|
921
|
+
await sdk.disposeNobleBleSupport(releaseNoble);
|
|
922
|
+
expect(releaseNoble).toHaveBeenCalledWith(native);
|
|
923
|
+
expect(native.stop).not.toHaveBeenCalled();
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
test('replacement window scanning waits for old cleanup and preserves native', async () => {
|
|
927
|
+
jest.useFakeTimers({ doNotFake: ['performance', 'setImmediate'] });
|
|
928
|
+
const { sdk, native, handlers, window } = await setup();
|
|
929
|
+
await handlers.get(EOneKeyBleMessageKeys.BLE_AVAILABILITY_CHECK)?.({});
|
|
930
|
+
let finishOldCleanup: (() => void) | undefined;
|
|
931
|
+
native.stopScanning.mockImplementationOnce(callback => {
|
|
932
|
+
finishOldCleanup = callback;
|
|
933
|
+
});
|
|
934
|
+
window.emit('destroyed');
|
|
935
|
+
sdk.setupNobleBleHandlers(new EventEmitter() as unknown as WebContents);
|
|
936
|
+
const scan = handlers.get(EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE)?.({});
|
|
937
|
+
await flushCallbacks();
|
|
938
|
+
expect(native.startScanning).not.toHaveBeenCalled();
|
|
939
|
+
finishOldCleanup?.();
|
|
940
|
+
await flushCallbacks();
|
|
941
|
+
expect(native.startScanning).toHaveBeenCalledTimes(1);
|
|
942
|
+
expect(native.listenerCount('discover')).toBe(1);
|
|
943
|
+
native.emit('discover', createPeripheral('replacement-device', 'OneKey Pro'));
|
|
944
|
+
jest.advanceTimersByTime(5000);
|
|
945
|
+
expect(await scan).toEqual([expect.objectContaining({ id: 'replacement-device' })]);
|
|
946
|
+
expect(native.stop).not.toHaveBeenCalled();
|
|
947
|
+
await sdk.disposeNobleBleSupport();
|
|
948
|
+
expect(native.stop).toHaveBeenCalledTimes(1);
|
|
949
|
+
});
|
|
950
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -10,8 +10,15 @@ export type {
|
|
|
10
10
|
NobleBleIpcErrorResponse,
|
|
11
11
|
NobleBleWriteOptions,
|
|
12
12
|
} from './types/desktop-api';
|
|
13
|
+
export { invokeNobleBleIpc, isNobleBleIpcErrorResponse } from './types/desktop-api';
|
|
13
14
|
|
|
14
15
|
export async function initNobleBleSupport(webContents: WebContents) {
|
|
15
16
|
const { setupNobleBleHandlers } = await import('./noble-ble-handler');
|
|
16
17
|
setupNobleBleHandlers(webContents);
|
|
17
18
|
}
|
|
19
|
+
|
|
20
|
+
/** Terminal process cleanup. A host sharing Noble can defer native stop until all users are idle. */
|
|
21
|
+
export async function disposeNobleBleSupport(releaseNoble?: (noble: { stop(): void }) => void) {
|
|
22
|
+
const handler = await import('./noble-ble-handler');
|
|
23
|
+
await handler.disposeNobleBleSupport(releaseNoble);
|
|
24
|
+
}
|