@onekeyfe/hd-core 1.1.27-alpha.42 → 1.1.27-alpha.45
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/__tests__/DeviceCommands.test.ts +99 -0
- package/__tests__/evmLedgerLegacySafety.test.ts +261 -0
- package/__tests__/preInitialize.test.ts +22 -0
- package/__tests__/protocol-v2.test.ts +233 -20
- package/dist/api/BaseMethod.d.ts +7 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/alephium/AlephiumSignMessage.d.ts.map +1 -1
- package/dist/api/alephium/AlephiumSignTransaction.d.ts.map +1 -1
- package/dist/api/algo/AlgoSignTransaction.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/aptos/AptosSignInMessage.d.ts.map +1 -1
- package/dist/api/aptos/AptosSignMessage.d.ts.map +1 -1
- package/dist/api/aptos/AptosSignTransaction.d.ts.map +1 -1
- package/dist/api/benfen/BenfenSignMessage.d.ts.map +1 -1
- package/dist/api/benfen/BenfenSignTransaction.d.ts.map +1 -1
- package/dist/api/btc/BTCSignMessage.d.ts.map +1 -1
- package/dist/api/btc/BTCSignPsbt.d.ts.map +1 -1
- package/dist/api/btc/BTCSignTransaction.d.ts.map +1 -1
- package/dist/api/cardano/CardanoSignMessage.d.ts.map +1 -1
- package/dist/api/cardano/CardanoSignTransaction.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
- package/dist/api/cosmos/CosmosSignTransaction.d.ts.map +1 -1
- package/dist/api/device/PreInitialize.d.ts +6 -0
- package/dist/api/device/PreInitialize.d.ts.map +1 -0
- package/dist/api/dynex/DnxSignTransaction.d.ts.map +1 -1
- package/dist/api/evm/EVMSignMessage.d.ts.map +1 -1
- package/dist/api/evm/EVMSignMessageEIP712.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTransaction.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/filecoin/FilecoinSignTransaction.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -10
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/near/NearSignTransaction.d.ts.map +1 -1
- package/dist/api/nem/NEMSignTransaction.d.ts.map +1 -1
- package/dist/api/neo/NeoSignTransaction.d.ts.map +1 -1
- package/dist/api/nervos/NervosSignTransaction.d.ts.map +1 -1
- package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
- package/dist/api/nostr/NostrSignEvent.d.ts.map +1 -1
- package/dist/api/nostr/NostrSignSchnorr.d.ts.map +1 -1
- package/dist/api/polkadot/PolkadotSignTransaction.d.ts.map +1 -1
- package/dist/api/protocol-v2/FilesystemDiskControl.d.ts +1 -1
- package/dist/api/scdo/ScdoSignMessage.d.ts.map +1 -1
- package/dist/api/scdo/ScdoSignTransaction.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts +0 -1
- package/dist/api/solana/SolSignMessage.d.ts.map +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts +0 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
- package/dist/api/solana/SolSignTransaction.d.ts +0 -2
- package/dist/api/solana/SolSignTransaction.d.ts.map +1 -1
- package/dist/api/starcoin/StarcoinSignMessage.d.ts.map +1 -1
- package/dist/api/starcoin/StarcoinSignTransaction.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts.map +1 -1
- package/dist/api/sui/SuiSignMessage.d.ts.map +1 -1
- package/dist/api/sui/SuiSignTransaction.d.ts.map +1 -1
- package/dist/api/ton/TonSignData.d.ts.map +1 -1
- package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
- package/dist/api/ton/TonSignProof.d.ts.map +1 -1
- package/dist/api/tron/TronSignMessage.d.ts.map +1 -1
- package/dist/api/tron/TronSignTransaction.d.ts.map +1 -1
- package/dist/api/xrp/XrpSignTransaction.d.ts.map +1 -1
- package/dist/core/PollingStateManager.d.ts +8 -0
- package/dist/core/PollingStateManager.d.ts.map +1 -0
- package/dist/core/RequestQueue.d.ts +1 -1
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +17 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/index.d.ts +22 -2
- package/dist/index.js +454 -195
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +2 -0
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/preInitialize.d.ts +3 -0
- package/dist/types/api/preInitialize.d.ts.map +1 -0
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +82 -2
- package/src/api/FirmwareUpdateV3.ts +0 -4
- package/src/api/FirmwareUpdateV4.ts +1 -19
- package/src/api/GetPassphraseState.ts +4 -3
- package/src/api/alephium/AlephiumSignMessage.ts +1 -0
- package/src/api/alephium/AlephiumSignTransaction.ts +1 -0
- package/src/api/algo/AlgoSignTransaction.ts +1 -0
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +8 -0
- package/src/api/aptos/AptosSignInMessage.ts +1 -0
- package/src/api/aptos/AptosSignMessage.ts +1 -0
- package/src/api/aptos/AptosSignTransaction.ts +1 -0
- package/src/api/benfen/BenfenSignMessage.ts +1 -0
- package/src/api/benfen/BenfenSignTransaction.ts +1 -0
- package/src/api/btc/BTCSignMessage.ts +1 -0
- package/src/api/btc/BTCSignPsbt.ts +1 -0
- package/src/api/btc/BTCSignTransaction.ts +1 -0
- package/src/api/cardano/CardanoSignMessage.ts +1 -0
- package/src/api/cardano/CardanoSignTransaction.ts +1 -0
- package/src/api/conflux/ConfluxSignMessage.ts +1 -0
- package/src/api/conflux/ConfluxSignMessageCIP23.ts +1 -0
- package/src/api/conflux/ConfluxSignTransaction.ts +1 -0
- package/src/api/cosmos/CosmosSignTransaction.ts +1 -0
- package/src/api/device/PreInitialize.ts +41 -0
- package/src/api/dynex/DnxSignTransaction.ts +1 -0
- package/src/api/evm/EVMSignMessage.ts +2 -0
- package/src/api/evm/EVMSignMessageEIP712.ts +1 -0
- package/src/api/evm/EVMSignTransaction.ts +2 -0
- package/src/api/evm/EVMSignTypedData.ts +2 -0
- package/src/api/filecoin/FilecoinSignTransaction.ts +1 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +4 -27
- package/src/api/index.ts +1 -0
- package/src/api/kaspa/KaspaSignTransaction.ts +1 -0
- package/src/api/near/NearSignTransaction.ts +1 -0
- package/src/api/nem/NEMSignTransaction.ts +1 -0
- package/src/api/neo/NeoSignTransaction.ts +1 -0
- package/src/api/nervos/NervosSignTransaction.ts +1 -0
- package/src/api/nexa/NexaSignTransaction.ts +2 -0
- package/src/api/nostr/NostrSignEvent.ts +1 -0
- package/src/api/nostr/NostrSignSchnorr.ts +1 -0
- package/src/api/polkadot/PolkadotSignTransaction.ts +1 -0
- package/src/api/scdo/ScdoSignMessage.ts +1 -0
- package/src/api/scdo/ScdoSignTransaction.ts +1 -0
- package/src/api/solana/SolSignMessage.ts +1 -1
- package/src/api/solana/SolSignOffchainMessage.ts +1 -1
- package/src/api/solana/SolSignTransaction.ts +1 -2
- package/src/api/starcoin/StarcoinSignMessage.ts +1 -0
- package/src/api/starcoin/StarcoinSignTransaction.ts +1 -0
- package/src/api/stellar/StellarSignTransaction.ts +1 -0
- package/src/api/sui/SuiSignMessage.ts +1 -0
- package/src/api/sui/SuiSignTransaction.ts +1 -0
- package/src/api/ton/TonSignData.ts +1 -0
- package/src/api/ton/TonSignMessage.ts +1 -0
- package/src/api/ton/TonSignProof.ts +1 -0
- package/src/api/tron/TronSignMessage.ts +1 -0
- package/src/api/tron/TronSignTransaction.ts +1 -0
- package/src/api/xrp/XrpSignTransaction.ts +1 -0
- package/src/core/PollingStateManager.ts +47 -0
- package/src/core/RequestQueue.ts +10 -3
- package/src/core/index.ts +153 -34
- package/src/device/Device.ts +91 -29
- package/src/inject.ts +1 -1
- package/src/protocols/protocol-v2/features.ts +5 -6
- package/src/types/api/index.ts +2 -0
- package/src/types/api/preInitialize.ts +3 -0
- package/src/types/params.ts +5 -0
- package/src/utils/deviceFeaturesUtils.ts +8 -17
package/src/core/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import semver from 'semver';
|
|
2
2
|
import EventEmitter from 'events';
|
|
3
3
|
import {
|
|
4
|
-
EDeviceType,
|
|
5
4
|
ERRORS,
|
|
6
5
|
ERROR_CODES_REQUIRE_RELEASE,
|
|
7
6
|
HardwareError,
|
|
@@ -20,7 +19,6 @@ import {
|
|
|
20
19
|
enableLog,
|
|
21
20
|
getDeviceBLEFirmwareVersion,
|
|
22
21
|
getDeviceFirmwareVersion,
|
|
23
|
-
getDeviceType,
|
|
24
22
|
getFirmwareType,
|
|
25
23
|
getLogger,
|
|
26
24
|
getMethodVersionRange,
|
|
@@ -45,6 +43,7 @@ import {
|
|
|
45
43
|
import { Device } from '../device/Device';
|
|
46
44
|
import { DeviceList } from '../device/DeviceList';
|
|
47
45
|
import { DevicePool } from '../device/DevicePool';
|
|
46
|
+
import { PollingStateManager } from './PollingStateManager';
|
|
48
47
|
import { findMethod } from '../api/utils';
|
|
49
48
|
import { DataManager } from '../data-manager';
|
|
50
49
|
import { UI_REQUEST as UI_REQUEST_CONST } from '../constants/ui-request';
|
|
@@ -76,6 +75,12 @@ import type {
|
|
|
76
75
|
import type { BaseMethod } from '../api/BaseMethod';
|
|
77
76
|
|
|
78
77
|
const Log = getLogger(LoggerNames.Core);
|
|
78
|
+
const PRE_INITIALIZE_TTL_MS = 60 * 1000;
|
|
79
|
+
|
|
80
|
+
// Dedup/coalesce state for "pre-warm signal" methods (isPreWarmSignal),
|
|
81
|
+
// keyed by getPreWarmKey(): coalesce in-flight, skip if warmed within TTL.
|
|
82
|
+
const preWarmInflight = new Map<string, Promise<any>>();
|
|
83
|
+
const preWarmDoneAt = new Map<string, number>();
|
|
79
84
|
|
|
80
85
|
export type CoreContext = ReturnType<Core['getCoreContext']>;
|
|
81
86
|
|
|
@@ -107,8 +112,7 @@ let _connector: DeviceConnector | undefined;
|
|
|
107
112
|
let _uiPromises: UiPromise<UiPromiseResponse['type']>[] = []; // Waiting for ui response
|
|
108
113
|
|
|
109
114
|
const deviceCacheMap = new Map<string, Device>();
|
|
110
|
-
|
|
111
|
-
const pollingState: Record<number, boolean> = {};
|
|
115
|
+
const pollingManager = new PollingStateManager();
|
|
112
116
|
|
|
113
117
|
let preConnectCache: {
|
|
114
118
|
passphraseState: string | undefined;
|
|
@@ -210,9 +214,63 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
|
|
|
210
214
|
return createResponseMessage(method.responseID, false, { error });
|
|
211
215
|
}
|
|
212
216
|
|
|
217
|
+
// only the pre-warm signal (PreInitialize) forks here; normal methods fall
|
|
218
|
+
// through to onCallDevice below, so the pre-warm dedup/guards never touch them
|
|
219
|
+
if (method.isPreWarmSignal) {
|
|
220
|
+
return handlePreWarmSignal(context, message, method);
|
|
221
|
+
}
|
|
222
|
+
|
|
213
223
|
return onCallDevice(context, message, method);
|
|
214
224
|
};
|
|
215
225
|
|
|
226
|
+
// Wrapper for "pre-warm signal" methods: coalesce in-flight same-key pre-warm,
|
|
227
|
+
// skip if warmed within TTL, else run + track. The "hang up so the next real
|
|
228
|
+
// call waits" part lives in onCallDevice (setPrePendingCallPromise).
|
|
229
|
+
const handlePreWarmSignal = async (
|
|
230
|
+
context: CoreContext,
|
|
231
|
+
message: CoreMessage,
|
|
232
|
+
method: BaseMethod
|
|
233
|
+
): Promise<any> => {
|
|
234
|
+
// no connectId: can't target a device safely, skip pre-warm (ack only)
|
|
235
|
+
if (!method.connectId) {
|
|
236
|
+
return createResponseMessage(method.responseID, true, true);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const key = method.getPreWarmKey();
|
|
240
|
+
|
|
241
|
+
const inflight = preWarmInflight.get(key);
|
|
242
|
+
if (inflight) {
|
|
243
|
+
// reply with THIS call's responseID (not the other call's response object)
|
|
244
|
+
try {
|
|
245
|
+
await inflight;
|
|
246
|
+
} catch {
|
|
247
|
+
// pre-warm is best-effort; ignore its failure for the coalesced caller
|
|
248
|
+
}
|
|
249
|
+
return createResponseMessage(method.responseID, true, true);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const doneAt = preWarmDoneAt.get(key);
|
|
253
|
+
if (typeof doneAt === 'number' && Date.now() - doneAt <= method.preWarmTtl) {
|
|
254
|
+
return createResponseMessage(method.responseID, true, true);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const run = onCallDevice(context, message, method);
|
|
258
|
+
preWarmInflight.set(key, run);
|
|
259
|
+
try {
|
|
260
|
+
const result = await run;
|
|
261
|
+
// Only remember the warm if it actually succeeded — a failed pre-warm must
|
|
262
|
+
// not suppress the next pre-warm within the TTL.
|
|
263
|
+
if (result?.success === true && result?.payload === true) {
|
|
264
|
+
preWarmDoneAt.set(key, Date.now());
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
} finally {
|
|
268
|
+
if (preWarmInflight.get(key) === run) {
|
|
269
|
+
preWarmInflight.delete(key);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
216
274
|
const waitWithTimeout = async (promise: Promise<any>, timeout: number) => {
|
|
217
275
|
const timeoutPromise = new Promise((_, reject) => {
|
|
218
276
|
setTimeout(() => reject(new Error('Request timeout')), timeout);
|
|
@@ -248,7 +306,15 @@ const onCallDevice = async (
|
|
|
248
306
|
|
|
249
307
|
updateMethodRequestContext(method, { status: 'running' });
|
|
250
308
|
|
|
251
|
-
|
|
309
|
+
// Normalize undefined / null / '' to '' — they all mean "main wallet, no
|
|
310
|
+
// passphrase". Without this, the first call (preConnectCache starts undefined)
|
|
311
|
+
// or any '' call after a non-'' one is wrongly treated as a passphrase switch
|
|
312
|
+
// and needlessly clears the device cache -> forces a re-enumeration Initialize.
|
|
313
|
+
// A real switch ('' <-> 'stateX', or 'stateX' <-> 'stateY') still differs.
|
|
314
|
+
const normalizePassphraseState = (s?: string | null) => s || '';
|
|
315
|
+
const connectStateChange =
|
|
316
|
+
normalizePassphraseState(preConnectCache.passphraseState) !==
|
|
317
|
+
normalizePassphraseState(method.payload.passphraseState);
|
|
252
318
|
|
|
253
319
|
preConnectCache = {
|
|
254
320
|
passphraseState: method.payload.passphraseState,
|
|
@@ -268,18 +334,31 @@ const onCallDevice = async (
|
|
|
268
334
|
|
|
269
335
|
const task = requestQueue.createTask(method);
|
|
270
336
|
|
|
337
|
+
// Pre-warm holds the device as a per-connectId callback task so a concurrent
|
|
338
|
+
// real call waits (before ensureConnected) instead of racing its Initialize.
|
|
339
|
+
// Only covers pre-warm -> real-call ordering; the reverse is fail-closed.
|
|
340
|
+
let preWarmCallbackTask: Deferred<void> | undefined;
|
|
341
|
+
if (method.isPreWarmSignal && method.connectId) {
|
|
342
|
+
preWarmCallbackTask = createDeferred<void>();
|
|
343
|
+
context.registerCallbackTask(method.connectId, preWarmCallbackTask);
|
|
344
|
+
}
|
|
345
|
+
|
|
271
346
|
let device: Device;
|
|
272
347
|
try {
|
|
273
348
|
/**
|
|
274
349
|
* Polling to ensure successful connection
|
|
275
350
|
*/
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
351
|
+
const connectId = method.connectId ?? '';
|
|
352
|
+
const pollingId = pollingManager.start(connectId);
|
|
353
|
+
device = await ensureConnected(
|
|
354
|
+
context,
|
|
355
|
+
method,
|
|
356
|
+
connectId,
|
|
357
|
+
pollingId,
|
|
358
|
+
task.abortController?.signal
|
|
359
|
+
);
|
|
282
360
|
} catch (e) {
|
|
361
|
+
preWarmCallbackTask?.resolve();
|
|
283
362
|
console.log('ensureConnected error: ', e);
|
|
284
363
|
|
|
285
364
|
completeMethodRequestContext(method, e);
|
|
@@ -295,6 +374,7 @@ const onCallDevice = async (
|
|
|
295
374
|
}
|
|
296
375
|
|
|
297
376
|
if (method.payload?.onlyConnectBleDevice) {
|
|
377
|
+
preWarmCallbackTask?.resolve();
|
|
298
378
|
Log.debug('Call API - only connect ble device: ', device?.mainId);
|
|
299
379
|
return createResponseMessage(method.responseID, true, null);
|
|
300
380
|
}
|
|
@@ -334,8 +414,9 @@ const onCallDevice = async (
|
|
|
334
414
|
);
|
|
335
415
|
|
|
336
416
|
try {
|
|
417
|
+
// Wait for any pending task except our own (self-wait would deadlock).
|
|
337
418
|
if (method.connectId) {
|
|
338
|
-
await context.waitForCallbackTasks(method.connectId);
|
|
419
|
+
await context.waitForCallbackTasks(method.connectId, preWarmCallbackTask);
|
|
339
420
|
}
|
|
340
421
|
|
|
341
422
|
await waitForPendingPromise(getPrePendingCallPromise, setPrePendingCallPromise);
|
|
@@ -533,7 +614,6 @@ const onCallDevice = async (
|
|
|
533
614
|
|
|
534
615
|
try {
|
|
535
616
|
const response: object = await method.run();
|
|
536
|
-
Log.debug('Call API - Inner Method Run: ');
|
|
537
617
|
messageResponse = createResponseMessage(method.responseID, true, response);
|
|
538
618
|
requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
539
619
|
completeMethodRequestContext(method);
|
|
@@ -556,6 +636,7 @@ const onCallDevice = async (
|
|
|
556
636
|
|
|
557
637
|
const runOptions: RunOptions = {
|
|
558
638
|
keepSession: method.payload.keepSession,
|
|
639
|
+
skipInitialize: canSkipInitialize(method, device),
|
|
559
640
|
...parseInitOptions(method),
|
|
560
641
|
};
|
|
561
642
|
const deviceRun = () => device.run(inner, runOptions);
|
|
@@ -577,6 +658,9 @@ const onCallDevice = async (
|
|
|
577
658
|
Log.debug('Call API - Run Error: ', error);
|
|
578
659
|
completeMethodRequestContext(method, error);
|
|
579
660
|
} finally {
|
|
661
|
+
// Release the pre-warm callback task so the next real call can proceed.
|
|
662
|
+
preWarmCallbackTask?.resolve();
|
|
663
|
+
|
|
580
664
|
const response = messageResponse;
|
|
581
665
|
|
|
582
666
|
if (response) {
|
|
@@ -707,9 +791,34 @@ function initDeviceForBle(method: BaseMethod) {
|
|
|
707
791
|
}
|
|
708
792
|
|
|
709
793
|
/**
|
|
710
|
-
*
|
|
794
|
+
* Check if we can skip initialize for this method
|
|
711
795
|
*/
|
|
712
|
-
|
|
796
|
+
function canSkipInitialize(method: BaseMethod, device: Device): boolean {
|
|
797
|
+
const reasons: string[] = [];
|
|
798
|
+
// only sign-style methods opt in; getAddress/getPublicKey never do
|
|
799
|
+
if (!method.allowUsePreInitialize) reasons.push('method.disallow');
|
|
800
|
+
// caller must opt in per call
|
|
801
|
+
if (!method.payload?.usePreInitialize) reasons.push('payload.usePreInitialize=false');
|
|
802
|
+
// no connectId: can't pin the target device, never skip
|
|
803
|
+
if (!method.connectId) reasons.push('connectId.missing');
|
|
804
|
+
// passphrase state must match the pre-initialize
|
|
805
|
+
if (!device.isPreInitializeMetaMatch(method.payload)) reasons.push('meta.mismatch');
|
|
806
|
+
// device must have been initialized before (has features)
|
|
807
|
+
if (!device.features) reasons.push('features.missing');
|
|
808
|
+
// within pre-initialize TTL
|
|
809
|
+
if (!device.isPreInitializedValid(PRE_INITIALIZE_TTL_MS)) reasons.push('ttl.expired');
|
|
810
|
+
|
|
811
|
+
if (reasons.length) {
|
|
812
|
+
Log.debug(`[PRE-INIT][MISS] method=${method.name} ${reasons.join(',')}`);
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const savedMs = device.getLastInitializeDuration();
|
|
817
|
+
const saved = typeof savedMs === 'number' ? `saved ${savedMs}ms` : 'within TTL + meta match';
|
|
818
|
+
Log.debug(`[PRE-INIT][HIT] method=${method.name} skip Initialize (${saved})`);
|
|
819
|
+
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
713
822
|
|
|
714
823
|
function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown) {
|
|
715
824
|
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
@@ -721,24 +830,35 @@ function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown)
|
|
|
721
830
|
);
|
|
722
831
|
}
|
|
723
832
|
|
|
724
|
-
|
|
833
|
+
/**
|
|
834
|
+
* If the Bluetooth connection times out, retry up to 6 times
|
|
835
|
+
* @param retryCount - Current retry count (default 0)
|
|
836
|
+
*/
|
|
837
|
+
async function connectDeviceForBle(method: BaseMethod, device: Device, retryCount = 0) {
|
|
725
838
|
try {
|
|
726
839
|
await device.acquire(method.payload.connectProtocol);
|
|
727
840
|
if (method.payload?.onlyConnectBleDevice) {
|
|
728
841
|
return;
|
|
729
842
|
}
|
|
730
|
-
|
|
843
|
+
// Skip initialize if conditions are met
|
|
844
|
+
if (!canSkipInitialize(method, device)) {
|
|
845
|
+
const initOptions = parseInitOptions(method);
|
|
846
|
+
await device.initialize(initOptions);
|
|
847
|
+
device.markPreInitialized({
|
|
848
|
+
passphraseState: initOptions.passphraseState,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
731
851
|
} catch (err) {
|
|
732
852
|
if (
|
|
733
853
|
(err.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
734
854
|
err.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
735
855
|
isRetryableBleProtocolV2ProbeError(method, err)) &&
|
|
736
|
-
|
|
856
|
+
retryCount < 6
|
|
737
857
|
) {
|
|
738
|
-
|
|
739
|
-
Log.debug(`
|
|
858
|
+
const nextRetry = retryCount + 1;
|
|
859
|
+
Log.debug(`Bluetooth connect timeout and will retry, retry count: ${nextRetry}`);
|
|
740
860
|
await wait(3000);
|
|
741
|
-
await connectDeviceForBle(method, device);
|
|
861
|
+
await connectDeviceForBle(method, device, nextRetry);
|
|
742
862
|
} else {
|
|
743
863
|
throw err;
|
|
744
864
|
}
|
|
@@ -750,6 +870,7 @@ type IPollFn<T> = (time?: number) => T;
|
|
|
750
870
|
const ensureConnected = async (
|
|
751
871
|
_context: CoreContext,
|
|
752
872
|
method: BaseMethod,
|
|
873
|
+
connectId: string,
|
|
753
874
|
pollingId: number,
|
|
754
875
|
abortSignal?: AbortSignal
|
|
755
876
|
) => {
|
|
@@ -781,7 +902,7 @@ const ensureConnected = async (
|
|
|
781
902
|
return;
|
|
782
903
|
}
|
|
783
904
|
|
|
784
|
-
if (!
|
|
905
|
+
if (!pollingManager.isActive(connectId, pollingId)) {
|
|
785
906
|
Log.debug('EnsureConnected function stop, polling id: ', pollingId);
|
|
786
907
|
reject(ERRORS.TypedError(HardwareErrorCode.PollingStop));
|
|
787
908
|
return;
|
|
@@ -839,8 +960,6 @@ const ensureConnected = async (
|
|
|
839
960
|
* Bluetooth should call initialize here
|
|
840
961
|
*/
|
|
841
962
|
if (DataManager.isBleConnect(env)) {
|
|
842
|
-
bleTimeoutRetry = 0;
|
|
843
|
-
|
|
844
963
|
if (abort()) {
|
|
845
964
|
return;
|
|
846
965
|
}
|
|
@@ -903,7 +1022,7 @@ const ensureConnected = async (
|
|
|
903
1022
|
// eslint-disable-next-line no-promise-executor-return
|
|
904
1023
|
return setTimeout(() => resolve(poll(time * 1.5)), time);
|
|
905
1024
|
});
|
|
906
|
-
|
|
1025
|
+
// pollingManager.start(connectId) already registered this pollingId as active
|
|
907
1026
|
return poll();
|
|
908
1027
|
};
|
|
909
1028
|
|
|
@@ -1015,11 +1134,7 @@ const checkPassphraseEnableState = (method: BaseMethod, features?: Features) =>
|
|
|
1015
1134
|
const shouldCheckPassphraseState = (method: BaseMethod, device: Device) => {
|
|
1016
1135
|
if (!method.useDevicePassphraseState) return false;
|
|
1017
1136
|
|
|
1018
|
-
|
|
1019
|
-
const pro2ExplicitWalletSelection =
|
|
1020
|
-
isPro2 && (!!method.payload?.passphraseState || !!method.payload?.useEmptyPassphrase);
|
|
1021
|
-
|
|
1022
|
-
return device.hasUsePassphrase() || pro2ExplicitWalletSelection;
|
|
1137
|
+
return device.hasUsePassphrase();
|
|
1023
1138
|
};
|
|
1024
1139
|
|
|
1025
1140
|
const cleanup = () => {
|
|
@@ -1047,6 +1162,7 @@ const onDeviceConnectHandler = (device: Device) => {
|
|
|
1047
1162
|
};
|
|
1048
1163
|
|
|
1049
1164
|
const onDeviceDisconnectHandler = (device: Device) => {
|
|
1165
|
+
device.clearPreInitialized();
|
|
1050
1166
|
const env = DataManager.getSettings('env');
|
|
1051
1167
|
const deviceObject = DataManager.isBleConnect(env) ? device : device.toMessageObject();
|
|
1052
1168
|
postMessage(createDeviceMessage(DEVICE.DISCONNECT, { device: deviceObject as KnownDevice }));
|
|
@@ -1223,8 +1339,8 @@ export default class Core extends EventEmitter {
|
|
|
1223
1339
|
registerCallbackTask: (connectId: string, callbackPromise: Deferred<any>) => {
|
|
1224
1340
|
this.requestQueue.registerPendingCallbackTask(connectId, callbackPromise);
|
|
1225
1341
|
},
|
|
1226
|
-
waitForCallbackTasks: (connectId: string) =>
|
|
1227
|
-
this.requestQueue.waitForPendingCallbackTasks(connectId),
|
|
1342
|
+
waitForCallbackTasks: (connectId: string, exceptTask?: Deferred<void>) =>
|
|
1343
|
+
this.requestQueue.waitForPendingCallbackTasks(connectId, exceptTask),
|
|
1228
1344
|
cancelCallbackTasks: (connectId: string) => this.requestQueue.cancelCallbackTasks(connectId),
|
|
1229
1345
|
};
|
|
1230
1346
|
}
|
|
@@ -1255,10 +1371,10 @@ export default class Core extends EventEmitter {
|
|
|
1255
1371
|
}
|
|
1256
1372
|
|
|
1257
1373
|
case IFRAME.CALL: {
|
|
1258
|
-
Log.log(
|
|
1374
|
+
Log.log(`[${Date.now()}][CALL_API]`, message);
|
|
1259
1375
|
const response = await callAPI(this.getCoreContext(), message);
|
|
1260
1376
|
const { success, payload } = response;
|
|
1261
|
-
Log.log(
|
|
1377
|
+
Log.log(`[${Date.now()}][CALL_API_RESPONSE]`, response);
|
|
1262
1378
|
if (success) {
|
|
1263
1379
|
return response;
|
|
1264
1380
|
}
|
|
@@ -1291,6 +1407,9 @@ export default class Core extends EventEmitter {
|
|
|
1291
1407
|
dispose() {
|
|
1292
1408
|
_deviceList = undefined;
|
|
1293
1409
|
_connector = undefined;
|
|
1410
|
+
deviceCacheMap.clear();
|
|
1411
|
+
preWarmInflight.clear();
|
|
1412
|
+
preWarmDoneAt.clear();
|
|
1294
1413
|
Log.debug(`[Core] Disposing SDK instance: ${this.sdkInstanceId}`);
|
|
1295
1414
|
cleanupSdkInstance(this.sdkInstanceId);
|
|
1296
1415
|
}
|
package/src/device/Device.ts
CHANGED
|
@@ -64,6 +64,7 @@ export type InitOptions = {
|
|
|
64
64
|
|
|
65
65
|
export type RunOptions = {
|
|
66
66
|
keepSession?: boolean;
|
|
67
|
+
skipInitialize?: boolean;
|
|
67
68
|
} & InitOptions;
|
|
68
69
|
|
|
69
70
|
const parseRunOptions = (options?: RunOptions): RunOptions => {
|
|
@@ -198,6 +199,17 @@ export class Device extends EventEmitter {
|
|
|
198
199
|
|
|
199
200
|
pendingCallbackPromise?: Deferred<void>;
|
|
200
201
|
|
|
202
|
+
/** Pre-initialize timestamp (ms) */
|
|
203
|
+
private preInitializedAt?: number;
|
|
204
|
+
|
|
205
|
+
/** Pre-initialize context, used to verify state consistency before skipping */
|
|
206
|
+
private preInitializeMeta?: {
|
|
207
|
+
passphraseState?: string;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/** Last Initialize duration (ms), reported as "saved" when a skip happens */
|
|
211
|
+
private lastInitializeDurationMs?: number;
|
|
212
|
+
|
|
201
213
|
constructor(descriptor: DeviceDescriptor, sdkInstanceId?: string) {
|
|
202
214
|
super();
|
|
203
215
|
this.originalDescriptor = descriptor;
|
|
@@ -378,6 +390,54 @@ export class Device extends EventEmitter {
|
|
|
378
390
|
this.deviceAcquired = false;
|
|
379
391
|
}
|
|
380
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Pre-initialize: connect + Initialize ahead of the sign. Only runs the
|
|
395
|
+
* fallback init when features are missing (gate on `!this.features`, not
|
|
396
|
+
* isUsedHere which is always false on BLE); otherwise just records the mark.
|
|
397
|
+
*/
|
|
398
|
+
async preInitialize(initOptions?: InitOptions) {
|
|
399
|
+
if (!this.features) {
|
|
400
|
+
await this.acquire();
|
|
401
|
+
await this.initialize(initOptions);
|
|
402
|
+
}
|
|
403
|
+
this.markPreInitialized({
|
|
404
|
+
passphraseState: initOptions?.passphraseState,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
markPreInitialized(meta?: { passphraseState?: string }) {
|
|
409
|
+
this.preInitializedAt = Date.now();
|
|
410
|
+
this.preInitializeMeta = meta
|
|
411
|
+
? {
|
|
412
|
+
passphraseState: meta.passphraseState === '' ? undefined : meta.passphraseState,
|
|
413
|
+
}
|
|
414
|
+
: undefined;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
clearPreInitialized() {
|
|
418
|
+
this.preInitializedAt = undefined;
|
|
419
|
+
this.preInitializeMeta = undefined;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
isPreInitializeMetaMatch(payload?: { passphraseState?: string }) {
|
|
423
|
+
if (!this.preInitializeMeta) return true;
|
|
424
|
+
const passphraseState = payload?.passphraseState === '' ? undefined : payload?.passphraseState;
|
|
425
|
+
return this.preInitializeMeta.passphraseState === passphraseState;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
isPreInitializedValid(ttlMs: number) {
|
|
429
|
+
if (!this.preInitializedAt) return false;
|
|
430
|
+
return Date.now() - this.preInitializedAt <= ttlMs;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
setLastInitializeDuration(durationMs: number) {
|
|
434
|
+
this.lastInitializeDurationMs = durationMs;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
getLastInitializeDuration() {
|
|
438
|
+
return this.lastInitializeDurationMs;
|
|
439
|
+
}
|
|
440
|
+
|
|
381
441
|
getCommands() {
|
|
382
442
|
return this.commands;
|
|
383
443
|
}
|
|
@@ -516,13 +576,7 @@ export class Device extends EventEmitter {
|
|
|
516
576
|
payload.passphrase_state = options?.passphraseState;
|
|
517
577
|
payload.is_contains_attach = true;
|
|
518
578
|
|
|
519
|
-
|
|
520
|
-
deviceId: options?.deviceId,
|
|
521
|
-
passphraseState: options?.passphraseState,
|
|
522
|
-
initSession: options?.initSession,
|
|
523
|
-
InitializePayload: payload,
|
|
524
|
-
});
|
|
525
|
-
|
|
579
|
+
const initStartAt = Date.now();
|
|
526
580
|
try {
|
|
527
581
|
// @ts-expect-error
|
|
528
582
|
const { message } = await Promise.race([
|
|
@@ -535,7 +589,8 @@ export class Device extends EventEmitter {
|
|
|
535
589
|
}),
|
|
536
590
|
]);
|
|
537
591
|
|
|
538
|
-
|
|
592
|
+
const initCostMs = Date.now() - initStartAt;
|
|
593
|
+
this.setLastInitializeDuration(initCostMs);
|
|
539
594
|
this._updateFeatures(message, options?.initSession);
|
|
540
595
|
await TransportManager.reconfigure(this.features);
|
|
541
596
|
} catch (error) {
|
|
@@ -555,11 +610,7 @@ export class Device extends EventEmitter {
|
|
|
555
610
|
|
|
556
611
|
try {
|
|
557
612
|
const features = await Promise.race([
|
|
558
|
-
|
|
559
|
-
commands: this.commands,
|
|
560
|
-
descriptor: this.originalDescriptor,
|
|
561
|
-
timeoutMs: 10 * 1000,
|
|
562
|
-
}),
|
|
613
|
+
this._readProtocolV2Features(10 * 1000),
|
|
563
614
|
new Promise<never>((_, reject) => {
|
|
564
615
|
setTimeout(() => {
|
|
565
616
|
reject(ERRORS.TypedError(HardwareErrorCode.DeviceInitializeFailed));
|
|
@@ -574,9 +625,24 @@ export class Device extends EventEmitter {
|
|
|
574
625
|
}
|
|
575
626
|
}
|
|
576
627
|
|
|
628
|
+
private async _readProtocolV2Features(timeoutMs?: number) {
|
|
629
|
+
return getProtocolV2Features({
|
|
630
|
+
commands: this.commands,
|
|
631
|
+
descriptor: this.originalDescriptor,
|
|
632
|
+
timeoutMs,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
577
636
|
async getFeatures() {
|
|
637
|
+
if (this.originalDescriptor.protocolType === 'V2') {
|
|
638
|
+
const features = await this._readProtocolV2Features();
|
|
639
|
+
this._updateFeatures(features);
|
|
640
|
+
return features;
|
|
641
|
+
}
|
|
642
|
+
|
|
578
643
|
const { message } = await this.commands.typedCall('GetFeatures', 'Features', {});
|
|
579
644
|
this._updateFeatures(message);
|
|
645
|
+
return message;
|
|
580
646
|
}
|
|
581
647
|
|
|
582
648
|
_updateFeatures(feat: Features, initSession?: boolean) {
|
|
@@ -652,7 +718,9 @@ export class Device extends EventEmitter {
|
|
|
652
718
|
|
|
653
719
|
try {
|
|
654
720
|
if (fn) {
|
|
655
|
-
|
|
721
|
+
if (!options?.skipInitialize) {
|
|
722
|
+
await this.initialize(options);
|
|
723
|
+
}
|
|
656
724
|
}
|
|
657
725
|
} catch (error) {
|
|
658
726
|
this.runPromise = null;
|
|
@@ -849,7 +917,8 @@ export class Device extends EventEmitter {
|
|
|
849
917
|
hasUsePassphrase() {
|
|
850
918
|
const isModeT =
|
|
851
919
|
getDeviceType(this.features) === EDeviceType.Touch ||
|
|
852
|
-
getDeviceType(this.features) === EDeviceType.Pro
|
|
920
|
+
getDeviceType(this.features) === EDeviceType.Pro ||
|
|
921
|
+
getDeviceType(this.features) === EDeviceType.Pro2;
|
|
853
922
|
const preCheckTouch = isModeT && this.features?.unlocked === false;
|
|
854
923
|
|
|
855
924
|
return this.features && (!!this.features.passphrase_protection || preCheckTouch);
|
|
@@ -863,10 +932,6 @@ export class Device extends EventEmitter {
|
|
|
863
932
|
}
|
|
864
933
|
|
|
865
934
|
async lockDevice(): Promise<Success> {
|
|
866
|
-
if (getDeviceType(this.features) === EDeviceType.Pro2) {
|
|
867
|
-
return { message: 'LockDevice skipped for Pro2' };
|
|
868
|
-
}
|
|
869
|
-
|
|
870
935
|
const res = await this.commands.typedCall('LockDevice', 'Success', {});
|
|
871
936
|
return res.message;
|
|
872
937
|
}
|
|
@@ -876,6 +941,9 @@ export class Device extends EventEmitter {
|
|
|
876
941
|
pro: {
|
|
877
942
|
min: '4.15.0',
|
|
878
943
|
},
|
|
944
|
+
pro2: {
|
|
945
|
+
min: '4.15.0',
|
|
946
|
+
},
|
|
879
947
|
};
|
|
880
948
|
}
|
|
881
949
|
|
|
@@ -890,12 +958,8 @@ export class Device extends EventEmitter {
|
|
|
890
958
|
this.features,
|
|
891
959
|
Enum_Capability.Capability_AttachToPin
|
|
892
960
|
);
|
|
893
|
-
const isPro2 = getDeviceType(this.features) === EDeviceType.Pro2;
|
|
894
|
-
|
|
895
961
|
const supportUnlock =
|
|
896
962
|
supportAttachPinCapability ||
|
|
897
|
-
// Pro2 V2 暂未从 features 暴露 capabilities,先直连该方法用于固件联调。
|
|
898
|
-
isPro2 ||
|
|
899
963
|
(versionRange && semver.gte(firmwareVersion, versionRange.min));
|
|
900
964
|
|
|
901
965
|
if (supportUnlock) {
|
|
@@ -910,9 +974,8 @@ export class Device extends EventEmitter {
|
|
|
910
974
|
return Promise.resolve(this.features);
|
|
911
975
|
}
|
|
912
976
|
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
return Promise.resolve(featuresRes.message);
|
|
977
|
+
const features = await this.getFeatures();
|
|
978
|
+
return Promise.resolve(features);
|
|
916
979
|
}
|
|
917
980
|
|
|
918
981
|
const { type } = await this.commands.typedCall('GetAddress', 'Address', {
|
|
@@ -926,9 +989,8 @@ export class Device extends EventEmitter {
|
|
|
926
989
|
if (type === 'CallMethodError') {
|
|
927
990
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'unlock device error');
|
|
928
991
|
}
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
return Promise.resolve(res.message);
|
|
992
|
+
const features = await this.getFeatures();
|
|
993
|
+
return Promise.resolve(features);
|
|
932
994
|
}
|
|
933
995
|
|
|
934
996
|
async checkPassphraseStateSafety(
|
package/src/inject.ts
CHANGED
|
@@ -143,7 +143,7 @@ export const createCoreApi = (
|
|
|
143
143
|
|
|
144
144
|
testInitializeDeviceDuration: (connectId, params) =>
|
|
145
145
|
call({ ...params, connectId, method: 'testInitializeDeviceDuration' }),
|
|
146
|
-
|
|
146
|
+
preInitialize: (connectId, params) => call({ ...params, connectId, method: 'preInitialize' }),
|
|
147
147
|
deviceBackup: connectId => call({ connectId, method: 'deviceBackup' }),
|
|
148
148
|
deviceChangePin: (connectId, params) => call({ ...params, connectId, method: 'deviceChangePin' }),
|
|
149
149
|
deviceFlags: (connectId, params) => call({ ...params, connectId, method: 'deviceFlags' }),
|
|
@@ -144,7 +144,7 @@ function createBaseFeatures(descriptor: DeviceDescriptor): Features {
|
|
|
144
144
|
minor_version: 0,
|
|
145
145
|
patch_version: 0,
|
|
146
146
|
bootloader_mode: false,
|
|
147
|
-
device_id:
|
|
147
|
+
device_id: '',
|
|
148
148
|
pin_protection: null,
|
|
149
149
|
passphrase_protection: null,
|
|
150
150
|
language: null,
|
|
@@ -190,8 +190,7 @@ export function normalizeProtocolV2Features(
|
|
|
190
190
|
const features = createBaseFeatures(descriptor);
|
|
191
191
|
if (!deviceInfo) return features;
|
|
192
192
|
|
|
193
|
-
const serialNo =
|
|
194
|
-
deviceInfo.hw?.serial_no || features.onekey_serial_no || getDescriptorId(descriptor);
|
|
193
|
+
const serialNo = deviceInfo.hw?.serial_no;
|
|
195
194
|
const firmwareVersion = getImageVersion(deviceInfo.fw?.app);
|
|
196
195
|
const [fwMajor, fwMinor, fwPatch] = parseVersion(firmwareVersion);
|
|
197
196
|
|
|
@@ -203,9 +202,9 @@ export function normalizeProtocolV2Features(
|
|
|
203
202
|
fw_major: fwMajor,
|
|
204
203
|
fw_minor: fwMinor,
|
|
205
204
|
fw_patch: fwPatch,
|
|
206
|
-
device_id: serialNo,
|
|
207
|
-
serial_no: serialNo,
|
|
208
|
-
onekey_serial_no: serialNo,
|
|
205
|
+
device_id: serialNo ?? features.device_id,
|
|
206
|
+
serial_no: serialNo ?? features.serial_no,
|
|
207
|
+
onekey_serial_no: serialNo ?? features.onekey_serial_no,
|
|
209
208
|
protocol_version: deviceInfo.protocol_version ?? features.protocol_version,
|
|
210
209
|
label: deviceInfo.status?.label ?? features.label,
|
|
211
210
|
language: deviceInfo.status?.language ?? features.language,
|
package/src/types/api/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ import type { off, on, removeAllListeners } from './event';
|
|
|
30
30
|
import type { uiResponse } from './uiResponse';
|
|
31
31
|
import type { init, updateSettings } from './init';
|
|
32
32
|
import type { testInitializeDeviceDuration } from './testInitializeDeviceDuration';
|
|
33
|
+
import type { preInitialize } from './preInitialize';
|
|
33
34
|
import type { getLogs } from './getLogs';
|
|
34
35
|
import type { checkBridgeStatus } from './checkBridgeStatus';
|
|
35
36
|
import type { checkBridgeRelease } from './checkBridgeRelease';
|
|
@@ -199,6 +200,7 @@ export type CoreApi = {
|
|
|
199
200
|
* Test function
|
|
200
201
|
*/
|
|
201
202
|
testInitializeDeviceDuration: typeof testInitializeDeviceDuration;
|
|
203
|
+
preInitialize: typeof preInitialize;
|
|
202
204
|
|
|
203
205
|
/**
|
|
204
206
|
* Core function
|
package/src/types/params.ts
CHANGED
|
@@ -50,6 +50,11 @@ export interface CommonParams {
|
|
|
50
50
|
*/
|
|
51
51
|
onlyConnectBleDevice?: boolean;
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Use pre-initialized device state (BLE only)
|
|
55
|
+
*/
|
|
56
|
+
usePreInitialize?: boolean;
|
|
57
|
+
|
|
53
58
|
/**
|
|
54
59
|
* Expected transport protocol. If omitted, SDK probes Protocol V1 then Protocol V2.
|
|
55
60
|
*/
|