@onekeyfe/hwk-adapter-core 1.2.3-alpha.1 → 1.2.3-alpha.3

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.mjs CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  UI_REQUEST_TIMEOUT_TAG,
11
11
  UI_RESPONSE,
12
12
  UiRequestRegistry
13
- } from "./chunk-KCIK56B5.mjs";
13
+ } from "./chunk-ELC6PLKH.mjs";
14
14
  import {
15
15
  HardwareErrorCode,
16
16
  ORPHAN_ELIGIBLE_ERROR_CODES,
@@ -19,7 +19,7 @@ import {
19
19
  defaultRecoveryForCode,
20
20
  isHwkRecoveryHint,
21
21
  operationMayHaveCompletedParams
22
- } from "./chunk-72KOSYTH.mjs";
22
+ } from "./chunk-E3UPXUN5.mjs";
23
23
 
24
24
  // src/types/response.ts
25
25
  function success(payload) {
@@ -76,10 +76,6 @@ var DEVICE = {
76
76
  DISCONNECT: "device-disconnect",
77
77
  CHANGED: "device-changed",
78
78
  FEATURES: "features",
79
- /** Trezor transport binding, emitted only after matching the expected firmware device_id. */
80
- TREZOR_CONNECTION_VERIFIED: "device-trezor-connection-verified",
81
- /** Ledger BLE address repair, emitted only after matching the stored chain fingerprint. */
82
- LEDGER_CONNECTION_VERIFIED: "device-ledger-connection-verified",
83
79
  /**
84
80
  * Trezor-only. Emitted after a successful THP handshake that minted or
85
81
  * refreshed pairing credentials. The host should persist `credentials`
@@ -99,7 +95,7 @@ var SDK = {
99
95
  DEVICE_UNRESPONSIVE: "device-unresponsive",
100
96
  DEVICE_RECOVERED: "device-recovered",
101
97
  DEVICE_INTERACTION: "device-interaction",
102
- INTERACTION_ENDED: "interaction-ended"
98
+ OPERATION_ENDED: "operation-ended"
103
99
  };
104
100
 
105
101
  // src/utils/DeviceJobQueue.ts
@@ -130,12 +126,13 @@ var DeviceJobQueue = class {
130
126
  abortController: ac,
131
127
  startedAt: Date.now()
132
128
  };
133
- this._jobs.set(jobToken, { deviceId });
129
+ this._jobs.set(jobToken, activeJob);
134
130
  const next = prev.catch(() => {
135
131
  }).then(async () => {
136
132
  if (this._generation !== gen) {
137
133
  throw this._generationCancelReasons.get(gen) ?? new Error("Job cancelled: queue was cleared");
138
134
  }
135
+ if (ac.signal.aborted) throw ac.signal.reason;
139
136
  this._active = activeJob;
140
137
  try {
141
138
  return await job(ac.signal);
@@ -151,31 +148,27 @@ var DeviceJobQueue = class {
151
148
  });
152
149
  return next;
153
150
  }
154
- /** Cancel the active job. If `deviceId` is given, only cancels when it matches. */
155
- cancelActive(deviceId) {
151
+ /** Cancel the running job. `reason` becomes signal.reason. */
152
+ cancelActive(deviceId, reason) {
156
153
  if (!this._active) return false;
157
154
  if (deviceId && this._active.deviceId !== deviceId) return false;
158
- this._active.abortController.abort(new Error("Manually cancelled"));
155
+ this._active.abortController.abort(reason ?? new Error("Cancelled"));
159
156
  return true;
160
157
  }
161
- /** Force cancel the active job. `reason` becomes signal.reason. */
162
- forceCancelActive(deviceId, reason) {
163
- if (!this._active) return false;
164
- if (deviceId && this._active.deviceId !== deviceId) return false;
165
- this._active.abortController.abort(reason ?? new Error("Force cancelled for recovery"));
166
- return true;
167
- }
168
- /** Cancel the active job (alias for callers that previously needed multi-device cancel). */
169
- cancelAllActive(reason) {
170
- if (!this._active) return;
171
- this._active.abortController.abort(reason ?? new Error("Cancelled by cancelAllActive"));
172
- }
173
158
  /** Cancel the active job and invalidate queued jobs that have not started. */
174
159
  cancelActiveAndPending(deviceId, reason) {
175
- if (deviceId && this._active && this._active.deviceId !== deviceId) {
176
- return false;
160
+ const cancelReason = reason ?? new Error("Cancelled by cancelActiveAndPending");
161
+ if (deviceId) {
162
+ let cancelled = false;
163
+ for (const job of this._jobs.values()) {
164
+ if (job.deviceId === deviceId) {
165
+ job.abortController.abort(cancelReason);
166
+ cancelled = true;
167
+ }
168
+ }
169
+ return cancelled;
177
170
  }
178
- this.clear(reason ?? new Error("Cancelled by cancelActiveAndPending"));
171
+ this.clear(cancelReason);
179
172
  return true;
180
173
  }
181
174
  /** Get info about the currently active job, or null if idle. */
@@ -188,10 +181,6 @@ var DeviceJobQueue = class {
188
181
  startedAt: this._active.startedAt
189
182
  };
190
183
  }
191
- /** True if any job is currently running. */
192
- isBusy() {
193
- return this._jobs.size > 0;
194
- }
195
184
  clear(reason) {
196
185
  const cancelledGeneration = this._generation;
197
186
  this._generation++;
@@ -210,22 +199,28 @@ var DeviceJobQueue = class {
210
199
 
211
200
  // src/utils/hardwareRuntimeId.ts
212
201
  import { bytesToHex as bytesToHex2, randomBytes } from "@noble/hashes/utils";
213
- var HARDWARE_RUNTIME_ID_PREFIX = "hwk:runtime:v1";
202
+ var HARDWARE_RUNTIME_ID_PREFIX = "hwk:runtime";
214
203
  var RANDOM_ID_BYTES = 16;
215
204
  var RANDOM_ID_PATTERN = /^[0-9a-f]{32}$/;
205
+ var KINDS = /* @__PURE__ */ new Set([
206
+ "operation",
207
+ "search-target",
208
+ "link"
209
+ ]);
216
210
  var VENDORS = /* @__PURE__ */ new Set(["trezor", "ledger", "keystone"]);
217
- var CONNECTION_TYPES = /* @__PURE__ */ new Set(["usb", "ble", "qr"]);
218
- function createNonce() {
219
- return bytesToHex2(randomBytes(RANDOM_ID_BYTES));
211
+ function create(kind, vendor) {
212
+ return `${HARDWARE_RUNTIME_ID_PREFIX}:${kind}:${vendor}:${bytesToHex2(
213
+ randomBytes(RANDOM_ID_BYTES)
214
+ )}`;
220
215
  }
221
- function createHardwareInteractionId(vendor) {
222
- return `${HARDWARE_RUNTIME_ID_PREFIX}:interaction:${vendor}:${createNonce()}`;
216
+ function createHardwareOperationId(vendor) {
217
+ return create("operation", vendor);
223
218
  }
224
- function createHardwareSearchTargetId(params) {
225
- return `${HARDWARE_RUNTIME_ID_PREFIX}:search-target:${params.vendor}:${params.connectionType}:${createNonce()}`;
219
+ function createHardwareSearchTargetId(vendor) {
220
+ return create("search-target", vendor);
226
221
  }
227
- function createHardwareConnectorSessionId(params) {
228
- return `${HARDWARE_RUNTIME_ID_PREFIX}:connector-session:${params.vendor}:${params.connectionType}:${createNonce()}`;
222
+ function createHardwareLinkId(vendor) {
223
+ return create("link", vendor);
229
224
  }
230
225
  function hasHardwareRuntimeIdPrefix(value) {
231
226
  return typeof value === "string" && value.startsWith(`${HARDWARE_RUNTIME_ID_PREFIX}:`);
@@ -233,50 +228,32 @@ function hasHardwareRuntimeIdPrefix(value) {
233
228
  function parseHardwareRuntimeId(value) {
234
229
  if (!hasHardwareRuntimeIdPrefix(value)) return void 0;
235
230
  const parts = value.split(":");
236
- const kind = parts[3];
237
- const vendor = parts[4];
238
- if (!VENDORS.has(vendor)) return void 0;
239
- if (kind === "interaction" && parts.length === 6 && RANDOM_ID_PATTERN.test(parts[5])) {
240
- return {
241
- kind,
242
- vendor,
243
- nonce: parts[5]
244
- };
245
- }
246
- const connectionType = parts[5];
247
- if ((kind === "search-target" || kind === "connector-session") && parts.length === 7 && CONNECTION_TYPES.has(connectionType) && RANDOM_ID_PATTERN.test(parts[6])) {
248
- return {
249
- kind,
250
- vendor,
251
- connectionType,
252
- nonce: parts[6]
253
- };
231
+ if (parts.length !== 5) return void 0;
232
+ const [, , kind, vendor, nonce] = parts;
233
+ if (!KINDS.has(kind) || !VENDORS.has(vendor) || !RANDOM_ID_PATTERN.test(nonce)) {
234
+ return void 0;
254
235
  }
255
- return void 0;
256
- }
257
- function isHardwareInteractionId(value) {
258
- return parseHardwareRuntimeId(value)?.kind === "interaction";
236
+ return { kind, vendor };
259
237
  }
260
- function isHardwareSearchTargetId(value) {
261
- return parseHardwareRuntimeId(value)?.kind === "search-target";
238
+ function isHardwareOperationId(value) {
239
+ return parseHardwareRuntimeId(value)?.kind === "operation";
262
240
  }
263
241
 
264
- // src/utils/InteractionRegistry.ts
265
- var INTERACTION_DEFAULT_TTL_MS = 6e5;
266
- var InteractionRegistry = class {
242
+ // src/utils/OperationRegistry.ts
243
+ var OPERATION_DEFAULT_TTL_MS = 6e5;
244
+ var OperationRegistry = class {
267
245
  constructor(options) {
268
246
  this._active = /* @__PURE__ */ new Map();
269
247
  this._ended = /* @__PURE__ */ new Map();
270
248
  this._vendor = options.vendor;
271
- this._ttlMs = options.ttlMs ?? INTERACTION_DEFAULT_TTL_MS;
249
+ this._ttlMs = options.ttlMs ?? OPERATION_DEFAULT_TTL_MS;
272
250
  this._onEnded = options.onEnded;
273
251
  }
274
252
  create(params) {
275
253
  const now = Date.now();
276
- const interactionId = createHardwareInteractionId(this._vendor);
277
- const interaction = {
278
- interactionId,
279
- searchTargetId: params.searchTargetId,
254
+ const operationId = createHardwareOperationId(this._vendor);
255
+ const operation = {
256
+ operationId,
280
257
  connectId: params.connectId,
281
258
  device: params.device,
282
259
  connectionKeys: Array.from(
@@ -289,35 +266,35 @@ var InteractionRegistry = class {
289
266
  createdAt: now,
290
267
  lastActiveAt: now
291
268
  };
292
- const timer = this._createTimer(interactionId);
293
- this._active.set(interactionId, { ...interaction, timer, retainCount: 0 });
294
- return interaction;
269
+ const timer = this._createTimer(operationId);
270
+ this._active.set(operationId, { ...operation, timer, retainCount: 0 });
271
+ return operation;
295
272
  }
296
- resolve(interactionId) {
297
- const active = this._active.get(interactionId);
273
+ resolve(operationId) {
274
+ const active = this._active.get(operationId);
298
275
  if (!active) {
299
- const ended = this._ended.get(interactionId);
276
+ const ended = this._ended.get(operationId);
300
277
  if (ended) {
301
278
  throw createHwkError({
302
- code: 10113 /* InteractionEnded */,
303
- message: `Hardware interaction has ended (${ended.reason})`,
304
- params: { interactionId, reason: ended.reason }
279
+ code: 10113 /* OperationEnded */,
280
+ message: `Hardware operation has ended (${ended.reason})`,
281
+ params: { operationId, reason: ended.reason }
305
282
  });
306
283
  }
307
284
  throw createHwkError({
308
- code: 10112 /* InteractionNotFound */,
309
- message: "Hardware interaction was not found",
310
- params: { interactionId }
285
+ code: 10112 /* OperationNotFound */,
286
+ message: "Hardware operation was not found",
287
+ params: { operationId }
311
288
  });
312
289
  }
313
290
  if (active.timer) clearTimeout(active.timer);
314
291
  active.lastActiveAt = Date.now();
315
- active.timer = active.retainCount === 0 ? this._createTimer(interactionId) : void 0;
292
+ active.timer = active.retainCount === 0 ? this._createTimer(operationId) : void 0;
316
293
  return active;
317
294
  }
318
- /** Keep an interaction alive while one device job is actively using it. */
319
- retain(interactionId) {
320
- const active = this.resolve(interactionId);
295
+ /** Keep an operation alive while one device job is actively using it. */
296
+ retain(operationId) {
297
+ const active = this.resolve(operationId);
321
298
  if (active.timer) clearTimeout(active.timer);
322
299
  active.timer = void 0;
323
300
  active.retainCount += 1;
@@ -325,31 +302,31 @@ var InteractionRegistry = class {
325
302
  return () => {
326
303
  if (released) return;
327
304
  released = true;
328
- const current = this._active.get(interactionId);
305
+ const current = this._active.get(operationId);
329
306
  if (!current) return;
330
307
  current.retainCount = Math.max(0, current.retainCount - 1);
331
308
  current.lastActiveAt = Date.now();
332
309
  if (current.retainCount === 0) {
333
- current.timer = this._createTimer(interactionId);
310
+ current.timer = this._createTimer(operationId);
334
311
  }
335
312
  };
336
313
  }
337
314
  /** Return active or tombstoned binding data without refreshing its TTL. */
338
- find(interactionId) {
339
- return this._active.get(interactionId) ?? this._ended.get(interactionId)?.interaction;
315
+ find(operationId) {
316
+ return this._active.get(operationId) ?? this._ended.get(operationId)?.operation;
340
317
  }
341
318
  findActiveByConnectionKey(connectionKey) {
342
319
  if (!connectionKey) return void 0;
343
320
  return [...this._active.values()].find(
344
- (interaction) => interaction.connectionKeys.includes(connectionKey)
321
+ (operation) => operation.connectionKeys.includes(connectionKey)
345
322
  );
346
323
  }
347
324
  /** Replace the live transport binding after the same target was reconnected. */
348
- rebind(interactionId, params) {
349
- const active = this._active.get(interactionId);
325
+ rebind(operationId, params) {
326
+ const active = this._active.get(operationId);
350
327
  if (!active) {
351
- this.resolve(interactionId);
352
- throw new Error("Unreachable interaction rebind");
328
+ this.resolve(operationId);
329
+ throw new Error("Unreachable operation rebind");
353
330
  }
354
331
  if (active.timer) clearTimeout(active.timer);
355
332
  active.connectId = params.connectId;
@@ -358,42 +335,42 @@ var InteractionRegistry = class {
358
335
  new Set([params.connectId, ...params.connectionKeys ?? []].filter(Boolean))
359
336
  );
360
337
  active.lastActiveAt = Date.now();
361
- active.timer = active.retainCount === 0 ? this._createTimer(interactionId) : void 0;
338
+ active.timer = active.retainCount === 0 ? this._createTimer(operationId) : void 0;
362
339
  return active;
363
340
  }
364
- end(interactionId, reason) {
365
- const active = this._active.get(interactionId);
341
+ end(operationId, reason) {
342
+ const active = this._active.get(operationId);
366
343
  if (!active) return void 0;
367
344
  if (active.timer) clearTimeout(active.timer);
368
- this._active.delete(interactionId);
345
+ this._active.delete(operationId);
369
346
  this._rememberEnded(active, reason);
370
347
  this._onEnded?.(active, reason);
371
348
  return active;
372
349
  }
373
- endByConnectionKey(connectionKey, reason, exceptInteractionId) {
350
+ endByConnectionKey(connectionKey, reason, exceptOperationId) {
374
351
  if (!connectionKey) return;
375
- for (const interaction of [...this._active.values()]) {
376
- if (interaction.interactionId === exceptInteractionId) continue;
377
- if (interaction.connectionKeys.includes(connectionKey)) {
378
- this.end(interaction.interactionId, reason);
352
+ for (const operation of [...this._active.values()]) {
353
+ if (operation.operationId === exceptOperationId) continue;
354
+ if (operation.connectionKeys.includes(connectionKey)) {
355
+ this.end(operation.operationId, reason);
379
356
  }
380
357
  }
381
358
  }
382
- endAll(reason, exceptInteractionId) {
383
- for (const interactionId of [...this._active.keys()]) {
384
- if (interactionId === exceptInteractionId) continue;
385
- this.end(interactionId, reason);
359
+ endAll(reason, exceptOperationId) {
360
+ for (const operationId of [...this._active.keys()]) {
361
+ if (operationId === exceptOperationId) continue;
362
+ this.end(operationId, reason);
386
363
  }
387
364
  }
388
- _createTimer(interactionId) {
365
+ _createTimer(operationId) {
389
366
  const timer = setTimeout(() => {
390
- this.end(interactionId, "timeout");
367
+ this.end(operationId, "timeout");
391
368
  }, this._ttlMs);
392
369
  timer.unref?.();
393
370
  return timer;
394
371
  }
395
- _rememberEnded(interaction, reason) {
396
- this._ended.set(interaction.interactionId, { reason, interaction });
372
+ _rememberEnded(operation, reason) {
373
+ this._ended.set(operation.operationId, { reason, operation });
397
374
  if (this._ended.size <= 100) return;
398
375
  const oldest = this._ended.keys().next().value;
399
376
  if (oldest) this._ended.delete(oldest);
@@ -401,40 +378,40 @@ var InteractionRegistry = class {
401
378
  };
402
379
 
403
380
  // src/utils/hardwareOperationTarget.ts
404
- function resolveHardwareOperationTarget(positionalTargetId, commonInteractionId, expectedVendor) {
381
+ function resolveHardwareOperationTarget(positionalTargetId, commonOperationId, expectedVendor) {
405
382
  const normalizedPositionalTargetId = positionalTargetId ?? void 0;
406
- const normalizedCommonInteractionId = commonInteractionId || void 0;
407
- const positionalInteractionId = isHardwareInteractionId(normalizedPositionalTargetId) ? normalizedPositionalTargetId : void 0;
383
+ const normalizedCommonOperationId = commonOperationId || void 0;
384
+ const positionalOperationId = isHardwareOperationId(normalizedPositionalTargetId) ? normalizedPositionalTargetId : void 0;
408
385
  const parsedPositionalTarget = parseHardwareRuntimeId(normalizedPositionalTargetId);
409
- const parsedCommonInteraction = parseHardwareRuntimeId(normalizedCommonInteractionId);
386
+ const parsedCommonOperation = parseHardwareRuntimeId(normalizedCommonOperationId);
410
387
  if (normalizedPositionalTargetId && hasHardwareRuntimeIdPrefix(normalizedPositionalTargetId) && !parsedPositionalTarget) {
411
388
  return failure(10002 /* InvalidParams */, "Invalid hardware operation target id");
412
389
  }
413
- if (parsedPositionalTarget?.kind === "connector-session") {
390
+ if (parsedPositionalTarget?.kind === "link") {
414
391
  return failure(
415
392
  10002 /* InvalidParams */,
416
- "Hardware connector session id cannot be used as an operation target"
393
+ "Hardware transport link id cannot be used as an operation target"
417
394
  );
418
395
  }
419
- if (normalizedCommonInteractionId && parsedCommonInteraction?.kind !== "interaction") {
420
- return failure(10002 /* InvalidParams */, "Invalid hardware interaction id");
396
+ if (normalizedCommonOperationId && parsedCommonOperation?.kind !== "operation") {
397
+ return failure(10002 /* InvalidParams */, "Invalid hardware operation id");
421
398
  }
422
- if (expectedVendor && (parsedPositionalTarget && parsedPositionalTarget.vendor !== expectedVendor || parsedCommonInteraction && parsedCommonInteraction.vendor !== expectedVendor)) {
399
+ if (expectedVendor && (parsedPositionalTarget && parsedPositionalTarget.vendor !== expectedVendor || parsedCommonOperation && parsedCommonOperation.vendor !== expectedVendor)) {
423
400
  return failure(
424
401
  10002 /* InvalidParams */,
425
- `Hardware interaction does not belong to ${expectedVendor}`
402
+ `Hardware operation does not belong to ${expectedVendor}`
426
403
  );
427
404
  }
428
- if (positionalInteractionId && normalizedCommonInteractionId && positionalInteractionId !== normalizedCommonInteractionId) {
429
- return failure(10002 /* InvalidParams */, "Conflicting hardware interaction ids", {
430
- positionalInteractionId,
431
- commonInteractionId: normalizedCommonInteractionId
405
+ if (positionalOperationId && normalizedCommonOperationId && positionalOperationId !== normalizedCommonOperationId) {
406
+ return failure(10002 /* InvalidParams */, "Conflicting hardware operation ids", {
407
+ positionalOperationId,
408
+ commonOperationId: normalizedCommonOperationId
432
409
  });
433
410
  }
434
- const interactionId = normalizedCommonInteractionId ?? positionalInteractionId;
411
+ const operationId = normalizedCommonOperationId ?? positionalOperationId;
435
412
  return success({
436
- interactionId,
437
- targetId: interactionId ?? normalizedPositionalTargetId
413
+ operationId,
414
+ targetId: operationId ?? normalizedPositionalTargetId
438
415
  });
439
416
  }
440
417
 
@@ -815,7 +792,16 @@ var TypedEventEmitter = class {
815
792
  // src/utils/requestSaveDeviceBinding.ts
816
793
  async function requestSaveDeviceBinding(emitter, registry, binding, signal) {
817
794
  const type = UI_REQUEST.REQUEST_SAVE_DEVICE_BINDING;
818
- if (!emitter.listenerCount(type)) return false;
795
+ const declined = (reason) => {
796
+ emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
797
+ type: UI_REQUEST.DEVICE_BINDING_STATUS,
798
+ payload: { selectionRequestId: binding.selectionRequestId, status: "failed" }
799
+ });
800
+ return { saved: false, reason };
801
+ };
802
+ if (!emitter.listenerCount(type)) {
803
+ return declined("skipped");
804
+ }
819
805
  if (signal?.aborted) throw signal.reason;
820
806
  const requestId = registry.createRequestId();
821
807
  const pending = registry.wait(type, { requestId });
@@ -827,16 +813,13 @@ async function requestSaveDeviceBinding(emitter, registry, binding, signal) {
827
813
  const response = await pending;
828
814
  if (signal?.aborted) throw signal.reason;
829
815
  if (response?.saved !== true) {
830
- throw createHwkError({
831
- code: 1e4 /* UnknownError */,
832
- message: "Verified device binding could not be saved"
833
- });
816
+ return declined(response?.reason ?? "skipped");
834
817
  }
835
818
  emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
836
819
  type: UI_REQUEST.DEVICE_BINDING_STATUS,
837
820
  payload: { selectionRequestId: binding.selectionRequestId, status: "saved" }
838
821
  });
839
- return true;
822
+ return { saved: true };
840
823
  } catch (error) {
841
824
  emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
842
825
  type: UI_REQUEST.DEVICE_BINDING_STATUS,
@@ -845,7 +828,8 @@ async function requestSaveDeviceBinding(emitter, registry, binding, signal) {
845
828
  status: signal?.aborted ? "cancelled" : "failed"
846
829
  }
847
830
  });
848
- throw error;
831
+ if (signal?.aborted) throw error;
832
+ return { saved: false, reason: "skipped" };
849
833
  } finally {
850
834
  signal?.removeEventListener("abort", cancel);
851
835
  registry.cancel(type, requestId);
@@ -859,6 +843,7 @@ async function requestBleDeviceSelection({
859
843
  request,
860
844
  scan,
861
845
  signal,
846
+ allowUsbFallback = false,
862
847
  pollIntervalMs = 1500
863
848
  }) {
864
849
  const type = UI_REQUEST.REQUEST_SELECT_DEVICE;
@@ -870,7 +855,8 @@ async function requestBleDeviceSelection({
870
855
  });
871
856
  }
872
857
  const requestId = registry.createRequestId();
873
- let { devices } = request;
858
+ let devices = allowUsbFallback ? request.devices.filter((device) => device.connectionType === "ble") : request.devices;
859
+ const publishedDevices = /* @__PURE__ */ new Map();
874
860
  let stopped = false;
875
861
  let timer;
876
862
  let wake;
@@ -887,10 +873,13 @@ async function requestBleDeviceSelection({
887
873
  const cancel = () => registry.cancel(type, requestId);
888
874
  const reply = registry.wait(type, { requestId }).finally(stop);
889
875
  void reply.catch(() => void 0);
890
- const publish = () => emitter.emit(type, {
891
- type,
892
- payload: { ...request, devices, requestId, scanning: true }
893
- });
876
+ const publish = () => {
877
+ for (const device of devices) publishedDevices.set(device.connectId, device);
878
+ emitter.emit(type, {
879
+ type,
880
+ payload: { ...request, devices, requestId, scanning: true }
881
+ });
882
+ };
894
883
  signal.addEventListener("abort", cancel, { once: true });
895
884
  let polling;
896
885
  try {
@@ -900,17 +889,22 @@ async function requestBleDeviceSelection({
900
889
  while (!stopped) {
901
890
  const snapshot = await scan();
902
891
  if (stopped || signal.aborted) return;
892
+ const usbFallback = allowUsbFallback ? snapshot.find((device2) => device2.connectionType === "usb") : void 0;
893
+ if (usbFallback) return usbFallback;
903
894
  devices = snapshot;
904
895
  publish();
905
896
  if (stopped) return;
906
897
  await waitForNextScan();
907
898
  }
908
899
  })();
909
- const selected = await Promise.race([reply, polling.then(() => reply)]);
900
+ const userSelection = reply.then((selected) => publishedDevices.get(selected.sdkConnectId));
901
+ const device = await Promise.race([
902
+ userSelection,
903
+ polling.then((usbFallback) => usbFallback ?? userSelection)
904
+ ]);
910
905
  stop();
911
906
  await polling;
912
907
  if (signal.aborted) throw signal.reason;
913
- const device = devices.find((candidate) => candidate.connectId === selected.sdkConnectId);
914
908
  if (!device) {
915
909
  throw createHwkError({
916
910
  code: 10100 /* DeviceNotFound */,
@@ -1253,9 +1247,9 @@ export {
1253
1247
  HARDWARE_METHOD_CATALOG,
1254
1248
  HARDWARE_RUNTIME_ID_PREFIX,
1255
1249
  HardwareErrorCode,
1256
- INTERACTION_DEFAULT_TTL_MS,
1257
- InteractionRegistry,
1250
+ OPERATION_DEFAULT_TTL_MS,
1258
1251
  ORPHAN_ELIGIBLE_ERROR_CODES,
1252
+ OperationRegistry,
1259
1253
  SDK,
1260
1254
  TypedEventEmitter,
1261
1255
  UI_EVENT,
@@ -1273,8 +1267,8 @@ export {
1273
1267
  compareSemver,
1274
1268
  createBridgedConnector,
1275
1269
  createCombinedConnector,
1276
- createHardwareConnectorSessionId,
1277
- createHardwareInteractionId,
1270
+ createHardwareLinkId,
1271
+ createHardwareOperationId,
1278
1272
  createHardwareSearchTargetId,
1279
1273
  createHwkError,
1280
1274
  defaultOriginForCode,
@@ -1290,8 +1284,7 @@ export {
1290
1284
  hasHardwareRuntimeIdPrefix,
1291
1285
  hexToBytes,
1292
1286
  isAllNetworkMethodName,
1293
- isHardwareInteractionId,
1294
- isHardwareSearchTargetId,
1287
+ isHardwareOperationId,
1295
1288
  isHwkRecoveryHint,
1296
1289
  isKnownNonTargetHardwareVendor,
1297
1290
  operationMayHaveCompletedParams,