@ukeyfe/hardware-transport-emulator 1.1.13
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/README.md +93 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +262 -0
- package/package.json +32 -0
- package/src/constants.ts +1 -0
- package/src/http.ts +82 -0
- package/src/index.ts +175 -0
- package/tsconfig.json +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# UKey Hardware Emulator Transport
|
|
2
|
+
|
|
3
|
+
This package provides HTTP-based transport for connecting to UKey hardware emulator.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- HTTP-based communication with emulator server
|
|
8
|
+
- Compatible with UKey Connect SDK
|
|
9
|
+
- Support for switchTransport functionality
|
|
10
|
+
- Default emulator server URL: `http://localhost:21333`
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @ukeyfe/hardware-transport-emulator
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Basic Usage
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
import EmulatorTransport from '@ukeyfe/hardware-transport-emulator';
|
|
24
|
+
|
|
25
|
+
// Create transport instance
|
|
26
|
+
const transport = new EmulatorTransport();
|
|
27
|
+
// or with custom URL
|
|
28
|
+
const transport = new EmulatorTransport('http://localhost:21333');
|
|
29
|
+
|
|
30
|
+
// Initialize transport
|
|
31
|
+
await transport.init(logger);
|
|
32
|
+
|
|
33
|
+
// Configure with protobuf messages
|
|
34
|
+
await transport.configure(signedData);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### With UKey Connect SDK
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import HardwareSDK from '@ukeyfe/hardware-web-sdk';
|
|
41
|
+
|
|
42
|
+
// Initialize with emulator environment
|
|
43
|
+
await HardwareSDK.init({
|
|
44
|
+
env: 'emulator',
|
|
45
|
+
debug: true
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Switch to emulator transport
|
|
49
|
+
await HardwareSDK.switchTransport('emulator');
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### In Connect Examples
|
|
53
|
+
|
|
54
|
+
The emulator transport is integrated into the connect examples:
|
|
55
|
+
|
|
56
|
+
1. **Expo Example**: Select "Emulator" from the transport picker
|
|
57
|
+
2. **Electron Example**: Use switchTransport API to switch to emulator
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
### Constructor
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
new EmulatorTransport(url?: string)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `url` (optional): Emulator server URL, defaults to `http://localhost:21333`
|
|
68
|
+
|
|
69
|
+
### Methods
|
|
70
|
+
|
|
71
|
+
All methods implement the standard UKey Transport interface:
|
|
72
|
+
|
|
73
|
+
- `init(logger)`: Initialize transport
|
|
74
|
+
- `configure(signedData)`: Configure protobuf messages
|
|
75
|
+
- `enumerate()`: List available devices
|
|
76
|
+
- `acquire(input)`: Acquire device session
|
|
77
|
+
- `release(session, onclose)`: Release device session
|
|
78
|
+
- `call(session, name, data)`: Call device method
|
|
79
|
+
- `stop()`: Stop transport
|
|
80
|
+
|
|
81
|
+
## Emulator Server
|
|
82
|
+
|
|
83
|
+
Make sure your UKey emulator server is running on the configured URL (default: `http://localhost:21333`) before using this transport.
|
|
84
|
+
|
|
85
|
+
## Development
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Build
|
|
89
|
+
npm run build
|
|
90
|
+
|
|
91
|
+
# Development mode
|
|
92
|
+
npm run dev
|
|
93
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,2BAA2B,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type HttpRequestOptions = {
|
|
2
|
+
body?: Array<any> | Record<string, unknown> | string;
|
|
3
|
+
url: string;
|
|
4
|
+
method: 'POST' | 'GET';
|
|
5
|
+
timeout?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function request(options: HttpRequestOptions): Promise<any>;
|
|
8
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AA4BF,wBAAsB,OAAO,CAAC,OAAO,EAAE,kBAAkB,gBA6BxD"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as _ukeyfe_hardware_transport from '@ukeyfe/hardware-transport';
|
|
2
|
+
import _ukeyfe_hardware_transport__default, { UKeyDeviceInfoWithSession, AcquireInput } from '@ukeyfe/hardware-transport';
|
|
3
|
+
|
|
4
|
+
type IncompleteRequestOptions = {
|
|
5
|
+
body?: Array<any> | Record<string, unknown> | string;
|
|
6
|
+
url: string;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
};
|
|
9
|
+
declare class EmulatorTransport {
|
|
10
|
+
_messages: ReturnType<typeof _ukeyfe_hardware_transport__default.parseConfigure> | undefined;
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
configured: boolean;
|
|
14
|
+
stopped: boolean;
|
|
15
|
+
isOutdated: boolean;
|
|
16
|
+
url: string;
|
|
17
|
+
Log?: any;
|
|
18
|
+
constructor(url?: string);
|
|
19
|
+
_post(options: IncompleteRequestOptions): Promise<any>;
|
|
20
|
+
init(logger: any): Promise<string>;
|
|
21
|
+
_silentInit(): Promise<string>;
|
|
22
|
+
configure(signedData: any): void;
|
|
23
|
+
listen(old?: Array<UKeyDeviceInfoWithSession>): Promise<UKeyDeviceInfoWithSession[]>;
|
|
24
|
+
enumerate(): Promise<UKeyDeviceInfoWithSession[]>;
|
|
25
|
+
_acquireMixed(input: AcquireInput): Promise<any>;
|
|
26
|
+
acquire(input: AcquireInput): Promise<string>;
|
|
27
|
+
release(session: string, onclose: boolean): Promise<void>;
|
|
28
|
+
call(session: string, name: string, data: Record<string, unknown>): Promise<_ukeyfe_hardware_transport.MessageFromUKey>;
|
|
29
|
+
post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
30
|
+
read(session: string): Promise<_ukeyfe_hardware_transport.MessageFromUKey>;
|
|
31
|
+
requestDevice(): Promise<never>;
|
|
32
|
+
stop(): void;
|
|
33
|
+
cancel(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { EmulatorTransport as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAA8B,MAAM,4BAA4B,CAAC;AAExE,OAAO,KAAK,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAM1F,KAAK,wBAAwB,GAAG;IAC9B,IAAI,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC,SAAS,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEnE,IAAI,SAAuB;IAE3B,OAAO,SAAW;IAElB,UAAU,UAAS;IAEnB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,EAAE,MAAM,CAAC;IAEZ,GAAG,CAAC,EAAE,GAAG,CAAC;gBAEE,GAAG,CAAC,EAAE,MAAM;IAIxB,KAAK,CAAC,OAAO,EAAE,wBAAwB;IAWjC,IAAI,CAAC,MAAM,EAAE,GAAG;IAMhB,WAAW;IAUjB,SAAS,CAAC,UAAU,EAAE,GAAG;IAMnB,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,yBAAyB,CAAC;IAY7C,SAAS;IAMf,aAAa,CAAC,KAAK,EAAE,YAAY;IAS3B,OAAO,CAAC,KAAK,EAAE,YAAY;IAK3B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAUzC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAyBjE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAYjE,IAAI,CAAC,OAAO,EAAE,MAAM;IAe1B,aAAa;IAKb,IAAI;IAIJ,MAAM;CAGP"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var transport = require('@ukeyfe/hardware-transport');
|
|
4
|
+
var hardwareShared = require('@ukeyfe/hardware-shared');
|
|
5
|
+
var axios = require('axios');
|
|
6
|
+
var secureJSON = require('secure-json-parse');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
9
|
+
|
|
10
|
+
var transport__default = /*#__PURE__*/_interopDefaultLegacy(transport);
|
|
11
|
+
var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
|
|
12
|
+
var secureJSON__default = /*#__PURE__*/_interopDefaultLegacy(secureJSON);
|
|
13
|
+
|
|
14
|
+
/******************************************************************************
|
|
15
|
+
Copyright (c) Microsoft Corporation.
|
|
16
|
+
|
|
17
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
18
|
+
purpose with or without fee is hereby granted.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
21
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
22
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
23
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
24
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
25
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
26
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
27
|
+
***************************************************************************** */
|
|
28
|
+
|
|
29
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
30
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
31
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
32
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
33
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
34
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
35
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
40
|
+
var e = new Error(message);
|
|
41
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function contentType(body) {
|
|
45
|
+
if (typeof body === 'string') {
|
|
46
|
+
return 'text/plain';
|
|
47
|
+
}
|
|
48
|
+
return 'application/json';
|
|
49
|
+
}
|
|
50
|
+
function wrapBody(body) {
|
|
51
|
+
if (typeof body === 'string') {
|
|
52
|
+
return body;
|
|
53
|
+
}
|
|
54
|
+
return JSON.stringify(body);
|
|
55
|
+
}
|
|
56
|
+
function parseResult(text) {
|
|
57
|
+
try {
|
|
58
|
+
const result = secureJSON__default["default"].parse(text);
|
|
59
|
+
if (typeof result !== 'object') {
|
|
60
|
+
throw new Error('Invalid response');
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return text;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function request(options) {
|
|
69
|
+
var _a;
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const fetchOptions = {
|
|
72
|
+
url: options.url,
|
|
73
|
+
method: options.method,
|
|
74
|
+
data: wrapBody(options.body),
|
|
75
|
+
withCredentials: false,
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': contentType(options.body == null ? '' : options.body),
|
|
78
|
+
},
|
|
79
|
+
timeout: (_a = options.timeout) !== null && _a !== void 0 ? _a : undefined,
|
|
80
|
+
transformResponse: data => data,
|
|
81
|
+
};
|
|
82
|
+
const res = yield axios__default["default"].request(fetchOptions);
|
|
83
|
+
if (+res.status === 200) {
|
|
84
|
+
return parseResult(res.data);
|
|
85
|
+
}
|
|
86
|
+
const resJson = parseResult(res.data);
|
|
87
|
+
if (typeof resJson === 'object' && resJson != null && resJson.error != null) {
|
|
88
|
+
throw new hardwareShared.HardwareError({
|
|
89
|
+
errorCode: hardwareShared.HardwareErrorCode.NetworkError,
|
|
90
|
+
message: resJson.error,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw new hardwareShared.HardwareError({ errorCode: hardwareShared.HardwareErrorCode.NetworkError, message: res.data });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
axios__default["default"].interceptors.request.use((config) => {
|
|
99
|
+
var _a;
|
|
100
|
+
if (typeof window !== 'undefined') {
|
|
101
|
+
return config;
|
|
102
|
+
}
|
|
103
|
+
if ((_a = config.url) === null || _a === void 0 ? void 0 : _a.startsWith('http://localhost:21333')) {
|
|
104
|
+
if (!config.headers.get('Origin')) {
|
|
105
|
+
console.log('set node request origin');
|
|
106
|
+
config.headers.set('Origin', 'https://jssdk.onekey.so');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return config;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const DEFAULT_URL = 'http://localhost:21333';
|
|
113
|
+
|
|
114
|
+
const { check, buildOne, receiveOne, parseConfigure } = transport__default["default"];
|
|
115
|
+
class EmulatorTransport {
|
|
116
|
+
constructor(url) {
|
|
117
|
+
this.name = 'EmulatorTransport';
|
|
118
|
+
this.version = '1.0.0';
|
|
119
|
+
this.configured = false;
|
|
120
|
+
this.stopped = false;
|
|
121
|
+
this.isOutdated = false;
|
|
122
|
+
this.url = url == null ? DEFAULT_URL : url;
|
|
123
|
+
}
|
|
124
|
+
_post(options) {
|
|
125
|
+
if (this.stopped) {
|
|
126
|
+
return Promise.reject(hardwareShared.ERRORS.TypedError('Transport stopped.'));
|
|
127
|
+
}
|
|
128
|
+
return request(Object.assign(Object.assign({}, options), { method: 'POST', url: this.url + options.url }));
|
|
129
|
+
}
|
|
130
|
+
init(logger) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
this.Log = logger;
|
|
133
|
+
const bridgeVersion = yield this._silentInit();
|
|
134
|
+
return bridgeVersion;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
_silentInit() {
|
|
138
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
139
|
+
const infoS = yield request({
|
|
140
|
+
url: this.url,
|
|
141
|
+
method: 'POST',
|
|
142
|
+
timeout: 3000,
|
|
143
|
+
});
|
|
144
|
+
const info = check.info(infoS);
|
|
145
|
+
return info.version;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
configure(signedData) {
|
|
149
|
+
const messages = parseConfigure(signedData);
|
|
150
|
+
this.configured = true;
|
|
151
|
+
this._messages = messages;
|
|
152
|
+
}
|
|
153
|
+
listen(old) {
|
|
154
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
155
|
+
if (old === null) {
|
|
156
|
+
throw hardwareShared.ERRORS.TypedError('Http-Transport does not support listen without previous.');
|
|
157
|
+
}
|
|
158
|
+
const devicesS = yield this._post({
|
|
159
|
+
url: '/listen',
|
|
160
|
+
body: old,
|
|
161
|
+
});
|
|
162
|
+
const devices = check.devices(devicesS);
|
|
163
|
+
return devices;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
enumerate() {
|
|
167
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
168
|
+
const devicesS = yield this._post({ url: '/enumerate' });
|
|
169
|
+
const devices = check.devices(devicesS);
|
|
170
|
+
return devices;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
_acquireMixed(input) {
|
|
174
|
+
const previousStr = input.previous == null ? 'null' : encodeURIComponent(input.previous);
|
|
175
|
+
const path = encodeURIComponent(input.path);
|
|
176
|
+
const url = `/acquire/${path}/${previousStr}`;
|
|
177
|
+
return this._post({ url });
|
|
178
|
+
}
|
|
179
|
+
acquire(input) {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
181
|
+
const acquireS = yield this._acquireMixed(input);
|
|
182
|
+
return check.acquire(acquireS);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
release(session, onclose) {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
const res = this._post({
|
|
188
|
+
url: `/release/${session}`,
|
|
189
|
+
});
|
|
190
|
+
if (onclose) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
yield res;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
call(session, name, data) {
|
|
197
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
198
|
+
if (this._messages == null) {
|
|
199
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.TransportNotConfigured);
|
|
200
|
+
}
|
|
201
|
+
const messages = this._messages;
|
|
202
|
+
if (transport.LogBlockCommand.has(name)) {
|
|
203
|
+
this.Log.debug('call-', ' name: ', name);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.Log.debug('call-', ' name: ', name, ' data: ', data);
|
|
207
|
+
}
|
|
208
|
+
const o = buildOne(messages, name, data);
|
|
209
|
+
const outData = o.toString('hex');
|
|
210
|
+
const resData = yield this._post({
|
|
211
|
+
url: `/call/${session}`,
|
|
212
|
+
body: outData,
|
|
213
|
+
timeout: name === 'Initialize' ? 10000 : undefined,
|
|
214
|
+
});
|
|
215
|
+
if (typeof resData !== 'string') {
|
|
216
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.NetworkError, 'Returning data is not string.');
|
|
217
|
+
}
|
|
218
|
+
const jsonData = receiveOne(messages, resData);
|
|
219
|
+
return check.call(jsonData);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
post(session, name, data) {
|
|
223
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
if (this._messages == null) {
|
|
225
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.TransportNotConfigured);
|
|
226
|
+
}
|
|
227
|
+
const messages = this._messages;
|
|
228
|
+
const outData = buildOne(messages, name, data).toString('hex');
|
|
229
|
+
yield this._post({
|
|
230
|
+
url: `/post/${session}`,
|
|
231
|
+
body: outData,
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
read(session) {
|
|
236
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
237
|
+
if (this._messages == null) {
|
|
238
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.TransportNotConfigured);
|
|
239
|
+
}
|
|
240
|
+
const messages = this._messages;
|
|
241
|
+
const resData = yield this._post({
|
|
242
|
+
url: `/read/${session}`,
|
|
243
|
+
});
|
|
244
|
+
if (typeof resData !== 'string') {
|
|
245
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.NetworkError, 'Returning data is not string.');
|
|
246
|
+
}
|
|
247
|
+
const jsonData = receiveOne(messages, resData);
|
|
248
|
+
return check.call(jsonData);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
requestDevice() {
|
|
252
|
+
return Promise.reject();
|
|
253
|
+
}
|
|
254
|
+
stop() {
|
|
255
|
+
this.stopped = true;
|
|
256
|
+
}
|
|
257
|
+
cancel() {
|
|
258
|
+
this.Log.debug('canceled');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = EmulatorTransport;
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ukeyfe/hardware-transport-emulator",
|
|
3
|
+
"version": "1.1.13",
|
|
4
|
+
"description": "hardware emulator transport",
|
|
5
|
+
"author": "UKey",
|
|
6
|
+
"homepage": "https://github.com/UKeyHQ/hardware-js-sdk#readme",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/bestyourwallet/hardware-js-sdk"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
|
|
19
|
+
"build": "rimraf dist && rollup -c ../../build/rollup.config.js",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"lint:fix": "eslint . --fix"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/UKeyHQ/hardware-js-sdk/issues"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@ukeyfe/hardware-shared": "1.1.13",
|
|
28
|
+
"@ukeyfe/hardware-transport": "1.1.13",
|
|
29
|
+
"axios": "1.12.2",
|
|
30
|
+
"secure-json-parse": "^4.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_URL = 'http://localhost:21333';
|
package/src/http.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import axios, { AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
|
|
2
|
+
import { HardwareError, HardwareErrorCode } from '@ukeyfe/hardware-shared';
|
|
3
|
+
import secureJSON from 'secure-json-parse';
|
|
4
|
+
|
|
5
|
+
export type HttpRequestOptions = {
|
|
6
|
+
body?: Array<any> | Record<string, unknown> | string;
|
|
7
|
+
url: string;
|
|
8
|
+
method: 'POST' | 'GET';
|
|
9
|
+
timeout?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function contentType(body: any) {
|
|
13
|
+
if (typeof body === 'string') {
|
|
14
|
+
return 'text/plain';
|
|
15
|
+
}
|
|
16
|
+
return 'application/json';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function wrapBody(body: any) {
|
|
20
|
+
if (typeof body === 'string') {
|
|
21
|
+
return body;
|
|
22
|
+
}
|
|
23
|
+
return JSON.stringify(body);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseResult(text: string) {
|
|
27
|
+
try {
|
|
28
|
+
const result = secureJSON.parse(text);
|
|
29
|
+
if (typeof result !== 'object') {
|
|
30
|
+
throw new Error('Invalid response');
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function request(options: HttpRequestOptions) {
|
|
39
|
+
const fetchOptions: AxiosRequestConfig = {
|
|
40
|
+
url: options.url,
|
|
41
|
+
method: options.method,
|
|
42
|
+
data: wrapBody(options.body),
|
|
43
|
+
withCredentials: false,
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': contentType(options.body == null ? '' : options.body),
|
|
46
|
+
},
|
|
47
|
+
timeout: options.timeout ?? undefined,
|
|
48
|
+
// Prevent string from converting to number
|
|
49
|
+
// see https://stackoverflow.com/questions/43787712/axios-how-to-deal-with-big-integers
|
|
50
|
+
transformResponse: data => data,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const res = await axios.request(fetchOptions);
|
|
54
|
+
|
|
55
|
+
if (+res.status === 200) {
|
|
56
|
+
return parseResult(res.data);
|
|
57
|
+
}
|
|
58
|
+
const resJson = parseResult(res.data);
|
|
59
|
+
if (typeof resJson === 'object' && resJson != null && resJson.error != null) {
|
|
60
|
+
throw new HardwareError({
|
|
61
|
+
errorCode: HardwareErrorCode.NetworkError,
|
|
62
|
+
message: resJson.error,
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
throw new HardwareError({ errorCode: HardwareErrorCode.NetworkError, message: res.data });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
axios.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
|
70
|
+
if (typeof window !== 'undefined') {
|
|
71
|
+
return config;
|
|
72
|
+
}
|
|
73
|
+
// node environment
|
|
74
|
+
if (config.url?.startsWith('http://localhost:21333')) {
|
|
75
|
+
if (!config.headers.get('Origin')) {
|
|
76
|
+
console.log('set node request origin');
|
|
77
|
+
// add Origin field for request headers
|
|
78
|
+
config.headers.set('Origin', 'https://jssdk.onekey.so');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return config;
|
|
82
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import transport, { LogBlockCommand } from '@ukeyfe/hardware-transport';
|
|
2
|
+
import { ERRORS, HardwareErrorCode } from '@ukeyfe/hardware-shared';
|
|
3
|
+
import type { AcquireInput, UKeyDeviceInfoWithSession } from '@ukeyfe/hardware-transport';
|
|
4
|
+
import { request as http } from './http';
|
|
5
|
+
import { DEFAULT_URL } from './constants';
|
|
6
|
+
|
|
7
|
+
const { check, buildOne, receiveOne, parseConfigure } = transport;
|
|
8
|
+
|
|
9
|
+
type IncompleteRequestOptions = {
|
|
10
|
+
body?: Array<any> | Record<string, unknown> | string;
|
|
11
|
+
url: string;
|
|
12
|
+
timeout?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default class EmulatorTransport {
|
|
16
|
+
_messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
17
|
+
|
|
18
|
+
name = 'EmulatorTransport';
|
|
19
|
+
|
|
20
|
+
version = '1.0.0';
|
|
21
|
+
|
|
22
|
+
configured = false;
|
|
23
|
+
|
|
24
|
+
stopped = false;
|
|
25
|
+
|
|
26
|
+
isOutdated = false;
|
|
27
|
+
|
|
28
|
+
url: string;
|
|
29
|
+
|
|
30
|
+
Log?: any;
|
|
31
|
+
|
|
32
|
+
constructor(url?: string) {
|
|
33
|
+
this.url = url == null ? DEFAULT_URL : url;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_post(options: IncompleteRequestOptions) {
|
|
37
|
+
if (this.stopped) {
|
|
38
|
+
return Promise.reject(ERRORS.TypedError('Transport stopped.'));
|
|
39
|
+
}
|
|
40
|
+
return http({
|
|
41
|
+
...options,
|
|
42
|
+
method: 'POST',
|
|
43
|
+
url: this.url + options.url,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async init(logger: any) {
|
|
48
|
+
this.Log = logger;
|
|
49
|
+
const bridgeVersion = await this._silentInit();
|
|
50
|
+
return bridgeVersion;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async _silentInit() {
|
|
54
|
+
const infoS = await http({
|
|
55
|
+
url: this.url,
|
|
56
|
+
method: 'POST',
|
|
57
|
+
timeout: 3000,
|
|
58
|
+
});
|
|
59
|
+
const info = check.info(infoS);
|
|
60
|
+
return info.version;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
configure(signedData: any) {
|
|
64
|
+
const messages = parseConfigure(signedData);
|
|
65
|
+
this.configured = true;
|
|
66
|
+
this._messages = messages;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async listen(old?: Array<UKeyDeviceInfoWithSession>) {
|
|
70
|
+
if (old === null) {
|
|
71
|
+
throw ERRORS.TypedError('Http-Transport does not support listen without previous.');
|
|
72
|
+
}
|
|
73
|
+
const devicesS = await this._post({
|
|
74
|
+
url: '/listen',
|
|
75
|
+
body: old,
|
|
76
|
+
});
|
|
77
|
+
const devices = check.devices(devicesS);
|
|
78
|
+
return devices;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async enumerate() {
|
|
82
|
+
const devicesS = await this._post({ url: '/enumerate' });
|
|
83
|
+
const devices = check.devices(devicesS);
|
|
84
|
+
return devices;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_acquireMixed(input: AcquireInput) {
|
|
88
|
+
const previousStr = input.previous == null ? 'null' : encodeURIComponent(input.previous);
|
|
89
|
+
// @ts-expect-error
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
91
|
+
const path = encodeURIComponent(input.path);
|
|
92
|
+
const url = `/acquire/${path}/${previousStr}`;
|
|
93
|
+
return this._post({ url });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async acquire(input: AcquireInput) {
|
|
97
|
+
const acquireS = await this._acquireMixed(input);
|
|
98
|
+
return check.acquire(acquireS);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async release(session: string, onclose: boolean) {
|
|
102
|
+
const res = this._post({
|
|
103
|
+
url: `/release/${session}`,
|
|
104
|
+
});
|
|
105
|
+
if (onclose) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
await res;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async call(session: string, name: string, data: Record<string, unknown>) {
|
|
112
|
+
if (this._messages == null) {
|
|
113
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
114
|
+
}
|
|
115
|
+
const messages = this._messages;
|
|
116
|
+
if (LogBlockCommand.has(name)) {
|
|
117
|
+
this.Log.debug('call-', ' name: ', name);
|
|
118
|
+
} else {
|
|
119
|
+
this.Log.debug('call-', ' name: ', name, ' data: ', data);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const o = buildOne(messages, name, data);
|
|
123
|
+
const outData = o.toString('hex');
|
|
124
|
+
const resData = await this._post({
|
|
125
|
+
url: `/call/${session}`,
|
|
126
|
+
body: outData,
|
|
127
|
+
timeout: name === 'Initialize' ? 10000 : undefined,
|
|
128
|
+
});
|
|
129
|
+
if (typeof resData !== 'string') {
|
|
130
|
+
throw ERRORS.TypedError(HardwareErrorCode.NetworkError, 'Returning data is not string.');
|
|
131
|
+
}
|
|
132
|
+
const jsonData = receiveOne(messages, resData);
|
|
133
|
+
return check.call(jsonData);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async post(session: string, name: string, data: Record<string, unknown>) {
|
|
137
|
+
if (this._messages == null) {
|
|
138
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
139
|
+
}
|
|
140
|
+
const messages = this._messages;
|
|
141
|
+
const outData = buildOne(messages, name, data).toString('hex');
|
|
142
|
+
await this._post({
|
|
143
|
+
url: `/post/${session}`,
|
|
144
|
+
body: outData,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async read(session: string) {
|
|
149
|
+
if (this._messages == null) {
|
|
150
|
+
throw ERRORS.TypedError(HardwareErrorCode.TransportNotConfigured);
|
|
151
|
+
}
|
|
152
|
+
const messages = this._messages;
|
|
153
|
+
const resData = await this._post({
|
|
154
|
+
url: `/read/${session}`,
|
|
155
|
+
});
|
|
156
|
+
if (typeof resData !== 'string') {
|
|
157
|
+
throw ERRORS.TypedError(HardwareErrorCode.NetworkError, 'Returning data is not string.');
|
|
158
|
+
}
|
|
159
|
+
const jsonData = receiveOne(messages, resData);
|
|
160
|
+
return check.call(jsonData);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
requestDevice() {
|
|
164
|
+
// eslint-disable-next-line prefer-promise-reject-errors
|
|
165
|
+
return Promise.reject();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
stop() {
|
|
169
|
+
this.stopped = true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
cancel() {
|
|
173
|
+
this.Log.debug('canceled');
|
|
174
|
+
}
|
|
175
|
+
}
|