@ocap/message 1.28.8 → 1.29.0

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/lib/message.js DELETED
@@ -1,605 +0,0 @@
1
- /**
2
- * @fileOverview Contains basic helper methods to encode/format/mock a protobuf message
3
- * @module @ocap/message
4
- * @requires @ocap/util
5
- * @requires @ocap/proto
6
- * @example
7
- * bun add @ocap/message
8
- *
9
- * const { createMessage, fakeMessage, formatMessage } = require('@ocap/message');
10
- */
11
-
12
- /* eslint no-console:"off" */
13
- const camelCase = require('lodash/camelCase');
14
- const jspb = require('google-protobuf');
15
- const { Any } = require('google-protobuf/google/protobuf/any_pb');
16
- const { Timestamp } = require('google-protobuf/google/protobuf/timestamp_pb');
17
- const { toBN, bytesToHex, isUint8Array, toUint8Array } = require('@ocap/util');
18
- const { enums, messages, getMessageType, toTypeUrl, fromTypeUrl, addProvider } = require('./provider');
19
- const debug = require('debug')(`${require('../package.json').name}`);
20
-
21
- const enumTypes = Object.keys(enums);
22
-
23
- const scalarTypes = [
24
- 'bool',
25
- 'bytes',
26
- 'string',
27
- 'double',
28
- 'float',
29
- 'sint32',
30
- 'uint32',
31
- 'sfixed32',
32
- 'sint64',
33
- 'uint64',
34
- 'sfixed64',
35
- ];
36
-
37
- // Utility map to generate random data when compose fake message
38
- const fakeValues = {
39
- bool: true,
40
- sint32: 1,
41
- uint32: 2,
42
- sfixed32: 3,
43
-
44
- sint64: 4,
45
- uint64: 5,
46
- sfixed64: 6,
47
-
48
- BigUint: () => '1234',
49
- BigSint: () => '4567',
50
-
51
- float: '12.2',
52
- double: '12.3',
53
-
54
- string: 'arcblock',
55
- bytes: new Uint8Array(),
56
- enums: (type) => Object.values(enums[type])[0],
57
- };
58
-
59
- /**
60
- * Generated a fake message for a type, the message can be RPC request/response
61
- *
62
- * @public
63
- * @static
64
- * @param {string} type - Message type string, should be defined in forge-abi or forge-core-protocol
65
- * @returns {object}
66
- * @example
67
- * const { fakeMessage} = require('@ocap/message');
68
- * const message = fakeMessage('CreateAssetTx');
69
- * // will output
70
- * {
71
- * moniker: 'arcblock',
72
- * data: { type: 'string', value: 'ABCD 1234' },
73
- * readonly: true,
74
- * transferrable: true,
75
- * ttl: 2,
76
- * parent: 'arcblock',
77
- * address: 'F2D072CBD4954A20F26280730795D91AC1039996CEB6E24A31E9CE548DCB5E55',
78
- * }
79
- */
80
- function fakeMessage(type) {
81
- if (!type) {
82
- return;
83
- }
84
-
85
- if (fakeValues[type]) {
86
- return fakeValues[type];
87
- }
88
-
89
- const { fields, oneofs } = getMessageType(type);
90
- if (!fields) {
91
- return;
92
- }
93
-
94
- let selectedFields = fields;
95
- if (oneofs && oneofs.value && Array.isArray(oneofs.value.oneof)) {
96
- const selectedField = oneofs.value.oneof[0];
97
- selectedFields = { [selectedField]: fields[selectedField] };
98
- }
99
-
100
- const result = {};
101
- Object.keys(selectedFields).forEach((key) => {
102
- const { type: subType, keyType, rule } = selectedFields[key];
103
- if (rule === 'repeated') {
104
- result[key] = [1, 2].map(() => fakeMessage(subType));
105
- return;
106
- }
107
- if (keyType) {
108
- result[key] = {
109
- [fakeValues[keyType]]: fakeMessage(subType),
110
- };
111
- return;
112
- }
113
-
114
- if (enumTypes.includes(subType)) {
115
- result[key] = fakeValues.enums(subType);
116
- return;
117
- }
118
-
119
- if (['hash', 'appHash', 'txHash', 'address', 'from', 'to', 'proposer'].includes(key)) {
120
- result[key] = 'F2D072CBD4954A20F26280730795D91AC1039996CEB6E24A31E9CE548DCB5E55';
121
- return;
122
- }
123
-
124
- if (fakeValues[subType]) {
125
- result[key] = fakeValues[subType];
126
- }
127
-
128
- if (subType === 'google.protobuf.Timestamp') {
129
- result[key] = new Date().toISOString();
130
- return;
131
- }
132
-
133
- if (subType === 'google.protobuf.Any') {
134
- result[key] = {
135
- type: 'string',
136
- value: 'ABCD 1234',
137
- };
138
- return;
139
- }
140
-
141
- // Other complex types
142
- result[key] = fakeMessage(subType);
143
- });
144
-
145
- return result;
146
- }
147
-
148
- /**
149
- * Format an message from RPC to UI friendly
150
- *
151
- * @public
152
- * @static
153
- * @param {string} type - input type
154
- * @param {object} data - input data
155
- * @returns {object} [almost same structure as input]
156
- */
157
- function formatMessage(type, data) {
158
- if (!type) {
159
- return data;
160
- }
161
-
162
- if (['json', 'vc'].includes(type)) {
163
- return data;
164
- }
165
-
166
- if (typeof data !== 'object') {
167
- return data;
168
- }
169
-
170
- if (scalarTypes.includes(type)) {
171
- return data;
172
- }
173
-
174
- const result = {};
175
- const { fields } = getMessageType(type);
176
- if (!fields) {
177
- console.log({ type, data });
178
- throw new Error(`Cannot get fields for type ${type}`);
179
- }
180
-
181
- Object.keys(fields).forEach((key) => {
182
- const { type: subType, keyType, rule } = fields[key];
183
- let value = data[key];
184
-
185
- // list
186
- if (rule === 'repeated') {
187
- value = data[camelCase(`${key}_list`)] || data[key];
188
- }
189
-
190
- // map
191
- if (keyType) {
192
- value = data[camelCase(`${key}_map`)] || data[key];
193
- }
194
- if (value === undefined) {
195
- return;
196
- }
197
-
198
- if (rule === 'repeated') {
199
- result[key] = value.map((x) => formatMessage(subType, x));
200
- return;
201
- }
202
-
203
- if (keyType) {
204
- debug('formatMessage.map', { type, subType, keyType, value });
205
- result[key] = (value || []).reduce((acc, [k, v]) => {
206
- acc[k] = formatMessage(subType, v);
207
- return acc;
208
- }, {});
209
- return;
210
- }
211
-
212
- if (enumTypes.includes(subType)) {
213
- result[key] = messages[subType][value];
214
- return;
215
- }
216
-
217
- if (['BigUint', 'BigSint'].includes(subType)) {
218
- result[key] = decodeBigInt(value);
219
- return;
220
- }
221
-
222
- if (isUint8Array(value)) {
223
- if (['appHash', 'blockHash'].includes(key)) {
224
- result[key] = Buffer.from(value).toString('hex');
225
- }
226
- if (['signature', 'pk', 'sk'].includes(key)) {
227
- result[key] = Buffer.from(value).toString('base64');
228
- }
229
- return;
230
- }
231
-
232
- if (subType === 'google.protobuf.Timestamp') {
233
- result[key] = decodeTimestamp(value);
234
- return;
235
- }
236
-
237
- if (subType === 'google.protobuf.Any') {
238
- if (value) {
239
- const decoded = decodeAny(value);
240
- result[key] = {
241
- type: decoded.type || value.typeUrl,
242
- value: formatMessage(decoded.type || value.typeUrl, decoded.value),
243
- };
244
- }
245
- return;
246
- }
247
-
248
- if (value && typeof value === 'object') {
249
- result[key] = formatMessage(subType, value);
250
- return;
251
- }
252
-
253
- result[key] = value;
254
- });
255
-
256
- return result;
257
- }
258
-
259
- /**
260
- * Create an protobuf encoded Typed message with specified data, ready to send to rpc server
261
- *
262
- * @public
263
- * @static
264
- * @param {string} type - message type defined in forge-proto
265
- * @param {object} params - message content
266
- * @returns {object} Message instance
267
- * @example
268
- * const { createMessage } = require('@ocap/message');
269
- * const message = createMessage ('CreateAssetTx', {
270
- * moniker: 'asset',
271
- * address: 'zaAKEJRKQWsdfjksdfjkASRD',
272
- * });
273
- *
274
- * message.getMoniker(); // 'asset'
275
- * message.getAddress(); // 'zaAKEJRKQWsdfjksdfjkASRD'
276
- * message.getReadonly(); // false
277
- * message.setReadonly(true);
278
- */
279
- function createMessage(type, params) {
280
- if (!type && !params) {
281
- console.log({ type, params });
282
- return;
283
- }
284
-
285
- const { fn: Message, fields } = getMessageType(type);
286
- if (!Message || !fields) {
287
- console.error({ type, params, fields, Message });
288
- throw new Error(`Unsupported messageType: ${type}`);
289
- }
290
-
291
- const message = new Message();
292
-
293
- // Fast return on empty data
294
- if (!params) {
295
- return message;
296
- }
297
-
298
- // Attach each field to message
299
- Object.keys(fields).forEach((key) => {
300
- const { type: subType, keyType, rule, id } = fields[key];
301
- // Hack: protobuf tools renamed list fields
302
- const value = params[key] || params[camelCase(`${key}_list`)];
303
- if (value === undefined) {
304
- return;
305
- }
306
-
307
- // map
308
- if (keyType) {
309
- const keys = Object.keys(value);
310
- if (keys.length) {
311
- const fn = camelCase(`get_${key}_map`);
312
- const map = message[fn]();
313
- debug('createMessage.map', { type, subType, keyType, id, fn, keys });
314
- keys.forEach((k) => {
315
- map.set(k, createMessage(subType, value[k]));
316
- });
317
-
318
- jspb.Message.setField(message, id, map);
319
- }
320
- return;
321
- }
322
-
323
- const fn = camelCase(rule === 'repeated' ? `add_${key}` : `set_${key}`);
324
- if (typeof message[fn] !== 'function') {
325
- throw new Error(`Unexpected field names ${JSON.stringify({ type, key, subType, fn, rule })}`);
326
- }
327
-
328
- const values = rule === 'repeated' ? value : [value];
329
- try {
330
- values.forEach((v) => {
331
- // enum types
332
- if (enumTypes.includes(subType)) {
333
- // debug('createMessage.Enum', { type, subType, key });
334
- message[fn](v);
335
- return;
336
- }
337
-
338
- if (['BigUint', 'BigSint'].includes(subType)) {
339
- message[fn](encodeBigInt(v, subType));
340
- return;
341
- }
342
-
343
- if (subType === 'google.protobuf.Timestamp') {
344
- debug(`createMessage.${subType}`, { v });
345
- message[fn](encodeTimestamp(v));
346
- return;
347
- }
348
-
349
- if (subType === 'google.protobuf.Any') {
350
- message[fn](encodeAny(v));
351
- return;
352
- }
353
-
354
- // Support different input types of `bytes`: Buffer, Uint8Array, Hex, Base58
355
- if (subType === 'bytes') {
356
- message[fn](toUint8Array(v));
357
- return;
358
- }
359
-
360
- const { fn: SubMessage, fields: subFields } = getMessageType(subType);
361
- // complex types
362
- if (SubMessage && subFields) {
363
- debug(`createMessage.${subType}`, { type, subType, key });
364
- const subMessage = createMessage(subType, v);
365
- message[fn](subMessage);
366
- return;
367
- }
368
-
369
- // primitive types
370
- message[fn](v);
371
- });
372
- } catch (err) {
373
- debug('createMessage.processField.error', { type, key, subType, fn, rule, values, err });
374
- throw err;
375
- }
376
- });
377
-
378
- return message;
379
- }
380
-
381
- /**
382
- * Decode an google.protobuf.Any%{ typeUrl, value } => { type, value }
383
- *
384
- * @public
385
- * @static
386
- * @param {object} data encoded data object
387
- * @returns {object} Object%{type, value}
388
- */
389
- function decodeAny(data) {
390
- if (!data) {
391
- return { type: 'Unknown', value: '' };
392
- }
393
-
394
- if (data.type && data.value) {
395
- return data;
396
- }
397
-
398
- const { typeUrl, value } = data;
399
- if (['json', 'vc'].includes(typeUrl)) {
400
- const decoded = JSON.parse(Buffer.from(value, 'base64'));
401
- return { type: typeUrl, value: decoded };
402
- }
403
-
404
- const type = fromTypeUrl(typeUrl);
405
- const { fn: Message } = getMessageType(type);
406
- if (!Message) {
407
- return data;
408
- }
409
-
410
- const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value, 'base64');
411
- const decoded = Message.deserializeBinary(buffer);
412
- return { type, value: decoded.toObject() };
413
- }
414
-
415
- /**
416
- * Encode { type, value } => google.protobuf.Any%{ typeUrl, value }
417
- * Does nothing on already encoded message
418
- *
419
- * @public
420
- * @static
421
- * @param {object} data
422
- * @returns {object} google.protobuf.Any
423
- */
424
- function encodeAny(data) {
425
- if (!data) {
426
- return;
427
- }
428
-
429
- const anyMessage = new Any();
430
- try {
431
- if (data.typeUrl && data.value && !data.type) {
432
- // avoid duplicate serialization
433
- anyMessage.setTypeUrl(data.typeUrl);
434
- if (data.typeUrl === 'fg:x:address') {
435
- anyMessage.setValue(data.value);
436
- } else if (['json', 'vc'].includes(data.typeUrl)) {
437
- anyMessage.setValue(new Uint8Array(Buffer.from(JSON.stringify(data.value))));
438
- } else {
439
- anyMessage.setValue(new Uint8Array(Buffer.from(data.value, 'base64')));
440
- }
441
- } else {
442
- const { value: anyValue, type: anyType } = data;
443
- const typeUrl = toTypeUrl(anyType);
444
- anyMessage.setTypeUrl(typeUrl);
445
- if (['json', 'vc'].includes(typeUrl)) {
446
- anyMessage.setValue(new Uint8Array(Buffer.from(JSON.stringify(anyValue))));
447
- } else {
448
- const anyValueBinary = createMessage(anyType, anyValue);
449
- anyMessage.setValue(anyValueBinary.serializeBinary());
450
- }
451
- }
452
- } catch (err) {
453
- console.error('error encode any type', data);
454
- throw err;
455
- }
456
-
457
- return anyMessage;
458
- }
459
-
460
- /**
461
- * Convert an { seconds, nanos } | date-string to google.protobuf.Timestamp object
462
- *
463
- * @public
464
- * @static
465
- * @param {string|object} value
466
- * @returns {object} instanceof google.protobuf.Timestamp
467
- */
468
- function encodeTimestamp(value) {
469
- if (!value) {
470
- return;
471
- }
472
-
473
- const timestamp = new Timestamp();
474
- if (typeof value === 'string') {
475
- const millionSeconds = Date.parse(value);
476
- if (isNaN(millionSeconds) === false) {
477
- timestamp.setSeconds(Math.floor(millionSeconds / 1e3));
478
- timestamp.setNanos(Math.floor((millionSeconds % 1e3) * 1e6));
479
- }
480
- } else {
481
- const { seconds, nanos = 0 } = value;
482
- timestamp.setSeconds(seconds);
483
- timestamp.setNanos(nanos);
484
- }
485
-
486
- return timestamp;
487
- }
488
-
489
- /**
490
- * Decode google.protobuf.Timestamp message to ISO Date String
491
- *
492
- * FIXME: node strictly equal because we rounded the `nanos` field
493
- *
494
- * @public
495
- * @static
496
- * @param {object} data
497
- * @returns {strong} String timestamp
498
- */
499
- function decodeTimestamp(data) {
500
- if (data && data.seconds) {
501
- const date = new Date();
502
- date.setTime(data.seconds * 1e3 + Math.ceil(data.nanos / 1e6));
503
- return date.toISOString();
504
- }
505
-
506
- return '';
507
- }
508
-
509
- /**
510
- * Encode BigUint and BigSint types defined in forge-sdk, double encoding is avoided
511
- *
512
- * @public
513
- * @static
514
- * @param {buffer|string|number} value - value to encode
515
- * @param {string} type - type names defined in forge-proto
516
- * @returns {object} Message
517
- */
518
- function encodeBigInt(value, type) {
519
- const { fn: BigInt } = getMessageType(type);
520
- const message = new BigInt();
521
- if (value && value.value && isUint8Array(value.value)) {
522
- message.setValue(value.value);
523
- if (type === 'BigSint') {
524
- message.setMinus(value.minus);
525
- }
526
- return message;
527
- }
528
-
529
- const number = toBN(value);
530
- const zero = toBN(0);
531
- message.setValue(new Uint8Array(number.toArray()));
532
- if (type === 'BigSint') {
533
- message.setMinus(number.lt(zero));
534
- }
535
-
536
- return message;
537
- }
538
-
539
- /**
540
- * Convert BigUint and BigSint to string representation of numbers
541
- *
542
- * @public
543
- * @static
544
- * @link https://stackoverflow.com/questions/23948278/how-to-convert-byte-array-into-a-signed-big-integer-in-javascript
545
- * @param {object} data - usually from encodeBigInt
546
- * @param {buffer} data.value
547
- * @param {boolean} data.minus
548
- * @returns {string} human readable number
549
- */
550
- function decodeBigInt(data) {
551
- const bn = toBN(bytesToHex(data.value));
552
- const symbol = data.minus ? '-' : '';
553
- return `${symbol}${bn.toString(10)}`;
554
- }
555
-
556
- /**
557
- * Attach an $format method to rpc response
558
- *
559
- * @private
560
- * @param {object} data
561
- * @param {string} type
562
- */
563
- function attachFormatFn(type, data, key = '$format') {
564
- Object.defineProperty(data, key, {
565
- writable: false,
566
- enumerable: false,
567
- configurable: false,
568
- value: () => formatMessage(type, data),
569
- });
570
- }
571
-
572
- /**
573
- * Attach an example method to
574
- *
575
- * @private
576
- * @param {object} data
577
- * @param {string} type
578
- */
579
- function attachExampleFn(type, host, key) {
580
- Object.defineProperty(host, key, {
581
- writable: false,
582
- enumerable: false,
583
- configurable: false,
584
- value: () => fakeMessage(type),
585
- });
586
- }
587
-
588
- module.exports = {
589
- formatMessage,
590
- createMessage,
591
- fakeMessage,
592
- decodeAny,
593
- encodeAny,
594
- encodeTimestamp,
595
- decodeTimestamp,
596
- encodeBigInt,
597
- decodeBigInt,
598
- attachFormatFn,
599
- attachExampleFn,
600
-
601
- addProvider,
602
- getMessageType,
603
- toTypeUrl,
604
- fromTypeUrl,
605
- };
package/lib/patch.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};
package/lib/patch.js DELETED
@@ -1,57 +0,0 @@
1
- /**
2
- * This patch fixes an issue in `google-protobuf` package
3
- * After this patch is applied: The deserialize of `map` fields can accept empty values
4
- */
5
- const assert = require('assert');
6
- const jspb = require('google-protobuf');
7
- const debug = require('debug')(`${require('../package.json').name}:Map`);
8
-
9
- // FIXME: remove this patch when `google-protobuf` is enable to handle this
10
- // @link https://github.com/protocolbuffers/protobuf/blob/46a48e49aa/js/map.js#L469
11
- if (typeof jspb.Map.deserializeBinary === 'function' && !jspb.Map.deserializeBinary.__patched__) {
12
- jspb.Map.deserializeBinary = (
13
- map,
14
- reader,
15
- keyReaderFn,
16
- valueReaderFn,
17
- opt_valueReaderCallback,
18
- opt_defaultKey = 0
19
- ) => {
20
- let key = opt_defaultKey;
21
- let value;
22
-
23
- while (reader.nextField()) {
24
- if (reader.isEndGroup()) {
25
- break;
26
- }
27
- const field = reader.getFieldNumber();
28
- debug('deserializeBinary.field', field);
29
-
30
- if (field === 1) {
31
- // Key.
32
- key = keyReaderFn.call(reader);
33
- debug('deserializeBinary.key', key);
34
- } else if (field === 2) {
35
- debug('deserializeBinary.map', map);
36
- // Value.
37
- if (map.valueCtor_) {
38
- assert(opt_valueReaderCallback, 'opt_valueReaderCallback');
39
- value = new map.valueCtor_();
40
- valueReaderFn.call(reader, value, opt_valueReaderCallback);
41
- } else {
42
- value = /** @type {function(this:jspb.BinaryReader):?} */ (valueReaderFn).call(reader);
43
- }
44
- debug('deserializeBinary.value', typeof value.toObject === 'function' ? value.toObject() : value);
45
- }
46
- }
47
-
48
- // HACK: these assertions are disabled
49
- // assert(key !== undefined, 'key is undefined');
50
- // assert(value !== undefined, 'value is undefined');
51
- if (typeof key !== 'undefined' && typeof value !== 'undefined') {
52
- map.set(key, value);
53
- }
54
- };
55
-
56
- jspb.Map.deserializeBinary.__patched__ = true;
57
- }
package/lib/provider.d.ts DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * Add type provider that can be used to format/create messages
3
- *
4
- * @param {object} provider - proto generated from {@see @ocap/proto}
5
- */
6
- export function addProvider(provider: object): void;
7
- export function resetProviders(): void;
8
- export const enums: {};
9
- export const messages: {};
10
- export function getMessageType(type: any): any;
11
- export function toTypeUrl(type: any): any;
12
- export function fromTypeUrl(url: any): any;