@onekeyfe/hd-core 1.2.2-alpha.112 → 1.2.2-alpha.114

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.
@@ -4,6 +4,8 @@ import { initConnector, initCore } from '../src/core';
4
4
  import { DataManager } from '../src/data-manager';
5
5
  import TransportManager from '../src/data-manager/TransportManager';
6
6
  import { IFRAME } from '../src/events';
7
+ import SearchDevices from '../src/api/SearchDevices';
8
+ import { DeviceList } from '../src/device/DeviceList';
7
9
 
8
10
  jest.mock('../src/data/config', () => ({
9
11
  getSDKVersion: jest.fn(() => '1.0.0-test'),
@@ -24,6 +26,81 @@ describe('Core 错误输出边界', () => {
24
26
  jest.restoreAllMocks();
25
27
  });
26
28
 
29
+ test.each([
30
+ ['webusb', false],
31
+ ['desktop-webusb', false],
32
+ ['desktop-webusb', true],
33
+ ] as const)(
34
+ '%s waits for discovery without masking initialization or cancellation (cancel=%s)',
35
+ async (env, shouldCancel) => {
36
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
37
+ const error = ERRORS.TypedError(HardwareErrorCode.DeviceInitializeFailed, 'probe failed');
38
+ let finishSearch!: () => void;
39
+ let searchStarted!: () => void;
40
+ const started = new Promise<void>(resolve => {
41
+ searchStarted = resolve;
42
+ });
43
+ const search = jest.spyOn(SearchDevices.prototype, 'run').mockImplementation(async () => {
44
+ searchStarted();
45
+ await new Promise<void>(resolve => {
46
+ finishSearch = resolve;
47
+ });
48
+ return [];
49
+ });
50
+ const initialize = jest
51
+ .spyOn(DeviceList.prototype, 'getDeviceLists')
52
+ .mockRejectedValue(error);
53
+ const core = initCore();
54
+ initConnector();
55
+ try {
56
+ const discovery = core.handleMessage({
57
+ id: 10,
58
+ type: IFRAME.CALL,
59
+ payload: { method: 'searchDevices' },
60
+ } as never);
61
+ await started;
62
+ const request = core.handleMessage({
63
+ id: 11,
64
+ type: IFRAME.CALL,
65
+ payload: {
66
+ method: 'getDeviceState',
67
+ connectId: 'serial-V2',
68
+ retryCount: 1,
69
+ pollIntervalTime: 1,
70
+ timeout: 1000,
71
+ },
72
+ } as never);
73
+ await new Promise(resolve => {
74
+ setTimeout(resolve, 0);
75
+ });
76
+ expect(initialize).not.toHaveBeenCalled();
77
+ if (shouldCancel) {
78
+ await core.handleMessage({
79
+ type: IFRAME.CANCEL,
80
+ payload: { connectId: 'serial-V2' },
81
+ } as never);
82
+ }
83
+ finishSearch();
84
+ await expect(discovery).resolves.toMatchObject({ success: true });
85
+ const expectedError = shouldCancel
86
+ ? ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled)
87
+ : error;
88
+ await expect(request).resolves.toMatchObject({
89
+ success: false,
90
+ payload: { code: expectedError.errorCode, error: expectedError.message },
91
+ });
92
+ await new Promise(resolve => {
93
+ setTimeout(resolve, 0);
94
+ });
95
+ expect(search).toHaveBeenCalledTimes(1);
96
+ expect(initialize).toHaveBeenCalledTimes(shouldCancel ? 0 : 2);
97
+ } finally {
98
+ finishSearch?.();
99
+ await core.dispose();
100
+ }
101
+ }
102
+ );
103
+
27
104
  test.each([
28
105
  HardwareErrorCode.BleDeviceNotBonded,
29
106
  HardwareErrorCode.BleDeviceBondedCanceled,
@@ -362,12 +362,12 @@ describe('public device lifecycle events', () => {
362
362
  }
363
363
  );
364
364
 
365
- test('keeps the cleanup barrier when its deadline expires', async () => {
365
+ test('clears the cleanup barrier when its deadline expires', async () => {
366
366
  const realSetTimeout = setTimeout;
367
367
  jest
368
368
  .spyOn(global, 'setTimeout')
369
369
  .mockImplementation((callback, delay, ...args) =>
370
- realSetTimeout(callback, delay === 15_000 ? 0 : delay, ...args)
370
+ realSetTimeout(callback, delay === 5_000 ? 0 : delay, ...args)
371
371
  );
372
372
  {
373
373
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
@@ -382,13 +382,13 @@ describe('public device lifecycle events', () => {
382
382
  type: IFRAME.CALL,
383
383
  payload: { method: 'getDeviceState', connectId: 'draining-device', connectProtocol: 'V2' },
384
384
  } as CoreMessage);
385
- await expect(result).resolves.toMatchObject({
386
- success: false,
387
- payload: { code: HardwareErrorCode.DeviceBusy },
388
- });
389
- expect(acquire).not.toHaveBeenCalled();
390
- expect(context.getPrePendingCallPromise('draining-device')).toBe(gate.promise);
385
+ setImmediate(() => cancel(context, 'draining-device'));
386
+ await expect(result).resolves.toBeDefined();
391
387
  gate.resolve();
388
+ await new Promise(resolve => {
389
+ setImmediate(resolve);
390
+ });
391
+ expect(context.getPrePendingCallPromise('draining-device')).toBeUndefined();
392
392
  }
393
393
  });
394
394
 
@@ -4,6 +4,7 @@ jest.mock('../src/data-manager/TransportManager', () => ({
4
4
  __esModule: true,
5
5
  default: {
6
6
  configure: jest.fn(),
7
+ ensureInitialized: jest.fn(),
7
8
  },
8
9
  }));
9
10
 
@@ -17,29 +18,151 @@ jest.mock('../src/data-manager', () => ({
17
18
  jest.mock('../src/device/DevicePool', () => ({
18
19
  DevicePool: {
19
20
  getDevices: jest.fn(),
21
+ getDeviceByPath: jest.fn(),
20
22
  },
21
23
  }));
22
24
 
23
- const transportManagerMock: { default: { configure: jest.Mock } } = jest.requireMock(
24
- '../src/data-manager/TransportManager'
25
- );
26
- const devicePoolMock: { DevicePool: { getDevices: jest.Mock } } = jest.requireMock(
27
- '../src/device/DevicePool'
28
- );
25
+ const transportManagerMock: {
26
+ default: { configure: jest.Mock; ensureInitialized: jest.Mock };
27
+ } = jest.requireMock('../src/data-manager/TransportManager');
28
+ const devicePoolMock: {
29
+ DevicePool: { getDevices: jest.Mock; getDeviceByPath: jest.Mock };
30
+ } = jest.requireMock('../src/device/DevicePool');
29
31
  const dataManagerMock: {
30
32
  DataManager: {
31
33
  getSettings: jest.Mock;
32
34
  isBleConnect: jest.Mock;
33
35
  };
34
36
  } = jest.requireMock('../src/data-manager');
35
- const { configure: mockConfigureTransport } = transportManagerMock.default;
36
- const { getDevices: mockGetDevices } = devicePoolMock.DevicePool;
37
+ const { configure: mockConfigureTransport, ensureInitialized: mockEnsureInitialized } =
38
+ transportManagerMock.default;
39
+ const { getDevices: mockGetDevices, getDeviceByPath: mockGetDeviceByPath } =
40
+ devicePoolMock.DevicePool;
37
41
  const { isBleConnect: mockIsBleConnect } = dataManagerMock.DataManager;
38
42
 
39
43
  describe('SearchDevices', () => {
40
44
  beforeEach(() => {
41
45
  jest.clearAllMocks();
42
46
  mockIsBleConnect.mockReturnValue(false);
47
+ dataManagerMock.DataManager.getSettings.mockReturnValue('webusb');
48
+ });
49
+
50
+ test.each(['webusb', 'desktop-webusb'])(
51
+ '%s discovery leaves active V1/V2 requests and their schemas untouched',
52
+ async env => {
53
+ dataManagerMock.DataManager.getSettings.mockReturnValue(env);
54
+ const devices = ['V1', 'V2'].map(protocol => ({
55
+ features: { protocol },
56
+ toMessageObject: () => ({ connectId: `serial-${protocol}` }),
57
+ }));
58
+ mockGetDeviceByPath.mockImplementation(
59
+ (path: string) => devices[['usb-V1', 'usb-V2'].indexOf(path)]
60
+ );
61
+ const extraDevice = {
62
+ toMessageObject: () => ({ connectId: 'not-initialized' }),
63
+ };
64
+ mockGetDevices.mockResolvedValue({
65
+ devices: { 'not-initialized': extraDevice },
66
+ deviceList: [extraDevice],
67
+ });
68
+ const method = new SearchDevices({
69
+ id: 1,
70
+ payload: { method: 'searchDevices' },
71
+ } as never);
72
+ method.init();
73
+ method.context = {
74
+ requestQueue: {
75
+ getRequestTasksId: () => [2],
76
+ getRequestTasksIdByConnectId: (connectId: string) =>
77
+ connectId === 'usb-V1' || connectId === 'usb-V2' ? [2] : [],
78
+ },
79
+ } as never;
80
+ method.connector = {
81
+ enumerate: jest.fn().mockResolvedValue({
82
+ descriptors: [
83
+ { path: 'usb-V1', commType: 'webusb' },
84
+ { path: 'usb-V2', commType: 'webusb' },
85
+ { path: 'not-initialized', commType: 'webusb' },
86
+ ],
87
+ }),
88
+ } as never;
89
+
90
+ await expect(method.run()).resolves.toEqual([
91
+ { connectId: 'serial-V1' },
92
+ { connectId: 'serial-V2' },
93
+ { connectId: 'not-initialized' },
94
+ ]);
95
+ expect(mockGetDevices).toHaveBeenCalledTimes(1);
96
+ expect(mockGetDevices).toHaveBeenCalledWith(
97
+ [{ path: 'not-initialized', commType: 'webusb' }],
98
+ 'not-initialized',
99
+ {
100
+ connectProtocol: undefined,
101
+ forceProtocolDetection: true,
102
+ refreshRuntimeState: true,
103
+ }
104
+ );
105
+ expect(mockConfigureTransport).not.toHaveBeenCalled();
106
+ }
107
+ );
108
+
109
+ test('owned WebUSB cache miss still reports a SearchDevice without probing', async () => {
110
+ mockGetDeviceByPath.mockReturnValue(undefined);
111
+ const usbDevice = { vendorId: 0x1209, productId: 0x4f4c };
112
+ const method = new SearchDevices({
113
+ id: 1,
114
+ payload: { method: 'searchDevices' },
115
+ } as never);
116
+ method.init();
117
+ method.context = {
118
+ requestQueue: {
119
+ getRequestTasksId: () => [2],
120
+ getRequestTasksIdByConnectId: (connectId: string) => (connectId === 'serial-V2' ? [2] : []),
121
+ },
122
+ } as never;
123
+ method.connector = {
124
+ enumerate: jest.fn().mockResolvedValue({
125
+ descriptors: [
126
+ {
127
+ path: 'serial-V2',
128
+ device: usbDevice,
129
+ commType: 'webusb',
130
+ },
131
+ ],
132
+ }),
133
+ } as never;
134
+
135
+ await expect(method.run()).resolves.toEqual([
136
+ {
137
+ connectId: 'serial-V2',
138
+ uuid: 'serial-V2',
139
+ serialNo: 'serial-V2',
140
+ deviceId: null,
141
+ deviceType: 'unknown',
142
+ name: 'serial-V2',
143
+ commType: 'webusb',
144
+ },
145
+ ]);
146
+ expect(mockGetDevices).not.toHaveBeenCalled();
147
+ expect(mockConfigureTransport).not.toHaveBeenCalled();
148
+ });
149
+
150
+ test('searchDevices resolves empty when WebUSB bring-up is unavailable', async () => {
151
+ mockEnsureInitialized.mockRejectedValueOnce(
152
+ new Error('WebUSB is not supported by current browsers')
153
+ );
154
+ const method = new SearchDevices({
155
+ id: 1,
156
+ payload: { method: 'searchDevices' },
157
+ } as never);
158
+ method.init();
159
+ method.connector = {
160
+ enumerate: jest.fn().mockResolvedValue({ descriptors: [] }),
161
+ } as never;
162
+
163
+ await expect(method.run()).resolves.toEqual([]);
164
+ expect(mockEnsureInitialized).toHaveBeenCalled();
165
+ expect(mockConfigureTransport).toHaveBeenCalledTimes(1);
43
166
  });
44
167
 
45
168
  test('搜索忽略调用方协议并主动探测,单个无响应设备不阻断后续结果', async () => {
@@ -1,22 +1,9 @@
1
1
  import { BaseMethod } from './BaseMethod';
2
+ import type { SearchDevice } from '../types/device';
2
3
  import type DeviceConnector from '../device/DeviceConnector';
3
4
  export default class SearchDevices extends BaseMethod {
4
5
  connector?: DeviceConnector;
5
6
  init(): void;
6
- run(): Promise<(import("..").KnownDevice | null)[] | {
7
- connectId: string;
8
- serialNo: null;
9
- uuid: string;
10
- deviceId: null;
11
- name: string | null;
12
- deviceType: import("..").IDeviceType;
13
- commType: import("packages/hd-transport/dist").OneKeyDeviceCommType;
14
- path: string;
15
- session?: string | null | undefined;
16
- debugSession?: string | null | undefined;
17
- debug: boolean;
18
- id: string;
19
- protocolType?: import("packages/hd-transport/dist").ProtocolType | undefined;
20
- }[]>;
7
+ run(): Promise<SearchDevice[]>;
21
8
  }
22
9
  //# sourceMappingURL=SearchDevices.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SearchDevices.d.ts","sourceRoot":"","sources":["../../src/api/SearchDevices.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM1C,OAAO,KAAK,eAAe,MAAM,2BAA2B,CAAC;AAI7D,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,UAAU;IACnD,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B,IAAI;IAME,GAAG;;;;;;;;;;;;;;;CA+DV"}
1
+ {"version":3,"file":"SearchDevices.d.ts","sourceRoot":"","sources":["../../src/api/SearchDevices.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,eAAe,MAAM,2BAA2B,CAAC;AAuC7D,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,UAAU;IACnD,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B,IAAI;IAME,GAAG,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;CAsFrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":";AACA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAClC,OAAO,EAEL,KAAK,6BAA6B,EAElC,KAAK,YAAY,EAIlB,MAAM,wBAAwB,CAAC;AAiChC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAsB1C,OAAO,eAAe,MAAM,2BAA2B,CAAC;AAYxD,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,UAAU,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,WAAW,CAAC;AAI9F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAYpD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;AA0E7D,eAAO,MAAM,OAAO,YAAmB,WAAW,WAAW,WAAW,iBAoFvE,CAAC;AAyFF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3F;AAomBD,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAWpF;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAoB/E;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAQlF;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,WAEzD;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,WAI3D;AAiKD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,CAMtF;AA+LD,eAAO,MAAM,MAAM,YAAa,WAAW,cAAc,MAAM,SA4G9D,CAAC;AA8GF,eAAO,MAAM,qBAAqB,gFAejC,CAAC;AAiLF,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,YAAY;IAC5C,OAAO,CAAC,cAAc,CAAoB;IAE1C,SAAgB,aAAa,EAAE,MAAM,CAAC;IAEtC,OAAO,CAAC,YAAY,CAAsB;IAE1C,OAAO,CAAC,cAAc,CAAC,CAAgB;IAGvC,OAAO,CAAC,sBAAsB,CAAoC;IAElE,OAAO,CAAC,iBAAiB,CAAoB;;IAS7C,OAAO,CAAC,cAAc;IAuChB,aAAa,CAAC,OAAO,EAAE,WAAW;IAuExC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAOV,gBAAgB;CAiC/B;AAED,eAAO,MAAM,QAAQ,YAIpB,CAAC;AAEF,eAAO,MAAM,aAAa,uBAYzB,CAAC;AAMF,eAAO,MAAM,IAAI,aACL,eAAe,aACd,GAAG,WACL,6BAA6B,8BAiBvC,CAAC;AAEF,eAAO,MAAM,eAAe;SAKrB,eAAe,CAAC,KAAK,CAAC;eAChB,GAAG;;UASf,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":";AACA,OAAO,YAAY,MAAM,QAAQ,CAAC;AAClC,OAAO,EAEL,KAAK,6BAA6B,EAElC,KAAK,YAAY,EAIlB,MAAM,wBAAwB,CAAC;AAiChC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAsB1C,OAAO,eAAe,MAAM,2BAA2B,CAAC;AAYxD,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,UAAU,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAmD,MAAM,WAAW,CAAC;AAI9F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAYpD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;AA0E7D,eAAO,MAAM,OAAO,YAAmB,WAAW,WAAW,WAAW,iBA0FvE,CAAC;AA2FF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3F;AAunBD,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAWpF;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAoB/E;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,WAQlF;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,WAEzD;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,WAI3D;AAiKD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,CAMtF;AA2MD,eAAO,MAAM,MAAM,YAAa,WAAW,cAAc,MAAM,SA4G9D,CAAC;AA8GF,eAAO,MAAM,qBAAqB,gFAejC,CAAC;AAiLF,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,YAAY;IAC5C,OAAO,CAAC,cAAc,CAAoB;IAE1C,SAAgB,aAAa,EAAE,MAAM,CAAC;IAEtC,OAAO,CAAC,YAAY,CAAsB;IAE1C,OAAO,CAAC,cAAc,CAAC,CAAgB;IAGvC,OAAO,CAAC,sBAAsB,CAAoC;IAElE,OAAO,CAAC,iBAAiB,CAAoB;;IAS7C,OAAO,CAAC,cAAc;IAuChB,aAAa,CAAC,OAAO,EAAE,WAAW;IAuExC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAOV,gBAAgB;CAiC/B;AAED,eAAO,MAAM,QAAQ,YAIpB,CAAC;AAEF,eAAO,MAAM,aAAa,uBAYzB,CAAC;AAMF,eAAO,MAAM,IAAI,aACL,eAAe,aACd,GAAG,WACL,6BAA6B,8BAiBvC,CAAC;AAEF,eAAO,MAAM,eAAe;SAKrB,eAAe,CAAC,KAAK,CAAC;eAChB,GAAG;;UASf,CAAC"}
@@ -6,9 +6,11 @@ export default class TransportManager {
6
6
  static defaultMessages: JSON | Record<string, any>;
7
7
  static currentMessages: JSON | Record<string, any>;
8
8
  static reactNativeInit: boolean;
9
+ static webUsbInit: boolean;
9
10
  static protocolV1MessageSchema: ProtocolV1MessageSchema;
10
11
  static plugin: LowlevelTransportSharedPlugin | null;
11
12
  static load(): void;
13
+ static ensureInitialized(): Promise<void>;
12
14
  static configure(): Promise<void>;
13
15
  static reconfigure(features?: Features): Promise<void>;
14
16
  static setTransport(TransportConstructor: any, plugin?: LowlevelTransportSharedPlugin): void;
@@ -1 +1 @@
1
- {"version":3,"file":"TransportManager.d.ts","sourceRoot":"","sources":["../../src/data-manager/TransportManager.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,6BAA6B,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAgBzC,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC;IAE5B,MAAM,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEnD,MAAM,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEnD,MAAM,CAAC,eAAe,UAAS;IAE/B,MAAM,CAAC,uBAAuB,EAAE,uBAAuB,CAAqB;IAE5E,MAAM,CAAC,MAAM,EAAE,6BAA6B,GAAG,IAAI,CAAQ;IAE3D,MAAM,CAAC,IAAI;WAOE,SAAS;WAqDT,WAAW,CAAC,QAAQ,CAAC,EAAE,QAAQ;IAyB5C,MAAM,CAAC,YAAY,CAAC,oBAAoB,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,6BAA6B;IA6BrF,MAAM,CAAC,YAAY;mBAIE,2BAA2B;IAShD,MAAM,CAAC,kBAAkB;IAIzB,MAAM,CAAC,kBAAkB;IAIzB,MAAM,CAAC,0BAA0B;CAGlC"}
1
+ {"version":3,"file":"TransportManager.d.ts","sourceRoot":"","sources":["../../src/data-manager/TransportManager.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,6BAA6B,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAgBzC,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC;IAE5B,MAAM,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEnD,MAAM,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEnD,MAAM,CAAC,eAAe,UAAS;IAE/B,MAAM,CAAC,UAAU,UAAS;IAE1B,MAAM,CAAC,uBAAuB,EAAE,uBAAuB,CAAqB;IAE5E,MAAM,CAAC,MAAM,EAAE,6BAA6B,GAAG,IAAI,CAAQ;IAE3D,MAAM,CAAC,IAAI;WAQE,iBAAiB;WAUjB,SAAS;WAmDT,WAAW,CAAC,QAAQ,CAAC,EAAE,QAAQ;IAyB5C,MAAM,CAAC,YAAY,CAAC,oBAAoB,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,6BAA6B;IA6BrF,MAAM,CAAC,YAAY;mBAIE,2BAA2B;IAShD,MAAM,CAAC,kBAAkB;IAIzB,MAAM,CAAC,kBAAkB;IAIzB,MAAM,CAAC,0BAA0B;CAGlC"}
package/dist/index.js CHANGED
@@ -43705,6 +43705,18 @@ class TransportManager {
43705
43705
  this.defaultMessages = DataManager.getProtobufMessages();
43706
43706
  this.currentMessages = this.defaultMessages;
43707
43707
  this.protocolV1MessageSchema = 'v1CurrentSchema';
43708
+ this.webUsbInit = false;
43709
+ }
43710
+ static ensureInitialized() {
43711
+ return __awaiter(this, void 0, void 0, function* () {
43712
+ const env = DataManager.getSettings('env');
43713
+ if (env !== 'webusb' && env !== 'desktop-webusb')
43714
+ return;
43715
+ if (this.webUsbInit)
43716
+ return;
43717
+ yield this.transport.init(WebUsbLogger, DevicePool.emitter);
43718
+ this.webUsbInit = true;
43719
+ });
43708
43720
  }
43709
43721
  static configure() {
43710
43722
  return __awaiter(this, void 0, void 0, function* () {
@@ -43733,7 +43745,7 @@ class TransportManager {
43733
43745
  yield this.transport.init(WebBleLogger, DevicePool.emitter);
43734
43746
  }
43735
43747
  else if (env === 'webusb' || env === 'desktop-webusb') {
43736
- yield this.transport.init(WebUsbLogger, DevicePool.emitter);
43748
+ yield this.ensureInitialized();
43737
43749
  }
43738
43750
  else {
43739
43751
  yield this.transport.init(HttpLogger);
@@ -43816,6 +43828,7 @@ class TransportManager {
43816
43828
  }
43817
43829
  }
43818
43830
  TransportManager.reactNativeInit = false;
43831
+ TransportManager.webUsbInit = false;
43819
43832
  TransportManager.protocolV1MessageSchema = 'v1CurrentSchema';
43820
43833
  TransportManager.plugin = null;
43821
43834
 
@@ -47263,6 +47276,28 @@ class PreInitialize extends BaseMethod {
47263
47276
  }
47264
47277
 
47265
47278
  const Log$e = getLogger(exports.LoggerNames.DevicePool);
47279
+ const isOwnedByActiveWebUsbRequest = (descriptor, requestQueue) => {
47280
+ if (!requestQueue)
47281
+ return false;
47282
+ const keys = [descriptor.path, descriptor.id].filter((key) => typeof key === 'string' && key.length > 0);
47283
+ return keys.some(key => requestQueue.getRequestTasksIdByConnectId(key).length > 0);
47284
+ };
47285
+ const toSearchDeviceFromDescriptor = (descriptor) => {
47286
+ const connectId = descriptor.path || descriptor.id || null;
47287
+ return {
47288
+ connectId,
47289
+ uuid: connectId !== null && connectId !== void 0 ? connectId : '',
47290
+ serialNo: connectId,
47291
+ deviceId: null,
47292
+ deviceType: hdShared.EDeviceType.Unknown,
47293
+ name: descriptor.name || connectId || '',
47294
+ commType: descriptor.commType,
47295
+ };
47296
+ };
47297
+ const toSearchDevice = (device) => {
47298
+ const message = device.toMessageObject();
47299
+ return message ? message : null;
47300
+ };
47266
47301
  class SearchDevices extends BaseMethod {
47267
47302
  init() {
47268
47303
  this.useDevice = false;
@@ -47270,20 +47305,32 @@ class SearchDevices extends BaseMethod {
47270
47305
  this.skipForceUpdateCheck = true;
47271
47306
  }
47272
47307
  run() {
47273
- var _a, _b, _c, _d, _e;
47308
+ var _a, _b, _c, _d, _e, _f, _g;
47274
47309
  return __awaiter(this, void 0, void 0, function* () {
47275
- yield TransportManager.configure();
47276
- const deviceDiff = yield ((_a = this.connector) === null || _a === void 0 ? void 0 : _a.enumerate());
47277
- const devicesDescriptor = (_b = deviceDiff === null || deviceDiff === void 0 ? void 0 : deviceDiff.descriptors) !== null && _b !== void 0 ? _b : [];
47278
47310
  const env = DataManager.getSettings('env');
47311
+ const isWebUsb = env === 'webusb' || env === 'desktop-webusb';
47312
+ const requestQueue = (_a = this.context) === null || _a === void 0 ? void 0 : _a.requestQueue;
47313
+ const hasActiveWebUsbRequest = isWebUsb && ((_b = requestQueue === null || requestQueue === void 0 ? void 0 : requestQueue.getRequestTasksId().length) !== null && _b !== void 0 ? _b : 0) > 0;
47314
+ if (isWebUsb) {
47315
+ try {
47316
+ yield TransportManager.ensureInitialized();
47317
+ }
47318
+ catch (error) {
47319
+ Log$e.debug('WebUSB bring-up unavailable', error);
47320
+ }
47321
+ }
47322
+ if (!hasActiveWebUsbRequest)
47323
+ yield TransportManager.configure();
47324
+ const deviceDiff = yield ((_c = this.connector) === null || _c === void 0 ? void 0 : _c.enumerate());
47325
+ const devicesDescriptor = (_d = deviceDiff === null || deviceDiff === void 0 ? void 0 : deviceDiff.descriptors) !== null && _d !== void 0 ? _d : [];
47279
47326
  if (DataManager.isBleConnect(env)) {
47280
47327
  const devices = [];
47281
47328
  const seenIds = new Set();
47282
47329
  for (const device of devicesDescriptor) {
47283
- const lowerId = (_c = device.id) === null || _c === void 0 ? void 0 : _c.toLowerCase();
47330
+ const lowerId = (_e = device.id) === null || _e === void 0 ? void 0 : _e.toLowerCase();
47284
47331
  if (!seenIds.has(lowerId)) {
47285
47332
  seenIds.add(lowerId);
47286
- const rawBleName = (_e = (_d = device.name) !== null && _d !== void 0 ? _d : device.localName) !== null && _e !== void 0 ? _e : '';
47333
+ const rawBleName = (_g = (_f = device.name) !== null && _f !== void 0 ? _f : device.localName) !== null && _g !== void 0 ? _g : '';
47287
47334
  const bleName = hdShared.canonicalizePro2BleAdvertisementName(rawBleName);
47288
47335
  devices.push(Object.assign(Object.assign({}, device), { connectId: device.id, serialNo: null, uuid: '', deviceId: null, name: bleName || device.name, deviceType: getDeviceTypeByBleName(bleName) }));
47289
47336
  }
@@ -47292,22 +47339,33 @@ class SearchDevices extends BaseMethod {
47292
47339
  }
47293
47340
  const deviceList = [];
47294
47341
  for (const descriptor of devicesDescriptor) {
47295
- try {
47296
- const result = yield DevicePool.getDevices([descriptor], descriptor.path, {
47297
- connectProtocol: undefined,
47298
- forceProtocolDetection: true,
47299
- refreshRuntimeState: true,
47300
- });
47301
- deviceList.push(...result.deviceList);
47342
+ if (hasActiveWebUsbRequest && isOwnedByActiveWebUsbRequest(descriptor, requestQueue)) {
47343
+ const cached = DevicePool.getDeviceByPath(descriptor.path);
47344
+ const message = (cached === null || cached === void 0 ? void 0 : cached.features) ? toSearchDevice(cached) : null;
47345
+ deviceList.push(message !== null && message !== void 0 ? message : toSearchDeviceFromDescriptor(descriptor));
47302
47346
  }
47303
- catch (error) {
47304
- const errorCode = error && typeof error === 'object' && 'errorCode' in error
47305
- ? error.errorCode
47306
- : undefined;
47307
- Log$e.debug('Skip unavailable device during search', Object.assign({ path: descriptor.path }, (errorCode !== undefined ? { errorCode } : {})));
47347
+ else {
47348
+ try {
47349
+ const result = yield DevicePool.getDevices([descriptor], descriptor.path, {
47350
+ connectProtocol: undefined,
47351
+ forceProtocolDetection: true,
47352
+ refreshRuntimeState: true,
47353
+ });
47354
+ for (const device of result.deviceList) {
47355
+ const message = toSearchDevice(device);
47356
+ if (message)
47357
+ deviceList.push(message);
47358
+ }
47359
+ }
47360
+ catch (error) {
47361
+ const errorCode = error && typeof error === 'object' && 'errorCode' in error
47362
+ ? error.errorCode
47363
+ : undefined;
47364
+ Log$e.debug('Skip unavailable device during search', Object.assign({ path: descriptor.path }, (errorCode !== undefined ? { errorCode } : {})));
47365
+ }
47308
47366
  }
47309
47367
  }
47310
- return deviceList.map(device => device.toMessageObject());
47368
+ return deviceList;
47311
47369
  });
47312
47370
  }
47313
47371
  }
@@ -65850,7 +65908,7 @@ const createUiProgressMessageFilter = (intervalMs = DEFAULT_UI_PROGRESS_INTERVAL
65850
65908
 
65851
65909
  const Log = getLogger(exports.LoggerNames.Core);
65852
65910
  const PRE_INITIALIZE_TTL_MS = 60 * 1000;
65853
- const PRE_PENDING_CALL_TIMEOUT_MS = 15 * 1000;
65911
+ const PRE_PENDING_CALL_TIMEOUT_MS = 5 * 1000;
65854
65912
  const PRO2_USB_SIGNING_COOLDOWN_MS = 1000;
65855
65913
  const preWarmInflight = new Map();
65856
65914
  const preWarmDoneAt = new Map();
@@ -65930,6 +65988,7 @@ const callAPI = (context, message) => __awaiter(void 0, void 0, void 0, function
65930
65988
  }
65931
65989
  };
65932
65990
  (_a = method.setContext) === null || _a === void 0 ? void 0 : _a.call(method, context);
65991
+ method.context = context;
65933
65992
  method.requestContext = createRequestContext(method.responseID, method.name, {
65934
65993
  sdkInstanceId: context.sdkInstanceId,
65935
65994
  connectId: method.connectId,
@@ -65947,7 +66006,11 @@ const callAPI = (context, message) => __awaiter(void 0, void 0, void 0, function
65947
66006
  if (!method.useDevice) {
65948
66007
  updateMethodRequestContext(method, { status: 'running' });
65949
66008
  try {
65950
- const response = yield method.run();
66009
+ const env = DataManager.getSettings('env');
66010
+ const response = method.name === 'searchDevices' &&
66011
+ (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env))
66012
+ ? yield context.methodSynchronize(() => method.run(), 'webusb-discovery')
66013
+ : yield method.run();
65951
66014
  completeMethodRequestContext(method);
65952
66015
  return createResponseMessage(method.responseID, true, response);
65953
66016
  }
@@ -66018,20 +66081,28 @@ const waitForPendingPromise = (connectId, getPrePendingCallPromise, removePrePen
66018
66081
  if (pendingPromise) {
66019
66082
  Log.debug('pre pending call promise before call method, wait for it');
66020
66083
  let timer;
66084
+ let timedOut = false;
66085
+ let completed = false;
66021
66086
  try {
66022
66087
  yield Promise.race([
66023
66088
  pendingPromise,
66024
- new Promise((_, reject) => {
66089
+ new Promise(resolve => {
66025
66090
  timer = setTimeout(() => {
66026
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceBusy, 'Previous device cancellation is still draining'));
66091
+ timedOut = true;
66092
+ resolve();
66027
66093
  }, PRE_PENDING_CALL_TIMEOUT_MS);
66028
66094
  }),
66029
66095
  ]);
66030
- removePrePendingCallPromise === null || removePrePendingCallPromise === void 0 ? void 0 : removePrePendingCallPromise(connectId, pendingPromise);
66096
+ completed = !timedOut;
66031
66097
  }
66032
66098
  finally {
66033
66099
  if (timer)
66034
66100
  clearTimeout(timer);
66101
+ if (timedOut || completed)
66102
+ removePrePendingCallPromise === null || removePrePendingCallPromise === void 0 ? void 0 : removePrePendingCallPromise(connectId, pendingPromise);
66103
+ }
66104
+ if (timedOut) {
66105
+ Log.warn('pre pending call promise timed out before call method', { connectId });
66035
66106
  }
66036
66107
  Log.debug('pre pending call promise before call method done');
66037
66108
  }
@@ -66079,7 +66150,22 @@ const onCallDevice = (context, message, method) => __awaiter(void 0, void 0, voi
66079
66150
  context.registerCallbackTask(method.connectId, preWarmCallbackTask);
66080
66151
  }
66081
66152
  const pollingId = pollingManager.start(connectId);
66082
- device = yield ensureConnected(context, method, connectId, pollingId, method.abortSignal);
66153
+ const env = DataManager.getSettings('env');
66154
+ const connect = () => ensureConnected(context, method, connectId, pollingId, method.abortSignal);
66155
+ if (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env)) {
66156
+ const connectPromise = context.methodSynchronize(() => __awaiter(void 0, void 0, void 0, function* () {
66157
+ var _k;
66158
+ if ((_k = method.abortSignal) === null || _k === void 0 ? void 0 : _k.aborted) {
66159
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallQueueActionCancelled);
66160
+ }
66161
+ return connect();
66162
+ }), 'webusb-discovery');
66163
+ connectPromise.catch(() => undefined);
66164
+ device = yield requestQueue.waitForTask(task, () => connectPromise);
66165
+ }
66166
+ else {
66167
+ device = yield connect();
66168
+ }
66083
66169
  if ((_e = method.abortSignal) === null || _e === void 0 ? void 0 : _e.aborted) {
66084
66170
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.CallQueueActionCancelled);
66085
66171
  }
@@ -66144,12 +66230,12 @@ const onCallDevice = (context, message, method) => __awaiter(void 0, void 0, voi
66144
66230
  return waitForPendingPromise((_a = method.connectId) !== null && _a !== void 0 ? _a : '', getPrePendingCallPromise, removePrePendingCallPromise);
66145
66231
  });
66146
66232
  const inner = () => __awaiter(void 0, void 0, void 0, function* () {
66147
- var _k, _l;
66233
+ var _l, _m;
66148
66234
  method.assertProtocolSupported(device.getProtocol(), device.getCurrentFirmwareType());
66149
66235
  protocolV2Operation = device.isProtocolV2();
66150
66236
  const versionRange = device.getCurrentMethodVersionRange(type => method.getVersionRange()[type]);
66151
- const currentFirmwareVersion = (_k = device.getCurrentFirmwareVersionString()) !== null && _k !== void 0 ? _k : '0.0.0';
66152
- const currentBleVersion = (_l = device.getCurrentBLEFirmwareVersionString()) !== null && _l !== void 0 ? _l : '0.0.0';
66237
+ const currentFirmwareVersion = (_l = device.getCurrentFirmwareVersionString()) !== null && _l !== void 0 ? _l : '0.0.0';
66238
+ const currentBleVersion = (_m = device.getCurrentBLEFirmwareVersionString()) !== null && _m !== void 0 ? _m : '0.0.0';
66153
66239
  const deviceFirmwareType = device.getCurrentFirmwareType();
66154
66240
  let newVersionStatus;
66155
66241
  if (device.features && !device.isProtocolV2()) {
@@ -66227,7 +66313,7 @@ const onCallDevice = (context, message, method) => __awaiter(void 0, void 0, voi
66227
66313
  }
66228
66314
  },
66229
66315
  prepare: () => __awaiter(void 0, void 0, void 0, function* () {
66230
- var _m, _o, _p, _q, _r, _s;
66316
+ var _o, _p, _q, _r, _s, _t;
66231
66317
  if (method.deviceId && method.checkDeviceId && !deviceIdCheckedDuringUnlockPreflight) {
66232
66318
  const isSameDeviceID = yield checkLiveDeviceId(device, method.deviceId);
66233
66319
  if (!isSameDeviceID) {
@@ -66247,7 +66333,7 @@ const onCallDevice = (context, message, method) => __awaiter(void 0, void 0, voi
66247
66333
  require: support.require,
66248
66334
  });
66249
66335
  }
66250
- const passphraseStateSafety = yield device.checkPassphraseStateSafety((_m = method.payload) === null || _m === void 0 ? void 0 : _m.passphraseState, (_o = method.payload) === null || _o === void 0 ? void 0 : _o.useEmptyPassphrase, (_p = method.payload) === null || _p === void 0 ? void 0 : _p.skipPassphraseCheck, resolveDeriveCardano(method), (_q = method.protocolV2UnlockContext) === null || _q === void 0 ? void 0 : _q.preflightMainPinSelected);
66336
+ const passphraseStateSafety = yield device.checkPassphraseStateSafety((_o = method.payload) === null || _o === void 0 ? void 0 : _o.passphraseState, (_p = method.payload) === null || _p === void 0 ? void 0 : _p.useEmptyPassphrase, (_q = method.payload) === null || _q === void 0 ? void 0 : _q.skipPassphraseCheck, resolveDeriveCardano(method), (_r = method.protocolV2UnlockContext) === null || _r === void 0 ? void 0 : _r.preflightMainPinSelected);
66251
66337
  checkPassphraseEnableState(method, device.features);
66252
66338
  if (!passphraseStateSafety) {
66253
66339
  DevicePool.clearDeviceCache(method.payload.connectId);
@@ -66265,7 +66351,7 @@ const onCallDevice = (context, message, method) => __awaiter(void 0, void 0, voi
66265
66351
  ? e
66266
66352
  : hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'open safety check failed.');
66267
66353
  }
66268
- (_s = (_r = method.device) === null || _r === void 0 ? void 0 : _r.commands) === null || _s === void 0 ? void 0 : _s.checkDisposed();
66354
+ (_t = (_s = method.device) === null || _s === void 0 ? void 0 : _s.commands) === null || _t === void 0 ? void 0 : _t.checkDisposed();
66269
66355
  }),
66270
66356
  });
66271
66357
  messageResponse = createResponseMessage(method.responseID, true, response);
@@ -66620,10 +66706,11 @@ const ensureConnected = (_context, method, connectId, pollingId, abortSignal) =>
66620
66706
  const POLL_INTERVAL_TIME = (method.payload && method.payload.pollIntervalTime) || 1000;
66621
66707
  const TIME_OUT = (method.payload && method.payload.timeout) || 10000;
66622
66708
  let timer = null;
66709
+ let lastInitializeError;
66623
66710
  Log.debug(`EnsureConnected function start, MAX_RETRY_COUNT=${MAX_RETRY_COUNT}, POLL_INTERVAL_TIME=${POLL_INTERVAL_TIME} `);
66624
66711
  const poll = (time = POLL_INTERVAL_TIME) => __awaiter(void 0, void 0, void 0, function* () {
66625
66712
  return new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
66626
- var _t;
66713
+ var _u;
66627
66714
  const abort = () => {
66628
66715
  if (abortSignal && abortSignal.aborted) {
66629
66716
  if (timer) {
@@ -66652,8 +66739,10 @@ const ensureConnected = (_context, method, connectId, pollingId, abortSignal) =>
66652
66739
  Log.debug('EnsureConnected function try count: ', tryCount, ' poll interval time: ', time);
66653
66740
  try {
66654
66741
  yield initDeviceList(method);
66742
+ lastInitializeError = undefined;
66655
66743
  }
66656
66744
  catch (error) {
66745
+ lastInitializeError = error;
66657
66746
  Log.debug('device list error: ', error);
66658
66747
  if ([
66659
66748
  hdShared.HardwareErrorCode.BridgeNotInstalled,
@@ -66666,6 +66755,7 @@ const ensureConnected = (_context, method, connectId, pollingId, abortSignal) =>
66666
66755
  }
66667
66756
  if (error.errorCode === hdShared.HardwareErrorCode.TransportNotConfigured) {
66668
66757
  yield TransportManager.configure();
66758
+ lastInitializeError = undefined;
66669
66759
  }
66670
66760
  }
66671
66761
  if (abort()) {
@@ -66748,13 +66838,16 @@ const ensureConnected = (_context, method, connectId, pollingId, abortSignal) =>
66748
66838
  clearTimeout(timer);
66749
66839
  }
66750
66840
  Log.debug('EnsureConnected get to max try count, will return: ', tryCount);
66751
- if (DataManager.isBrowserWebUsb(env) && !((_t = method.payload) === null || _t === void 0 ? void 0 : _t.skipWebDevicePrompt)) {
66841
+ const preserveWebUsbInitError = DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env);
66842
+ const needsPermissionPrompt = DataManager.isBrowserWebUsb(env) && !((_u = method.payload) === null || _u === void 0 ? void 0 : _u.skipWebDevicePrompt);
66843
+ const fallbackError = needsPermissionPrompt
66844
+ ? hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.WebDeviceNotFoundOrNeedsPermission)
66845
+ : hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound);
66846
+ const errorToReject = preserveWebUsbInitError && lastInitializeError ? lastInitializeError : fallbackError;
66847
+ if (needsPermissionPrompt && errorToReject === fallbackError) {
66752
66848
  postMessage(createUiMessage(UI_REQUEST.WEB_DEVICE_PROMPT_ACCESS_PERMISSION));
66753
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.WebDeviceNotFoundOrNeedsPermission));
66754
- }
66755
- else {
66756
- reject(hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceNotFound));
66757
66849
  }
66850
+ reject(errorToReject);
66758
66851
  return;
66759
66852
  }
66760
66853
  if (abort()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.2-alpha.112",
3
+ "version": "1.2.2-alpha.114",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.2.2-alpha.112",
29
- "@onekeyfe/hd-transport": "1.2.2-alpha.112",
28
+ "@onekeyfe/hd-shared": "1.2.2-alpha.114",
29
+ "@onekeyfe/hd-transport": "1.2.2-alpha.114",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "buffer": "^6.0.3",
@@ -46,5 +46,5 @@
46
46
  "@types/w3c-web-usb": "^1.0.10",
47
47
  "@types/web-bluetooth": "^0.0.21"
48
48
  },
49
- "gitHead": "3b31cf365ea4042a5fb2e78dd7ede9a2d9d96e04"
49
+ "gitHead": "6490382eeeb6bda264b71c7e9bf96143840ee1cb"
50
50
  }
@@ -1,4 +1,4 @@
1
- import { canonicalizePro2BleAdvertisementName } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, canonicalizePro2BleAdvertisementName } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import { BaseMethod } from './BaseMethod';
4
4
  import TransportManager from '../data-manager/TransportManager';
@@ -6,10 +6,46 @@ import { DataManager } from '../data-manager';
6
6
  import { LoggerNames, getDeviceTypeByBleName, getLogger } from '../utils';
7
7
  import { DevicePool } from '../device/DevicePool';
8
8
 
9
+ import type { SearchDevice } from '../types/device';
9
10
  import type DeviceConnector from '../device/DeviceConnector';
11
+ import type { OneKeyDeviceInfo as DeviceDescriptor } from '@onekeyfe/hd-transport';
12
+ import type { Device } from '../device/Device';
10
13
 
11
14
  const Log = getLogger(LoggerNames.DevicePool);
12
15
 
16
+ type RequestQueueLookup = {
17
+ getRequestTasksIdByConnectId: (connectId: string) => number[];
18
+ };
19
+
20
+ const isOwnedByActiveWebUsbRequest = (
21
+ descriptor: DeviceDescriptor,
22
+ requestQueue?: RequestQueueLookup
23
+ ) => {
24
+ if (!requestQueue) return false;
25
+ const keys = [descriptor.path, descriptor.id].filter(
26
+ (key): key is string => typeof key === 'string' && key.length > 0
27
+ );
28
+ return keys.some(key => requestQueue.getRequestTasksIdByConnectId(key).length > 0);
29
+ };
30
+
31
+ const toSearchDeviceFromDescriptor = (descriptor: DeviceDescriptor): SearchDevice => {
32
+ const connectId = descriptor.path || descriptor.id || null;
33
+ return {
34
+ connectId,
35
+ uuid: connectId ?? '',
36
+ serialNo: connectId,
37
+ deviceId: null,
38
+ deviceType: EDeviceType.Unknown,
39
+ name: descriptor.name || connectId || '',
40
+ commType: descriptor.commType,
41
+ };
42
+ };
43
+
44
+ const toSearchDevice = (device: Device): SearchDevice | null => {
45
+ const message = device.toMessageObject();
46
+ return message ? (message as SearchDevice) : null;
47
+ };
48
+
13
49
  export default class SearchDevices extends BaseMethod {
14
50
  connector?: DeviceConnector;
15
51
 
@@ -19,19 +55,31 @@ export default class SearchDevices extends BaseMethod {
19
55
  this.skipForceUpdateCheck = true;
20
56
  }
21
57
 
22
- async run() {
23
- await TransportManager.configure();
58
+ async run(): Promise<SearchDevice[]> {
59
+ const env = DataManager.getSettings('env');
60
+ const isWebUsb = env === 'webusb' || env === 'desktop-webusb';
61
+ const requestQueue = this.context?.requestQueue;
62
+ const hasActiveWebUsbRequest = isWebUsb && (requestQueue?.getRequestTasksId().length ?? 0) > 0;
63
+ // Bring up WebUSB even when schema configuration is deferred while a
64
+ // business request owns the discovery lock. Failures here used to be
65
+ // swallowed by configure(); keep searchDevices resolving empty.
66
+ if (isWebUsb) {
67
+ try {
68
+ await TransportManager.ensureInitialized();
69
+ } catch (error) {
70
+ Log.debug('WebUSB bring-up unavailable', error);
71
+ }
72
+ }
73
+ if (!hasActiveWebUsbRequest) await TransportManager.configure();
24
74
  const deviceDiff = await this.connector?.enumerate();
25
75
  const devicesDescriptor = deviceDiff?.descriptors ?? [];
26
76
 
27
- const env = DataManager.getSettings('env');
28
-
29
77
  /**
30
78
  * No need to call features during Bluetooth scaning
31
79
  * to avoid device pairing
32
80
  */
33
81
  if (DataManager.isBleConnect(env)) {
34
- const devices = [];
82
+ const devices: SearchDevice[] = [];
35
83
  const seenIds = new Set<string>();
36
84
 
37
85
  for (const device of devicesDescriptor) {
@@ -56,30 +104,41 @@ export default class SearchDevices extends BaseMethod {
56
104
  return devices;
57
105
  }
58
106
 
59
- const deviceList = [];
107
+ const deviceList: SearchDevice[] = [];
60
108
  for (const descriptor of devicesDescriptor) {
61
- try {
62
- // Discovery is best effort. Browsers may retain WebUSB grants for devices that
63
- // are offline, busy, or not ready, so one descriptor must not abort the scan.
64
- const result = await DevicePool.getDevices([descriptor], descriptor.path, {
65
- // Discovery must actively identify the protocol instead of trusting a caller hint.
66
- connectProtocol: undefined,
67
- forceProtocolDetection: true,
68
- refreshRuntimeState: true,
69
- });
70
- deviceList.push(...result.deviceList);
71
- } catch (error) {
72
- const errorCode =
73
- error && typeof error === 'object' && 'errorCode' in error
74
- ? (error as { errorCode?: unknown }).errorCode
75
- : undefined;
76
- Log.debug('Skip unavailable device during search', {
77
- path: descriptor.path,
78
- ...(errorCode !== undefined ? { errorCode } : {}),
79
- });
109
+ if (hasActiveWebUsbRequest && isOwnedByActiveWebUsbRequest(descriptor, requestQueue)) {
110
+ const cached = DevicePool.getDeviceByPath(descriptor.path);
111
+ const message = cached?.features ? toSearchDevice(cached) : null;
112
+ // Do not probe a path an in-flight request already owns. A cache miss
113
+ // (passphrase switch / initSession) is still a connected device.
114
+ deviceList.push(message ?? toSearchDeviceFromDescriptor(descriptor));
115
+ } else {
116
+ try {
117
+ // Discovery is best effort. Browsers may retain WebUSB grants for devices that
118
+ // are offline, busy, or not ready, so one descriptor must not abort the scan.
119
+ const result = await DevicePool.getDevices([descriptor], descriptor.path, {
120
+ // Discovery must actively identify the protocol instead of trusting a caller hint.
121
+ connectProtocol: undefined,
122
+ forceProtocolDetection: true,
123
+ refreshRuntimeState: true,
124
+ });
125
+ for (const device of result.deviceList) {
126
+ const message = toSearchDevice(device);
127
+ if (message) deviceList.push(message);
128
+ }
129
+ } catch (error) {
130
+ const errorCode =
131
+ error && typeof error === 'object' && 'errorCode' in error
132
+ ? (error as { errorCode?: unknown }).errorCode
133
+ : undefined;
134
+ Log.debug('Skip unavailable device during search', {
135
+ path: descriptor.path,
136
+ ...(errorCode !== undefined ? { errorCode } : {}),
137
+ });
138
+ }
80
139
  }
81
140
  }
82
141
 
83
- return deviceList.map(device => device.toMessageObject());
142
+ return deviceList;
84
143
  }
85
144
  }
package/src/core/index.ts CHANGED
@@ -84,7 +84,7 @@ import type { BaseMethod } from '../api/BaseMethod';
84
84
 
85
85
  const Log = getLogger(LoggerNames.Core);
86
86
  const PRE_INITIALIZE_TTL_MS = 60 * 1000;
87
- const PRE_PENDING_CALL_TIMEOUT_MS = 15 * 1000;
87
+ const PRE_PENDING_CALL_TIMEOUT_MS = 5 * 1000;
88
88
  const PRO2_USB_SIGNING_COOLDOWN_MS = 1000;
89
89
 
90
90
  // Dedup/coalesce state for "pre-warm signal" methods (isPreWarmSignal),
@@ -183,6 +183,7 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
183
183
  }
184
184
  };
185
185
  method.setContext?.(context);
186
+ method.context = context;
186
187
 
187
188
  method.requestContext = createRequestContext(method.responseID, method.name, {
188
189
  sdkInstanceId: context.sdkInstanceId,
@@ -203,7 +204,12 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
203
204
  if (!method.useDevice) {
204
205
  updateMethodRequestContext(method, { status: 'running' });
205
206
  try {
206
- const response = await method.run();
207
+ const env = DataManager.getSettings('env');
208
+ const response =
209
+ method.name === 'searchDevices' &&
210
+ (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env))
211
+ ? await context.methodSynchronize(() => method.run(), 'webusb-discovery')
212
+ : await method.run();
207
213
  completeMethodRequestContext(method);
208
214
  return createResponseMessage(method.responseID, true, response);
209
215
  } catch (error) {
@@ -315,25 +321,27 @@ const waitForPendingPromise = async (
315
321
  if (pendingPromise) {
316
322
  Log.debug('pre pending call promise before call method, wait for it');
317
323
  let timer: ReturnType<typeof setTimeout> | undefined;
324
+ let timedOut = false;
325
+ let completed = false;
318
326
  try {
319
327
  await Promise.race([
320
328
  pendingPromise,
321
- new Promise<void>((_, reject) => {
329
+ new Promise<void>(resolve => {
322
330
  timer = setTimeout(() => {
323
- reject(
324
- ERRORS.TypedError(
325
- HardwareErrorCode.DeviceBusy,
326
- 'Previous device cancellation is still draining'
327
- )
328
- );
331
+ timedOut = true;
332
+ resolve();
329
333
  }, PRE_PENDING_CALL_TIMEOUT_MS);
330
334
  }),
331
335
  ]);
332
- // A deadline is not evidence that old I/O is safe to reuse. Keep the
333
- // barrier on failure; a later call may proceed only after cleanup settles.
334
- removePrePendingCallPromise?.(connectId, pendingPromise);
336
+ completed = !timedOut;
335
337
  } finally {
336
338
  if (timer) clearTimeout(timer);
339
+ // Keep a rejected cleanup barrier for the next caller's safety check;
340
+ // only a completed cleanup or a timeout may clear it.
341
+ if (timedOut || completed) removePrePendingCallPromise?.(connectId, pendingPromise);
342
+ }
343
+ if (timedOut) {
344
+ Log.warn('pre pending call promise timed out before call method', { connectId });
337
345
  }
338
346
  Log.debug('pre pending call promise before call method done');
339
347
  }
@@ -411,7 +419,26 @@ const onCallDevice = async (
411
419
  * Polling to ensure successful connection
412
420
  */
413
421
  const pollingId = pollingManager.start(connectId);
414
- device = await ensureConnected(context, method, connectId, pollingId, method.abortSignal);
422
+ const env = DataManager.getSettings('env');
423
+ const connect = () =>
424
+ ensureConnected(context, method, connectId, pollingId, method.abortSignal);
425
+ // Discovery may acquire USB endpoints. Finish it before initializing a public
426
+ // request; once registered, that request makes discovery use cached state only.
427
+ if (DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env)) {
428
+ // Synchronization can keep the connect action queued after the caller is
429
+ // cancelled. Observe that promise so a late device-not-found error does
430
+ // not become an unhandled rejection.
431
+ const connectPromise = context.methodSynchronize(async () => {
432
+ if (method.abortSignal?.aborted) {
433
+ throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
434
+ }
435
+ return connect();
436
+ }, 'webusb-discovery');
437
+ connectPromise.catch(() => undefined);
438
+ device = await requestQueue.waitForTask(task, () => connectPromise);
439
+ } else {
440
+ device = await connect();
441
+ }
415
442
  if (method.abortSignal?.aborted) {
416
443
  throw ERRORS.TypedError(HardwareErrorCode.CallQueueActionCancelled);
417
444
  }
@@ -1200,6 +1227,7 @@ const ensureConnected = async (
1200
1227
  const POLL_INTERVAL_TIME = (method.payload && method.payload.pollIntervalTime) || 1000;
1201
1228
  const TIME_OUT = (method.payload && method.payload.timeout) || 10000;
1202
1229
  let timer: ReturnType<typeof setTimeout> | null = null;
1230
+ let lastInitializeError: any;
1203
1231
  Log.debug(
1204
1232
  `EnsureConnected function start, MAX_RETRY_COUNT=${MAX_RETRY_COUNT}, POLL_INTERVAL_TIME=${POLL_INTERVAL_TIME} `
1205
1233
  );
@@ -1240,7 +1268,9 @@ const ensureConnected = async (
1240
1268
  Log.debug('EnsureConnected function try count: ', tryCount, ' poll interval time: ', time);
1241
1269
  try {
1242
1270
  await initDeviceList(method);
1271
+ lastInitializeError = undefined;
1243
1272
  } catch (error) {
1273
+ lastInitializeError = error;
1244
1274
  Log.debug('device list error: ', error);
1245
1275
  if (
1246
1276
  [
@@ -1255,6 +1285,7 @@ const ensureConnected = async (
1255
1285
  }
1256
1286
  if (error.errorCode === HardwareErrorCode.TransportNotConfigured) {
1257
1287
  await TransportManager.configure();
1288
+ lastInitializeError = undefined;
1258
1289
  }
1259
1290
  }
1260
1291
 
@@ -1352,14 +1383,22 @@ const ensureConnected = async (
1352
1383
  clearTimeout(timer);
1353
1384
  }
1354
1385
  Log.debug('EnsureConnected get to max try count, will return: ', tryCount);
1355
- // Browser WebUSB needs permission prompt, desktop WebUSB doesn't
1356
- // skipWebDevicePrompt can override this behavior for special cases
1357
- if (DataManager.isBrowserWebUsb(env) && !method.payload?.skipWebDevicePrompt) {
1386
+ const preserveWebUsbInitError =
1387
+ DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env);
1388
+ const needsPermissionPrompt =
1389
+ DataManager.isBrowserWebUsb(env) && !method.payload?.skipWebDevicePrompt;
1390
+ const fallbackError = needsPermissionPrompt
1391
+ ? ERRORS.TypedError(HardwareErrorCode.WebDeviceNotFoundOrNeedsPermission)
1392
+ : ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
1393
+ const errorToReject =
1394
+ preserveWebUsbInitError && lastInitializeError ? lastInitializeError : fallbackError;
1395
+ // Only ask the host for a WebUSB grant when the failure is actually
1396
+ // "not found / needs permission". A preserved initialize error must
1397
+ // not fire that prompt with a different public code.
1398
+ if (needsPermissionPrompt && errorToReject === fallbackError) {
1358
1399
  postMessage(createUiMessage(UI_REQUEST.WEB_DEVICE_PROMPT_ACCESS_PERMISSION));
1359
- reject(ERRORS.TypedError(HardwareErrorCode.WebDeviceNotFoundOrNeedsPermission));
1360
- } else {
1361
- reject(ERRORS.TypedError(HardwareErrorCode.DeviceNotFound));
1362
1400
  }
1401
+ reject(errorToReject);
1363
1402
  return;
1364
1403
  }
1365
1404
 
@@ -33,6 +33,8 @@ export default class TransportManager {
33
33
 
34
34
  static reactNativeInit = false;
35
35
 
36
+ static webUsbInit = false;
37
+
36
38
  static protocolV1MessageSchema: ProtocolV1MessageSchema = 'v1CurrentSchema';
37
39
 
38
40
  static plugin: LowlevelTransportSharedPlugin | null = null;
@@ -42,6 +44,17 @@ export default class TransportManager {
42
44
  this.defaultMessages = DataManager.getProtobufMessages();
43
45
  this.currentMessages = this.defaultMessages;
44
46
  this.protocolV1MessageSchema = 'v1CurrentSchema';
47
+ this.webUsbInit = false;
48
+ }
49
+
50
+ static async ensureInitialized() {
51
+ const env = DataManager.getSettings('env');
52
+ if (env !== 'webusb' && env !== 'desktop-webusb') return;
53
+ if (this.webUsbInit) return;
54
+ // The emitter registers USB disconnect events; this must happen even when
55
+ // schema configuration is intentionally deferred during discovery.
56
+ await this.transport.init(WebUsbLogger, DevicePool.emitter);
57
+ this.webUsbInit = true;
45
58
  }
46
59
 
47
60
  static async configure() {
@@ -69,9 +82,7 @@ export default class TransportManager {
69
82
  } else if (env === 'desktop-web-ble') {
70
83
  await this.transport.init(WebBleLogger, DevicePool.emitter);
71
84
  } else if (env === 'webusb' || env === 'desktop-webusb') {
72
- // The emitter is what turns a navigator.usb 'disconnect' into a
73
- // DEVICE.DISCONNECT; without it WebUSB never reports device removal.
74
- await this.transport.init(WebUsbLogger, DevicePool.emitter);
85
+ await this.ensureInitialized();
75
86
  } else {
76
87
  await this.transport.init(HttpLogger);
77
88
  }