@onekeyfe/hd-core 1.1.26-patch.2 → 1.1.27-alpha.1
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/api/BaseMethod.d.ts +4 -0
- package/dist/api/BaseMethod.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/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/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/scdo/ScdoSignMessage.d.ts.map +1 -1
- package/dist/api/scdo/ScdoSignTransaction.d.ts.map +1 -1
- package/dist/api/solana/SolSignMessage.d.ts.map +1 -1
- package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
- 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 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.js +347 -113
- 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/package.json +4 -4
- package/src/api/BaseMethod.ts +24 -0
- 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/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 +43 -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/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 -0
- package/src/api/solana/SolSignOffchainMessage.ts +1 -0
- package/src/api/solana/SolSignTransaction.ts +1 -0
- 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 +145 -27
- package/src/device/Device.ts +73 -9
- package/src/inject.ts +1 -1
- package/src/types/api/index.ts +2 -0
- package/src/types/api/preInitialize.ts +3 -0
- package/src/types/params.ts +5 -0
|
@@ -26,6 +26,7 @@ export default class TonSignMessage extends BaseMethod<HardwareTonSignMessage> {
|
|
|
26
26
|
this.strictCheckDeviceSupport = true;
|
|
27
27
|
this.checkDeviceId = true;
|
|
28
28
|
this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
|
|
29
|
+
this.allowUsePreInitialize = true;
|
|
29
30
|
|
|
30
31
|
// init params
|
|
31
32
|
validateParams(this.payload, [
|
|
@@ -11,6 +11,7 @@ export default class TonSignProof extends BaseMethod<HardwareTonSignProof> {
|
|
|
11
11
|
this.strictCheckDeviceSupport = true;
|
|
12
12
|
this.checkDeviceId = true;
|
|
13
13
|
this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
|
|
14
|
+
this.allowUsePreInitialize = true;
|
|
14
15
|
|
|
15
16
|
// init params
|
|
16
17
|
validateParams(this.payload, [
|
|
@@ -14,6 +14,7 @@ export default class TronSignMessage extends BaseMethod<HardwareTronSignMessage>
|
|
|
14
14
|
init() {
|
|
15
15
|
this.checkDeviceId = true;
|
|
16
16
|
this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
|
|
17
|
+
this.allowUsePreInitialize = true;
|
|
17
18
|
|
|
18
19
|
// check payload
|
|
19
20
|
validateParams(this.payload, [
|
|
@@ -123,6 +123,7 @@ export default class TronSignTransaction extends BaseMethod<TronSignTx> {
|
|
|
123
123
|
init() {
|
|
124
124
|
this.checkDeviceId = true;
|
|
125
125
|
this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
|
|
126
|
+
this.allowUsePreInitialize = true;
|
|
126
127
|
|
|
127
128
|
// check payload
|
|
128
129
|
validateParams(this.payload, [
|
|
@@ -11,6 +11,7 @@ export default class XrpGetAddress extends BaseMethod<XrpSignTransactionParams>
|
|
|
11
11
|
init() {
|
|
12
12
|
this.checkDeviceId = true;
|
|
13
13
|
this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
|
|
14
|
+
this.allowUsePreInitialize = true;
|
|
14
15
|
|
|
15
16
|
const { payload } = this;
|
|
16
17
|
validateParams(payload, [
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages polling state for device connection attempts.
|
|
3
|
+
*
|
|
4
|
+
* Polling is isolated by connectId (device), so:
|
|
5
|
+
* - New request for device A only stops device A's previous polling
|
|
6
|
+
* - Device B's polling is unaffected
|
|
7
|
+
*/
|
|
8
|
+
export class PollingStateManager {
|
|
9
|
+
// connectId -> current polling ID
|
|
10
|
+
private activePolls = new Map<string, number>();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Start a new polling session for a device.
|
|
14
|
+
* Automatically stops the previous polling for the same device.
|
|
15
|
+
* @param connectId - Device identifier (use empty string for USB without connectId)
|
|
16
|
+
* @returns The new polling ID
|
|
17
|
+
*/
|
|
18
|
+
start(connectId: string): number {
|
|
19
|
+
const currentId = (this.activePolls.get(connectId) ?? 0) + 1;
|
|
20
|
+
this.activePolls.set(connectId, currentId);
|
|
21
|
+
return currentId;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if a polling session is still active.
|
|
26
|
+
* @param connectId - Device identifier
|
|
27
|
+
* @param pollingId - The polling ID to check
|
|
28
|
+
*/
|
|
29
|
+
isActive(connectId: string, pollingId: number): boolean {
|
|
30
|
+
return this.activePolls.get(connectId) === pollingId;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Stop polling for a specific device.
|
|
35
|
+
* @param connectId - Device identifier
|
|
36
|
+
*/
|
|
37
|
+
stop(connectId: string): void {
|
|
38
|
+
this.activePolls.delete(connectId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Stop all active polling sessions.
|
|
43
|
+
*/
|
|
44
|
+
stopAll(): void {
|
|
45
|
+
this.activePolls.clear();
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/core/RequestQueue.ts
CHANGED
|
@@ -113,13 +113,20 @@ export default class RequestQueue {
|
|
|
113
113
|
|
|
114
114
|
callbackPromise.promise.finally(() => {
|
|
115
115
|
Log.debug(`Callback task completed for connectId: ${connectId}`);
|
|
116
|
-
this.
|
|
116
|
+
// Delete by identity so a newer task that replaced this slot isn't orphaned.
|
|
117
|
+
if (this.pendingCallbackTasks.get(connectId) === callbackPromise) {
|
|
118
|
+
this.pendingCallbackTasks.delete(connectId);
|
|
119
|
+
}
|
|
117
120
|
});
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
public async waitForPendingCallbackTasks(
|
|
123
|
+
public async waitForPendingCallbackTasks(
|
|
124
|
+
connectId: string,
|
|
125
|
+
exceptTask?: Deferred<void>
|
|
126
|
+
): Promise<void> {
|
|
121
127
|
const pendingTask = this.pendingCallbackTasks.get(connectId);
|
|
122
|
-
|
|
128
|
+
// Skip only the caller's own task (self-wait); a different one is still awaited.
|
|
129
|
+
if (pendingTask && pendingTask !== exceptTask) {
|
|
123
130
|
Log.debug(`Waiting for pending callback task to complete for connectId: ${connectId}`);
|
|
124
131
|
await pendingTask.promise;
|
|
125
132
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
import { Device } from '../device/Device';
|
|
43
43
|
import { DeviceList } from '../device/DeviceList';
|
|
44
44
|
import { DevicePool } from '../device/DevicePool';
|
|
45
|
+
import { PollingStateManager } from './PollingStateManager';
|
|
45
46
|
import { findMethod } from '../api/utils';
|
|
46
47
|
import { DataManager } from '../data-manager';
|
|
47
48
|
import { UI_REQUEST as UI_REQUEST_CONST } from '../constants/ui-request';
|
|
@@ -73,6 +74,12 @@ import type {
|
|
|
73
74
|
import type { BaseMethod } from '../api/BaseMethod';
|
|
74
75
|
|
|
75
76
|
const Log = getLogger(LoggerNames.Core);
|
|
77
|
+
const PRE_INITIALIZE_TTL_MS = 60 * 1000;
|
|
78
|
+
|
|
79
|
+
// Dedup/coalesce state for "pre-warm signal" methods (isPreWarmSignal),
|
|
80
|
+
// keyed by getPreWarmKey(): coalesce in-flight, skip if warmed within TTL.
|
|
81
|
+
const preWarmInflight = new Map<string, Promise<any>>();
|
|
82
|
+
const preWarmDoneAt = new Map<string, number>();
|
|
76
83
|
|
|
77
84
|
export type CoreContext = ReturnType<Core['getCoreContext']>;
|
|
78
85
|
|
|
@@ -103,8 +110,7 @@ let _connector: DeviceConnector | undefined;
|
|
|
103
110
|
let _uiPromises: UiPromise<UiPromiseResponse['type']>[] = []; // Waiting for ui response
|
|
104
111
|
|
|
105
112
|
const deviceCacheMap = new Map<string, Device>();
|
|
106
|
-
|
|
107
|
-
const pollingState: Record<number, boolean> = {};
|
|
113
|
+
const pollingManager = new PollingStateManager();
|
|
108
114
|
|
|
109
115
|
let preConnectCache: {
|
|
110
116
|
passphraseState: string | undefined;
|
|
@@ -206,9 +212,56 @@ export const callAPI = async (context: CoreContext, message: CoreMessage) => {
|
|
|
206
212
|
return createResponseMessage(method.responseID, false, { error });
|
|
207
213
|
}
|
|
208
214
|
|
|
215
|
+
if (method.isPreWarmSignal) {
|
|
216
|
+
return handlePreWarmSignal(context, message, method);
|
|
217
|
+
}
|
|
218
|
+
|
|
209
219
|
return onCallDevice(context, message, method);
|
|
210
220
|
};
|
|
211
221
|
|
|
222
|
+
// Wrapper for "pre-warm signal" methods: coalesce in-flight same-key pre-warm,
|
|
223
|
+
// skip if warmed within TTL, else run + track. The "hang up so the next real
|
|
224
|
+
// call waits" part lives in onCallDevice (setPrePendingCallPromise).
|
|
225
|
+
const handlePreWarmSignal = async (
|
|
226
|
+
context: CoreContext,
|
|
227
|
+
message: CoreMessage,
|
|
228
|
+
method: BaseMethod
|
|
229
|
+
): Promise<any> => {
|
|
230
|
+
const key = method.getPreWarmKey();
|
|
231
|
+
|
|
232
|
+
const inflight = preWarmInflight.get(key);
|
|
233
|
+
if (inflight) {
|
|
234
|
+
// reply with THIS call's responseID (not the other call's response object)
|
|
235
|
+
try {
|
|
236
|
+
await inflight;
|
|
237
|
+
} catch {
|
|
238
|
+
// pre-warm is best-effort; ignore its failure for the coalesced caller
|
|
239
|
+
}
|
|
240
|
+
return createResponseMessage(method.responseID, true, true);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const doneAt = preWarmDoneAt.get(key);
|
|
244
|
+
if (typeof doneAt === 'number' && Date.now() - doneAt <= method.preWarmTtl) {
|
|
245
|
+
return createResponseMessage(method.responseID, true, true);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const run = onCallDevice(context, message, method);
|
|
249
|
+
preWarmInflight.set(key, run);
|
|
250
|
+
try {
|
|
251
|
+
const result = await run;
|
|
252
|
+
// Only remember the warm if it actually succeeded — a failed pre-warm must
|
|
253
|
+
// not suppress the next pre-warm within the TTL.
|
|
254
|
+
if (result?.success === true && result?.payload === true) {
|
|
255
|
+
preWarmDoneAt.set(key, Date.now());
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
} finally {
|
|
259
|
+
if (preWarmInflight.get(key) === run) {
|
|
260
|
+
preWarmInflight.delete(key);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
212
265
|
const waitWithTimeout = async (promise: Promise<any>, timeout: number) => {
|
|
213
266
|
const timeoutPromise = new Promise((_, reject) => {
|
|
214
267
|
setTimeout(() => reject(new Error('Request timeout')), timeout);
|
|
@@ -244,7 +297,15 @@ const onCallDevice = async (
|
|
|
244
297
|
|
|
245
298
|
updateMethodRequestContext(method, { status: 'running' });
|
|
246
299
|
|
|
247
|
-
|
|
300
|
+
// Normalize undefined / null / '' to '' — they all mean "main wallet, no
|
|
301
|
+
// passphrase". Without this, the first call (preConnectCache starts undefined)
|
|
302
|
+
// or any '' call after a non-'' one is wrongly treated as a passphrase switch
|
|
303
|
+
// and needlessly clears the device cache -> forces a re-enumeration Initialize.
|
|
304
|
+
// A real switch ('' <-> 'stateX', or 'stateX' <-> 'stateY') still differs.
|
|
305
|
+
const normalizePassphraseState = (s?: string | null) => s || '';
|
|
306
|
+
const connectStateChange =
|
|
307
|
+
normalizePassphraseState(preConnectCache.passphraseState) !==
|
|
308
|
+
normalizePassphraseState(method.payload.passphraseState);
|
|
248
309
|
|
|
249
310
|
preConnectCache = {
|
|
250
311
|
passphraseState: method.payload.passphraseState,
|
|
@@ -264,18 +325,31 @@ const onCallDevice = async (
|
|
|
264
325
|
|
|
265
326
|
const task = requestQueue.createTask(method);
|
|
266
327
|
|
|
328
|
+
// Pre-warm holds the device as a per-connectId callback task so a concurrent
|
|
329
|
+
// real call waits (before ensureConnected) instead of racing its Initialize.
|
|
330
|
+
// Only covers pre-warm -> real-call ordering; the reverse is fail-closed.
|
|
331
|
+
let preWarmCallbackTask: Deferred<void> | undefined;
|
|
332
|
+
if (method.isPreWarmSignal && method.connectId) {
|
|
333
|
+
preWarmCallbackTask = createDeferred<void>();
|
|
334
|
+
context.registerCallbackTask(method.connectId, preWarmCallbackTask);
|
|
335
|
+
}
|
|
336
|
+
|
|
267
337
|
let device: Device;
|
|
268
338
|
try {
|
|
269
339
|
/**
|
|
270
340
|
* Polling to ensure successful connection
|
|
271
341
|
*/
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
342
|
+
const connectId = method.connectId ?? '';
|
|
343
|
+
const pollingId = pollingManager.start(connectId);
|
|
344
|
+
device = await ensureConnected(
|
|
345
|
+
context,
|
|
346
|
+
method,
|
|
347
|
+
connectId,
|
|
348
|
+
pollingId,
|
|
349
|
+
task.abortController?.signal
|
|
350
|
+
);
|
|
278
351
|
} catch (e) {
|
|
352
|
+
preWarmCallbackTask?.resolve();
|
|
279
353
|
console.log('ensureConnected error: ', e);
|
|
280
354
|
|
|
281
355
|
completeMethodRequestContext(method, e);
|
|
@@ -291,6 +365,7 @@ const onCallDevice = async (
|
|
|
291
365
|
}
|
|
292
366
|
|
|
293
367
|
if (method.payload?.onlyConnectBleDevice) {
|
|
368
|
+
preWarmCallbackTask?.resolve();
|
|
294
369
|
Log.debug('Call API - only connect ble device: ', device?.mainId);
|
|
295
370
|
return createResponseMessage(method.responseID, true, null);
|
|
296
371
|
}
|
|
@@ -330,8 +405,9 @@ const onCallDevice = async (
|
|
|
330
405
|
);
|
|
331
406
|
|
|
332
407
|
try {
|
|
408
|
+
// Wait for any pending task except our own (self-wait would deadlock).
|
|
333
409
|
if (method.connectId) {
|
|
334
|
-
await context.waitForCallbackTasks(method.connectId);
|
|
410
|
+
await context.waitForCallbackTasks(method.connectId, preWarmCallbackTask);
|
|
335
411
|
}
|
|
336
412
|
|
|
337
413
|
await waitForPendingPromise(getPrePendingCallPromise, setPrePendingCallPromise);
|
|
@@ -525,7 +601,6 @@ const onCallDevice = async (
|
|
|
525
601
|
|
|
526
602
|
try {
|
|
527
603
|
const response: object = await method.run();
|
|
528
|
-
Log.debug('Call API - Inner Method Run: ');
|
|
529
604
|
messageResponse = createResponseMessage(method.responseID, true, response);
|
|
530
605
|
requestQueue.resolveRequest(method.responseID, messageResponse);
|
|
531
606
|
completeMethodRequestContext(method);
|
|
@@ -548,6 +623,7 @@ const onCallDevice = async (
|
|
|
548
623
|
|
|
549
624
|
const runOptions: RunOptions = {
|
|
550
625
|
keepSession: method.payload.keepSession,
|
|
626
|
+
skipInitialize: canSkipInitialize(method, device),
|
|
551
627
|
...parseInitOptions(method),
|
|
552
628
|
};
|
|
553
629
|
const deviceRun = () => device.run(inner, runOptions);
|
|
@@ -569,6 +645,9 @@ const onCallDevice = async (
|
|
|
569
645
|
Log.debug('Call API - Run Error: ', error);
|
|
570
646
|
completeMethodRequestContext(method, error);
|
|
571
647
|
} finally {
|
|
648
|
+
// Release the pre-warm callback task so the next real call can proceed.
|
|
649
|
+
preWarmCallbackTask?.resolve();
|
|
650
|
+
|
|
572
651
|
const response = messageResponse;
|
|
573
652
|
|
|
574
653
|
if (response) {
|
|
@@ -698,23 +777,59 @@ function initDeviceForBle(method: BaseMethod) {
|
|
|
698
777
|
}
|
|
699
778
|
|
|
700
779
|
/**
|
|
701
|
-
*
|
|
780
|
+
* Check if we can skip initialize for this method
|
|
702
781
|
*/
|
|
703
|
-
|
|
782
|
+
function canSkipInitialize(method: BaseMethod, device: Device): boolean {
|
|
783
|
+
const reasons: string[] = [];
|
|
784
|
+
// Must have allowUsePreInitialize enabled on method (the safety gate:
|
|
785
|
+
// only sign-style methods opt in; getAddress/getPublicKey never do).
|
|
786
|
+
if (!method.allowUsePreInitialize) reasons.push('method.disallow');
|
|
787
|
+
// Caller must explicitly opt in per call (on-demand, more flexible).
|
|
788
|
+
if (!method.payload?.usePreInitialize) reasons.push('payload.usePreInitialize=false');
|
|
789
|
+
// Context must match (passphrase/deviceId)
|
|
790
|
+
if (!device.isPreInitializeMetaMatch(method.payload)) reasons.push('meta.mismatch');
|
|
791
|
+
// Device must have been initialized before (has features)
|
|
792
|
+
if (!device.features) reasons.push('features.missing');
|
|
793
|
+
// Must be within pre-initialize TTL
|
|
794
|
+
if (!device.isPreInitializedValid(PRE_INITIALIZE_TTL_MS)) reasons.push('ttl.expired');
|
|
795
|
+
|
|
796
|
+
if (reasons.length) {
|
|
797
|
+
Log.debug(`[PRE-INIT][MISS] method=${method.name} ${reasons.join(',')}`);
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const savedMs = device.getLastInitializeDuration();
|
|
802
|
+
const saved = typeof savedMs === 'number' ? `saved ${savedMs}ms` : 'within TTL + meta match';
|
|
803
|
+
Log.debug(`[PRE-INIT][HIT] method=${method.name} skip Initialize (${saved})`);
|
|
704
804
|
|
|
705
|
-
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* If the Bluetooth connection times out, retry up to 6 times
|
|
810
|
+
* @param retryCount - Current retry count (default 0)
|
|
811
|
+
*/
|
|
812
|
+
async function connectDeviceForBle(method: BaseMethod, device: Device, retryCount = 0) {
|
|
706
813
|
try {
|
|
707
814
|
await device.acquire();
|
|
708
815
|
if (method.payload?.onlyConnectBleDevice) {
|
|
709
816
|
return;
|
|
710
817
|
}
|
|
711
|
-
|
|
818
|
+
// Skip initialize if conditions are met
|
|
819
|
+
if (!canSkipInitialize(method, device)) {
|
|
820
|
+
const initOptions = parseInitOptions(method);
|
|
821
|
+
await device.initialize(initOptions);
|
|
822
|
+
device.markPreInitialized({
|
|
823
|
+
passphraseState: initOptions.passphraseState,
|
|
824
|
+
deviceId: initOptions.deviceId,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
712
827
|
} catch (err) {
|
|
713
|
-
if (err.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
714
|
-
|
|
715
|
-
Log.debug(`
|
|
828
|
+
if (err.errorCode === HardwareErrorCode.BleTimeoutError && retryCount < 6) {
|
|
829
|
+
const nextRetry = retryCount + 1;
|
|
830
|
+
Log.debug(`Bluetooth connect timeout and will retry, retry count: ${nextRetry}`);
|
|
716
831
|
await wait(3000);
|
|
717
|
-
await connectDeviceForBle(method, device);
|
|
832
|
+
await connectDeviceForBle(method, device, nextRetry);
|
|
718
833
|
} else {
|
|
719
834
|
throw err;
|
|
720
835
|
}
|
|
@@ -726,6 +841,7 @@ type IPollFn<T> = (time?: number) => T;
|
|
|
726
841
|
const ensureConnected = async (
|
|
727
842
|
_context: CoreContext,
|
|
728
843
|
method: BaseMethod,
|
|
844
|
+
connectId: string,
|
|
729
845
|
pollingId: number,
|
|
730
846
|
abortSignal?: AbortSignal
|
|
731
847
|
) => {
|
|
@@ -757,7 +873,7 @@ const ensureConnected = async (
|
|
|
757
873
|
return;
|
|
758
874
|
}
|
|
759
875
|
|
|
760
|
-
if (!
|
|
876
|
+
if (!pollingManager.isActive(connectId, pollingId)) {
|
|
761
877
|
Log.debug('EnsureConnected function stop, polling id: ', pollingId);
|
|
762
878
|
reject(ERRORS.TypedError(HardwareErrorCode.PollingStop));
|
|
763
879
|
return;
|
|
@@ -815,8 +931,6 @@ const ensureConnected = async (
|
|
|
815
931
|
* Bluetooth should call initialize here
|
|
816
932
|
*/
|
|
817
933
|
if (DataManager.isBleConnect(env)) {
|
|
818
|
-
bleTimeoutRetry = 0;
|
|
819
|
-
|
|
820
934
|
if (abort()) {
|
|
821
935
|
return;
|
|
822
936
|
}
|
|
@@ -879,7 +993,7 @@ const ensureConnected = async (
|
|
|
879
993
|
// eslint-disable-next-line no-promise-executor-return
|
|
880
994
|
return setTimeout(() => resolve(poll(time * 1.5)), time);
|
|
881
995
|
});
|
|
882
|
-
|
|
996
|
+
// pollingManager.start(connectId) already registered this pollingId as active
|
|
883
997
|
return poll();
|
|
884
998
|
};
|
|
885
999
|
|
|
@@ -1013,6 +1127,7 @@ const onDeviceConnectHandler = (device: Device) => {
|
|
|
1013
1127
|
};
|
|
1014
1128
|
|
|
1015
1129
|
const onDeviceDisconnectHandler = (device: Device) => {
|
|
1130
|
+
device.clearPreInitialized();
|
|
1016
1131
|
const env = DataManager.getSettings('env');
|
|
1017
1132
|
const deviceObject = DataManager.isBleConnect(env) ? device : device.toMessageObject();
|
|
1018
1133
|
postMessage(createDeviceMessage(DEVICE.DISCONNECT, { device: deviceObject as KnownDevice }));
|
|
@@ -1189,8 +1304,8 @@ export default class Core extends EventEmitter {
|
|
|
1189
1304
|
registerCallbackTask: (connectId: string, callbackPromise: Deferred<any>) => {
|
|
1190
1305
|
this.requestQueue.registerPendingCallbackTask(connectId, callbackPromise);
|
|
1191
1306
|
},
|
|
1192
|
-
waitForCallbackTasks: (connectId: string) =>
|
|
1193
|
-
this.requestQueue.waitForPendingCallbackTasks(connectId),
|
|
1307
|
+
waitForCallbackTasks: (connectId: string, exceptTask?: Deferred<void>) =>
|
|
1308
|
+
this.requestQueue.waitForPendingCallbackTasks(connectId, exceptTask),
|
|
1194
1309
|
cancelCallbackTasks: (connectId: string) => this.requestQueue.cancelCallbackTasks(connectId),
|
|
1195
1310
|
};
|
|
1196
1311
|
}
|
|
@@ -1221,10 +1336,10 @@ export default class Core extends EventEmitter {
|
|
|
1221
1336
|
}
|
|
1222
1337
|
|
|
1223
1338
|
case IFRAME.CALL: {
|
|
1224
|
-
Log.log(
|
|
1339
|
+
Log.log(`[${Date.now()}][CALL_API]`, message);
|
|
1225
1340
|
const response = await callAPI(this.getCoreContext(), message);
|
|
1226
1341
|
const { success, payload } = response;
|
|
1227
|
-
Log.log(
|
|
1342
|
+
Log.log(`[${Date.now()}][CALL_API_RESPONSE]`, response);
|
|
1228
1343
|
if (success) {
|
|
1229
1344
|
return response;
|
|
1230
1345
|
}
|
|
@@ -1257,6 +1372,9 @@ export default class Core extends EventEmitter {
|
|
|
1257
1372
|
dispose() {
|
|
1258
1373
|
_deviceList = undefined;
|
|
1259
1374
|
_connector = undefined;
|
|
1375
|
+
deviceCacheMap.clear();
|
|
1376
|
+
preWarmInflight.clear();
|
|
1377
|
+
preWarmDoneAt.clear();
|
|
1260
1378
|
Log.debug(`[Core] Disposing SDK instance: ${this.sdkInstanceId}`);
|
|
1261
1379
|
cleanupSdkInstance(this.sdkInstanceId);
|
|
1262
1380
|
}
|
package/src/device/Device.ts
CHANGED
|
@@ -62,6 +62,7 @@ export type InitOptions = {
|
|
|
62
62
|
|
|
63
63
|
export type RunOptions = {
|
|
64
64
|
keepSession?: boolean;
|
|
65
|
+
skipInitialize?: boolean;
|
|
65
66
|
} & InitOptions;
|
|
66
67
|
|
|
67
68
|
const parseRunOptions = (options?: RunOptions): RunOptions => {
|
|
@@ -196,6 +197,18 @@ export class Device extends EventEmitter {
|
|
|
196
197
|
|
|
197
198
|
pendingCallbackPromise?: Deferred<void>;
|
|
198
199
|
|
|
200
|
+
/** Pre-initialize timestamp (ms) */
|
|
201
|
+
private preInitializedAt?: number;
|
|
202
|
+
|
|
203
|
+
/** Pre-initialize context, used to verify state consistency before skipping */
|
|
204
|
+
private preInitializeMeta?: {
|
|
205
|
+
passphraseState?: string;
|
|
206
|
+
deviceId?: string;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/** Last Initialize duration (ms), reported as "saved" when a skip happens */
|
|
210
|
+
private lastInitializeDurationMs?: number;
|
|
211
|
+
|
|
199
212
|
constructor(descriptor: DeviceDescriptor, sdkInstanceId?: string) {
|
|
200
213
|
super();
|
|
201
214
|
this.originalDescriptor = descriptor;
|
|
@@ -355,6 +368,60 @@ export class Device extends EventEmitter {
|
|
|
355
368
|
this.deviceAcquired = false;
|
|
356
369
|
}
|
|
357
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Pre-initialize: connect + Initialize ahead of the sign. Only runs the
|
|
373
|
+
* fallback init when features are missing (gate on `!this.features`, not
|
|
374
|
+
* isUsedHere which is always false on BLE); otherwise just records the mark.
|
|
375
|
+
*/
|
|
376
|
+
async preInitialize(initOptions?: InitOptions) {
|
|
377
|
+
if (!this.features) {
|
|
378
|
+
await this.acquire();
|
|
379
|
+
await this.initialize(initOptions);
|
|
380
|
+
}
|
|
381
|
+
this.markPreInitialized({
|
|
382
|
+
passphraseState: initOptions?.passphraseState,
|
|
383
|
+
deviceId: initOptions?.deviceId,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
markPreInitialized(meta?: { passphraseState?: string; deviceId?: string }) {
|
|
388
|
+
this.preInitializedAt = Date.now();
|
|
389
|
+
this.preInitializeMeta = meta
|
|
390
|
+
? {
|
|
391
|
+
passphraseState: meta.passphraseState === '' ? undefined : meta.passphraseState,
|
|
392
|
+
deviceId: meta.deviceId === '' ? undefined : meta.deviceId,
|
|
393
|
+
}
|
|
394
|
+
: undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
clearPreInitialized() {
|
|
398
|
+
this.preInitializedAt = undefined;
|
|
399
|
+
this.preInitializeMeta = undefined;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
isPreInitializeMetaMatch(payload?: { passphraseState?: string; deviceId?: string }) {
|
|
403
|
+
if (!this.preInitializeMeta) return true;
|
|
404
|
+
const passphraseState = payload?.passphraseState === '' ? undefined : payload?.passphraseState;
|
|
405
|
+
const deviceId = payload?.deviceId === '' ? undefined : payload?.deviceId;
|
|
406
|
+
return (
|
|
407
|
+
this.preInitializeMeta.passphraseState === passphraseState &&
|
|
408
|
+
this.preInitializeMeta.deviceId === deviceId
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
isPreInitializedValid(ttlMs: number) {
|
|
413
|
+
if (!this.preInitializedAt) return false;
|
|
414
|
+
return Date.now() - this.preInitializedAt <= ttlMs;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
setLastInitializeDuration(durationMs: number) {
|
|
418
|
+
this.lastInitializeDurationMs = durationMs;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
getLastInitializeDuration() {
|
|
422
|
+
return this.lastInitializeDurationMs;
|
|
423
|
+
}
|
|
424
|
+
|
|
358
425
|
getCommands() {
|
|
359
426
|
return this.commands;
|
|
360
427
|
}
|
|
@@ -482,13 +549,7 @@ export class Device extends EventEmitter {
|
|
|
482
549
|
payload.passphrase_state = options?.passphraseState;
|
|
483
550
|
payload.is_contains_attach = true;
|
|
484
551
|
|
|
485
|
-
|
|
486
|
-
deviceId: options?.deviceId,
|
|
487
|
-
passphraseState: options?.passphraseState,
|
|
488
|
-
initSession: options?.initSession,
|
|
489
|
-
InitializePayload: payload,
|
|
490
|
-
});
|
|
491
|
-
|
|
552
|
+
const initStartAt = Date.now();
|
|
492
553
|
try {
|
|
493
554
|
// @ts-expect-error
|
|
494
555
|
const { message } = await Promise.race([
|
|
@@ -501,7 +562,8 @@ export class Device extends EventEmitter {
|
|
|
501
562
|
}),
|
|
502
563
|
]);
|
|
503
564
|
|
|
504
|
-
|
|
565
|
+
const initCostMs = Date.now() - initStartAt;
|
|
566
|
+
this.setLastInitializeDuration(initCostMs);
|
|
505
567
|
this._updateFeatures(message, options?.initSession);
|
|
506
568
|
await TransportManager.reconfigure(this.features);
|
|
507
569
|
} catch (error) {
|
|
@@ -588,7 +650,9 @@ export class Device extends EventEmitter {
|
|
|
588
650
|
|
|
589
651
|
try {
|
|
590
652
|
if (fn) {
|
|
591
|
-
|
|
653
|
+
if (!options?.skipInitialize) {
|
|
654
|
+
await this.initialize(options);
|
|
655
|
+
}
|
|
592
656
|
}
|
|
593
657
|
} catch (error) {
|
|
594
658
|
this.runPromise = null;
|
package/src/inject.ts
CHANGED
|
@@ -142,7 +142,7 @@ export const createCoreApi = (
|
|
|
142
142
|
|
|
143
143
|
testInitializeDeviceDuration: (connectId, params) =>
|
|
144
144
|
call({ ...params, connectId, method: 'testInitializeDeviceDuration' }),
|
|
145
|
-
|
|
145
|
+
preInitialize: (connectId, params) => call({ ...params, connectId, method: 'preInitialize' }),
|
|
146
146
|
deviceBackup: connectId => call({ connectId, method: 'deviceBackup' }),
|
|
147
147
|
deviceChangePin: (connectId, params) => call({ ...params, connectId, method: 'deviceChangePin' }),
|
|
148
148
|
deviceFlags: (connectId, params) => call({ ...params, connectId, method: 'deviceFlags' }),
|
package/src/types/api/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { off, on, removeAllListeners } from './event';
|
|
|
2
2
|
import type { uiResponse } from './uiResponse';
|
|
3
3
|
import type { init, updateSettings } from './init';
|
|
4
4
|
import type { testInitializeDeviceDuration } from './testInitializeDeviceDuration';
|
|
5
|
+
import type { preInitialize } from './preInitialize';
|
|
5
6
|
import type { getLogs } from './getLogs';
|
|
6
7
|
import type { checkBridgeStatus } from './checkBridgeStatus';
|
|
7
8
|
import type { checkBridgeRelease } from './checkBridgeRelease';
|
|
@@ -152,6 +153,7 @@ export type CoreApi = {
|
|
|
152
153
|
* Test function
|
|
153
154
|
*/
|
|
154
155
|
testInitializeDeviceDuration: typeof testInitializeDeviceDuration;
|
|
156
|
+
preInitialize: typeof preInitialize;
|
|
155
157
|
|
|
156
158
|
/**
|
|
157
159
|
* Core function
|
package/src/types/params.ts
CHANGED
|
@@ -47,6 +47,11 @@ export interface CommonParams {
|
|
|
47
47
|
* Only connect device, not initialize device, only ble connect
|
|
48
48
|
*/
|
|
49
49
|
onlyConnectBleDevice?: boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Use pre-initialized device state (BLE only)
|
|
53
|
+
*/
|
|
54
|
+
usePreInitialize?: boolean;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
export type Params<T> = CommonParams & T & { bundle?: undefined };
|