@onekeyfe/hardware-cli 1.2.0-alpha.25 → 1.2.0-alpha.27

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/cli.d.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  import { Command } from 'commander';
2
2
  declare const program: Command;
3
3
  export declare function getLegacyFirmwareConnectTimeout(transport: 'usb' | 'ble'): 90000 | undefined;
4
+ /**
5
+ * Prepare passphrase session before SDK calls.
6
+ *
7
+ * 1. If --use-empty-passphrase or --passphrase-state provided → use as-is
8
+ * 2. Try keychain → preloadSessionCache → use cached session
9
+ * 3. Keychain miss → openWalletSession (triggers 1/2/3 prompt) → save to keychain
10
+ *
11
+ * After this, globalOpts.passphraseState is set and getCommonParams will include it.
12
+ */
13
+ export declare function prepareSession(sdk: typeof import('@onekeyfe/hd-common-connect-sdk').default, globalOpts: Record<string, any>): Promise<string | undefined>;
4
14
  export declare function buildWallpaperUploadMetrics({ totalBytes, transferredBytes, startedAt, endedAt, lastProgress, }: {
5
15
  totalBytes: number;
6
16
  transferredBytes: number;
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.program = exports.buildWallpaperUploadMetrics = exports.getLegacyFirmwareConnectTimeout = void 0;
4
+ exports.program = exports.buildWallpaperUploadMetrics = exports.prepareSession = exports.getLegacyFirmwareConnectTimeout = void 0;
5
5
  const node_fs_1 = require("node:fs");
6
6
  const node_path_1 = require("node:path");
7
7
  const commander_1 = require("commander");
@@ -12,30 +12,6 @@ const deviceSelection_1 = require("./deviceSelection");
12
12
  const deviceStateCommands_1 = require("./deviceStateCommands");
13
13
  const sdk_1 = require("./sdk");
14
14
  const session_1 = require("./session");
15
- function extractPassphraseSession(payload) {
16
- if (typeof payload === 'string') {
17
- return { passphraseState: payload };
18
- }
19
- if (!payload || typeof payload !== 'object') {
20
- return {};
21
- }
22
- const statePayload = payload;
23
- let passphraseState;
24
- if (typeof statePayload.passphrase_state === 'string') {
25
- passphraseState = statePayload.passphrase_state;
26
- }
27
- else if (typeof statePayload.passphraseState === 'string') {
28
- passphraseState = statePayload.passphraseState;
29
- }
30
- let sessionId;
31
- if (typeof statePayload.session_id === 'string') {
32
- sessionId = statePayload.session_id;
33
- }
34
- else if (typeof statePayload.sessionId === 'string') {
35
- sessionId = statePayload.sessionId;
36
- }
37
- return { passphraseState, sessionId };
38
- }
39
15
  const program = new commander_1.Command();
40
16
  exports.program = program;
41
17
  const { version: cliVersion } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(__dirname, '../package.json'), 'utf8'));
@@ -666,48 +642,30 @@ sessionCmd
666
642
  process.stderr.write('[onekey-hw] Device is locked. Unlocking (PIN required)...\n');
667
643
  await unlockWithRetry(sdk, connectId);
668
644
  }
669
- // 3. Get passphraseState (triggers 1/2/3 selection)
670
- const psResult = await sdk.getPassphraseState(connectId, {
671
- initSession: true,
672
- useEmptyPassphrase: false,
645
+ // 3. Open a hidden wallet session (triggers 1/2/3 selection).
646
+ const sessionResult = await sdk.openWalletSession(connectId, {
647
+ mode: 'select-hidden',
673
648
  });
674
- if (!psResult.success) {
675
- outputResult(globalOpts, psResult);
649
+ if (!sessionResult.success) {
650
+ outputResult(globalOpts, sessionResult);
676
651
  return;
677
652
  }
678
- const { passphraseState, sessionId: passphraseSessionId } = extractPassphraseSession(psResult.payload);
679
- if (!passphraseState) {
653
+ if (sessionResult.payload.walletType !== 'hidden') {
680
654
  outputResult(globalOpts, {
681
655
  success: false,
682
- payload: { error: 'getPassphraseState did not return passphraseState' },
656
+ payload: { error: 'Hidden wallet selection did not return a hidden wallet session' },
683
657
  });
684
658
  return;
685
659
  }
660
+ const { deviceId, passphraseState, sessionId } = sessionResult.payload;
686
661
  // 4. Get address to verify + extract deviceId
687
- const addrResult = await sdk.evmGetAddress(connectId, device.deviceId || '', {
662
+ const addrResult = await sdk.evmGetAddress(connectId, deviceId, {
688
663
  path: "m/44'/60'/0'/0/0",
689
664
  showOnOneKey: false,
690
665
  passphraseState,
691
666
  });
692
- // 5. Fetch the now-active session_id via getFeatures.
693
- //
694
- // IMPORTANT: pass `passphraseState` here. Without it, the SDK's
695
- // connectStateChange guard (core/index.ts) would see the payload's
696
- // passphraseState flip from mnNy → undefined, clear the cached Device,
697
- // and call Initialize again with no passphrase_state / no session_id.
698
- // That Initialize resets the device to the standard wallet and returns
699
- // a *standard-wallet* session_id — which we'd then save in the keychain
700
- // paired with the hidden-wallet passphraseState. On the next CLI run
701
- // the mismatch would trigger PassphraseRequest (1/2/3 again).
702
- const featResult = await sdk.getFeatures(connectId, {
703
- passphraseState,
704
- skipPassphraseCheck: true,
705
- });
706
- const featPayload = featResult?.success ? featResult.payload : undefined;
707
- const deviceId = featPayload?.deviceId || device.deviceId || '';
708
- const sessionId = passphraseSessionId || featPayload?.sessionId || '';
709
- // 6. Save to keychain
710
- if (passphraseState && deviceId && sessionId) {
667
+ // 5. Persist only the session id returned by the explicit wallet-session API.
668
+ if (sessionId) {
711
669
  await (0, session_1.saveSessionToKeychain)(deviceId, passphraseState, sessionId);
712
670
  }
713
671
  outputResult(globalOpts, {
@@ -804,7 +762,7 @@ async function unlockWithRetry(sdk, connectId, maxAttempts = 3) {
804
762
  *
805
763
  * 1. If --use-empty-passphrase or --passphrase-state provided → use as-is
806
764
  * 2. Try keychain → preloadSessionCache → use cached session
807
- * 3. Keychain miss → getPassphraseState (triggers 1/2/3 prompt) → save to keychain
765
+ * 3. Keychain miss → openWalletSession (triggers 1/2/3 prompt) → save to keychain
808
766
  *
809
767
  * After this, globalOpts.passphraseState is set and getCommonParams will include it.
810
768
  */
@@ -816,7 +774,7 @@ globalOpts) {
816
774
  return globalOpts.passphraseState;
817
775
  }
818
776
  // Errors from the SDK calls below (PIN cancelled, transport broken,
819
- // getPassphraseState rejection) intentionally propagate to runCommand's
777
+ // openWalletSession rejection) intentionally propagate to runCommand's
820
778
  // catch block, which renders them as structured `{ success: false,
821
779
  // payload: { error, code } }` output instead of silently falling through
822
780
  // to a confusing downstream error 112 / 114.
@@ -887,38 +845,26 @@ globalOpts) {
887
845
  return cached;
888
846
  }
889
847
  }
890
- // ── Step 6: Keychain miss → getPassphraseState (triggers 1/2/3 prompt) ──
891
- const psResult = await sdk.getPassphraseState(connectId, {
892
- initSession: true,
893
- useEmptyPassphrase: false,
848
+ // ── Step 6: Keychain miss → openWalletSession (triggers 1/2/3 prompt) ──
849
+ const sessionResult = await sdk.openWalletSession(connectId, {
850
+ mode: 'select-hidden',
894
851
  });
895
- if (psResult.success && psResult.payload) {
896
- const { passphraseState, sessionId: passphraseSessionId } = extractPassphraseSession(psResult.payload);
897
- if (!passphraseState) {
852
+ if (sessionResult.success && sessionResult.payload) {
853
+ if (sessionResult.payload.walletType !== 'hidden') {
898
854
  return undefined;
899
855
  }
856
+ const { deviceId: sessionDeviceId, passphraseState, sessionId } = sessionResult.payload;
857
+ globalOpts.deviceId = sessionDeviceId;
900
858
  globalOpts.passphraseState = passphraseState;
901
- // Save session to keychain for next invocation.
902
- //
903
- // Pass passphraseState to keep connectStateChange=false — otherwise
904
- // Initialize would be re-run without passphrase_state, resetting the
905
- // device to the standard wallet and returning a mismatched session_id.
906
- // See the matching comment in `session connect`.
907
- if (deviceId) {
908
- const featAfter = await sdk.getFeatures(connectId, {
909
- passphraseState,
910
- skipPassphraseCheck: true,
911
- });
912
- const sessionId = passphraseSessionId || (featAfter?.success ? featAfter.payload?.sessionId : undefined);
913
- if (sessionId) {
914
- await (0, session_1.saveSessionToKeychain)(deviceId, passphraseState, sessionId);
915
- await (0, session_1.preloadSessionFromKeychain)(deviceId);
916
- }
859
+ if (sessionId) {
860
+ await (0, session_1.saveSessionToKeychain)(sessionDeviceId, passphraseState, sessionId);
861
+ await (0, session_1.preloadSessionFromKeychain)(sessionDeviceId);
917
862
  }
918
863
  return passphraseState;
919
864
  }
920
865
  return undefined;
921
866
  }
867
+ exports.prepareSession = prepareSession;
922
868
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
923
869
  function outputResult(_globalOpts, result) {
924
870
  console.log(JSON.stringify(result, null, 2));
package/dist/session.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Passphrase session management for hd-cli.
3
3
  *
4
4
  * Aligns with app-monorepo's CLI pattern:
5
- * Login: getPassphraseState → passphraseState + sessionId → keychain
5
+ * Login: openWalletSession → passphraseState + sessionId → keychain
6
6
  * Command: keychain → preloadSessionCache → SDK call (no passphrase prompt)
7
7
  * Stale: error 112 → clear keychain → re-prompt → retry
8
8
  * Logout: keychain delete
package/dist/session.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Passphrase session management for hd-cli.
4
4
  *
5
5
  * Aligns with app-monorepo's CLI pattern:
6
- * Login: getPassphraseState → passphraseState + sessionId → keychain
6
+ * Login: openWalletSession → passphraseState + sessionId → keychain
7
7
  * Command: keychain → preloadSessionCache → SDK call (no passphrase prompt)
8
8
  * Stale: error 112 → clear keychain → re-prompt → retry
9
9
  * Logout: keychain delete
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hardware-cli",
3
- "version": "1.2.0-alpha.25",
3
+ "version": "1.2.0-alpha.27",
4
4
  "description": "OneKey hardware wallet CLI for testing device communication",
5
5
  "author": "OneKey",
6
6
  "license": "Apache-2.0",
@@ -31,12 +31,12 @@
31
31
  "test": "jest"
32
32
  },
33
33
  "dependencies": {
34
- "@onekeyfe/hd-common-connect-sdk": "1.2.0-alpha.25",
35
- "@onekeyfe/hd-core": "1.2.0-alpha.25",
36
- "@onekeyfe/hd-shared": "1.2.0-alpha.25",
37
- "@onekeyfe/hd-transport-usb": "1.2.0-alpha.25",
34
+ "@onekeyfe/hd-common-connect-sdk": "1.2.0-alpha.27",
35
+ "@onekeyfe/hd-core": "1.2.0-alpha.27",
36
+ "@onekeyfe/hd-shared": "1.2.0-alpha.27",
37
+ "@onekeyfe/hd-transport-usb": "1.2.0-alpha.27",
38
38
  "@stoprocent/noble": "2.3.16",
39
39
  "commander": "^12.0.0"
40
40
  },
41
- "gitHead": "20bb98b8530c509299b9e109d2b5ada7c6a6d034"
41
+ "gitHead": "ea3c188364a6fb968875484805085e4466d3ef99"
42
42
  }
@@ -0,0 +1,62 @@
1
+ import { prepareSession } from '../cli';
2
+ import { preloadSessionFromKeychain, saveSessionToKeychain } from '../session';
3
+
4
+ jest.mock('../session', () => ({
5
+ clearSessionFromKeychain: jest.fn(),
6
+ preloadSessionFromKeychain: jest.fn(),
7
+ saveSessionToKeychain: jest.fn(),
8
+ }));
9
+
10
+ describe('CLI wallet session', () => {
11
+ test('persists the session returned by openWalletSession without reading it from features', async () => {
12
+ const sdk = {
13
+ searchDevices: jest.fn().mockResolvedValue({
14
+ success: true,
15
+ payload: [
16
+ {
17
+ connectId: 'pro2-connect-id',
18
+ deviceId: 'device-id',
19
+ deviceType: 'pro2',
20
+ features: {
21
+ deviceId: 'device-id',
22
+ deviceType: 'pro2',
23
+ unlocked: true,
24
+ passphraseProtection: true,
25
+ },
26
+ },
27
+ ],
28
+ }),
29
+ getFeatures: jest.fn(),
30
+ openWalletSession: jest.fn().mockResolvedValue({
31
+ success: true,
32
+ payload: {
33
+ protocol: 'V2',
34
+ walletType: 'hidden',
35
+ deviceId: 'device-id',
36
+ passphraseState: 'wallet-state',
37
+ sessionId: 'wallet-session-id',
38
+ resumed: false,
39
+ },
40
+ }),
41
+ };
42
+ const globalOpts: Record<string, unknown> = {};
43
+ (preloadSessionFromKeychain as jest.Mock).mockResolvedValueOnce(undefined);
44
+
45
+ await expect(prepareSession(sdk as never, globalOpts)).resolves.toBe('wallet-state');
46
+
47
+ expect(sdk.openWalletSession).toHaveBeenCalledWith('pro2-connect-id', {
48
+ mode: 'select-hidden',
49
+ });
50
+ expect(sdk.getFeatures).not.toHaveBeenCalled();
51
+ expect(saveSessionToKeychain).toHaveBeenCalledWith(
52
+ 'device-id',
53
+ 'wallet-state',
54
+ 'wallet-session-id'
55
+ );
56
+ expect(globalOpts).toMatchObject({
57
+ connectId: 'pro2-connect-id',
58
+ deviceId: 'device-id',
59
+ passphraseState: 'wallet-state',
60
+ });
61
+ });
62
+ });
package/src/cli.ts CHANGED
@@ -31,41 +31,6 @@ import type {
31
31
  /** SearchDevice enriched with features fetched after discovery */
32
32
  type EnrichedSearchDevice = SearchDevice & { features?: Features };
33
33
 
34
- function extractPassphraseSession(payload: unknown): {
35
- passphraseState?: string;
36
- sessionId?: string;
37
- } {
38
- if (typeof payload === 'string') {
39
- return { passphraseState: payload };
40
- }
41
- if (!payload || typeof payload !== 'object') {
42
- return {};
43
- }
44
-
45
- const statePayload = payload as {
46
- passphrase_state?: unknown;
47
- passphraseState?: unknown;
48
- session_id?: unknown;
49
- sessionId?: unknown;
50
- };
51
-
52
- let passphraseState: string | undefined;
53
- if (typeof statePayload.passphrase_state === 'string') {
54
- passphraseState = statePayload.passphrase_state;
55
- } else if (typeof statePayload.passphraseState === 'string') {
56
- passphraseState = statePayload.passphraseState;
57
- }
58
-
59
- let sessionId: string | undefined;
60
- if (typeof statePayload.session_id === 'string') {
61
- sessionId = statePayload.session_id;
62
- } else if (typeof statePayload.sessionId === 'string') {
63
- sessionId = statePayload.sessionId;
64
- }
65
-
66
- return { passphraseState, sessionId };
67
- }
68
-
69
34
  const program = new Command();
70
35
  const { version: cliVersion } = JSON.parse(
71
36
  readFileSync(resolve(__dirname, '../package.json'), 'utf8')
@@ -847,7 +812,7 @@ sessionCmd
847
812
  });
848
813
  return;
849
814
  }
850
- const device: EnrichedSearchDevice = searchResult.payload[0];
815
+ const device = searchResult.payload[0] as SearchDevice & { features?: Features };
851
816
  const connectId = device.connectId || globalOpts.connectId;
852
817
 
853
818
  // 2. Unlock if locked — getPassphraseState below talks to a live
@@ -858,53 +823,32 @@ sessionCmd
858
823
  await unlockWithRetry(sdk, connectId);
859
824
  }
860
825
 
861
- // 3. Get passphraseState (triggers 1/2/3 selection)
862
- const psResult = await sdk.getPassphraseState(connectId, {
863
- initSession: true,
864
- useEmptyPassphrase: false,
826
+ // 3. Open a hidden wallet session (triggers 1/2/3 selection).
827
+ const sessionResult = await sdk.openWalletSession(connectId, {
828
+ mode: 'select-hidden',
865
829
  });
866
- if (!psResult.success) {
867
- outputResult(globalOpts, psResult);
830
+ if (!sessionResult.success) {
831
+ outputResult(globalOpts, sessionResult);
868
832
  return;
869
833
  }
870
- const { passphraseState, sessionId: passphraseSessionId } = extractPassphraseSession(
871
- psResult.payload
872
- );
873
- if (!passphraseState) {
834
+ if (sessionResult.payload.walletType !== 'hidden') {
874
835
  outputResult(globalOpts, {
875
836
  success: false,
876
- payload: { error: 'getPassphraseState did not return passphraseState' },
837
+ payload: { error: 'Hidden wallet selection did not return a hidden wallet session' },
877
838
  });
878
839
  return;
879
840
  }
841
+ const { deviceId, passphraseState, sessionId } = sessionResult.payload;
880
842
 
881
843
  // 4. Get address to verify + extract deviceId
882
- const addrResult = await sdk.evmGetAddress(connectId, device.deviceId || '', {
844
+ const addrResult = await sdk.evmGetAddress(connectId, deviceId, {
883
845
  path: "m/44'/60'/0'/0/0",
884
846
  showOnOneKey: false,
885
847
  passphraseState,
886
848
  });
887
849
 
888
- // 5. Fetch the now-active session_id via getFeatures.
889
- //
890
- // IMPORTANT: pass `passphraseState` here. Without it, the SDK's
891
- // connectStateChange guard (core/index.ts) would see the payload's
892
- // passphraseState flip from mnNy → undefined, clear the cached Device,
893
- // and call Initialize again with no passphrase_state / no session_id.
894
- // That Initialize resets the device to the standard wallet and returns
895
- // a *standard-wallet* session_id — which we'd then save in the keychain
896
- // paired with the hidden-wallet passphraseState. On the next CLI run
897
- // the mismatch would trigger PassphraseRequest (1/2/3 again).
898
- const featResult = await sdk.getFeatures(connectId, {
899
- passphraseState,
900
- skipPassphraseCheck: true,
901
- });
902
- const featPayload = featResult?.success ? featResult.payload : undefined;
903
- const deviceId = featPayload?.deviceId || device.deviceId || '';
904
- const sessionId = passphraseSessionId || featPayload?.sessionId || '';
905
-
906
- // 6. Save to keychain
907
- if (passphraseState && deviceId && sessionId) {
850
+ // 5. Persist only the session id returned by the explicit wallet-session API.
851
+ if (sessionId) {
908
852
  await saveSessionToKeychain(deviceId, passphraseState, sessionId);
909
853
  }
910
854
 
@@ -1023,11 +967,11 @@ async function unlockWithRetry(
1023
967
  *
1024
968
  * 1. If --use-empty-passphrase or --passphrase-state provided → use as-is
1025
969
  * 2. Try keychain → preloadSessionCache → use cached session
1026
- * 3. Keychain miss → getPassphraseState (triggers 1/2/3 prompt) → save to keychain
970
+ * 3. Keychain miss → openWalletSession (triggers 1/2/3 prompt) → save to keychain
1027
971
  *
1028
972
  * After this, globalOpts.passphraseState is set and getCommonParams will include it.
1029
973
  */
1030
- async function prepareSession(
974
+ export async function prepareSession(
1031
975
  sdk: typeof import('@onekeyfe/hd-common-connect-sdk').default,
1032
976
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1033
977
  globalOpts: Record<string, any>
@@ -1038,7 +982,7 @@ async function prepareSession(
1038
982
  }
1039
983
 
1040
984
  // Errors from the SDK calls below (PIN cancelled, transport broken,
1041
- // getPassphraseState rejection) intentionally propagate to runCommand's
985
+ // openWalletSession rejection) intentionally propagate to runCommand's
1042
986
  // catch block, which renders them as structured `{ success: false,
1043
987
  // payload: { error, code } }` output instead of silently falling through
1044
988
  // to a confusing downstream error 112 / 114.
@@ -1147,38 +1091,22 @@ async function prepareSession(
1147
1091
  }
1148
1092
  }
1149
1093
 
1150
- // ── Step 6: Keychain miss → getPassphraseState (triggers 1/2/3 prompt) ──
1151
- const psResult = await sdk.getPassphraseState(connectId, {
1152
- initSession: true,
1153
- useEmptyPassphrase: false,
1094
+ // ── Step 6: Keychain miss → openWalletSession (triggers 1/2/3 prompt) ──
1095
+ const sessionResult = await sdk.openWalletSession(connectId, {
1096
+ mode: 'select-hidden',
1154
1097
  });
1155
1098
 
1156
- if (psResult.success && psResult.payload) {
1157
- const { passphraseState, sessionId: passphraseSessionId } = extractPassphraseSession(
1158
- psResult.payload
1159
- );
1160
- if (!passphraseState) {
1099
+ if (sessionResult.success && sessionResult.payload) {
1100
+ if (sessionResult.payload.walletType !== 'hidden') {
1161
1101
  return undefined;
1162
1102
  }
1103
+ const { deviceId: sessionDeviceId, passphraseState, sessionId } = sessionResult.payload;
1104
+ globalOpts.deviceId = sessionDeviceId;
1163
1105
  globalOpts.passphraseState = passphraseState;
1164
1106
 
1165
- // Save session to keychain for next invocation.
1166
- //
1167
- // Pass passphraseState to keep connectStateChange=false — otherwise
1168
- // Initialize would be re-run without passphrase_state, resetting the
1169
- // device to the standard wallet and returning a mismatched session_id.
1170
- // See the matching comment in `session connect`.
1171
- if (deviceId) {
1172
- const featAfter = await sdk.getFeatures(connectId, {
1173
- passphraseState,
1174
- skipPassphraseCheck: true,
1175
- });
1176
- const sessionId =
1177
- passphraseSessionId || (featAfter?.success ? featAfter.payload?.sessionId : undefined);
1178
- if (sessionId) {
1179
- await saveSessionToKeychain(deviceId, passphraseState, sessionId);
1180
- await preloadSessionFromKeychain(deviceId);
1181
- }
1107
+ if (sessionId) {
1108
+ await saveSessionToKeychain(sessionDeviceId, passphraseState, sessionId);
1109
+ await preloadSessionFromKeychain(sessionDeviceId);
1182
1110
  }
1183
1111
 
1184
1112
  return passphraseState;
package/src/session.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Passphrase session management for hd-cli.
3
3
  *
4
4
  * Aligns with app-monorepo's CLI pattern:
5
- * Login: getPassphraseState → passphraseState + sessionId → keychain
5
+ * Login: openWalletSession → passphraseState + sessionId → keychain
6
6
  * Command: keychain → preloadSessionCache → SDK call (no passphrase prompt)
7
7
  * Stale: error 112 → clear keychain → re-prompt → retry
8
8
  * Logout: keychain delete