@onekeyfe/hd-transport 0.1.39 → 0.1.42
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/.eslintrc +3 -1
- package/__tests__/build-receive.test.js +117 -0
- package/__tests__/encode-decode-basic.test.js +272 -0
- package/__tests__/encode-decode.test.js +532 -0
- package/__tests__/messages.test.js +86 -0
- package/dist/index.js +127 -161
- package/dist/serialization/protobuf/decode.d.ts +1 -1
- package/dist/serialization/protobuf/decode.d.ts.map +1 -1
- package/dist/serialization/protobuf/encode.d.ts +1 -1
- package/dist/serialization/protobuf/encode.d.ts.map +1 -1
- package/dist/serialization/receive.d.ts.map +1 -1
- package/dist/serialization/send.d.ts +1 -1
- package/dist/serialization/send.d.ts.map +1 -1
- package/jest.config.js +7 -0
- package/package.json +5 -3
- package/scripts/protobuf-build.sh +57 -0
- package/scripts/protobuf-patches/TxAck.js +44 -0
- package/scripts/protobuf-patches/TxInputType.js +49 -0
- package/scripts/protobuf-patches/TxOutputType.js +50 -0
- package/scripts/protobuf-patches/index.js +305 -0
- package/scripts/protobuf-types.js +283 -0
- package/src/serialization/protobuf/decode.ts +1 -1
- package/src/serialization/protobuf/encode.ts +1 -1
- package/src/serialization/receive.ts +1 -1
- package/src/serialization/send.ts +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
/* eslint-disable import/no-unresolved */
|
|
3
|
+
// flowtype only
|
|
4
|
+
// flowtype doesn't have `enum` declarations like typescript
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const json = require('../messages.json');
|
|
10
|
+
const { RULE_PATCH, TYPE_PATCH, DEFINITION_PATCH, SKIP, UINT_TYPE } = require('./protobuf-patches');
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
|
|
14
|
+
const isTypescript = args.includes('typescript');
|
|
15
|
+
|
|
16
|
+
// proto types to javascript types
|
|
17
|
+
const FIELD_TYPES = {
|
|
18
|
+
uint32: 'number',
|
|
19
|
+
uint64: 'number',
|
|
20
|
+
sint32: 'number',
|
|
21
|
+
sint64: 'number',
|
|
22
|
+
bool: 'boolean',
|
|
23
|
+
bytes: 'string',
|
|
24
|
+
// 'bytes': 'Uint8Array | number[] | Buffer | string', // protobuf will handle conversion
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const types = []; // { type: 'enum | message', name: string, value: string[], exact?: boolean };
|
|
28
|
+
|
|
29
|
+
// enums used as keys (string), used as values (number) by default
|
|
30
|
+
const ENUM_KEYS = [
|
|
31
|
+
'InputScriptType',
|
|
32
|
+
'OutputScriptType',
|
|
33
|
+
'RequestType',
|
|
34
|
+
'BackupType',
|
|
35
|
+
'Capability',
|
|
36
|
+
'SafetyCheckLevel',
|
|
37
|
+
'ButtonRequestType',
|
|
38
|
+
'PinMatrixRequestType',
|
|
39
|
+
'WordRequestType',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const parseEnumTypescript = (itemName, item) => {
|
|
43
|
+
const value = [];
|
|
44
|
+
const IS_KEY = ENUM_KEYS.includes(itemName);
|
|
45
|
+
// declare enum
|
|
46
|
+
if (IS_KEY) {
|
|
47
|
+
value.push(`export enum Enum_${itemName} {`);
|
|
48
|
+
} else {
|
|
49
|
+
value.push(`export enum ${itemName} {`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// declare fields
|
|
53
|
+
Object.entries(item.values).forEach(([name, id]) => {
|
|
54
|
+
value.push(` ${name} = ${id},`);
|
|
55
|
+
});
|
|
56
|
+
// close enum declaration
|
|
57
|
+
value.push('}');
|
|
58
|
+
|
|
59
|
+
if (IS_KEY) {
|
|
60
|
+
value.push(`export type ${itemName} = keyof typeof Enum_${itemName};`);
|
|
61
|
+
}
|
|
62
|
+
// empty line
|
|
63
|
+
value.push('');
|
|
64
|
+
|
|
65
|
+
types.push({
|
|
66
|
+
type: 'enum',
|
|
67
|
+
name: itemName,
|
|
68
|
+
value: value.join('\n'),
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const parseEnum = (itemName, item) => {
|
|
73
|
+
if (isTypescript) return parseEnumTypescript(itemName, item);
|
|
74
|
+
const value = [];
|
|
75
|
+
// declare enum
|
|
76
|
+
value.push(`export const Enum_${itemName} = Object.freeze({`);
|
|
77
|
+
// declare fields
|
|
78
|
+
Object.entries(item.values).forEach(([name, id]) => {
|
|
79
|
+
value.push(` ${name}: ${id},`);
|
|
80
|
+
});
|
|
81
|
+
// close enum declaration
|
|
82
|
+
value.push('});');
|
|
83
|
+
// declare enum type using Keys or Values
|
|
84
|
+
const KEY = ENUM_KEYS.includes(itemName) ? 'Keys' : 'Values';
|
|
85
|
+
value.push(`export type ${itemName} = $${KEY}<typeof Enum_${itemName}>;`);
|
|
86
|
+
// empty line
|
|
87
|
+
value.push('');
|
|
88
|
+
|
|
89
|
+
types.push({
|
|
90
|
+
type: 'enum',
|
|
91
|
+
name: itemName,
|
|
92
|
+
value: value.join('\n'),
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const useDefinition = def => {
|
|
97
|
+
// remove flow overhead
|
|
98
|
+
const clean = def.replace(/\/\/ @flow/, '').replace(/\/\/ @overhead-start(.*)@overhead-end/s, '');
|
|
99
|
+
|
|
100
|
+
if (isTypescript) {
|
|
101
|
+
// use typescript variant
|
|
102
|
+
// replace flowtype $Exact declaration: {| foo: 1 |} => { foo: 1 }
|
|
103
|
+
// replace flowtype spread with typescript union: { ...T, foo: 1 } => T & { foo: 1 }, see TxInputType patch
|
|
104
|
+
return clean
|
|
105
|
+
.replace(/\/\/ @typescript-variant:/, '')
|
|
106
|
+
.replace(/\/\/ @flowtype-variant(.*)/, '')
|
|
107
|
+
.replace(/{\|/gi, '{')
|
|
108
|
+
.replace(/\|}/gi, '}')
|
|
109
|
+
.replace(/\{\n.*.\.{3}(.*?),/g, '$1 & {');
|
|
110
|
+
}
|
|
111
|
+
return clean.replace(/\/\/ @typescript-variant(.*)/, '').replace(/\/\/ @flowtype-variant:/, '');
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const parseMessage = (messageName, message, depth = 0) => {
|
|
115
|
+
if (messageName === 'google') return;
|
|
116
|
+
const value = [];
|
|
117
|
+
// add comment line
|
|
118
|
+
if (!depth) value.push(`// ${messageName}`);
|
|
119
|
+
// declare nested enums
|
|
120
|
+
|
|
121
|
+
// declare nested values
|
|
122
|
+
if (message.nested) {
|
|
123
|
+
Object.keys(message.nested).map(item => parseMessage(item, message.nested[item], depth + 1));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (message.values) {
|
|
127
|
+
return parseEnum(messageName, message);
|
|
128
|
+
}
|
|
129
|
+
if (!message.fields || !Object.keys(message.fields).length) {
|
|
130
|
+
// few types are just empty objects, make it one line
|
|
131
|
+
value.push(`export type ${messageName} = {};`);
|
|
132
|
+
value.push('');
|
|
133
|
+
} else {
|
|
134
|
+
// find patch
|
|
135
|
+
const definition = DEFINITION_PATCH[messageName];
|
|
136
|
+
if (definition) {
|
|
137
|
+
// replace whole declaration
|
|
138
|
+
value.push(useDefinition(definition));
|
|
139
|
+
} else {
|
|
140
|
+
// declare type
|
|
141
|
+
value.push(`export type ${messageName} = {`);
|
|
142
|
+
Object.keys(message.fields).forEach(fieldName => {
|
|
143
|
+
const field = message.fields[fieldName];
|
|
144
|
+
const fieldKey = `${messageName}.${fieldName}`;
|
|
145
|
+
// find patch for "rule"
|
|
146
|
+
const fieldRule = RULE_PATCH[fieldKey] || field.rule;
|
|
147
|
+
const rule = fieldRule === 'required' || fieldRule === 'repeated' ? ': ' : '?: ';
|
|
148
|
+
// find patch for "type"
|
|
149
|
+
let type = TYPE_PATCH[fieldKey] || FIELD_TYPES[field.type] || field.type;
|
|
150
|
+
// automatically convert all amount and fee fields to UINT_TYPE
|
|
151
|
+
if (['amount', 'fee'].includes(fieldName)) {
|
|
152
|
+
type = UINT_TYPE;
|
|
153
|
+
}
|
|
154
|
+
// array
|
|
155
|
+
if (field.rule === 'repeated') {
|
|
156
|
+
type = type.split('|').length > 1 ? `Array<${type}>` : `${type}[]`;
|
|
157
|
+
}
|
|
158
|
+
value.push(` ${fieldName}${rule}${type};`);
|
|
159
|
+
});
|
|
160
|
+
// close type declaration
|
|
161
|
+
value.push('};');
|
|
162
|
+
// empty line
|
|
163
|
+
value.push('');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// type doest have to be e
|
|
167
|
+
const exact = message.fields && Object.values(message.fields).find(f => f.rule === 'required');
|
|
168
|
+
types.push({
|
|
169
|
+
type: 'message',
|
|
170
|
+
name: messageName,
|
|
171
|
+
value: value.join('\n'),
|
|
172
|
+
exact,
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// top level messages and nested messages
|
|
177
|
+
Object.keys(json.nested).map(e => parseMessage(e, json.nested[e]));
|
|
178
|
+
|
|
179
|
+
// types needs reordering (used before defined)
|
|
180
|
+
const ORDER = {
|
|
181
|
+
BinanceCoin: 'BinanceInputOutput',
|
|
182
|
+
HDNodeType: 'HDNodePathType',
|
|
183
|
+
CardanoAssetGroupType: 'CardanoTxOutputType',
|
|
184
|
+
CardanoTokenType: 'CardanoAssetGroupType',
|
|
185
|
+
TxAck: 'TxAckInputWrapper',
|
|
186
|
+
EthereumFieldType: 'EthereumStructMember',
|
|
187
|
+
EthereumDataType: 'EthereumFieldType',
|
|
188
|
+
PaymentRequestMemo: 'TxAckPaymentRequest',
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
Object.keys(ORDER).forEach(key => {
|
|
192
|
+
// find indexes
|
|
193
|
+
const indexA = types.findIndex(t => t && t.name === key);
|
|
194
|
+
const indexB = types.findIndex(t => t && t.name === ORDER[key]);
|
|
195
|
+
const prevA = types[indexA];
|
|
196
|
+
// replace values
|
|
197
|
+
delete types[indexA];
|
|
198
|
+
types.splice(indexB, 0, prevA);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// skip not needed types
|
|
202
|
+
SKIP.forEach(key => {
|
|
203
|
+
const index = types.findIndex(t => t && t.name === key);
|
|
204
|
+
delete types[index];
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// create content from types
|
|
208
|
+
const content = types.flatMap(t => (t ? [t.value] : [])).join('\n');
|
|
209
|
+
|
|
210
|
+
const lines = []; // string[]
|
|
211
|
+
if (!isTypescript) lines.push('// @flow');
|
|
212
|
+
lines.push('// This file is auto generated from data/messages/message.json');
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push('// custom type uint32/64 may be represented as string');
|
|
215
|
+
lines.push(`export type ${UINT_TYPE} = string | number;`);
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push(content);
|
|
218
|
+
|
|
219
|
+
// create custom definition
|
|
220
|
+
if (!isTypescript) {
|
|
221
|
+
lines.push('// custom connect definitions');
|
|
222
|
+
lines.push('export type MessageType = {');
|
|
223
|
+
types
|
|
224
|
+
.flatMap(t => (t && t.type === 'message' ? [t] : []))
|
|
225
|
+
.forEach(t => {
|
|
226
|
+
if (t.exact) {
|
|
227
|
+
lines.push(` ${t.name}: $Exact<${t.name}>;`);
|
|
228
|
+
} else {
|
|
229
|
+
lines.push(` ${t.name}: ${t.name};`);
|
|
230
|
+
}
|
|
231
|
+
// lines.push(' ' + t.name + ': $Exact<' + t.name + '>;');
|
|
232
|
+
});
|
|
233
|
+
lines.push('};');
|
|
234
|
+
|
|
235
|
+
// additional types utilities
|
|
236
|
+
lines.push(`
|
|
237
|
+
export type MessageKey = $Keys<MessageType>;
|
|
238
|
+
|
|
239
|
+
export type MessageResponse<T: MessageKey> = {
|
|
240
|
+
type: T;
|
|
241
|
+
message: $ElementType<MessageType, T>;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export type TypedCall = <T: MessageKey, R: MessageKey>(
|
|
245
|
+
type: T,
|
|
246
|
+
resType: R,
|
|
247
|
+
message?: $ElementType<MessageType, T>
|
|
248
|
+
) => Promise<MessageResponse<R>>;
|
|
249
|
+
`);
|
|
250
|
+
} else {
|
|
251
|
+
lines.push('// custom connect definitions');
|
|
252
|
+
lines.push('export type MessageType = {');
|
|
253
|
+
types
|
|
254
|
+
.flatMap(t => (t && t.type === 'message' ? [t] : []))
|
|
255
|
+
.forEach(t => {
|
|
256
|
+
lines.push(` ${t.name}: ${t.name};`);
|
|
257
|
+
});
|
|
258
|
+
lines.push('};');
|
|
259
|
+
|
|
260
|
+
// additional types utilities
|
|
261
|
+
lines.push(`
|
|
262
|
+
export type MessageKey = keyof MessageType;
|
|
263
|
+
|
|
264
|
+
export type MessageResponse<T extends MessageKey> = {
|
|
265
|
+
type: T;
|
|
266
|
+
message: MessageType[T];
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export type TypedCall = <T extends MessageKey, R extends MessageKey>(
|
|
270
|
+
type: T,
|
|
271
|
+
resType: R,
|
|
272
|
+
message?: MessageType[T],
|
|
273
|
+
) => Promise<MessageResponse<R>>;
|
|
274
|
+
`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// save to file
|
|
278
|
+
const filePath = isTypescript
|
|
279
|
+
? path.join(__dirname, '../src/types/messages.ts')
|
|
280
|
+
: path.join(__dirname, '../protobuf.js');
|
|
281
|
+
fs.writeFile(filePath, lines.join('\n'), err => {
|
|
282
|
+
if (err) return console.log(err);
|
|
283
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Logic of sending data to trezor
|
|
2
2
|
//
|
|
3
3
|
// Logic of "call" is broken to two parts - sending and receiving
|
|
4
|
-
import ByteBuffer from 'bytebuffer';
|
|
5
4
|
import { Root } from 'protobufjs/light';
|
|
5
|
+
import ByteBuffer from 'bytebuffer';
|
|
6
6
|
import { encode as encodeProtobuf } from './protobuf';
|
|
7
7
|
import { encode as encodeProtocol } from './protocol';
|
|
8
8
|
import { createMessageFromName } from './protobuf/messages';
|