@bytezhang/hardware-wallet-core 0.0.1 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +63 -5
- package/dist/index.d.ts +63 -5
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -18,6 +18,7 @@ declare enum HardwareErrorCode {
|
|
|
18
18
|
DeviceInBootloader = 5532,
|
|
19
19
|
FirmwareTooOld = 5533,
|
|
20
20
|
WrongApp = 5540,
|
|
21
|
+
DeviceMismatch = 5560,
|
|
21
22
|
BridgeNotFound = 5550,
|
|
22
23
|
TransportNotAvailable = 5551
|
|
23
24
|
}
|
|
@@ -40,6 +41,24 @@ declare function failure(code: HardwareErrorCode, error: string): Failure;
|
|
|
40
41
|
type VendorType = 'trezor' | 'ledger' | 'keystone' | 'keystoneqr';
|
|
41
42
|
type ConnectionType = 'usb' | 'ble' | 'qr';
|
|
42
43
|
type TransportType = 'usb' | 'ble' | 'hid' | 'bridge' | 'qr';
|
|
44
|
+
/**
|
|
45
|
+
* Device capabilities — describes what a specific device/connection
|
|
46
|
+
* combination can or cannot do. Varies by vendor, model, and connection type.
|
|
47
|
+
*
|
|
48
|
+
* This enables business logic to check capabilities instead of hard-coding
|
|
49
|
+
* vendor-specific conditions (e.g., `if (vendor === 'ledger')`).
|
|
50
|
+
*/
|
|
51
|
+
interface DeviceCapabilities {
|
|
52
|
+
/**
|
|
53
|
+
* Whether connectId/deviceId persist across sessions.
|
|
54
|
+
*
|
|
55
|
+
* - `true`: IDs are stable (e.g., OneKey USB, Trezor USB).
|
|
56
|
+
* Business logic can match devices by stored connectId/deviceId.
|
|
57
|
+
* - `false`: IDs are ephemeral, regenerated each session (e.g., Ledger WebHID).
|
|
58
|
+
* Business logic should NOT rely on stored connectId/deviceId for matching.
|
|
59
|
+
*/
|
|
60
|
+
persistentDeviceIdentity: boolean;
|
|
61
|
+
}
|
|
43
62
|
interface DeviceInfo {
|
|
44
63
|
vendor: VendorType;
|
|
45
64
|
model: string;
|
|
@@ -49,6 +68,8 @@ interface DeviceInfo {
|
|
|
49
68
|
label?: string;
|
|
50
69
|
connectionType: ConnectionType;
|
|
51
70
|
battery?: number;
|
|
71
|
+
/** Device capabilities — varies by vendor, model, and connection type */
|
|
72
|
+
capabilities?: DeviceCapabilities;
|
|
52
73
|
}
|
|
53
74
|
interface DeviceTarget {
|
|
54
75
|
connectId: string;
|
|
@@ -74,12 +95,18 @@ interface EvmPublicKey {
|
|
|
74
95
|
}
|
|
75
96
|
interface EvmSignTxParams {
|
|
76
97
|
path: string;
|
|
98
|
+
/**
|
|
99
|
+
* RLP-serialized transaction hex (0x-prefixed or plain).
|
|
100
|
+
* When provided, the connector uses this directly instead of individual fields.
|
|
101
|
+
* Required for Ledger; Trezor may use individual fields instead.
|
|
102
|
+
*/
|
|
103
|
+
serializedTx?: string;
|
|
77
104
|
/** Contract address or recipient. Optional for contract deployment transactions. */
|
|
78
105
|
to?: string;
|
|
79
|
-
value
|
|
80
|
-
chainId
|
|
81
|
-
nonce
|
|
82
|
-
gasLimit
|
|
106
|
+
value?: string;
|
|
107
|
+
chainId?: number;
|
|
108
|
+
nonce?: string;
|
|
109
|
+
gasLimit?: string;
|
|
83
110
|
gasPrice?: string;
|
|
84
111
|
maxFeePerGas?: string;
|
|
85
112
|
maxPriorityFeePerGas?: string;
|
|
@@ -303,6 +330,25 @@ interface QrResponseData {
|
|
|
303
330
|
urData: string;
|
|
304
331
|
}
|
|
305
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Chain fingerprint utilities for device identity verification.
|
|
335
|
+
*
|
|
336
|
+
* Ledger devices have ephemeral IDs that change every session.
|
|
337
|
+
* To verify that the same seed/device is connected, we derive an address
|
|
338
|
+
* at a fixed testnet path and hash it into a stable "chain fingerprint".
|
|
339
|
+
*/
|
|
340
|
+
/** Testnet derivation paths used to generate chain fingerprints. */
|
|
341
|
+
declare const CHAIN_FINGERPRINT_PATHS: Record<ChainForFingerprint, string>;
|
|
342
|
+
type ChainForFingerprint = 'evm' | 'btc' | 'sol';
|
|
343
|
+
/**
|
|
344
|
+
* Hash an address string into a 16-character hex fingerprint.
|
|
345
|
+
*
|
|
346
|
+
* Uses a simple non-cryptographic hash (FNV-1a based) to avoid
|
|
347
|
+
* pulling in a SHA-256 dependency. This is NOT used for security —
|
|
348
|
+
* only for device identity matching.
|
|
349
|
+
*/
|
|
350
|
+
declare function deriveDeviceFingerprint(address: string): string;
|
|
351
|
+
|
|
306
352
|
declare const DEVICE_EVENT = "DEVICE_EVENT";
|
|
307
353
|
/** Events originating from the hardware device. */
|
|
308
354
|
declare const DEVICE: {
|
|
@@ -588,6 +634,16 @@ interface IHardwareWallet<TConfig = unknown> extends IEvmMethods, IBtcMethods, I
|
|
|
588
634
|
getDeviceInfo(connectId: string, deviceId: string): Promise<Response<DeviceInfo>>;
|
|
589
635
|
getSupportedChains(): ChainCapability[];
|
|
590
636
|
cancel(connectId: string): void;
|
|
637
|
+
/**
|
|
638
|
+
* Derive a chain-specific fingerprint for the connected device.
|
|
639
|
+
*
|
|
640
|
+
* For Ledger: derives an address at a fixed testnet path and hashes it.
|
|
641
|
+
* For Trezor: returns the hardware device_id from firmware features.
|
|
642
|
+
*
|
|
643
|
+
* Used to verify that the same seed/device is connected across sessions,
|
|
644
|
+
* especially for vendors with ephemeral connectId/deviceId.
|
|
645
|
+
*/
|
|
646
|
+
getChainFingerprint(connectId: string, deviceId: string, chain: ChainForFingerprint): Promise<Response<string>>;
|
|
591
647
|
setUiHandler(handler: Partial<IUiHandler>): void;
|
|
592
648
|
on<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
|
|
593
649
|
on(event: string, listener: DeviceEventListener): void;
|
|
@@ -699,6 +755,8 @@ interface ConnectorDevice {
|
|
|
699
755
|
deviceId: string;
|
|
700
756
|
name: string;
|
|
701
757
|
model?: string;
|
|
758
|
+
/** Device capabilities — available from scan time */
|
|
759
|
+
capabilities?: DeviceCapabilities;
|
|
702
760
|
}
|
|
703
761
|
interface ConnectorSession {
|
|
704
762
|
sessionId: string;
|
|
@@ -819,4 +877,4 @@ declare class TypedEventEmitter<TMap extends Record<string, any> = Record<string
|
|
|
819
877
|
*/
|
|
820
878
|
declare function compareSemver(a: string, b: string): number;
|
|
821
879
|
|
|
822
|
-
export { type ActiveJobInfo, type BtcAddress, type BtcGetAddressParams, type BtcGetPublicKeyParams, type BtcPublicKey, type BtcRefTransaction, type BtcSignMsgParams, type BtcSignTxParams, type BtcSignature, type BtcSignedTx, type BtcTxInput, type BtcTxOutput, type ChainCapability, type ConnectionType, type ConnectorDevice, type ConnectorEventMap, type ConnectorEventType, type ConnectorSession, DEVICE, DEVICE_EVENT, type DeviceChangeEvent, type DeviceConnectEvent, type DeviceDescriptor, type DeviceDisconnectEvent, type DeviceEvent, type DeviceEventListener, type DeviceInfo, DeviceJobQueue, type DeviceTarget, type EIP712Domain, type EvmAddress, type EvmGetAddressParams, type EvmGetPublicKeyParams, type EvmPublicKey, type EvmSignMsgParams, type EvmSignTxParams, type EvmSignTypedDataFull, type EvmSignTypedDataHash, type EvmSignTypedDataParams, type EvmSignature, type EvmSignedTx, type Failure, HardwareErrorCode, type HardwareEvent, type HardwareEventMap, type IBtcMethods, type IConnector, type IDesktopHardwareBridge, type IEvmMethods, type IHardwareWallet, type ISolMethods, type IUiBridge, type IUiHandler, type Interruptibility, type JobOptions, type PassphraseResponse, type PreemptionDecision, type PreemptionEvent, type ProgressCallback, type QrDisplayData, type QrResponseData, type Response, SDK, type SdkEvent, type SolAddress, type SolGetAddressParams, type SolGetPublicKeyParams, type SolPublicKey, type SolSignMsgParams, type SolSignTxParams, type SolSignature, type SolSignedTx, type Success, type TransportType, TypedEventEmitter, UI_EVENT, UI_REQUEST, UI_RESPONSE, type UiRequestEvent, type VendorType, compareSemver, createDesktopBridgeConnector, failure, success };
|
|
880
|
+
export { type ActiveJobInfo, type BtcAddress, type BtcGetAddressParams, type BtcGetPublicKeyParams, type BtcPublicKey, type BtcRefTransaction, type BtcSignMsgParams, type BtcSignTxParams, type BtcSignature, type BtcSignedTx, type BtcTxInput, type BtcTxOutput, CHAIN_FINGERPRINT_PATHS, type ChainCapability, type ChainForFingerprint, type ConnectionType, type ConnectorDevice, type ConnectorEventMap, type ConnectorEventType, type ConnectorSession, DEVICE, DEVICE_EVENT, type DeviceCapabilities, type DeviceChangeEvent, type DeviceConnectEvent, type DeviceDescriptor, type DeviceDisconnectEvent, type DeviceEvent, type DeviceEventListener, type DeviceInfo, DeviceJobQueue, type DeviceTarget, type EIP712Domain, type EvmAddress, type EvmGetAddressParams, type EvmGetPublicKeyParams, type EvmPublicKey, type EvmSignMsgParams, type EvmSignTxParams, type EvmSignTypedDataFull, type EvmSignTypedDataHash, type EvmSignTypedDataParams, type EvmSignature, type EvmSignedTx, type Failure, HardwareErrorCode, type HardwareEvent, type HardwareEventMap, type IBtcMethods, type IConnector, type IDesktopHardwareBridge, type IEvmMethods, type IHardwareWallet, type ISolMethods, type IUiBridge, type IUiHandler, type Interruptibility, type JobOptions, type PassphraseResponse, type PreemptionDecision, type PreemptionEvent, type ProgressCallback, type QrDisplayData, type QrResponseData, type Response, SDK, type SdkEvent, type SolAddress, type SolGetAddressParams, type SolGetPublicKeyParams, type SolPublicKey, type SolSignMsgParams, type SolSignTxParams, type SolSignature, type SolSignedTx, type Success, type TransportType, TypedEventEmitter, UI_EVENT, UI_REQUEST, UI_RESPONSE, type UiRequestEvent, type VendorType, compareSemver, createDesktopBridgeConnector, deriveDeviceFingerprint, failure, success };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ declare enum HardwareErrorCode {
|
|
|
18
18
|
DeviceInBootloader = 5532,
|
|
19
19
|
FirmwareTooOld = 5533,
|
|
20
20
|
WrongApp = 5540,
|
|
21
|
+
DeviceMismatch = 5560,
|
|
21
22
|
BridgeNotFound = 5550,
|
|
22
23
|
TransportNotAvailable = 5551
|
|
23
24
|
}
|
|
@@ -40,6 +41,24 @@ declare function failure(code: HardwareErrorCode, error: string): Failure;
|
|
|
40
41
|
type VendorType = 'trezor' | 'ledger' | 'keystone' | 'keystoneqr';
|
|
41
42
|
type ConnectionType = 'usb' | 'ble' | 'qr';
|
|
42
43
|
type TransportType = 'usb' | 'ble' | 'hid' | 'bridge' | 'qr';
|
|
44
|
+
/**
|
|
45
|
+
* Device capabilities — describes what a specific device/connection
|
|
46
|
+
* combination can or cannot do. Varies by vendor, model, and connection type.
|
|
47
|
+
*
|
|
48
|
+
* This enables business logic to check capabilities instead of hard-coding
|
|
49
|
+
* vendor-specific conditions (e.g., `if (vendor === 'ledger')`).
|
|
50
|
+
*/
|
|
51
|
+
interface DeviceCapabilities {
|
|
52
|
+
/**
|
|
53
|
+
* Whether connectId/deviceId persist across sessions.
|
|
54
|
+
*
|
|
55
|
+
* - `true`: IDs are stable (e.g., OneKey USB, Trezor USB).
|
|
56
|
+
* Business logic can match devices by stored connectId/deviceId.
|
|
57
|
+
* - `false`: IDs are ephemeral, regenerated each session (e.g., Ledger WebHID).
|
|
58
|
+
* Business logic should NOT rely on stored connectId/deviceId for matching.
|
|
59
|
+
*/
|
|
60
|
+
persistentDeviceIdentity: boolean;
|
|
61
|
+
}
|
|
43
62
|
interface DeviceInfo {
|
|
44
63
|
vendor: VendorType;
|
|
45
64
|
model: string;
|
|
@@ -49,6 +68,8 @@ interface DeviceInfo {
|
|
|
49
68
|
label?: string;
|
|
50
69
|
connectionType: ConnectionType;
|
|
51
70
|
battery?: number;
|
|
71
|
+
/** Device capabilities — varies by vendor, model, and connection type */
|
|
72
|
+
capabilities?: DeviceCapabilities;
|
|
52
73
|
}
|
|
53
74
|
interface DeviceTarget {
|
|
54
75
|
connectId: string;
|
|
@@ -74,12 +95,18 @@ interface EvmPublicKey {
|
|
|
74
95
|
}
|
|
75
96
|
interface EvmSignTxParams {
|
|
76
97
|
path: string;
|
|
98
|
+
/**
|
|
99
|
+
* RLP-serialized transaction hex (0x-prefixed or plain).
|
|
100
|
+
* When provided, the connector uses this directly instead of individual fields.
|
|
101
|
+
* Required for Ledger; Trezor may use individual fields instead.
|
|
102
|
+
*/
|
|
103
|
+
serializedTx?: string;
|
|
77
104
|
/** Contract address or recipient. Optional for contract deployment transactions. */
|
|
78
105
|
to?: string;
|
|
79
|
-
value
|
|
80
|
-
chainId
|
|
81
|
-
nonce
|
|
82
|
-
gasLimit
|
|
106
|
+
value?: string;
|
|
107
|
+
chainId?: number;
|
|
108
|
+
nonce?: string;
|
|
109
|
+
gasLimit?: string;
|
|
83
110
|
gasPrice?: string;
|
|
84
111
|
maxFeePerGas?: string;
|
|
85
112
|
maxPriorityFeePerGas?: string;
|
|
@@ -303,6 +330,25 @@ interface QrResponseData {
|
|
|
303
330
|
urData: string;
|
|
304
331
|
}
|
|
305
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Chain fingerprint utilities for device identity verification.
|
|
335
|
+
*
|
|
336
|
+
* Ledger devices have ephemeral IDs that change every session.
|
|
337
|
+
* To verify that the same seed/device is connected, we derive an address
|
|
338
|
+
* at a fixed testnet path and hash it into a stable "chain fingerprint".
|
|
339
|
+
*/
|
|
340
|
+
/** Testnet derivation paths used to generate chain fingerprints. */
|
|
341
|
+
declare const CHAIN_FINGERPRINT_PATHS: Record<ChainForFingerprint, string>;
|
|
342
|
+
type ChainForFingerprint = 'evm' | 'btc' | 'sol';
|
|
343
|
+
/**
|
|
344
|
+
* Hash an address string into a 16-character hex fingerprint.
|
|
345
|
+
*
|
|
346
|
+
* Uses a simple non-cryptographic hash (FNV-1a based) to avoid
|
|
347
|
+
* pulling in a SHA-256 dependency. This is NOT used for security —
|
|
348
|
+
* only for device identity matching.
|
|
349
|
+
*/
|
|
350
|
+
declare function deriveDeviceFingerprint(address: string): string;
|
|
351
|
+
|
|
306
352
|
declare const DEVICE_EVENT = "DEVICE_EVENT";
|
|
307
353
|
/** Events originating from the hardware device. */
|
|
308
354
|
declare const DEVICE: {
|
|
@@ -588,6 +634,16 @@ interface IHardwareWallet<TConfig = unknown> extends IEvmMethods, IBtcMethods, I
|
|
|
588
634
|
getDeviceInfo(connectId: string, deviceId: string): Promise<Response<DeviceInfo>>;
|
|
589
635
|
getSupportedChains(): ChainCapability[];
|
|
590
636
|
cancel(connectId: string): void;
|
|
637
|
+
/**
|
|
638
|
+
* Derive a chain-specific fingerprint for the connected device.
|
|
639
|
+
*
|
|
640
|
+
* For Ledger: derives an address at a fixed testnet path and hashes it.
|
|
641
|
+
* For Trezor: returns the hardware device_id from firmware features.
|
|
642
|
+
*
|
|
643
|
+
* Used to verify that the same seed/device is connected across sessions,
|
|
644
|
+
* especially for vendors with ephemeral connectId/deviceId.
|
|
645
|
+
*/
|
|
646
|
+
getChainFingerprint(connectId: string, deviceId: string, chain: ChainForFingerprint): Promise<Response<string>>;
|
|
591
647
|
setUiHandler(handler: Partial<IUiHandler>): void;
|
|
592
648
|
on<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
|
|
593
649
|
on(event: string, listener: DeviceEventListener): void;
|
|
@@ -699,6 +755,8 @@ interface ConnectorDevice {
|
|
|
699
755
|
deviceId: string;
|
|
700
756
|
name: string;
|
|
701
757
|
model?: string;
|
|
758
|
+
/** Device capabilities — available from scan time */
|
|
759
|
+
capabilities?: DeviceCapabilities;
|
|
702
760
|
}
|
|
703
761
|
interface ConnectorSession {
|
|
704
762
|
sessionId: string;
|
|
@@ -819,4 +877,4 @@ declare class TypedEventEmitter<TMap extends Record<string, any> = Record<string
|
|
|
819
877
|
*/
|
|
820
878
|
declare function compareSemver(a: string, b: string): number;
|
|
821
879
|
|
|
822
|
-
export { type ActiveJobInfo, type BtcAddress, type BtcGetAddressParams, type BtcGetPublicKeyParams, type BtcPublicKey, type BtcRefTransaction, type BtcSignMsgParams, type BtcSignTxParams, type BtcSignature, type BtcSignedTx, type BtcTxInput, type BtcTxOutput, type ChainCapability, type ConnectionType, type ConnectorDevice, type ConnectorEventMap, type ConnectorEventType, type ConnectorSession, DEVICE, DEVICE_EVENT, type DeviceChangeEvent, type DeviceConnectEvent, type DeviceDescriptor, type DeviceDisconnectEvent, type DeviceEvent, type DeviceEventListener, type DeviceInfo, DeviceJobQueue, type DeviceTarget, type EIP712Domain, type EvmAddress, type EvmGetAddressParams, type EvmGetPublicKeyParams, type EvmPublicKey, type EvmSignMsgParams, type EvmSignTxParams, type EvmSignTypedDataFull, type EvmSignTypedDataHash, type EvmSignTypedDataParams, type EvmSignature, type EvmSignedTx, type Failure, HardwareErrorCode, type HardwareEvent, type HardwareEventMap, type IBtcMethods, type IConnector, type IDesktopHardwareBridge, type IEvmMethods, type IHardwareWallet, type ISolMethods, type IUiBridge, type IUiHandler, type Interruptibility, type JobOptions, type PassphraseResponse, type PreemptionDecision, type PreemptionEvent, type ProgressCallback, type QrDisplayData, type QrResponseData, type Response, SDK, type SdkEvent, type SolAddress, type SolGetAddressParams, type SolGetPublicKeyParams, type SolPublicKey, type SolSignMsgParams, type SolSignTxParams, type SolSignature, type SolSignedTx, type Success, type TransportType, TypedEventEmitter, UI_EVENT, UI_REQUEST, UI_RESPONSE, type UiRequestEvent, type VendorType, compareSemver, createDesktopBridgeConnector, failure, success };
|
|
880
|
+
export { type ActiveJobInfo, type BtcAddress, type BtcGetAddressParams, type BtcGetPublicKeyParams, type BtcPublicKey, type BtcRefTransaction, type BtcSignMsgParams, type BtcSignTxParams, type BtcSignature, type BtcSignedTx, type BtcTxInput, type BtcTxOutput, CHAIN_FINGERPRINT_PATHS, type ChainCapability, type ChainForFingerprint, type ConnectionType, type ConnectorDevice, type ConnectorEventMap, type ConnectorEventType, type ConnectorSession, DEVICE, DEVICE_EVENT, type DeviceCapabilities, type DeviceChangeEvent, type DeviceConnectEvent, type DeviceDescriptor, type DeviceDisconnectEvent, type DeviceEvent, type DeviceEventListener, type DeviceInfo, DeviceJobQueue, type DeviceTarget, type EIP712Domain, type EvmAddress, type EvmGetAddressParams, type EvmGetPublicKeyParams, type EvmPublicKey, type EvmSignMsgParams, type EvmSignTxParams, type EvmSignTypedDataFull, type EvmSignTypedDataHash, type EvmSignTypedDataParams, type EvmSignature, type EvmSignedTx, type Failure, HardwareErrorCode, type HardwareEvent, type HardwareEventMap, type IBtcMethods, type IConnector, type IDesktopHardwareBridge, type IEvmMethods, type IHardwareWallet, type ISolMethods, type IUiBridge, type IUiHandler, type Interruptibility, type JobOptions, type PassphraseResponse, type PreemptionDecision, type PreemptionEvent, type ProgressCallback, type QrDisplayData, type QrResponseData, type Response, SDK, type SdkEvent, type SolAddress, type SolGetAddressParams, type SolGetPublicKeyParams, type SolPublicKey, type SolSignMsgParams, type SolSignTxParams, type SolSignature, type SolSignedTx, type Success, type TransportType, TypedEventEmitter, UI_EVENT, UI_REQUEST, UI_RESPONSE, type UiRequestEvent, type VendorType, compareSemver, createDesktopBridgeConnector, deriveDeviceFingerprint, failure, success };
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
CHAIN_FINGERPRINT_PATHS: () => CHAIN_FINGERPRINT_PATHS,
|
|
23
24
|
DEVICE: () => DEVICE,
|
|
24
25
|
DEVICE_EVENT: () => DEVICE_EVENT,
|
|
25
26
|
DeviceJobQueue: () => DeviceJobQueue,
|
|
@@ -31,6 +32,7 @@ __export(index_exports, {
|
|
|
31
32
|
UI_RESPONSE: () => UI_RESPONSE,
|
|
32
33
|
compareSemver: () => compareSemver,
|
|
33
34
|
createDesktopBridgeConnector: () => createDesktopBridgeConnector,
|
|
35
|
+
deriveDeviceFingerprint: () => deriveDeviceFingerprint,
|
|
34
36
|
failure: () => failure,
|
|
35
37
|
success: () => success
|
|
36
38
|
});
|
|
@@ -57,6 +59,7 @@ var HardwareErrorCode = /* @__PURE__ */ ((HardwareErrorCode2) => {
|
|
|
57
59
|
HardwareErrorCode2[HardwareErrorCode2["DeviceInBootloader"] = 5532] = "DeviceInBootloader";
|
|
58
60
|
HardwareErrorCode2[HardwareErrorCode2["FirmwareTooOld"] = 5533] = "FirmwareTooOld";
|
|
59
61
|
HardwareErrorCode2[HardwareErrorCode2["WrongApp"] = 5540] = "WrongApp";
|
|
62
|
+
HardwareErrorCode2[HardwareErrorCode2["DeviceMismatch"] = 5560] = "DeviceMismatch";
|
|
60
63
|
HardwareErrorCode2[HardwareErrorCode2["BridgeNotFound"] = 5550] = "BridgeNotFound";
|
|
61
64
|
HardwareErrorCode2[HardwareErrorCode2["TransportNotAvailable"] = 5551] = "TransportNotAvailable";
|
|
62
65
|
return HardwareErrorCode2;
|
|
@@ -70,6 +73,27 @@ function failure(code, error) {
|
|
|
70
73
|
return { success: false, payload: { error, code } };
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
// src/types/fingerprint.ts
|
|
77
|
+
var CHAIN_FINGERPRINT_PATHS = {
|
|
78
|
+
evm: "m/44'/1'/0'/0/0",
|
|
79
|
+
btc: "m/44'/1'/0'/0/0",
|
|
80
|
+
sol: "m/44'/501'/0'/0'"
|
|
81
|
+
};
|
|
82
|
+
function deriveDeviceFingerprint(address) {
|
|
83
|
+
let h1 = 2166136261;
|
|
84
|
+
let h2 = 16777619;
|
|
85
|
+
for (let i = 0; i < address.length; i++) {
|
|
86
|
+
const c = address.charCodeAt(i);
|
|
87
|
+
h1 = Math.imul(h1 ^ c, h2);
|
|
88
|
+
h2 = Math.imul(h2 ^ c >>> 4, 16777619);
|
|
89
|
+
}
|
|
90
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 73244475);
|
|
91
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 73244475);
|
|
92
|
+
const hex1 = (h1 >>> 0).toString(16).padStart(8, "0");
|
|
93
|
+
const hex2 = (h2 >>> 0).toString(16).padStart(8, "0");
|
|
94
|
+
return `${hex1}${hex2}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
// src/events/device.ts
|
|
74
98
|
var DEVICE_EVENT = "DEVICE_EVENT";
|
|
75
99
|
var DEVICE = {
|
|
@@ -311,6 +335,7 @@ function compareSemver(a, b) {
|
|
|
311
335
|
}
|
|
312
336
|
// Annotate the CommonJS export names for ESM import in node:
|
|
313
337
|
0 && (module.exports = {
|
|
338
|
+
CHAIN_FINGERPRINT_PATHS,
|
|
314
339
|
DEVICE,
|
|
315
340
|
DEVICE_EVENT,
|
|
316
341
|
DeviceJobQueue,
|
|
@@ -322,6 +347,7 @@ function compareSemver(a, b) {
|
|
|
322
347
|
UI_RESPONSE,
|
|
323
348
|
compareSemver,
|
|
324
349
|
createDesktopBridgeConnector,
|
|
350
|
+
deriveDeviceFingerprint,
|
|
325
351
|
failure,
|
|
326
352
|
success
|
|
327
353
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types/errors.ts","../src/types/response.ts","../src/events/device.ts","../src/events/ui-request.ts","../src/events/sdk.ts","../src/utils/DeviceJobQueue.ts","../src/types/connector.ts","../src/utils/TypedEventEmitter.ts","../src/utils/semver.ts"],"sourcesContent":["export { HardwareErrorCode } from './types/errors';\n\nexport type { Success, Failure, Response } from './types/response';\nexport { success, failure } from './types/response';\n\nexport type { VendorType, ConnectionType, TransportType, DeviceInfo, DeviceTarget } from './types/device';\n\nexport type {\n EvmGetAddressParams,\n EvmAddress,\n EvmGetPublicKeyParams,\n EvmPublicKey,\n EvmSignTxParams,\n EvmSignedTx,\n EvmSignMsgParams,\n EvmSignTypedDataParams,\n EvmSignTypedDataFull,\n EvmSignTypedDataHash,\n EIP712Domain,\n EvmSignature,\n ProgressCallback,\n IEvmMethods,\n} from './types/chain-evm';\n\nexport type {\n BtcGetAddressParams,\n BtcAddress,\n BtcGetPublicKeyParams,\n BtcPublicKey,\n BtcSignTxParams,\n BtcTxInput,\n BtcTxOutput,\n BtcRefTransaction,\n BtcSignedTx,\n BtcSignMsgParams,\n BtcSignature,\n IBtcMethods,\n} from './types/chain-btc';\n\nexport type {\n SolGetAddressParams,\n SolAddress,\n SolGetPublicKeyParams,\n SolPublicKey,\n SolSignTxParams,\n SolSignedTx,\n SolSignMsgParams,\n SolSignature,\n ISolMethods,\n} from './types/chain-sol';\n\nexport type { QrDisplayData, QrResponseData } from './types/qr';\n\nexport type {\n IHardwareWallet,\n IUiHandler,\n PassphraseResponse,\n ChainCapability,\n DeviceEvent,\n UiRequestEvent,\n SdkEvent,\n HardwareEvent,\n HardwareEventMap,\n DeviceEventListener,\n} from './types/wallet';\n\nexport { DEVICE_EVENT, DEVICE } from './events/device';\nexport { UI_EVENT, UI_REQUEST, UI_RESPONSE } from './events/ui-request';\nexport { SDK } from './events/sdk';\n\nexport type {\n DeviceDescriptor,\n DeviceConnectEvent,\n DeviceDisconnectEvent,\n DeviceChangeEvent,\n} from './types/transport';\n\nexport { DeviceJobQueue } from './utils/DeviceJobQueue';\nexport type {\n Interruptibility,\n PreemptionDecision,\n JobOptions,\n ActiveJobInfo,\n PreemptionEvent,\n} from './utils/DeviceJobQueue';\nexport type { IUiBridge } from './types/ui-bridge';\n\nexport type {\n ConnectorDevice,\n ConnectorSession,\n ConnectorEventType,\n ConnectorEventMap,\n IConnector,\n IDesktopHardwareBridge,\n} from './types/connector';\nexport { createDesktopBridgeConnector } from './types/connector';\n\nexport { TypedEventEmitter } from './utils/TypedEventEmitter';\nexport { compareSemver } from './utils/semver';\n","export enum HardwareErrorCode {\n UnknownError = 0,\n DeviceNotFound = 1,\n DeviceDisconnected = 2,\n UserRejected = 3,\n DeviceBusy = 4,\n FirmwareUpdateRequired = 5,\n AppNotOpen = 6,\n InvalidParams = 7,\n TransportError = 8,\n OperationTimeout = 9,\n MethodNotSupported = 10,\n\n // PIN / Passphrase\n PinInvalid = 5520,\n PinCancelled = 5521,\n PassphraseRejected = 5522,\n\n // Device state\n DeviceLocked = 5530,\n DeviceNotInitialized = 5531,\n DeviceInBootloader = 5532,\n FirmwareTooOld = 5533,\n\n // Ledger specific\n WrongApp = 5540,\n\n // Transport\n BridgeNotFound = 5550,\n TransportNotAvailable = 5551,\n}\n","import { HardwareErrorCode } from './errors';\n\nexport interface Success<T> {\n success: true;\n payload: T;\n}\n\nexport interface Failure {\n success: false;\n payload: {\n error: string;\n code: HardwareErrorCode;\n };\n}\n\nexport type Response<T> = Success<T> | Failure;\n\nexport function success<T>(payload: T): Success<T> {\n return { success: true, payload };\n}\n\nexport function failure(code: HardwareErrorCode, error: string): Failure {\n return { success: false, payload: { error, code } };\n}\n","export const DEVICE_EVENT = 'DEVICE_EVENT';\n\n/** Events originating from the hardware device. */\nexport const DEVICE = {\n CONNECT: 'device-connect',\n DISCONNECT: 'device-disconnect',\n CHANGED: 'device-changed',\n ACQUIRE: 'device-acquire',\n RELEASE: 'device-release',\n FEATURES: 'features',\n SUPPORT_FEATURES: 'support_features',\n} as const;\n","export const UI_EVENT = 'UI_EVENT';\n\nexport const UI_REQUEST = {\n REQUEST_PIN: 'ui-request-pin',\n REQUEST_PASSPHRASE: 'ui-request-passphrase',\n REQUEST_PASSPHRASE_ON_DEVICE: 'ui-request-passphrase-on-device',\n REQUEST_BUTTON: 'ui-request-button',\n REQUEST_QR_DISPLAY: 'ui-request-qr-display',\n REQUEST_QR_SCAN: 'ui-request-qr-scan',\n REQUEST_DEVICE_PERMISSION: 'ui-request-device-permission',\n REQUEST_SELECT_DEVICE: 'ui-request-select-device',\n CLOSE_UI_WINDOW: 'ui-close',\n DEVICE_PROGRESS: 'ui-device_progress',\n FIRMWARE_PROGRESS: 'ui-firmware-progress',\n FIRMWARE_TIP: 'ui-firmware-tip',\n} as const;\n\nexport const UI_RESPONSE = {\n RECEIVE_PIN: 'receive-pin',\n RECEIVE_PASSPHRASE: 'receive-passphrase',\n RECEIVE_PASSPHRASE_ON_DEVICE: 'receive-passphrase-on-device',\n RECEIVE_QR_RESPONSE: 'receive-qr-response',\n RECEIVE_SELECT_DEVICE: 'receive-select-device',\n CANCEL: 'cancel',\n} as const;\n","/** Events generated by SDK internal detection (not from hardware directly). */\nexport const SDK = {\n DEVICE_STUCK: 'device-stuck',\n DEVICE_UNRESPONSIVE: 'device-unresponsive',\n DEVICE_RECOVERED: 'device-recovered',\n DEVICE_INTERACTION: 'device-interaction',\n} as const;\n","/**\n * Per-device serial job queue with preemption support and stuck recovery.\n * Ensures that only one operation runs at a time per device, with intelligent\n * handling of conflicting operations.\n */\n\nexport type Interruptibility = 'none' | 'safe' | 'confirm';\n\nexport type PreemptionDecision = 'cancel-current' | 'wait' | 'reject-new';\n\nexport interface JobOptions {\n interruptibility?: Interruptibility;\n label?: string;\n}\n\nexport interface ActiveJobInfo {\n label?: string;\n interruptibility: Interruptibility;\n startedAt: number;\n}\n\nexport interface PreemptionEvent {\n deviceId: string;\n currentJob: ActiveJobInfo;\n newJob: { label?: string; interruptibility: Interruptibility };\n}\n\ninterface ActiveJob {\n options: Required<Pick<JobOptions, 'interruptibility'>> & Pick<JobOptions, 'label'>;\n abortController: AbortController;\n startedAt: number;\n}\n\nexport class DeviceJobQueue {\n private readonly _queues = new Map<string, Promise<unknown>>();\n private readonly _active = new Map<string, ActiveJob>();\n\n /**\n * Called when a new job conflicts with an active 'confirm'-level job.\n * UI should show a dialog and return the user's decision.\n * If not set, defaults to 'wait' (queue behind current job).\n */\n onPreemptionRequest?: (event: PreemptionEvent) => Promise<PreemptionDecision>;\n\n /**\n * Enqueue a job for a specific device.\n * If a job is already running for this device, behavior depends on interruptibility:\n * - 'none': new job queues silently (no preemption possible)\n * - 'safe': current job is auto-cancelled, new job runs immediately after\n * - 'confirm': onPreemptionRequest is called to ask user\n */\n async enqueue<T>(\n deviceId: string,\n job: (signal: AbortSignal) => Promise<T>,\n options: JobOptions = {},\n ): Promise<T> {\n const interruptibility = options.interruptibility ?? 'confirm';\n const active = this._active.get(deviceId);\n\n if (active) {\n switch (active.options.interruptibility) {\n case 'none':\n // Cannot interrupt, just queue behind\n break;\n case 'safe':\n // Auto-cancel current safe operation\n active.abortController.abort(new Error('Preempted by new operation'));\n break;\n case 'confirm': {\n if (this.onPreemptionRequest) {\n const decision = await this.onPreemptionRequest({\n deviceId,\n currentJob: {\n label: active.options.label,\n interruptibility: active.options.interruptibility,\n startedAt: active.startedAt,\n },\n newJob: {\n label: options.label,\n interruptibility,\n },\n });\n switch (decision) {\n case 'cancel-current':\n active.abortController.abort(new Error('Cancelled by user via preemption'));\n break;\n case 'reject-new':\n throw Object.assign(\n new Error(`Device busy: ${active.options.label ?? 'unknown operation'}`),\n { hardwareErrorCode: 'DEVICE_BUSY' },\n );\n case 'wait':\n break;\n }\n }\n break;\n }\n }\n }\n\n const ac = new AbortController();\n const prev = this._queues.get(deviceId) ?? Promise.resolve();\n\n const next = prev.catch(() => {}).then(async () => {\n this._active.set(deviceId, {\n options: { interruptibility, label: options.label },\n abortController: ac,\n startedAt: Date.now(),\n });\n try {\n return await job(ac.signal);\n } finally {\n this._active.delete(deviceId);\n }\n });\n\n const tail = next.catch(() => {});\n this._queues.set(deviceId, tail);\n tail.then(() => {\n if (this._queues.get(deviceId) === tail) {\n this._queues.delete(deviceId);\n }\n });\n return next;\n }\n\n /** Manually cancel the active job on a device. Returns false if job is non-interruptible. */\n cancelActive(deviceId: string): boolean {\n const active = this._active.get(deviceId);\n if (!active) return false;\n if (active.options.interruptibility === 'none') return false;\n active.abortController.abort(new Error('Manually cancelled'));\n return true;\n }\n\n /** Force cancel regardless of interruptibility. Use for device stuck recovery. */\n forceCancelActive(deviceId: string): boolean {\n const active = this._active.get(deviceId);\n if (!active) return false;\n active.abortController.abort(new Error('Force cancelled for recovery'));\n return true;\n }\n\n /** Get info about the currently active job for a device, or null if idle. */\n getActiveJob(deviceId: string): ActiveJobInfo | null {\n const active = this._active.get(deviceId);\n if (!active) return null;\n return {\n label: active.options.label,\n interruptibility: active.options.interruptibility,\n startedAt: active.startedAt,\n };\n }\n\n clear(): void {\n // Abort all active jobs\n for (const active of this._active.values()) {\n active.abortController.abort(new Error('Queue cleared'));\n }\n this._active.clear();\n this._queues.clear();\n }\n}\n","import type { DeviceInfo, VendorType } from './device';\n\n// =====================================================================\n// Connector types — transport-level abstraction for device communication\n// =====================================================================\n\n/**\n * Minimal device info returned during discovery (searchDevices).\n * At scan time, full DeviceInfo fields like firmwareVersion are not yet available.\n */\nexport interface ConnectorDevice {\n connectId: string;\n deviceId: string;\n name: string;\n model?: string;\n}\n\nexport interface ConnectorSession {\n sessionId: string;\n deviceInfo: DeviceInfo;\n}\n\nexport type ConnectorEventType =\n | 'device-connect'\n | 'device-disconnect'\n | 'ui-request'\n | 'ui-event';\n\nexport interface ConnectorEventMap {\n 'device-connect': { device: ConnectorDevice };\n 'device-disconnect': { connectId: string };\n 'ui-request': { type: string; payload?: unknown };\n 'ui-event': { type: string; payload?: unknown };\n}\n\nexport interface IConnector {\n searchDevices(): Promise<ConnectorDevice[]>;\n connect(deviceId?: string): Promise<ConnectorSession>;\n disconnect(sessionId: string): Promise<void>;\n call(sessionId: string, method: string, params: unknown): Promise<unknown>;\n cancel(sessionId: string): Promise<void>;\n\n /** Send a UI response (e.g. PIN, passphrase) to the device. */\n uiResponse(response: { type: string; payload: unknown }): void;\n\n on<K extends ConnectorEventType>(\n event: K,\n handler: (data: ConnectorEventMap[K]) => void,\n ): void;\n off<K extends ConnectorEventType>(\n event: K,\n handler: (data: ConnectorEventMap[K]) => void,\n ): void;\n\n reset(): void;\n}\n\n// =====================================================================\n// Desktop IPC bridge — generic interface for main-process hardware access\n// =====================================================================\n\nexport interface IDesktopHardwareBridge {\n searchDevices(params: { vendor: VendorType }): Promise<ConnectorDevice[]>;\n connect(params: {\n vendor: VendorType;\n deviceId?: string;\n }): Promise<ConnectorSession>;\n disconnect(params: { vendor: VendorType; sessionId: string }): Promise<void>;\n call(params: {\n vendor: VendorType;\n sessionId: string;\n method: string;\n callParams: unknown;\n }): Promise<unknown>;\n cancel(params: { vendor: VendorType; sessionId: string }): Promise<void>;\n uiResponse(params: {\n vendor: VendorType;\n response: { type: string; payload: unknown };\n }): void;\n reset(params: { vendor: VendorType }): void;\n\n /** Register an event handler for connector events forwarded from the main process. */\n onEvent(\n params: { vendor: VendorType },\n handler: (event: { type: ConnectorEventType; data: unknown }) => void,\n ): void;\n\n /** Unregister a previously registered event handler. */\n offEvent(\n params: { vendor: VendorType },\n handler: (event: { type: ConnectorEventType; data: unknown }) => void,\n ): void;\n}\n\n/**\n * Create an IConnector from a desktop IPC bridge + vendor name.\n * Events are forwarded via bridge.onEvent/offEvent.\n */\nexport function createDesktopBridgeConnector(\n vendor: VendorType,\n bridge: IDesktopHardwareBridge,\n): IConnector {\n // Map from typed IConnector handlers to the bridge handler so we can\n // unregister them correctly via off().\n const handlerMap = new Map<\n (data: ConnectorEventMap[ConnectorEventType]) => void,\n (event: { type: ConnectorEventType; data: unknown }) => void\n >();\n\n return {\n searchDevices: () => bridge.searchDevices({ vendor }),\n connect: (deviceId) => bridge.connect({ vendor, deviceId }),\n disconnect: (sessionId) => bridge.disconnect({ vendor, sessionId }),\n call: (sessionId, method, callParams) =>\n bridge.call({ vendor, sessionId, method, callParams }),\n cancel: (sessionId) => bridge.cancel({ vendor, sessionId }),\n uiResponse: (response) => bridge.uiResponse({ vendor, response }),\n on: (event, handler) => {\n const bridgeHandler = (e: { type: ConnectorEventType; data: unknown }) => {\n if (e.type === event) {\n handler(e.data as ConnectorEventMap[typeof event]);\n }\n };\n handlerMap.set(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n bridgeHandler,\n );\n bridge.onEvent({ vendor }, bridgeHandler);\n },\n off: (_event, handler) => {\n const bridgeHandler = handlerMap.get(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n );\n if (bridgeHandler) {\n bridge.offEvent({ vendor }, bridgeHandler);\n handlerMap.delete(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n );\n }\n },\n reset: () => bridge.reset({ vendor }),\n };\n}\n","/**\n * Minimal typed event emitter using Map<string, Set<listener>>.\n * Each adapter uses this for device events (connect, disconnect, pin, etc.).\n *\n * TMap is a record mapping event name strings to their payload types.\n * Example:\n * type MyEvents = { 'connect': { id: string }; 'disconnect': { id: string } };\n * const emitter = new TypedEventEmitter<MyEvents>();\n * emitter.on('connect', (data) => { data.id }); // data is { id: string }\n *\n * For backward compatibility, TMap defaults to Record<string, any> so that\n * existing code using `new TypedEventEmitter<SomeUnionType>()` still compiles.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class TypedEventEmitter<TMap extends Record<string, any> = Record<string, any>> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _listeners = new Map<string, Set<(event: any) => void>>();\n\n on<K extends keyof TMap & string>(event: K, listener: (event: TMap[K]) => void): void;\n on(event: string, listener: (event: any) => void): void;\n on(event: string, listener: (event: any) => void): void {\n let set = this._listeners.get(event);\n if (!set) {\n set = new Set();\n this._listeners.set(event, set);\n }\n set.add(listener);\n }\n\n off<K extends keyof TMap & string>(event: K, listener: (event: TMap[K]) => void): void;\n off(event: string, listener: (event: any) => void): void;\n off(event: string, listener: (event: any) => void): void {\n const set = this._listeners.get(event);\n if (set) {\n set.delete(listener);\n if (set.size === 0) this._listeners.delete(event);\n }\n }\n\n emit<K extends keyof TMap & string>(event: K, data: TMap[K]): void;\n emit(event: string, data: unknown): void;\n emit(event: string, data: unknown): void {\n const set = this._listeners.get(event);\n if (set) {\n for (const listener of set) listener(data);\n }\n }\n\n removeAllListeners(): void {\n this._listeners.clear();\n }\n}\n","/**\n * Compare two semver strings (e.g. \"2.1.0\" vs \"2.3.1\").\n * Returns -1 if a < b, 0 if equal, 1 if a > b.\n */\nexport function compareSemver(a: string, b: string): number {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n const va = pa[i] ?? 0;\n const vb = pb[i] ?? 0;\n if (va < vb) return -1;\n if (va > vb) return 1;\n }\n return 0;\n}\n"],"mappings":"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","names":["HardwareErrorCode"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types/errors.ts","../src/types/response.ts","../src/types/fingerprint.ts","../src/events/device.ts","../src/events/ui-request.ts","../src/events/sdk.ts","../src/utils/DeviceJobQueue.ts","../src/types/connector.ts","../src/utils/TypedEventEmitter.ts","../src/utils/semver.ts"],"sourcesContent":["export { HardwareErrorCode } from './types/errors';\n\nexport type { Success, Failure, Response } from './types/response';\nexport { success, failure } from './types/response';\n\nexport type { VendorType, ConnectionType, TransportType, DeviceInfo, DeviceTarget, DeviceCapabilities } from './types/device';\n\nexport type {\n EvmGetAddressParams,\n EvmAddress,\n EvmGetPublicKeyParams,\n EvmPublicKey,\n EvmSignTxParams,\n EvmSignedTx,\n EvmSignMsgParams,\n EvmSignTypedDataParams,\n EvmSignTypedDataFull,\n EvmSignTypedDataHash,\n EIP712Domain,\n EvmSignature,\n ProgressCallback,\n IEvmMethods,\n} from './types/chain-evm';\n\nexport type {\n BtcGetAddressParams,\n BtcAddress,\n BtcGetPublicKeyParams,\n BtcPublicKey,\n BtcSignTxParams,\n BtcTxInput,\n BtcTxOutput,\n BtcRefTransaction,\n BtcSignedTx,\n BtcSignMsgParams,\n BtcSignature,\n IBtcMethods,\n} from './types/chain-btc';\n\nexport type {\n SolGetAddressParams,\n SolAddress,\n SolGetPublicKeyParams,\n SolPublicKey,\n SolSignTxParams,\n SolSignedTx,\n SolSignMsgParams,\n SolSignature,\n ISolMethods,\n} from './types/chain-sol';\n\nexport type { QrDisplayData, QrResponseData } from './types/qr';\n\nexport type { ChainForFingerprint } from './types/fingerprint';\nexport { CHAIN_FINGERPRINT_PATHS, deriveDeviceFingerprint } from './types/fingerprint';\n\nexport type {\n IHardwareWallet,\n IUiHandler,\n PassphraseResponse,\n ChainCapability,\n DeviceEvent,\n UiRequestEvent,\n SdkEvent,\n HardwareEvent,\n HardwareEventMap,\n DeviceEventListener,\n} from './types/wallet';\n\nexport { DEVICE_EVENT, DEVICE } from './events/device';\nexport { UI_EVENT, UI_REQUEST, UI_RESPONSE } from './events/ui-request';\nexport { SDK } from './events/sdk';\n\nexport type {\n DeviceDescriptor,\n DeviceConnectEvent,\n DeviceDisconnectEvent,\n DeviceChangeEvent,\n} from './types/transport';\n\nexport { DeviceJobQueue } from './utils/DeviceJobQueue';\nexport type {\n Interruptibility,\n PreemptionDecision,\n JobOptions,\n ActiveJobInfo,\n PreemptionEvent,\n} from './utils/DeviceJobQueue';\nexport type { IUiBridge } from './types/ui-bridge';\n\nexport type {\n ConnectorDevice,\n ConnectorSession,\n ConnectorEventType,\n ConnectorEventMap,\n IConnector,\n IDesktopHardwareBridge,\n} from './types/connector';\nexport { createDesktopBridgeConnector } from './types/connector';\n\nexport { TypedEventEmitter } from './utils/TypedEventEmitter';\nexport { compareSemver } from './utils/semver';\n","export enum HardwareErrorCode {\n UnknownError = 0,\n DeviceNotFound = 1,\n DeviceDisconnected = 2,\n UserRejected = 3,\n DeviceBusy = 4,\n FirmwareUpdateRequired = 5,\n AppNotOpen = 6,\n InvalidParams = 7,\n TransportError = 8,\n OperationTimeout = 9,\n MethodNotSupported = 10,\n\n // PIN / Passphrase\n PinInvalid = 5520,\n PinCancelled = 5521,\n PassphraseRejected = 5522,\n\n // Device state\n DeviceLocked = 5530,\n DeviceNotInitialized = 5531,\n DeviceInBootloader = 5532,\n FirmwareTooOld = 5533,\n\n // Ledger specific\n WrongApp = 5540,\n\n // Device identity\n DeviceMismatch = 5560,\n\n // Transport\n BridgeNotFound = 5550,\n TransportNotAvailable = 5551,\n}\n","import { HardwareErrorCode } from './errors';\n\nexport interface Success<T> {\n success: true;\n payload: T;\n}\n\nexport interface Failure {\n success: false;\n payload: {\n error: string;\n code: HardwareErrorCode;\n };\n}\n\nexport type Response<T> = Success<T> | Failure;\n\nexport function success<T>(payload: T): Success<T> {\n return { success: true, payload };\n}\n\nexport function failure(code: HardwareErrorCode, error: string): Failure {\n return { success: false, payload: { error, code } };\n}\n","/**\n * Chain fingerprint utilities for device identity verification.\n *\n * Ledger devices have ephemeral IDs that change every session.\n * To verify that the same seed/device is connected, we derive an address\n * at a fixed testnet path and hash it into a stable \"chain fingerprint\".\n */\n\n/** Testnet derivation paths used to generate chain fingerprints. */\nexport const CHAIN_FINGERPRINT_PATHS: Record<ChainForFingerprint, string> = {\n evm: \"m/44'/1'/0'/0/0\",\n btc: \"m/44'/1'/0'/0/0\",\n sol: \"m/44'/501'/0'/0'\",\n};\n\nexport type ChainForFingerprint = 'evm' | 'btc' | 'sol';\n\n/**\n * Hash an address string into a 16-character hex fingerprint.\n *\n * Uses a simple non-cryptographic hash (FNV-1a based) to avoid\n * pulling in a SHA-256 dependency. This is NOT used for security —\n * only for device identity matching.\n */\nexport function deriveDeviceFingerprint(address: string): string {\n // FNV-1a 64-bit constants (split into two 32-bit halves for JS)\n let h1 = 0x811c9dc5;\n let h2 = 0x01000193;\n\n for (let i = 0; i < address.length; i++) {\n const c = address.charCodeAt(i);\n h1 = Math.imul(h1 ^ c, h2);\n h2 = Math.imul(h2 ^ (c >>> 4), 0x01000193);\n }\n\n // Mix the two halves for better distribution\n h1 = Math.imul(h1 ^ (h1 >>> 16), 0x45d9f3b);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 0x45d9f3b);\n\n const hex1 = (h1 >>> 0).toString(16).padStart(8, '0');\n const hex2 = (h2 >>> 0).toString(16).padStart(8, '0');\n\n return `${hex1}${hex2}`;\n}\n","export const DEVICE_EVENT = 'DEVICE_EVENT';\n\n/** Events originating from the hardware device. */\nexport const DEVICE = {\n CONNECT: 'device-connect',\n DISCONNECT: 'device-disconnect',\n CHANGED: 'device-changed',\n ACQUIRE: 'device-acquire',\n RELEASE: 'device-release',\n FEATURES: 'features',\n SUPPORT_FEATURES: 'support_features',\n} as const;\n","export const UI_EVENT = 'UI_EVENT';\n\nexport const UI_REQUEST = {\n REQUEST_PIN: 'ui-request-pin',\n REQUEST_PASSPHRASE: 'ui-request-passphrase',\n REQUEST_PASSPHRASE_ON_DEVICE: 'ui-request-passphrase-on-device',\n REQUEST_BUTTON: 'ui-request-button',\n REQUEST_QR_DISPLAY: 'ui-request-qr-display',\n REQUEST_QR_SCAN: 'ui-request-qr-scan',\n REQUEST_DEVICE_PERMISSION: 'ui-request-device-permission',\n REQUEST_SELECT_DEVICE: 'ui-request-select-device',\n CLOSE_UI_WINDOW: 'ui-close',\n DEVICE_PROGRESS: 'ui-device_progress',\n FIRMWARE_PROGRESS: 'ui-firmware-progress',\n FIRMWARE_TIP: 'ui-firmware-tip',\n} as const;\n\nexport const UI_RESPONSE = {\n RECEIVE_PIN: 'receive-pin',\n RECEIVE_PASSPHRASE: 'receive-passphrase',\n RECEIVE_PASSPHRASE_ON_DEVICE: 'receive-passphrase-on-device',\n RECEIVE_QR_RESPONSE: 'receive-qr-response',\n RECEIVE_SELECT_DEVICE: 'receive-select-device',\n CANCEL: 'cancel',\n} as const;\n","/** Events generated by SDK internal detection (not from hardware directly). */\nexport const SDK = {\n DEVICE_STUCK: 'device-stuck',\n DEVICE_UNRESPONSIVE: 'device-unresponsive',\n DEVICE_RECOVERED: 'device-recovered',\n DEVICE_INTERACTION: 'device-interaction',\n} as const;\n","/**\n * Per-device serial job queue with preemption support and stuck recovery.\n * Ensures that only one operation runs at a time per device, with intelligent\n * handling of conflicting operations.\n */\n\nexport type Interruptibility = 'none' | 'safe' | 'confirm';\n\nexport type PreemptionDecision = 'cancel-current' | 'wait' | 'reject-new';\n\nexport interface JobOptions {\n interruptibility?: Interruptibility;\n label?: string;\n}\n\nexport interface ActiveJobInfo {\n label?: string;\n interruptibility: Interruptibility;\n startedAt: number;\n}\n\nexport interface PreemptionEvent {\n deviceId: string;\n currentJob: ActiveJobInfo;\n newJob: { label?: string; interruptibility: Interruptibility };\n}\n\ninterface ActiveJob {\n options: Required<Pick<JobOptions, 'interruptibility'>> & Pick<JobOptions, 'label'>;\n abortController: AbortController;\n startedAt: number;\n}\n\nexport class DeviceJobQueue {\n private readonly _queues = new Map<string, Promise<unknown>>();\n private readonly _active = new Map<string, ActiveJob>();\n\n /**\n * Called when a new job conflicts with an active 'confirm'-level job.\n * UI should show a dialog and return the user's decision.\n * If not set, defaults to 'wait' (queue behind current job).\n */\n onPreemptionRequest?: (event: PreemptionEvent) => Promise<PreemptionDecision>;\n\n /**\n * Enqueue a job for a specific device.\n * If a job is already running for this device, behavior depends on interruptibility:\n * - 'none': new job queues silently (no preemption possible)\n * - 'safe': current job is auto-cancelled, new job runs immediately after\n * - 'confirm': onPreemptionRequest is called to ask user\n */\n async enqueue<T>(\n deviceId: string,\n job: (signal: AbortSignal) => Promise<T>,\n options: JobOptions = {},\n ): Promise<T> {\n const interruptibility = options.interruptibility ?? 'confirm';\n const active = this._active.get(deviceId);\n\n if (active) {\n switch (active.options.interruptibility) {\n case 'none':\n // Cannot interrupt, just queue behind\n break;\n case 'safe':\n // Auto-cancel current safe operation\n active.abortController.abort(new Error('Preempted by new operation'));\n break;\n case 'confirm': {\n if (this.onPreemptionRequest) {\n const decision = await this.onPreemptionRequest({\n deviceId,\n currentJob: {\n label: active.options.label,\n interruptibility: active.options.interruptibility,\n startedAt: active.startedAt,\n },\n newJob: {\n label: options.label,\n interruptibility,\n },\n });\n switch (decision) {\n case 'cancel-current':\n active.abortController.abort(new Error('Cancelled by user via preemption'));\n break;\n case 'reject-new':\n throw Object.assign(\n new Error(`Device busy: ${active.options.label ?? 'unknown operation'}`),\n { hardwareErrorCode: 'DEVICE_BUSY' },\n );\n case 'wait':\n break;\n }\n }\n break;\n }\n }\n }\n\n const ac = new AbortController();\n const prev = this._queues.get(deviceId) ?? Promise.resolve();\n\n const next = prev.catch(() => {}).then(async () => {\n this._active.set(deviceId, {\n options: { interruptibility, label: options.label },\n abortController: ac,\n startedAt: Date.now(),\n });\n try {\n return await job(ac.signal);\n } finally {\n this._active.delete(deviceId);\n }\n });\n\n const tail = next.catch(() => {});\n this._queues.set(deviceId, tail);\n tail.then(() => {\n if (this._queues.get(deviceId) === tail) {\n this._queues.delete(deviceId);\n }\n });\n return next;\n }\n\n /** Manually cancel the active job on a device. Returns false if job is non-interruptible. */\n cancelActive(deviceId: string): boolean {\n const active = this._active.get(deviceId);\n if (!active) return false;\n if (active.options.interruptibility === 'none') return false;\n active.abortController.abort(new Error('Manually cancelled'));\n return true;\n }\n\n /** Force cancel regardless of interruptibility. Use for device stuck recovery. */\n forceCancelActive(deviceId: string): boolean {\n const active = this._active.get(deviceId);\n if (!active) return false;\n active.abortController.abort(new Error('Force cancelled for recovery'));\n return true;\n }\n\n /** Get info about the currently active job for a device, or null if idle. */\n getActiveJob(deviceId: string): ActiveJobInfo | null {\n const active = this._active.get(deviceId);\n if (!active) return null;\n return {\n label: active.options.label,\n interruptibility: active.options.interruptibility,\n startedAt: active.startedAt,\n };\n }\n\n clear(): void {\n // Abort all active jobs\n for (const active of this._active.values()) {\n active.abortController.abort(new Error('Queue cleared'));\n }\n this._active.clear();\n this._queues.clear();\n }\n}\n","import type { DeviceCapabilities, DeviceInfo, VendorType } from './device';\n\n// =====================================================================\n// Connector types — transport-level abstraction for device communication\n// =====================================================================\n\n/**\n * Minimal device info returned during discovery (searchDevices).\n * At scan time, full DeviceInfo fields like firmwareVersion are not yet available.\n */\nexport interface ConnectorDevice {\n connectId: string;\n deviceId: string;\n name: string;\n model?: string;\n\n /** Device capabilities — available from scan time */\n capabilities?: DeviceCapabilities;\n}\n\nexport interface ConnectorSession {\n sessionId: string;\n deviceInfo: DeviceInfo;\n}\n\nexport type ConnectorEventType =\n | 'device-connect'\n | 'device-disconnect'\n | 'ui-request'\n | 'ui-event';\n\nexport interface ConnectorEventMap {\n 'device-connect': { device: ConnectorDevice };\n 'device-disconnect': { connectId: string };\n 'ui-request': { type: string; payload?: unknown };\n 'ui-event': { type: string; payload?: unknown };\n}\n\nexport interface IConnector {\n searchDevices(): Promise<ConnectorDevice[]>;\n connect(deviceId?: string): Promise<ConnectorSession>;\n disconnect(sessionId: string): Promise<void>;\n call(sessionId: string, method: string, params: unknown): Promise<unknown>;\n cancel(sessionId: string): Promise<void>;\n\n /** Send a UI response (e.g. PIN, passphrase) to the device. */\n uiResponse(response: { type: string; payload: unknown }): void;\n\n on<K extends ConnectorEventType>(\n event: K,\n handler: (data: ConnectorEventMap[K]) => void,\n ): void;\n off<K extends ConnectorEventType>(\n event: K,\n handler: (data: ConnectorEventMap[K]) => void,\n ): void;\n\n reset(): void;\n}\n\n// =====================================================================\n// Desktop IPC bridge — generic interface for main-process hardware access\n// =====================================================================\n\nexport interface IDesktopHardwareBridge {\n searchDevices(params: { vendor: VendorType }): Promise<ConnectorDevice[]>;\n connect(params: {\n vendor: VendorType;\n deviceId?: string;\n }): Promise<ConnectorSession>;\n disconnect(params: { vendor: VendorType; sessionId: string }): Promise<void>;\n call(params: {\n vendor: VendorType;\n sessionId: string;\n method: string;\n callParams: unknown;\n }): Promise<unknown>;\n cancel(params: { vendor: VendorType; sessionId: string }): Promise<void>;\n uiResponse(params: {\n vendor: VendorType;\n response: { type: string; payload: unknown };\n }): void;\n reset(params: { vendor: VendorType }): void;\n\n /** Register an event handler for connector events forwarded from the main process. */\n onEvent(\n params: { vendor: VendorType },\n handler: (event: { type: ConnectorEventType; data: unknown }) => void,\n ): void;\n\n /** Unregister a previously registered event handler. */\n offEvent(\n params: { vendor: VendorType },\n handler: (event: { type: ConnectorEventType; data: unknown }) => void,\n ): void;\n}\n\n/**\n * Create an IConnector from a desktop IPC bridge + vendor name.\n * Events are forwarded via bridge.onEvent/offEvent.\n */\nexport function createDesktopBridgeConnector(\n vendor: VendorType,\n bridge: IDesktopHardwareBridge,\n): IConnector {\n // Map from typed IConnector handlers to the bridge handler so we can\n // unregister them correctly via off().\n const handlerMap = new Map<\n (data: ConnectorEventMap[ConnectorEventType]) => void,\n (event: { type: ConnectorEventType; data: unknown }) => void\n >();\n\n return {\n searchDevices: () => bridge.searchDevices({ vendor }),\n connect: (deviceId) => bridge.connect({ vendor, deviceId }),\n disconnect: (sessionId) => bridge.disconnect({ vendor, sessionId }),\n call: (sessionId, method, callParams) =>\n bridge.call({ vendor, sessionId, method, callParams }),\n cancel: (sessionId) => bridge.cancel({ vendor, sessionId }),\n uiResponse: (response) => bridge.uiResponse({ vendor, response }),\n on: (event, handler) => {\n const bridgeHandler = (e: { type: ConnectorEventType; data: unknown }) => {\n if (e.type === event) {\n handler(e.data as ConnectorEventMap[typeof event]);\n }\n };\n handlerMap.set(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n bridgeHandler,\n );\n bridge.onEvent({ vendor }, bridgeHandler);\n },\n off: (_event, handler) => {\n const bridgeHandler = handlerMap.get(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n );\n if (bridgeHandler) {\n bridge.offEvent({ vendor }, bridgeHandler);\n handlerMap.delete(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n );\n }\n },\n reset: () => bridge.reset({ vendor }),\n };\n}\n","/**\n * Minimal typed event emitter using Map<string, Set<listener>>.\n * Each adapter uses this for device events (connect, disconnect, pin, etc.).\n *\n * TMap is a record mapping event name strings to their payload types.\n * Example:\n * type MyEvents = { 'connect': { id: string }; 'disconnect': { id: string } };\n * const emitter = new TypedEventEmitter<MyEvents>();\n * emitter.on('connect', (data) => { data.id }); // data is { id: string }\n *\n * For backward compatibility, TMap defaults to Record<string, any> so that\n * existing code using `new TypedEventEmitter<SomeUnionType>()` still compiles.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class TypedEventEmitter<TMap extends Record<string, any> = Record<string, any>> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _listeners = new Map<string, Set<(event: any) => void>>();\n\n on<K extends keyof TMap & string>(event: K, listener: (event: TMap[K]) => void): void;\n on(event: string, listener: (event: any) => void): void;\n on(event: string, listener: (event: any) => void): void {\n let set = this._listeners.get(event);\n if (!set) {\n set = new Set();\n this._listeners.set(event, set);\n }\n set.add(listener);\n }\n\n off<K extends keyof TMap & string>(event: K, listener: (event: TMap[K]) => void): void;\n off(event: string, listener: (event: any) => void): void;\n off(event: string, listener: (event: any) => void): void {\n const set = this._listeners.get(event);\n if (set) {\n set.delete(listener);\n if (set.size === 0) this._listeners.delete(event);\n }\n }\n\n emit<K extends keyof TMap & string>(event: K, data: TMap[K]): void;\n emit(event: string, data: unknown): void;\n emit(event: string, data: unknown): void {\n const set = this._listeners.get(event);\n if (set) {\n for (const listener of set) listener(data);\n }\n }\n\n removeAllListeners(): void {\n this._listeners.clear();\n }\n}\n","/**\n * Compare two semver strings (e.g. \"2.1.0\" vs \"2.3.1\").\n * Returns -1 if a < b, 0 if equal, 1 if a > b.\n */\nexport function compareSemver(a: string, b: string): number {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n const va = pa[i] ?? 0;\n const vb = pb[i] ?? 0;\n if (va < vb) return -1;\n if (va > vb) return 1;\n }\n return 0;\n}\n"],"mappings":"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","names":["HardwareErrorCode"]}
|
package/dist/index.mjs
CHANGED
|
@@ -19,6 +19,7 @@ var HardwareErrorCode = /* @__PURE__ */ ((HardwareErrorCode2) => {
|
|
|
19
19
|
HardwareErrorCode2[HardwareErrorCode2["DeviceInBootloader"] = 5532] = "DeviceInBootloader";
|
|
20
20
|
HardwareErrorCode2[HardwareErrorCode2["FirmwareTooOld"] = 5533] = "FirmwareTooOld";
|
|
21
21
|
HardwareErrorCode2[HardwareErrorCode2["WrongApp"] = 5540] = "WrongApp";
|
|
22
|
+
HardwareErrorCode2[HardwareErrorCode2["DeviceMismatch"] = 5560] = "DeviceMismatch";
|
|
22
23
|
HardwareErrorCode2[HardwareErrorCode2["BridgeNotFound"] = 5550] = "BridgeNotFound";
|
|
23
24
|
HardwareErrorCode2[HardwareErrorCode2["TransportNotAvailable"] = 5551] = "TransportNotAvailable";
|
|
24
25
|
return HardwareErrorCode2;
|
|
@@ -32,6 +33,27 @@ function failure(code, error) {
|
|
|
32
33
|
return { success: false, payload: { error, code } };
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// src/types/fingerprint.ts
|
|
37
|
+
var CHAIN_FINGERPRINT_PATHS = {
|
|
38
|
+
evm: "m/44'/1'/0'/0/0",
|
|
39
|
+
btc: "m/44'/1'/0'/0/0",
|
|
40
|
+
sol: "m/44'/501'/0'/0'"
|
|
41
|
+
};
|
|
42
|
+
function deriveDeviceFingerprint(address) {
|
|
43
|
+
let h1 = 2166136261;
|
|
44
|
+
let h2 = 16777619;
|
|
45
|
+
for (let i = 0; i < address.length; i++) {
|
|
46
|
+
const c = address.charCodeAt(i);
|
|
47
|
+
h1 = Math.imul(h1 ^ c, h2);
|
|
48
|
+
h2 = Math.imul(h2 ^ c >>> 4, 16777619);
|
|
49
|
+
}
|
|
50
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 73244475);
|
|
51
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 73244475);
|
|
52
|
+
const hex1 = (h1 >>> 0).toString(16).padStart(8, "0");
|
|
53
|
+
const hex2 = (h2 >>> 0).toString(16).padStart(8, "0");
|
|
54
|
+
return `${hex1}${hex2}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
35
57
|
// src/events/device.ts
|
|
36
58
|
var DEVICE_EVENT = "DEVICE_EVENT";
|
|
37
59
|
var DEVICE = {
|
|
@@ -272,6 +294,7 @@ function compareSemver(a, b) {
|
|
|
272
294
|
return 0;
|
|
273
295
|
}
|
|
274
296
|
export {
|
|
297
|
+
CHAIN_FINGERPRINT_PATHS,
|
|
275
298
|
DEVICE,
|
|
276
299
|
DEVICE_EVENT,
|
|
277
300
|
DeviceJobQueue,
|
|
@@ -283,6 +306,7 @@ export {
|
|
|
283
306
|
UI_RESPONSE,
|
|
284
307
|
compareSemver,
|
|
285
308
|
createDesktopBridgeConnector,
|
|
309
|
+
deriveDeviceFingerprint,
|
|
286
310
|
failure,
|
|
287
311
|
success
|
|
288
312
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types/errors.ts","../src/types/response.ts","../src/events/device.ts","../src/events/ui-request.ts","../src/events/sdk.ts","../src/utils/DeviceJobQueue.ts","../src/types/connector.ts","../src/utils/TypedEventEmitter.ts","../src/utils/semver.ts"],"sourcesContent":["export enum HardwareErrorCode {\n UnknownError = 0,\n DeviceNotFound = 1,\n DeviceDisconnected = 2,\n UserRejected = 3,\n DeviceBusy = 4,\n FirmwareUpdateRequired = 5,\n AppNotOpen = 6,\n InvalidParams = 7,\n TransportError = 8,\n OperationTimeout = 9,\n MethodNotSupported = 10,\n\n // PIN / Passphrase\n PinInvalid = 5520,\n PinCancelled = 5521,\n PassphraseRejected = 5522,\n\n // Device state\n DeviceLocked = 5530,\n DeviceNotInitialized = 5531,\n DeviceInBootloader = 5532,\n FirmwareTooOld = 5533,\n\n // Ledger specific\n WrongApp = 5540,\n\n // Transport\n BridgeNotFound = 5550,\n TransportNotAvailable = 5551,\n}\n","import { HardwareErrorCode } from './errors';\n\nexport interface Success<T> {\n success: true;\n payload: T;\n}\n\nexport interface Failure {\n success: false;\n payload: {\n error: string;\n code: HardwareErrorCode;\n };\n}\n\nexport type Response<T> = Success<T> | Failure;\n\nexport function success<T>(payload: T): Success<T> {\n return { success: true, payload };\n}\n\nexport function failure(code: HardwareErrorCode, error: string): Failure {\n return { success: false, payload: { error, code } };\n}\n","export const DEVICE_EVENT = 'DEVICE_EVENT';\n\n/** Events originating from the hardware device. */\nexport const DEVICE = {\n CONNECT: 'device-connect',\n DISCONNECT: 'device-disconnect',\n CHANGED: 'device-changed',\n ACQUIRE: 'device-acquire',\n RELEASE: 'device-release',\n FEATURES: 'features',\n SUPPORT_FEATURES: 'support_features',\n} as const;\n","export const UI_EVENT = 'UI_EVENT';\n\nexport const UI_REQUEST = {\n REQUEST_PIN: 'ui-request-pin',\n REQUEST_PASSPHRASE: 'ui-request-passphrase',\n REQUEST_PASSPHRASE_ON_DEVICE: 'ui-request-passphrase-on-device',\n REQUEST_BUTTON: 'ui-request-button',\n REQUEST_QR_DISPLAY: 'ui-request-qr-display',\n REQUEST_QR_SCAN: 'ui-request-qr-scan',\n REQUEST_DEVICE_PERMISSION: 'ui-request-device-permission',\n REQUEST_SELECT_DEVICE: 'ui-request-select-device',\n CLOSE_UI_WINDOW: 'ui-close',\n DEVICE_PROGRESS: 'ui-device_progress',\n FIRMWARE_PROGRESS: 'ui-firmware-progress',\n FIRMWARE_TIP: 'ui-firmware-tip',\n} as const;\n\nexport const UI_RESPONSE = {\n RECEIVE_PIN: 'receive-pin',\n RECEIVE_PASSPHRASE: 'receive-passphrase',\n RECEIVE_PASSPHRASE_ON_DEVICE: 'receive-passphrase-on-device',\n RECEIVE_QR_RESPONSE: 'receive-qr-response',\n RECEIVE_SELECT_DEVICE: 'receive-select-device',\n CANCEL: 'cancel',\n} as const;\n","/** Events generated by SDK internal detection (not from hardware directly). */\nexport const SDK = {\n DEVICE_STUCK: 'device-stuck',\n DEVICE_UNRESPONSIVE: 'device-unresponsive',\n DEVICE_RECOVERED: 'device-recovered',\n DEVICE_INTERACTION: 'device-interaction',\n} as const;\n","/**\n * Per-device serial job queue with preemption support and stuck recovery.\n * Ensures that only one operation runs at a time per device, with intelligent\n * handling of conflicting operations.\n */\n\nexport type Interruptibility = 'none' | 'safe' | 'confirm';\n\nexport type PreemptionDecision = 'cancel-current' | 'wait' | 'reject-new';\n\nexport interface JobOptions {\n interruptibility?: Interruptibility;\n label?: string;\n}\n\nexport interface ActiveJobInfo {\n label?: string;\n interruptibility: Interruptibility;\n startedAt: number;\n}\n\nexport interface PreemptionEvent {\n deviceId: string;\n currentJob: ActiveJobInfo;\n newJob: { label?: string; interruptibility: Interruptibility };\n}\n\ninterface ActiveJob {\n options: Required<Pick<JobOptions, 'interruptibility'>> & Pick<JobOptions, 'label'>;\n abortController: AbortController;\n startedAt: number;\n}\n\nexport class DeviceJobQueue {\n private readonly _queues = new Map<string, Promise<unknown>>();\n private readonly _active = new Map<string, ActiveJob>();\n\n /**\n * Called when a new job conflicts with an active 'confirm'-level job.\n * UI should show a dialog and return the user's decision.\n * If not set, defaults to 'wait' (queue behind current job).\n */\n onPreemptionRequest?: (event: PreemptionEvent) => Promise<PreemptionDecision>;\n\n /**\n * Enqueue a job for a specific device.\n * If a job is already running for this device, behavior depends on interruptibility:\n * - 'none': new job queues silently (no preemption possible)\n * - 'safe': current job is auto-cancelled, new job runs immediately after\n * - 'confirm': onPreemptionRequest is called to ask user\n */\n async enqueue<T>(\n deviceId: string,\n job: (signal: AbortSignal) => Promise<T>,\n options: JobOptions = {},\n ): Promise<T> {\n const interruptibility = options.interruptibility ?? 'confirm';\n const active = this._active.get(deviceId);\n\n if (active) {\n switch (active.options.interruptibility) {\n case 'none':\n // Cannot interrupt, just queue behind\n break;\n case 'safe':\n // Auto-cancel current safe operation\n active.abortController.abort(new Error('Preempted by new operation'));\n break;\n case 'confirm': {\n if (this.onPreemptionRequest) {\n const decision = await this.onPreemptionRequest({\n deviceId,\n currentJob: {\n label: active.options.label,\n interruptibility: active.options.interruptibility,\n startedAt: active.startedAt,\n },\n newJob: {\n label: options.label,\n interruptibility,\n },\n });\n switch (decision) {\n case 'cancel-current':\n active.abortController.abort(new Error('Cancelled by user via preemption'));\n break;\n case 'reject-new':\n throw Object.assign(\n new Error(`Device busy: ${active.options.label ?? 'unknown operation'}`),\n { hardwareErrorCode: 'DEVICE_BUSY' },\n );\n case 'wait':\n break;\n }\n }\n break;\n }\n }\n }\n\n const ac = new AbortController();\n const prev = this._queues.get(deviceId) ?? Promise.resolve();\n\n const next = prev.catch(() => {}).then(async () => {\n this._active.set(deviceId, {\n options: { interruptibility, label: options.label },\n abortController: ac,\n startedAt: Date.now(),\n });\n try {\n return await job(ac.signal);\n } finally {\n this._active.delete(deviceId);\n }\n });\n\n const tail = next.catch(() => {});\n this._queues.set(deviceId, tail);\n tail.then(() => {\n if (this._queues.get(deviceId) === tail) {\n this._queues.delete(deviceId);\n }\n });\n return next;\n }\n\n /** Manually cancel the active job on a device. Returns false if job is non-interruptible. */\n cancelActive(deviceId: string): boolean {\n const active = this._active.get(deviceId);\n if (!active) return false;\n if (active.options.interruptibility === 'none') return false;\n active.abortController.abort(new Error('Manually cancelled'));\n return true;\n }\n\n /** Force cancel regardless of interruptibility. Use for device stuck recovery. */\n forceCancelActive(deviceId: string): boolean {\n const active = this._active.get(deviceId);\n if (!active) return false;\n active.abortController.abort(new Error('Force cancelled for recovery'));\n return true;\n }\n\n /** Get info about the currently active job for a device, or null if idle. */\n getActiveJob(deviceId: string): ActiveJobInfo | null {\n const active = this._active.get(deviceId);\n if (!active) return null;\n return {\n label: active.options.label,\n interruptibility: active.options.interruptibility,\n startedAt: active.startedAt,\n };\n }\n\n clear(): void {\n // Abort all active jobs\n for (const active of this._active.values()) {\n active.abortController.abort(new Error('Queue cleared'));\n }\n this._active.clear();\n this._queues.clear();\n }\n}\n","import type { DeviceInfo, VendorType } from './device';\n\n// =====================================================================\n// Connector types — transport-level abstraction for device communication\n// =====================================================================\n\n/**\n * Minimal device info returned during discovery (searchDevices).\n * At scan time, full DeviceInfo fields like firmwareVersion are not yet available.\n */\nexport interface ConnectorDevice {\n connectId: string;\n deviceId: string;\n name: string;\n model?: string;\n}\n\nexport interface ConnectorSession {\n sessionId: string;\n deviceInfo: DeviceInfo;\n}\n\nexport type ConnectorEventType =\n | 'device-connect'\n | 'device-disconnect'\n | 'ui-request'\n | 'ui-event';\n\nexport interface ConnectorEventMap {\n 'device-connect': { device: ConnectorDevice };\n 'device-disconnect': { connectId: string };\n 'ui-request': { type: string; payload?: unknown };\n 'ui-event': { type: string; payload?: unknown };\n}\n\nexport interface IConnector {\n searchDevices(): Promise<ConnectorDevice[]>;\n connect(deviceId?: string): Promise<ConnectorSession>;\n disconnect(sessionId: string): Promise<void>;\n call(sessionId: string, method: string, params: unknown): Promise<unknown>;\n cancel(sessionId: string): Promise<void>;\n\n /** Send a UI response (e.g. PIN, passphrase) to the device. */\n uiResponse(response: { type: string; payload: unknown }): void;\n\n on<K extends ConnectorEventType>(\n event: K,\n handler: (data: ConnectorEventMap[K]) => void,\n ): void;\n off<K extends ConnectorEventType>(\n event: K,\n handler: (data: ConnectorEventMap[K]) => void,\n ): void;\n\n reset(): void;\n}\n\n// =====================================================================\n// Desktop IPC bridge — generic interface for main-process hardware access\n// =====================================================================\n\nexport interface IDesktopHardwareBridge {\n searchDevices(params: { vendor: VendorType }): Promise<ConnectorDevice[]>;\n connect(params: {\n vendor: VendorType;\n deviceId?: string;\n }): Promise<ConnectorSession>;\n disconnect(params: { vendor: VendorType; sessionId: string }): Promise<void>;\n call(params: {\n vendor: VendorType;\n sessionId: string;\n method: string;\n callParams: unknown;\n }): Promise<unknown>;\n cancel(params: { vendor: VendorType; sessionId: string }): Promise<void>;\n uiResponse(params: {\n vendor: VendorType;\n response: { type: string; payload: unknown };\n }): void;\n reset(params: { vendor: VendorType }): void;\n\n /** Register an event handler for connector events forwarded from the main process. */\n onEvent(\n params: { vendor: VendorType },\n handler: (event: { type: ConnectorEventType; data: unknown }) => void,\n ): void;\n\n /** Unregister a previously registered event handler. */\n offEvent(\n params: { vendor: VendorType },\n handler: (event: { type: ConnectorEventType; data: unknown }) => void,\n ): void;\n}\n\n/**\n * Create an IConnector from a desktop IPC bridge + vendor name.\n * Events are forwarded via bridge.onEvent/offEvent.\n */\nexport function createDesktopBridgeConnector(\n vendor: VendorType,\n bridge: IDesktopHardwareBridge,\n): IConnector {\n // Map from typed IConnector handlers to the bridge handler so we can\n // unregister them correctly via off().\n const handlerMap = new Map<\n (data: ConnectorEventMap[ConnectorEventType]) => void,\n (event: { type: ConnectorEventType; data: unknown }) => void\n >();\n\n return {\n searchDevices: () => bridge.searchDevices({ vendor }),\n connect: (deviceId) => bridge.connect({ vendor, deviceId }),\n disconnect: (sessionId) => bridge.disconnect({ vendor, sessionId }),\n call: (sessionId, method, callParams) =>\n bridge.call({ vendor, sessionId, method, callParams }),\n cancel: (sessionId) => bridge.cancel({ vendor, sessionId }),\n uiResponse: (response) => bridge.uiResponse({ vendor, response }),\n on: (event, handler) => {\n const bridgeHandler = (e: { type: ConnectorEventType; data: unknown }) => {\n if (e.type === event) {\n handler(e.data as ConnectorEventMap[typeof event]);\n }\n };\n handlerMap.set(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n bridgeHandler,\n );\n bridge.onEvent({ vendor }, bridgeHandler);\n },\n off: (_event, handler) => {\n const bridgeHandler = handlerMap.get(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n );\n if (bridgeHandler) {\n bridge.offEvent({ vendor }, bridgeHandler);\n handlerMap.delete(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n );\n }\n },\n reset: () => bridge.reset({ vendor }),\n };\n}\n","/**\n * Minimal typed event emitter using Map<string, Set<listener>>.\n * Each adapter uses this for device events (connect, disconnect, pin, etc.).\n *\n * TMap is a record mapping event name strings to their payload types.\n * Example:\n * type MyEvents = { 'connect': { id: string }; 'disconnect': { id: string } };\n * const emitter = new TypedEventEmitter<MyEvents>();\n * emitter.on('connect', (data) => { data.id }); // data is { id: string }\n *\n * For backward compatibility, TMap defaults to Record<string, any> so that\n * existing code using `new TypedEventEmitter<SomeUnionType>()` still compiles.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class TypedEventEmitter<TMap extends Record<string, any> = Record<string, any>> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _listeners = new Map<string, Set<(event: any) => void>>();\n\n on<K extends keyof TMap & string>(event: K, listener: (event: TMap[K]) => void): void;\n on(event: string, listener: (event: any) => void): void;\n on(event: string, listener: (event: any) => void): void {\n let set = this._listeners.get(event);\n if (!set) {\n set = new Set();\n this._listeners.set(event, set);\n }\n set.add(listener);\n }\n\n off<K extends keyof TMap & string>(event: K, listener: (event: TMap[K]) => void): void;\n off(event: string, listener: (event: any) => void): void;\n off(event: string, listener: (event: any) => void): void {\n const set = this._listeners.get(event);\n if (set) {\n set.delete(listener);\n if (set.size === 0) this._listeners.delete(event);\n }\n }\n\n emit<K extends keyof TMap & string>(event: K, data: TMap[K]): void;\n emit(event: string, data: unknown): void;\n emit(event: string, data: unknown): void {\n const set = this._listeners.get(event);\n if (set) {\n for (const listener of set) listener(data);\n }\n }\n\n removeAllListeners(): void {\n this._listeners.clear();\n }\n}\n","/**\n * Compare two semver strings (e.g. \"2.1.0\" vs \"2.3.1\").\n * Returns -1 if a < b, 0 if equal, 1 if a > b.\n */\nexport function compareSemver(a: string, b: string): number {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n const va = pa[i] ?? 0;\n const vb = pb[i] ?? 0;\n if (va < vb) return -1;\n if (va > vb) return 1;\n }\n return 0;\n}\n"],"mappings":"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","names":["HardwareErrorCode"]}
|
|
1
|
+
{"version":3,"sources":["../src/types/errors.ts","../src/types/response.ts","../src/types/fingerprint.ts","../src/events/device.ts","../src/events/ui-request.ts","../src/events/sdk.ts","../src/utils/DeviceJobQueue.ts","../src/types/connector.ts","../src/utils/TypedEventEmitter.ts","../src/utils/semver.ts"],"sourcesContent":["export enum HardwareErrorCode {\n UnknownError = 0,\n DeviceNotFound = 1,\n DeviceDisconnected = 2,\n UserRejected = 3,\n DeviceBusy = 4,\n FirmwareUpdateRequired = 5,\n AppNotOpen = 6,\n InvalidParams = 7,\n TransportError = 8,\n OperationTimeout = 9,\n MethodNotSupported = 10,\n\n // PIN / Passphrase\n PinInvalid = 5520,\n PinCancelled = 5521,\n PassphraseRejected = 5522,\n\n // Device state\n DeviceLocked = 5530,\n DeviceNotInitialized = 5531,\n DeviceInBootloader = 5532,\n FirmwareTooOld = 5533,\n\n // Ledger specific\n WrongApp = 5540,\n\n // Device identity\n DeviceMismatch = 5560,\n\n // Transport\n BridgeNotFound = 5550,\n TransportNotAvailable = 5551,\n}\n","import { HardwareErrorCode } from './errors';\n\nexport interface Success<T> {\n success: true;\n payload: T;\n}\n\nexport interface Failure {\n success: false;\n payload: {\n error: string;\n code: HardwareErrorCode;\n };\n}\n\nexport type Response<T> = Success<T> | Failure;\n\nexport function success<T>(payload: T): Success<T> {\n return { success: true, payload };\n}\n\nexport function failure(code: HardwareErrorCode, error: string): Failure {\n return { success: false, payload: { error, code } };\n}\n","/**\n * Chain fingerprint utilities for device identity verification.\n *\n * Ledger devices have ephemeral IDs that change every session.\n * To verify that the same seed/device is connected, we derive an address\n * at a fixed testnet path and hash it into a stable \"chain fingerprint\".\n */\n\n/** Testnet derivation paths used to generate chain fingerprints. */\nexport const CHAIN_FINGERPRINT_PATHS: Record<ChainForFingerprint, string> = {\n evm: \"m/44'/1'/0'/0/0\",\n btc: \"m/44'/1'/0'/0/0\",\n sol: \"m/44'/501'/0'/0'\",\n};\n\nexport type ChainForFingerprint = 'evm' | 'btc' | 'sol';\n\n/**\n * Hash an address string into a 16-character hex fingerprint.\n *\n * Uses a simple non-cryptographic hash (FNV-1a based) to avoid\n * pulling in a SHA-256 dependency. This is NOT used for security —\n * only for device identity matching.\n */\nexport function deriveDeviceFingerprint(address: string): string {\n // FNV-1a 64-bit constants (split into two 32-bit halves for JS)\n let h1 = 0x811c9dc5;\n let h2 = 0x01000193;\n\n for (let i = 0; i < address.length; i++) {\n const c = address.charCodeAt(i);\n h1 = Math.imul(h1 ^ c, h2);\n h2 = Math.imul(h2 ^ (c >>> 4), 0x01000193);\n }\n\n // Mix the two halves for better distribution\n h1 = Math.imul(h1 ^ (h1 >>> 16), 0x45d9f3b);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 0x45d9f3b);\n\n const hex1 = (h1 >>> 0).toString(16).padStart(8, '0');\n const hex2 = (h2 >>> 0).toString(16).padStart(8, '0');\n\n return `${hex1}${hex2}`;\n}\n","export const DEVICE_EVENT = 'DEVICE_EVENT';\n\n/** Events originating from the hardware device. */\nexport const DEVICE = {\n CONNECT: 'device-connect',\n DISCONNECT: 'device-disconnect',\n CHANGED: 'device-changed',\n ACQUIRE: 'device-acquire',\n RELEASE: 'device-release',\n FEATURES: 'features',\n SUPPORT_FEATURES: 'support_features',\n} as const;\n","export const UI_EVENT = 'UI_EVENT';\n\nexport const UI_REQUEST = {\n REQUEST_PIN: 'ui-request-pin',\n REQUEST_PASSPHRASE: 'ui-request-passphrase',\n REQUEST_PASSPHRASE_ON_DEVICE: 'ui-request-passphrase-on-device',\n REQUEST_BUTTON: 'ui-request-button',\n REQUEST_QR_DISPLAY: 'ui-request-qr-display',\n REQUEST_QR_SCAN: 'ui-request-qr-scan',\n REQUEST_DEVICE_PERMISSION: 'ui-request-device-permission',\n REQUEST_SELECT_DEVICE: 'ui-request-select-device',\n CLOSE_UI_WINDOW: 'ui-close',\n DEVICE_PROGRESS: 'ui-device_progress',\n FIRMWARE_PROGRESS: 'ui-firmware-progress',\n FIRMWARE_TIP: 'ui-firmware-tip',\n} as const;\n\nexport const UI_RESPONSE = {\n RECEIVE_PIN: 'receive-pin',\n RECEIVE_PASSPHRASE: 'receive-passphrase',\n RECEIVE_PASSPHRASE_ON_DEVICE: 'receive-passphrase-on-device',\n RECEIVE_QR_RESPONSE: 'receive-qr-response',\n RECEIVE_SELECT_DEVICE: 'receive-select-device',\n CANCEL: 'cancel',\n} as const;\n","/** Events generated by SDK internal detection (not from hardware directly). */\nexport const SDK = {\n DEVICE_STUCK: 'device-stuck',\n DEVICE_UNRESPONSIVE: 'device-unresponsive',\n DEVICE_RECOVERED: 'device-recovered',\n DEVICE_INTERACTION: 'device-interaction',\n} as const;\n","/**\n * Per-device serial job queue with preemption support and stuck recovery.\n * Ensures that only one operation runs at a time per device, with intelligent\n * handling of conflicting operations.\n */\n\nexport type Interruptibility = 'none' | 'safe' | 'confirm';\n\nexport type PreemptionDecision = 'cancel-current' | 'wait' | 'reject-new';\n\nexport interface JobOptions {\n interruptibility?: Interruptibility;\n label?: string;\n}\n\nexport interface ActiveJobInfo {\n label?: string;\n interruptibility: Interruptibility;\n startedAt: number;\n}\n\nexport interface PreemptionEvent {\n deviceId: string;\n currentJob: ActiveJobInfo;\n newJob: { label?: string; interruptibility: Interruptibility };\n}\n\ninterface ActiveJob {\n options: Required<Pick<JobOptions, 'interruptibility'>> & Pick<JobOptions, 'label'>;\n abortController: AbortController;\n startedAt: number;\n}\n\nexport class DeviceJobQueue {\n private readonly _queues = new Map<string, Promise<unknown>>();\n private readonly _active = new Map<string, ActiveJob>();\n\n /**\n * Called when a new job conflicts with an active 'confirm'-level job.\n * UI should show a dialog and return the user's decision.\n * If not set, defaults to 'wait' (queue behind current job).\n */\n onPreemptionRequest?: (event: PreemptionEvent) => Promise<PreemptionDecision>;\n\n /**\n * Enqueue a job for a specific device.\n * If a job is already running for this device, behavior depends on interruptibility:\n * - 'none': new job queues silently (no preemption possible)\n * - 'safe': current job is auto-cancelled, new job runs immediately after\n * - 'confirm': onPreemptionRequest is called to ask user\n */\n async enqueue<T>(\n deviceId: string,\n job: (signal: AbortSignal) => Promise<T>,\n options: JobOptions = {},\n ): Promise<T> {\n const interruptibility = options.interruptibility ?? 'confirm';\n const active = this._active.get(deviceId);\n\n if (active) {\n switch (active.options.interruptibility) {\n case 'none':\n // Cannot interrupt, just queue behind\n break;\n case 'safe':\n // Auto-cancel current safe operation\n active.abortController.abort(new Error('Preempted by new operation'));\n break;\n case 'confirm': {\n if (this.onPreemptionRequest) {\n const decision = await this.onPreemptionRequest({\n deviceId,\n currentJob: {\n label: active.options.label,\n interruptibility: active.options.interruptibility,\n startedAt: active.startedAt,\n },\n newJob: {\n label: options.label,\n interruptibility,\n },\n });\n switch (decision) {\n case 'cancel-current':\n active.abortController.abort(new Error('Cancelled by user via preemption'));\n break;\n case 'reject-new':\n throw Object.assign(\n new Error(`Device busy: ${active.options.label ?? 'unknown operation'}`),\n { hardwareErrorCode: 'DEVICE_BUSY' },\n );\n case 'wait':\n break;\n }\n }\n break;\n }\n }\n }\n\n const ac = new AbortController();\n const prev = this._queues.get(deviceId) ?? Promise.resolve();\n\n const next = prev.catch(() => {}).then(async () => {\n this._active.set(deviceId, {\n options: { interruptibility, label: options.label },\n abortController: ac,\n startedAt: Date.now(),\n });\n try {\n return await job(ac.signal);\n } finally {\n this._active.delete(deviceId);\n }\n });\n\n const tail = next.catch(() => {});\n this._queues.set(deviceId, tail);\n tail.then(() => {\n if (this._queues.get(deviceId) === tail) {\n this._queues.delete(deviceId);\n }\n });\n return next;\n }\n\n /** Manually cancel the active job on a device. Returns false if job is non-interruptible. */\n cancelActive(deviceId: string): boolean {\n const active = this._active.get(deviceId);\n if (!active) return false;\n if (active.options.interruptibility === 'none') return false;\n active.abortController.abort(new Error('Manually cancelled'));\n return true;\n }\n\n /** Force cancel regardless of interruptibility. Use for device stuck recovery. */\n forceCancelActive(deviceId: string): boolean {\n const active = this._active.get(deviceId);\n if (!active) return false;\n active.abortController.abort(new Error('Force cancelled for recovery'));\n return true;\n }\n\n /** Get info about the currently active job for a device, or null if idle. */\n getActiveJob(deviceId: string): ActiveJobInfo | null {\n const active = this._active.get(deviceId);\n if (!active) return null;\n return {\n label: active.options.label,\n interruptibility: active.options.interruptibility,\n startedAt: active.startedAt,\n };\n }\n\n clear(): void {\n // Abort all active jobs\n for (const active of this._active.values()) {\n active.abortController.abort(new Error('Queue cleared'));\n }\n this._active.clear();\n this._queues.clear();\n }\n}\n","import type { DeviceCapabilities, DeviceInfo, VendorType } from './device';\n\n// =====================================================================\n// Connector types — transport-level abstraction for device communication\n// =====================================================================\n\n/**\n * Minimal device info returned during discovery (searchDevices).\n * At scan time, full DeviceInfo fields like firmwareVersion are not yet available.\n */\nexport interface ConnectorDevice {\n connectId: string;\n deviceId: string;\n name: string;\n model?: string;\n\n /** Device capabilities — available from scan time */\n capabilities?: DeviceCapabilities;\n}\n\nexport interface ConnectorSession {\n sessionId: string;\n deviceInfo: DeviceInfo;\n}\n\nexport type ConnectorEventType =\n | 'device-connect'\n | 'device-disconnect'\n | 'ui-request'\n | 'ui-event';\n\nexport interface ConnectorEventMap {\n 'device-connect': { device: ConnectorDevice };\n 'device-disconnect': { connectId: string };\n 'ui-request': { type: string; payload?: unknown };\n 'ui-event': { type: string; payload?: unknown };\n}\n\nexport interface IConnector {\n searchDevices(): Promise<ConnectorDevice[]>;\n connect(deviceId?: string): Promise<ConnectorSession>;\n disconnect(sessionId: string): Promise<void>;\n call(sessionId: string, method: string, params: unknown): Promise<unknown>;\n cancel(sessionId: string): Promise<void>;\n\n /** Send a UI response (e.g. PIN, passphrase) to the device. */\n uiResponse(response: { type: string; payload: unknown }): void;\n\n on<K extends ConnectorEventType>(\n event: K,\n handler: (data: ConnectorEventMap[K]) => void,\n ): void;\n off<K extends ConnectorEventType>(\n event: K,\n handler: (data: ConnectorEventMap[K]) => void,\n ): void;\n\n reset(): void;\n}\n\n// =====================================================================\n// Desktop IPC bridge — generic interface for main-process hardware access\n// =====================================================================\n\nexport interface IDesktopHardwareBridge {\n searchDevices(params: { vendor: VendorType }): Promise<ConnectorDevice[]>;\n connect(params: {\n vendor: VendorType;\n deviceId?: string;\n }): Promise<ConnectorSession>;\n disconnect(params: { vendor: VendorType; sessionId: string }): Promise<void>;\n call(params: {\n vendor: VendorType;\n sessionId: string;\n method: string;\n callParams: unknown;\n }): Promise<unknown>;\n cancel(params: { vendor: VendorType; sessionId: string }): Promise<void>;\n uiResponse(params: {\n vendor: VendorType;\n response: { type: string; payload: unknown };\n }): void;\n reset(params: { vendor: VendorType }): void;\n\n /** Register an event handler for connector events forwarded from the main process. */\n onEvent(\n params: { vendor: VendorType },\n handler: (event: { type: ConnectorEventType; data: unknown }) => void,\n ): void;\n\n /** Unregister a previously registered event handler. */\n offEvent(\n params: { vendor: VendorType },\n handler: (event: { type: ConnectorEventType; data: unknown }) => void,\n ): void;\n}\n\n/**\n * Create an IConnector from a desktop IPC bridge + vendor name.\n * Events are forwarded via bridge.onEvent/offEvent.\n */\nexport function createDesktopBridgeConnector(\n vendor: VendorType,\n bridge: IDesktopHardwareBridge,\n): IConnector {\n // Map from typed IConnector handlers to the bridge handler so we can\n // unregister them correctly via off().\n const handlerMap = new Map<\n (data: ConnectorEventMap[ConnectorEventType]) => void,\n (event: { type: ConnectorEventType; data: unknown }) => void\n >();\n\n return {\n searchDevices: () => bridge.searchDevices({ vendor }),\n connect: (deviceId) => bridge.connect({ vendor, deviceId }),\n disconnect: (sessionId) => bridge.disconnect({ vendor, sessionId }),\n call: (sessionId, method, callParams) =>\n bridge.call({ vendor, sessionId, method, callParams }),\n cancel: (sessionId) => bridge.cancel({ vendor, sessionId }),\n uiResponse: (response) => bridge.uiResponse({ vendor, response }),\n on: (event, handler) => {\n const bridgeHandler = (e: { type: ConnectorEventType; data: unknown }) => {\n if (e.type === event) {\n handler(e.data as ConnectorEventMap[typeof event]);\n }\n };\n handlerMap.set(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n bridgeHandler,\n );\n bridge.onEvent({ vendor }, bridgeHandler);\n },\n off: (_event, handler) => {\n const bridgeHandler = handlerMap.get(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n );\n if (bridgeHandler) {\n bridge.offEvent({ vendor }, bridgeHandler);\n handlerMap.delete(\n handler as (data: ConnectorEventMap[ConnectorEventType]) => void,\n );\n }\n },\n reset: () => bridge.reset({ vendor }),\n };\n}\n","/**\n * Minimal typed event emitter using Map<string, Set<listener>>.\n * Each adapter uses this for device events (connect, disconnect, pin, etc.).\n *\n * TMap is a record mapping event name strings to their payload types.\n * Example:\n * type MyEvents = { 'connect': { id: string }; 'disconnect': { id: string } };\n * const emitter = new TypedEventEmitter<MyEvents>();\n * emitter.on('connect', (data) => { data.id }); // data is { id: string }\n *\n * For backward compatibility, TMap defaults to Record<string, any> so that\n * existing code using `new TypedEventEmitter<SomeUnionType>()` still compiles.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class TypedEventEmitter<TMap extends Record<string, any> = Record<string, any>> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _listeners = new Map<string, Set<(event: any) => void>>();\n\n on<K extends keyof TMap & string>(event: K, listener: (event: TMap[K]) => void): void;\n on(event: string, listener: (event: any) => void): void;\n on(event: string, listener: (event: any) => void): void {\n let set = this._listeners.get(event);\n if (!set) {\n set = new Set();\n this._listeners.set(event, set);\n }\n set.add(listener);\n }\n\n off<K extends keyof TMap & string>(event: K, listener: (event: TMap[K]) => void): void;\n off(event: string, listener: (event: any) => void): void;\n off(event: string, listener: (event: any) => void): void {\n const set = this._listeners.get(event);\n if (set) {\n set.delete(listener);\n if (set.size === 0) this._listeners.delete(event);\n }\n }\n\n emit<K extends keyof TMap & string>(event: K, data: TMap[K]): void;\n emit(event: string, data: unknown): void;\n emit(event: string, data: unknown): void {\n const set = this._listeners.get(event);\n if (set) {\n for (const listener of set) listener(data);\n }\n }\n\n removeAllListeners(): void {\n this._listeners.clear();\n }\n}\n","/**\n * Compare two semver strings (e.g. \"2.1.0\" vs \"2.3.1\").\n * Returns -1 if a < b, 0 if equal, 1 if a > b.\n */\nexport function compareSemver(a: string, b: string): number {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n const va = pa[i] ?? 0;\n const vb = pb[i] ?? 0;\n if (va < vb) return -1;\n if (va > vb) return 1;\n }\n return 0;\n}\n"],"mappings":"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","names":["HardwareErrorCode"]}
|