@onekeyfe/hd-transport 1.2.2-alpha.100 → 1.2.2-alpha.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-transport",
3
- "version": "1.2.2-alpha.100",
3
+ "version": "1.2.2-alpha.2",
4
4
  "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -28,5 +28,5 @@
28
28
  "long": "^4.0.0",
29
29
  "protobufjs": "^6.11.2"
30
30
  },
31
- "gitHead": "c40dad085297b0e3cc2020bd218c146dff6ed3e1"
31
+ "gitHead": "fc914d0361028f1012ca764e53f996b9893b40f9"
32
32
  }
@@ -1,6 +1,7 @@
1
1
  import { ProtocolV2SequenceCursor } from './sequence-cursor';
2
2
  import { ProtocolV2LinkError } from './errors';
3
3
  import { ProtocolV2Session, getErrorMessage } from './session';
4
+ import { PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS } from '../../constants';
4
5
 
5
6
  import type { MessageFromOneKey, TransportCallOptions } from '../../types';
6
7
  import type { ProtocolV2CallContext, ProtocolV2Schemas, ProtocolV2SessionOptions } from './session';
@@ -61,15 +62,45 @@ export class ProtocolV2LinkManager<Key> {
61
62
  options?: TransportCallOptions
62
63
  ): Promise<MessageFromOneKey> {
63
64
  const generation = this.generations.get(key) ?? 0;
65
+ const previousQueue = this.callQueues.get(key);
66
+ const timeoutMs =
67
+ options?.timeoutMs && options.timeoutMs > 0
68
+ ? options.timeoutMs
69
+ : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
70
+ const queuedAt = Date.now();
71
+ let queueTimeout: ReturnType<typeof setTimeout> | undefined;
72
+ let queueTimeoutError: Error | undefined;
73
+ const timeoutWhileQueued = new Promise<never>((_, reject) => {
74
+ queueTimeout = setTimeout(() => {
75
+ queueTimeout = undefined;
76
+ queueTimeoutError = this.createQueuedCallTimeoutError(key, name, timeoutMs);
77
+ reject(queueTimeoutError);
78
+ }, timeoutMs);
79
+ });
64
80
  const run = () => {
81
+ if (queueTimeout) {
82
+ clearTimeout(queueTimeout);
83
+ queueTimeout = undefined;
84
+ }
85
+ if (queueTimeoutError) {
86
+ throw queueTimeoutError;
87
+ }
65
88
  this.assertCallGeneration(key, generation);
66
- return this.executeCall(key, createAdapter, name, data, options);
89
+ const remainingTimeoutMs = previousQueue ? timeoutMs - (Date.now() - queuedAt) : timeoutMs;
90
+ if (remainingTimeoutMs <= 0) {
91
+ throw this.createQueuedCallTimeoutError(key, name, timeoutMs);
92
+ }
93
+ return this.executeCall(key, createAdapter, name, data, {
94
+ ...options,
95
+ timeoutMs: remainingTimeoutMs,
96
+ });
67
97
  };
68
- const previous = this.callQueues.get(key) ?? Promise.resolve();
69
- const result = previous.then(run, run);
70
- const queue = result.catch(() => undefined);
98
+ const previous = previousQueue ?? Promise.resolve();
99
+ const execution = previous.then(run, run);
100
+ const result = Promise.race([execution, timeoutWhileQueued]);
101
+ const queue = execution.catch(() => undefined);
71
102
  this.callQueues.set(key, queue);
72
- result
103
+ execution
73
104
  .then(
74
105
  () => this.clearSettledCallQueue(key, queue),
75
106
  () => this.clearSettledCallQueue(key, queue)
@@ -206,6 +237,16 @@ export class ProtocolV2LinkManager<Key> {
206
237
  }
207
238
  }
208
239
 
240
+ private createQueuedCallTimeoutError(key: Key, name: string, timeoutMs: number): Error {
241
+ const adapterTimeoutError = this.links.get(key)?.adapter.createTimeoutError;
242
+ return adapterTimeoutError
243
+ ? adapterTimeoutError(name, timeoutMs)
244
+ : new ProtocolV2LinkError(
245
+ 'response-timeout',
246
+ `Protocol V2 call timeout after ${timeoutMs}ms while queued for ${name}`
247
+ );
248
+ }
249
+
209
250
  private assertCallGeneration(key: Key, generation: number) {
210
251
  const currentGeneration = this.generations.get(key) ?? 0;
211
252
  if (currentGeneration === generation) return;
@@ -60,6 +60,7 @@ export type ProtocolV2CallOptions = {
60
60
  onIntermediateResponse?: (response: MessageFromOneKey) => void;
61
61
  onWriteCompleted?: (metrics: TransportWriteMetrics) => void;
62
62
  returnAfterWrite?: boolean;
63
+ onResponseAfterWrite?: (response: MessageFromOneKey) => void;
63
64
  writeWithResponse?: boolean;
64
65
  };
65
66
 
@@ -81,6 +82,33 @@ export function hexToBytes(hex: string): Uint8Array {
81
82
  return bytes;
82
83
  }
83
84
 
85
+ export function detectProtocolV2LinkDisabledError({
86
+ schemas,
87
+ assembler,
88
+ bytes,
89
+ }: {
90
+ schemas: ProtocolV2Schemas;
91
+ assembler: ProtocolV2FrameAssembler;
92
+ bytes: Uint8Array;
93
+ }) {
94
+ try {
95
+ for (const frame of assembler.drain(bytes)) {
96
+ const response = check.call(ProtocolV2.decodeFrame(schemas, frame));
97
+ if (response.type === 'Failure') {
98
+ const failureCode = response.message?.code;
99
+ const firmwareMessage = response.message?.message;
100
+ if (isProtocolV2LinkDisabledFailure(failureCode, firmwareMessage)) {
101
+ return createProtocolV2LinkDisabledError(failureCode, firmwareMessage);
102
+ }
103
+ }
104
+ }
105
+ } catch {
106
+ // Cross-protocol detection may receive a normal Protocol V1 notification.
107
+ assembler.reset();
108
+ }
109
+ return undefined;
110
+ }
111
+
84
112
  export function bytesToHex(bytes: Uint8Array): string {
85
113
  return Array.from(bytes)
86
114
  .map(b => b.toString(16).padStart(2, '0'))
@@ -199,6 +227,11 @@ export class ProtocolV2Session {
199
227
  // response, but their frames must never interleave with the active request write.
200
228
  private pendingWrite: Promise<unknown> = Promise.resolve();
201
229
 
230
+ private pendingResponseAfterWrite?: {
231
+ expectedTypes: Set<string>;
232
+ onResponse?: (response: MessageFromOneKey) => void;
233
+ };
234
+
202
235
  private lastResponseSequence?: number;
203
236
 
204
237
  constructor(options: ProtocolV2SessionOptions) {
@@ -297,7 +330,11 @@ export class ProtocolV2Session {
297
330
  };
298
331
 
299
332
  const runCall = async (): Promise<MessageFromOneKey> => {
300
- await prepareCall?.(baseCallContext);
333
+ // A write-only call may still have a terminal response in flight. Preserve
334
+ // the continuous receive queue until the next call consumes that response.
335
+ if (!this.pendingResponseAfterWrite) {
336
+ await prepareCall?.(baseCallContext);
337
+ }
301
338
  const protoSeq = this.sequenceCursor.next();
302
339
  const frame = ProtocolV2.encodeFrame(schemas, name, data, {
303
340
  packetSrc,
@@ -323,6 +360,12 @@ export class ProtocolV2Session {
323
360
  }
324
361
 
325
362
  if (callOptions.returnAfterWrite) {
363
+ if (callOptions.expectedTypes?.length) {
364
+ this.pendingResponseAfterWrite = {
365
+ expectedTypes: new Set(callOptions.expectedTypes),
366
+ onResponse: callOptions.onResponseAfterWrite,
367
+ };
368
+ }
326
369
  return { type: 'WriteCompleted', message: {} };
327
370
  }
328
371
 
@@ -395,7 +438,26 @@ export class ProtocolV2Session {
395
438
  this.lastResponseSequence = decoded.seq;
396
439
 
397
440
  const response = check.call(decoded);
398
- if (callOptions.intermediateTypes?.includes(response.type)) {
441
+ const { pendingResponseAfterWrite } = this;
442
+ let belongsToWriteOnlyCall = false;
443
+ if (pendingResponseAfterWrite) {
444
+ belongsToWriteOnlyCall = pendingResponseAfterWrite.expectedTypes.has(response.type);
445
+ if (belongsToWriteOnlyCall || COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
446
+ this.pendingResponseAfterWrite = undefined;
447
+ if (belongsToWriteOnlyCall) {
448
+ try {
449
+ pendingResponseAfterWrite.onResponse?.(response);
450
+ } catch (error) {
451
+ logger?.error?.(
452
+ `${logPrefix} delayed response callback failed: ${String(error)}`
453
+ );
454
+ }
455
+ }
456
+ }
457
+ }
458
+ if (belongsToWriteOnlyCall) {
459
+ // The delayed response belongs to the preceding write-only call.
460
+ } else if (callOptions.intermediateTypes?.includes(response.type)) {
399
461
  callOptions.onIntermediateResponse?.(response);
400
462
  } else if (isExpectedTerminalResponse(response, callOptions.expectedTypes)) {
401
463
  return response;
@@ -433,6 +495,7 @@ export async function probeProtocolV2({
433
495
  logPrefix = 'ProtocolV2',
434
496
  onBeforeProbe,
435
497
  onProbeFailed,
498
+ shouldRethrow,
436
499
  }: {
437
500
  call: (
438
501
  name: string,
@@ -444,6 +507,7 @@ export async function probeProtocolV2({
444
507
  logPrefix?: string;
445
508
  onBeforeProbe?: () => Promise<void> | void;
446
509
  onProbeFailed?: (error: unknown) => Promise<void> | void;
510
+ shouldRethrow?: (error: unknown) => boolean;
447
511
  }) {
448
512
  let probeError: unknown;
449
513
  try {
@@ -468,7 +532,7 @@ export async function probeProtocolV2({
468
532
  }
469
533
  probeError = new Error(`unexpected response type ${response.type}`);
470
534
  } catch (error) {
471
- if (isProtocolV2LinkDisabledError(error)) {
535
+ if (isProtocolV2LinkDisabledError(error) || shouldRethrow?.(error)) {
472
536
  throw error;
473
537
  }
474
538
  probeError = error;
@@ -1950,6 +1950,8 @@ export type EthereumSignTxOneKey = {
1950
1950
  data_length?: number;
1951
1951
  chain_id: number;
1952
1952
  tx_type?: number;
1953
+ expected_address?: string;
1954
+ source_fingerprint?: number;
1953
1955
  };
1954
1956
 
1955
1957
  // EthereumAccessListOneKey
@@ -1971,6 +1973,8 @@ export type EthereumSignTxEIP1559OneKey = {
1971
1973
  data_length: number;
1972
1974
  chain_id: number;
1973
1975
  access_list: EthereumAccessListOneKey[];
1976
+ expected_address?: string;
1977
+ source_fingerprint?: number;
1974
1978
  };
1975
1979
 
1976
1980
  // EthereumAuthorizationSignature
@@ -2023,6 +2027,7 @@ export type EthereumSignMessageOneKey = {
2023
2027
  address_n: number[];
2024
2028
  message: string;
2025
2029
  chain_id?: number;
2030
+ source_fingerprint?: number;
2026
2031
  };
2027
2032
 
2028
2033
  // EthereumMessageSignatureOneKey
@@ -3815,6 +3820,7 @@ export type SolanaSignTx = {
3815
3820
  address_n: number[];
3816
3821
  raw_tx: string;
3817
3822
  extra_info?: SolanaTxExtraInfo;
3823
+ source_fingerprint?: number;
3818
3824
  };
3819
3825
 
3820
3826
  // SolanaSignedTx
@@ -3838,12 +3844,14 @@ export type SolanaSignOffChainMessage = {
3838
3844
  message_version?: SolanaOffChainMessageVersion;
3839
3845
  message_format?: SolanaOffChainMessageFormat;
3840
3846
  application_domain?: string;
3847
+ source_fingerprint?: number;
3841
3848
  };
3842
3849
 
3843
3850
  // SolanaSignUnsafeMessage
3844
3851
  export type SolanaSignUnsafeMessage = {
3845
3852
  address_n: number[];
3846
3853
  message: string;
3854
+ source_fingerprint?: number;
3847
3855
  };
3848
3856
 
3849
3857
  // SolanaMessageSignature
@@ -4550,12 +4558,6 @@ export enum CommandFlags {
4550
4558
  Factory_Only = 1,
4551
4559
  }
4552
4560
 
4553
- // experimental_message
4554
- export type experimental_message = {};
4555
-
4556
- // experimental_field
4557
- export type experimental_field = {};
4558
-
4559
4561
  export type TextMemo = {
4560
4562
  text: string;
4561
4563
  };
@@ -4594,6 +4596,7 @@ export type EthereumSignTypedDataQR = {
4594
4596
  chain_id?: number;
4595
4597
  metamask_v4_compat?: boolean;
4596
4598
  request_id?: string;
4599
+ source_fingerprint?: number;
4597
4600
  };
4598
4601
 
4599
4602
  // SetBusy
@@ -4660,6 +4663,12 @@ export type UiAnimationRequest = {
4660
4663
  type?: UiAnimationType;
4661
4664
  };
4662
4665
 
4666
+ // experimental_message
4667
+ export type experimental_message = {};
4668
+
4669
+ // experimental_field
4670
+ export type experimental_field = {};
4671
+
4663
4672
  // ProtocolInfoRequest
4664
4673
  export type ProtocolInfoRequest = {
4665
4674
  eventless_wallet_session?: boolean;
@@ -4766,6 +4775,19 @@ export type DeviceMiscUsbMscControl = {
4766
4775
  enable: boolean;
4767
4776
  };
4768
4777
 
4778
+ // DeviceFindMyTokenUpdate
4779
+ export type DeviceFindMyTokenUpdate = {
4780
+ token: string;
4781
+ };
4782
+
4783
+ // DeviceFindMyTokenStateGet
4784
+ export type DeviceFindMyTokenStateGet = {};
4785
+
4786
+ // DeviceFindMyTokenState
4787
+ export type DeviceFindMyTokenState = {
4788
+ burned: boolean;
4789
+ };
4790
+
4769
4791
  export enum DeviceFactoryAck {
4770
4792
  FACTORY_ACK_SUCCESS = 0,
4771
4793
  FACTORY_ACK_FAIL = 1,
@@ -4837,6 +4859,12 @@ export enum DeviceFirmwareUpdateTaskStatus {
4837
4859
  FW_MGMT_UPDATER_TASK_STATUS_FAILED_ENTRY_OUT_OF_BOUNDS = 10,
4838
4860
  }
4839
4861
 
4862
+ export enum DeviceFirmwareUpdatePhase {
4863
+ FW_MGMT_UPDATER_PHASE_PREPARE = 0,
4864
+ FW_MGMT_UPDATER_PHASE_INSTALL = 1,
4865
+ FW_MGMT_UPDATER_PHASE_VERIFY = 2,
4866
+ }
4867
+
4840
4868
  // DeviceFirmwareTarget
4841
4869
  export type DeviceFirmwareTarget = {
4842
4870
  target_id: DeviceFirmwareTargetType;
@@ -4849,12 +4877,22 @@ export type DeviceFirmwareUpdateStage = {
4849
4877
  };
4850
4878
 
4851
4879
  // DeviceFirmwareUpdateRequest
4852
- export type DeviceFirmwareUpdateRequest = {};
4880
+ export type DeviceFirmwareUpdateRequest = {
4881
+ reboot_after_update?: boolean;
4882
+ };
4883
+
4884
+ // DeviceFirmwareUpdatePhaseInfo
4885
+ export type DeviceFirmwareUpdatePhaseInfo = {
4886
+ phase: DeviceFirmwareUpdatePhase;
4887
+ progress_percent: number;
4888
+ };
4853
4889
 
4854
4890
  // DeviceFirmwareUpdateRecord
4855
4891
  export type DeviceFirmwareUpdateRecord = {
4856
4892
  target_id: DeviceFirmwareTargetType;
4857
4893
  status?: DeviceFirmwareUpdateTaskStatus;
4894
+ progress_percent?: number;
4895
+ phase_info?: DeviceFirmwareUpdatePhaseInfo;
4858
4896
  payload_version?: number;
4859
4897
  path?: string;
4860
4898
  };
@@ -4862,6 +4900,8 @@ export type DeviceFirmwareUpdateRecord = {
4862
4900
  // DeviceFirmwareUpdateRecordFields
4863
4901
  export type DeviceFirmwareUpdateRecordFields = {
4864
4902
  status?: boolean;
4903
+ progress_percent?: boolean;
4904
+ phase_info?: boolean;
4865
4905
  payload_version?: boolean;
4866
4906
  path?: boolean;
4867
4907
  };
@@ -4992,7 +5032,6 @@ export enum DeviceSessionSeedDomain {
4992
5032
  export type DeviceSessionGet = {
4993
5033
  session_id?: string;
4994
5034
  btc_test_address?: string;
4995
- seed_domains: DeviceSessionSeedDomain[];
4996
5035
  };
4997
5036
 
4998
5037
  // DeviceSession
@@ -5016,6 +5055,7 @@ export type DeviceSessionAskPin = {
5016
5055
  export type DeviceSessionAskPassphrase = {
5017
5056
  passphrase?: string;
5018
5057
  on_device: boolean;
5058
+ seed_domains: DeviceSessionSeedDomain[];
5019
5059
  };
5020
5060
 
5021
5061
  export enum DeviceSessionAskPin_FailureSubCodes {
@@ -5207,6 +5247,12 @@ export type ViewDetail = {
5207
5247
  has_icon: boolean;
5208
5248
  };
5209
5249
 
5250
+ // ViewCustomField
5251
+ export type ViewCustomField = {
5252
+ key: string;
5253
+ value: string;
5254
+ };
5255
+
5210
5256
  export enum ViewTipType {
5211
5257
  Default = 0,
5212
5258
  Highlight = 1,
@@ -5220,12 +5266,33 @@ export type ViewTip = {
5220
5266
  type: ViewTipType;
5221
5267
  text?: string;
5222
5268
  text_id?: number;
5269
+ text_arg?: string;
5223
5270
  };
5224
5271
 
5225
- // ViewRawData
5226
- export type ViewRawData = {
5272
+ // ViewActionCard
5273
+ export type ViewActionCard = {
5227
5274
  initial_data: string;
5228
- placeholder: number;
5275
+ };
5276
+
5277
+ // ViewContentPreview
5278
+ export type ViewContentPreview = {
5279
+ content_key: number;
5280
+ preview: string;
5281
+ total_bytes?: number;
5282
+ };
5283
+
5284
+ // ViewContentEntry
5285
+ export type ViewContentEntry = {
5286
+ entry_key: number;
5287
+ value: string;
5288
+ };
5289
+
5290
+ // ViewContentPage
5291
+ export type ViewContentPage = {
5292
+ page_index: number;
5293
+ page_count: number;
5294
+ chunk?: string;
5295
+ entry?: ViewContentEntry;
5229
5296
  };
5230
5297
 
5231
5298
  export enum ViewSignLayout {
@@ -5243,10 +5310,21 @@ export type ViewSignPage = {
5243
5310
  amount?: UintType;
5244
5311
  general: ViewDetail[];
5245
5312
  tip?: ViewTip;
5246
- raw_data?: ViewRawData;
5313
+ action_card?: ViewActionCard;
5247
5314
  slide_to_confirm?: boolean;
5248
5315
  layout?: ViewSignLayout;
5249
5316
  title_id?: number;
5317
+ title_arg?: string;
5318
+ content?: ViewContentPreview;
5319
+ custom_field?: ViewCustomField;
5320
+ };
5321
+
5322
+ // ViewWarningPage
5323
+ export type ViewWarningPage = {
5324
+ title_id: number;
5325
+ text_id: number;
5326
+ text_arg?: string;
5327
+ cancellable?: boolean;
5250
5328
  };
5251
5329
 
5252
5330
  // ViewVerifyPage
@@ -5259,6 +5337,7 @@ export type ViewVerifyPage = {
5259
5337
  value_key?: number;
5260
5338
  title_id?: number;
5261
5339
  chain_id?: number;
5340
+ content?: ViewContentPreview;
5262
5341
  };
5263
5342
 
5264
5343
  export enum ProtocolV2FailureType {
@@ -5858,8 +5937,6 @@ export type MessageType = {
5858
5937
  TronSignMessage: TronSignMessage;
5859
5938
  TronMessageSignature: TronMessageSignature;
5860
5939
  facotry: facotry;
5861
- experimental_message: experimental_message;
5862
- experimental_field: experimental_field;
5863
5940
  TextMemo: TextMemo;
5864
5941
  RefundMemo: RefundMemo;
5865
5942
  CoinPurchaseMemo: CoinPurchaseMemo;
@@ -5875,6 +5952,8 @@ export type MessageType = {
5875
5952
  UnlockPath: UnlockPath;
5876
5953
  UnlockedPathRequest: UnlockedPathRequest;
5877
5954
  UiAnimationRequest: UiAnimationRequest;
5955
+ experimental_message: experimental_message;
5956
+ experimental_field: experimental_field;
5878
5957
  ProtocolInfoRequest: ProtocolInfoRequest;
5879
5958
  ProtocolInfo: ProtocolInfo;
5880
5959
  DeviceReboot: DeviceReboot;
@@ -5888,6 +5967,9 @@ export type MessageType = {
5888
5967
  DeviceCertificateSignature: DeviceCertificateSignature;
5889
5968
  DeviceCertificateSign: DeviceCertificateSign;
5890
5969
  DeviceMiscUsbMscControl: DeviceMiscUsbMscControl;
5970
+ DeviceFindMyTokenUpdate: DeviceFindMyTokenUpdate;
5971
+ DeviceFindMyTokenStateGet: DeviceFindMyTokenStateGet;
5972
+ DeviceFindMyTokenState: DeviceFindMyTokenState;
5891
5973
  DeviceFactoryInfoManufactureTime: DeviceFactoryInfoManufactureTime;
5892
5974
  DeviceFactoryInfo: DeviceFactoryInfo;
5893
5975
  DeviceFactoryInfoSet: DeviceFactoryInfoSet;
@@ -5897,6 +5979,7 @@ export type MessageType = {
5897
5979
  DeviceFirmwareTarget: DeviceFirmwareTarget;
5898
5980
  DeviceFirmwareUpdateStage: DeviceFirmwareUpdateStage;
5899
5981
  DeviceFirmwareUpdateRequest: DeviceFirmwareUpdateRequest;
5982
+ DeviceFirmwareUpdatePhaseInfo: DeviceFirmwareUpdatePhaseInfo;
5900
5983
  DeviceFirmwareUpdateRecord: DeviceFirmwareUpdateRecord;
5901
5984
  DeviceFirmwareUpdateRecordFields: DeviceFirmwareUpdateRecordFields;
5902
5985
  DeviceFirmwareUpdateStatusGet: DeviceFirmwareUpdateStatusGet;
@@ -5935,9 +6018,14 @@ export type MessageType = {
5935
6018
  PortfolioUpdate: PortfolioUpdate;
5936
6019
  ViewAmount: ViewAmount;
5937
6020
  ViewDetail: ViewDetail;
6021
+ ViewCustomField: ViewCustomField;
5938
6022
  ViewTip: ViewTip;
5939
- ViewRawData: ViewRawData;
6023
+ ViewActionCard: ViewActionCard;
6024
+ ViewContentPreview: ViewContentPreview;
6025
+ ViewContentEntry: ViewContentEntry;
6026
+ ViewContentPage: ViewContentPage;
5940
6027
  ViewSignPage: ViewSignPage;
6028
+ ViewWarningPage: ViewWarningPage;
5941
6029
  ViewVerifyPage: ViewVerifyPage;
5942
6030
  };
5943
6031
 
@@ -59,6 +59,8 @@ export type AcquireInput = {
59
59
  * transport must probe the protocol on the wire, bypassing any cached result.
60
60
  */
61
61
  forceProtocolDetection?: boolean;
62
+ /** Reuse expectedProtocol only when this transport previously confirmed it for the same endpoint. */
63
+ skipProtocolProbe?: boolean;
62
64
  };
63
65
 
64
66
  export type MessageFromOneKey = { type: string; message: Record<string, any> };
@@ -77,6 +79,8 @@ export type TransportCallOptions = {
77
79
  onWriteCompleted?: (metrics: TransportWriteMetrics) => void;
78
80
  /** Resolve after the complete request frame is written without waiting for a response. */
79
81
  returnAfterWrite?: boolean;
82
+ /** Observe the delayed terminal response of a write-only call while a later call is active. */
83
+ onResponseAfterWrite?: (response: MessageFromOneKey) => void;
80
84
  /** Prefer acknowledged BLE characteristic writes for this call when supported. */
81
85
  writeWithResponse?: boolean;
82
86
  };