koilib 5.5.2 → 5.5.4

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.
@@ -0,0 +1,366 @@
1
+ /* eslint-disable @typescript-eslint/require-await */
2
+ import { Root, Type, INamespace, parse } from "protobufjs";
3
+ import * as koinosPbToProto from "@roamin/koinos-pb-to-proto";
4
+ import { TypeField } from "./interface";
5
+ import {
6
+ btypeDecodeValue,
7
+ btypeEncodeValue,
8
+ decodeBase64url,
9
+ decodeBase64,
10
+ } from "./utils";
11
+
12
+ const OP_BYTES_1 = "(btype)";
13
+ const OP_BYTES_2 = "(koinos.btype)";
14
+
15
+ const nativeTypes = [
16
+ "double",
17
+ "float",
18
+ "int32",
19
+ "int64",
20
+ "uint32",
21
+ "uint64",
22
+ "sint32",
23
+ "sint64",
24
+ "fixed32",
25
+ "fixed64",
26
+ "sfixed32",
27
+ "sfixed64",
28
+ "bool",
29
+ "string",
30
+ "bytes",
31
+ ];
32
+
33
+ /**
34
+ * The serializer class serialize and deserialize data using
35
+ * protocol buffers. It accepts the descriptor in JSON or binary format
36
+ *
37
+ * NOTE: This class uses the [protobufjs](https://www.npmjs.com/package/protobufjs)
38
+ * library internally, which uses reflection (use of _eval_
39
+ * and _new Function_) for the construction of the types.
40
+ * This could cause issues in environments where _eval_ is not
41
+ * allowed, like in browser extensions. In such cases, this class
42
+ * must be confined in a [sandbox environment](https://developer.chrome.com/docs/apps/app_external/#sandboxing)
43
+ * where _eval_ is allowed. This is the principal reason of
44
+ * having the serializer in a separate class.
45
+ *
46
+ * @example
47
+ *
48
+ * ```ts
49
+ * // using descriptor JSON
50
+ * const descriptorJson = {
51
+ * nested: {
52
+ * awesomepackage: {
53
+ * nested: {
54
+ * AwesomeMessage: {
55
+ * fields: {
56
+ * awesome_field: {
57
+ * type: "string",
58
+ * id: 1,
59
+ * },
60
+ * },
61
+ * },
62
+ * },
63
+ * },
64
+ * },
65
+ * };
66
+ * const serializer1 = new Serializer(descriptorJson);
67
+ * const message1 = await serializer1.deserialize(
68
+ * "CgZrb2lub3M=",
69
+ * "AwesomeMessage"
70
+ * );
71
+ * console.log(message1);
72
+ * // { awesome_field: 'koinos' }
73
+ *
74
+ * // using descriptor binary
75
+ * const descriptorBinary =
76
+ * "Cl4KDWF3ZXNvbWUucHJvdG8SDmF3ZXNvbWVwYWN" +
77
+ * "rYWdlIjUKDkF3ZXNvbWVNZXNzYWdlEiMKDWF3ZX" +
78
+ * "NvbWVfZmllbGQYASABKAlSDGF3ZXNvbWVGaWVsZ" +
79
+ * "GIGcHJvdG8z";
80
+ * const serializer2 = new Serializer(descriptorBinary);
81
+ * const message2 = await serializer2.deserialize(
82
+ * "CgZrb2lub3M=",
83
+ * "AwesomeMessage"
84
+ * );
85
+ * console.log(message2);
86
+ * // { awesome_field: 'koinos' }
87
+ * ```
88
+ */
89
+ export class Serializer {
90
+ /**
91
+ * Protobuffers descriptor in JSON format.
92
+ * See https://www.npmjs.com/package/protobufjs#using-json-descriptors
93
+ */
94
+ types: INamespace | string;
95
+
96
+ /**
97
+ * Protobuffer definitions
98
+ */
99
+ root: Root;
100
+
101
+ /**
102
+ * Default type for all serializations
103
+ */
104
+ defaultType?: Type;
105
+
106
+ /**
107
+ * Preformat bytes for base64url, base58 or hex string
108
+ */
109
+ bytesConversion = true;
110
+
111
+ /**
112
+ * Verify checksum in addresses during serialization
113
+ * or deserialization
114
+ */
115
+ verifyChecksum = {
116
+ serialize: true,
117
+ deserialize: false,
118
+ };
119
+
120
+ constructor(
121
+ types: INamespace | string,
122
+ opts?: {
123
+ /**
124
+ * Default type name. Use this option when you
125
+ * always want to serialize/deserialize the same type
126
+ */
127
+ defaultTypeName?: string;
128
+
129
+ /**
130
+ * Bytes conversion. Option to preformat bytes
131
+ * when "(koinos_bytes_type)" is defined in the type
132
+ * definitions. By default it is true.
133
+ */
134
+ bytesConversion?: boolean;
135
+ }
136
+ ) {
137
+ this.types = types;
138
+ if (typeof types === "string") {
139
+ const protos = koinosPbToProto.convert(decodeBase64(types) as Buffer);
140
+ this.root = new Root();
141
+ for (const proto of protos) {
142
+ parse(proto.definition, this.root, { keepCase: true });
143
+ }
144
+ } else {
145
+ this.root = Root.fromJSON(types);
146
+ }
147
+ if (opts?.defaultTypeName)
148
+ this.defaultType = this.root.lookupType(opts.defaultTypeName);
149
+ if (opts && typeof opts.bytesConversion !== "undefined")
150
+ this.bytesConversion = opts.bytesConversion;
151
+ }
152
+
153
+ btypeDecode(
154
+ valueBtypeEncoded: Record<string, unknown> | unknown[],
155
+ protobufType: Type,
156
+ verifyChecksum: boolean
157
+ ) {
158
+ const valueBtypeDecoded = {} as Record<string, unknown>;
159
+ Object.keys(protobufType.fields).forEach((fieldName) => {
160
+ // @ts-ignore
161
+ const { options, name, type, rule } = protobufType.fields[fieldName];
162
+ if (!valueBtypeEncoded[name]) return;
163
+
164
+ const typeField: TypeField = { type };
165
+ if (options) {
166
+ if (options[OP_BYTES_1])
167
+ typeField.btype = options[OP_BYTES_1] as string;
168
+ else if (options[OP_BYTES_2])
169
+ typeField.btype = options[OP_BYTES_2] as string;
170
+ }
171
+
172
+ // arrays
173
+ if (rule === "repeated") {
174
+ valueBtypeDecoded[name] = (valueBtypeEncoded[name] as unknown[]).map(
175
+ (itemEncoded) => {
176
+ // custom objects
177
+ if (!nativeTypes.includes(type)) {
178
+ const protoBuf = this.root.lookupTypeOrEnum(type);
179
+ if (!protoBuf.fields) {
180
+ // it's an enum
181
+ return itemEncoded;
182
+ }
183
+ return this.btypeDecode(
184
+ itemEncoded as Record<string, unknown>,
185
+ protoBuf,
186
+ verifyChecksum
187
+ );
188
+ }
189
+ // native types
190
+ return btypeDecodeValue(itemEncoded, typeField, verifyChecksum);
191
+ }
192
+ );
193
+ return;
194
+ }
195
+
196
+ // custom objects
197
+ if (!nativeTypes.includes(type)) {
198
+ const protoBuf = this.root.lookupTypeOrEnum(type);
199
+ if (!protoBuf.fields) {
200
+ // it's an enum
201
+ valueBtypeDecoded[name] = valueBtypeEncoded[name];
202
+ return;
203
+ }
204
+ valueBtypeDecoded[name] = this.btypeDecode(
205
+ valueBtypeEncoded[name] as Record<string, unknown>,
206
+ protoBuf,
207
+ verifyChecksum
208
+ );
209
+ return;
210
+ }
211
+
212
+ // native types
213
+ valueBtypeDecoded[name] = btypeDecodeValue(
214
+ valueBtypeEncoded[name],
215
+ typeField,
216
+ verifyChecksum
217
+ );
218
+ });
219
+
220
+ return valueBtypeDecoded;
221
+ }
222
+
223
+ btypeEncode(
224
+ valueBtypeDecoded: Record<string, unknown> | unknown[],
225
+ protobufType: Type,
226
+ verifyChecksum: boolean
227
+ ) {
228
+ const valueBtypeEncoded = {} as Record<string, unknown>;
229
+ Object.keys(protobufType.fields).forEach((fieldName) => {
230
+ // @ts-ignore
231
+ const { options, name, type, rule } = protobufType.fields[fieldName];
232
+ if (!valueBtypeDecoded[name]) return;
233
+
234
+ const typeField: TypeField = { type };
235
+ if (options) {
236
+ if (options[OP_BYTES_1])
237
+ typeField.btype = options[OP_BYTES_1] as string;
238
+ else if (options[OP_BYTES_2])
239
+ typeField.btype = options[OP_BYTES_2] as string;
240
+ }
241
+
242
+ // arrays
243
+ if (rule === "repeated") {
244
+ valueBtypeEncoded[name] = (valueBtypeDecoded[name] as unknown[]).map(
245
+ (itemDecoded) => {
246
+ // custom objects
247
+ if (!nativeTypes.includes(type)) {
248
+ const protoBuf = this.root.lookupTypeOrEnum(type);
249
+ if (!protoBuf.fields) {
250
+ // it's an enum
251
+ return itemDecoded;
252
+ }
253
+ return this.btypeEncode(
254
+ itemDecoded as Record<string, unknown>,
255
+ protoBuf,
256
+ verifyChecksum
257
+ );
258
+ }
259
+ // native types
260
+ return btypeEncodeValue(itemDecoded, typeField, verifyChecksum);
261
+ }
262
+ );
263
+ return;
264
+ }
265
+
266
+ // custom objects
267
+ if (!nativeTypes.includes(type)) {
268
+ const protoBuf = this.root.lookupTypeOrEnum(type);
269
+ if (!protoBuf.fields) {
270
+ // it's an enum
271
+ valueBtypeEncoded[name] = valueBtypeDecoded[name];
272
+ return;
273
+ }
274
+ valueBtypeEncoded[name] = this.btypeEncode(
275
+ valueBtypeDecoded[name] as Record<string, unknown>,
276
+ protoBuf,
277
+ verifyChecksum
278
+ );
279
+ return;
280
+ }
281
+
282
+ // native types
283
+ valueBtypeEncoded[name] = btypeEncodeValue(
284
+ valueBtypeDecoded[name],
285
+ typeField,
286
+ verifyChecksum
287
+ );
288
+ });
289
+
290
+ return valueBtypeEncoded;
291
+ }
292
+
293
+ /**
294
+ * Function to encode a type using the protobuffer definitions
295
+ * It also prepares the bytes for special cases (base58, hex string)
296
+ * when bytesConversion param is true.
297
+ */
298
+ async serialize(
299
+ valueDecoded: Record<string, unknown>,
300
+ typeName?: string,
301
+ opts?: { bytesConversion?: boolean; verifyChecksum?: boolean }
302
+ ): Promise<Uint8Array> {
303
+ let protobufType: Type;
304
+ if (this.defaultType) protobufType = this.defaultType;
305
+ else if (!typeName) throw new Error("no typeName defined");
306
+ else protobufType = this.root.lookupType(typeName);
307
+ let object: Record<string, unknown> = {};
308
+ const bytesConversion =
309
+ opts?.bytesConversion === undefined
310
+ ? this.bytesConversion
311
+ : opts.bytesConversion;
312
+ const verifyChecksum =
313
+ opts?.verifyChecksum === undefined
314
+ ? this.verifyChecksum.serialize
315
+ : opts.verifyChecksum;
316
+ if (bytesConversion) {
317
+ object = this.btypeDecode(valueDecoded, protobufType, verifyChecksum);
318
+ } else {
319
+ object = valueDecoded;
320
+ }
321
+
322
+ const message = protobufType.create(object);
323
+
324
+ const buffer = protobufType.encode(message).finish();
325
+ return buffer;
326
+ }
327
+
328
+ /**
329
+ * Function to decode bytes using the protobuffer definitions
330
+ * It also encodes the bytes for special cases (base58, hex string)
331
+ * when bytesConversion param is true.
332
+ */
333
+ async deserialize<T = Record<string, unknown>>(
334
+ valueEncoded: string | Uint8Array,
335
+ typeName?: string,
336
+ opts?: { bytesConversion?: boolean; verifyChecksum?: boolean }
337
+ ): Promise<T> {
338
+ const valueBuffer =
339
+ typeof valueEncoded === "string"
340
+ ? decodeBase64url(valueEncoded)
341
+ : valueEncoded;
342
+ let protobufType: Type;
343
+ if (this.defaultType) protobufType = this.defaultType;
344
+ else if (!typeName) throw new Error("no typeName defined");
345
+ else protobufType = this.root.lookupType(typeName);
346
+ const message = protobufType.decode(valueBuffer);
347
+ const object = protobufType.toObject(message, {
348
+ longs: String,
349
+ defaults: true,
350
+ });
351
+ const bytesConversion =
352
+ opts?.bytesConversion === undefined
353
+ ? this.bytesConversion
354
+ : opts.bytesConversion;
355
+ const verifyChecksum =
356
+ opts?.verifyChecksum === undefined
357
+ ? this.verifyChecksum.deserialize
358
+ : opts.verifyChecksum;
359
+ if (bytesConversion) {
360
+ return this.btypeEncode(object, protobufType, verifyChecksum) as T;
361
+ }
362
+ return object as T;
363
+ }
364
+ }
365
+
366
+ export default Serializer;