hive-stream 2.0.5 → 3.0.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/.claude/settings.local.json +12 -0
- package/.env.example +2 -2
- package/.travis.yml +11 -11
- package/CHANGELOG.md +166 -0
- package/CLAUDE.md +75 -0
- package/LICENSE +21 -21
- package/README.md +338 -238
- package/dist/actions.d.ts +41 -10
- package/dist/actions.js +126 -23
- package/dist/actions.js.map +1 -1
- package/dist/adapters/base.adapter.d.ts +25 -25
- package/dist/adapters/base.adapter.js +63 -49
- package/dist/adapters/base.adapter.js.map +1 -1
- package/dist/adapters/mongodb.adapter.d.ts +50 -37
- package/dist/adapters/mongodb.adapter.js +363 -158
- package/dist/adapters/mongodb.adapter.js.map +1 -1
- package/dist/adapters/postgresql.adapter.d.ts +49 -0
- package/dist/adapters/postgresql.adapter.js +507 -0
- package/dist/adapters/postgresql.adapter.js.map +1 -0
- package/dist/adapters/sqlite.adapter.d.ts +40 -41
- package/dist/adapters/sqlite.adapter.js +470 -397
- package/dist/adapters/sqlite.adapter.js.map +1 -1
- package/dist/api.d.ts +6 -6
- package/dist/api.js +95 -55
- package/dist/api.js.map +1 -1
- package/dist/config.d.ts +16 -16
- package/dist/config.js +18 -18
- package/dist/config.js.map +1 -1
- package/dist/contracts/coinflip.contract.d.ts +27 -14
- package/dist/contracts/coinflip.contract.js +253 -94
- package/dist/contracts/coinflip.contract.js.map +1 -1
- package/dist/contracts/dice.contract.d.ts +37 -29
- package/dist/contracts/dice.contract.js +282 -155
- package/dist/contracts/dice.contract.js.map +1 -1
- package/dist/contracts/lotto.contract.d.ts +20 -20
- package/dist/contracts/lotto.contract.js +246 -246
- package/dist/contracts/nft.contract.d.ts +24 -0
- package/dist/contracts/nft.contract.js +533 -0
- package/dist/contracts/nft.contract.js.map +1 -0
- package/dist/contracts/token.contract.d.ts +18 -0
- package/dist/contracts/token.contract.js +263 -0
- package/dist/contracts/token.contract.js.map +1 -0
- package/dist/exchanges/bittrex.d.ts +6 -6
- package/dist/exchanges/bittrex.js +34 -34
- package/dist/exchanges/coingecko.d.ts +5 -0
- package/dist/exchanges/coingecko.js +40 -0
- package/dist/exchanges/coingecko.js.map +1 -0
- package/dist/exchanges/exchange.d.ts +9 -9
- package/dist/exchanges/exchange.js +26 -26
- package/dist/hive-rates.d.ts +9 -9
- package/dist/hive-rates.js +121 -75
- package/dist/hive-rates.js.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.js +33 -32
- package/dist/index.js.map +1 -1
- package/dist/streamer.d.ts +140 -93
- package/dist/streamer.js +793 -545
- package/dist/streamer.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/dist/test.js +25 -25
- package/dist/test.js.map +1 -1
- package/dist/types/hive-stream.d.ts +35 -6
- package/dist/types/hive-stream.js +2 -2
- package/dist/utils.d.ts +27 -27
- package/dist/utils.js +271 -261
- package/dist/utils.js.map +1 -1
- package/ecosystem.config.js +17 -17
- package/jest.config.js +8 -8
- package/package.json +53 -48
- package/test-contract-block.md +18 -18
- package/tests/actions.spec.ts +252 -0
- package/tests/adapters/actions-persistence.spec.ts +144 -0
- package/tests/adapters/postgresql.adapter.spec.ts +127 -0
- package/tests/adapters/sqlite.adapter.spec.ts +180 -42
- package/tests/contracts/coinflip.contract.spec.ts +221 -131
- package/tests/contracts/dice.contract.spec.ts +202 -159
- package/tests/contracts/entrants.json +728 -728
- package/tests/contracts/lotto.contract.spec.ts +323 -323
- package/tests/contracts/nft.contract.spec.ts +948 -0
- package/tests/contracts/token.contract.spec.ts +334 -0
- package/tests/helpers/mock-adapter.ts +214 -0
- package/tests/setup.ts +29 -18
- package/tests/streamer-actions.spec.ts +263 -0
- package/tests/streamer.spec.ts +248 -151
- package/tests/utils.spec.ts +91 -94
- package/tsconfig.build.json +3 -22
- package/tslint.json +20 -20
- package/wallaby.js +26 -26
- package/.env +0 -3
package/dist/streamer.js
CHANGED
|
@@ -1,546 +1,794 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Streamer = void 0;
|
|
7
|
-
const api_1 = require("./api");
|
|
8
|
-
const sqlite_adapter_1 = require("./adapters/sqlite.adapter");
|
|
9
|
-
const utils_1 = require("@hiveio/dhive/lib/utils");
|
|
10
|
-
const actions_1 = require("./actions");
|
|
11
|
-
const dhive_1 = require("@hiveio/dhive");
|
|
12
|
-
const utils_2 = require("./utils");
|
|
13
|
-
const config_1 = require("./config");
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this.
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
await this.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
this.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
this.
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Streamer = void 0;
|
|
7
|
+
const api_1 = require("./api");
|
|
8
|
+
const sqlite_adapter_1 = require("./adapters/sqlite.adapter");
|
|
9
|
+
const utils_1 = require("@hiveio/dhive/lib/utils");
|
|
10
|
+
const actions_1 = require("./actions");
|
|
11
|
+
const dhive_1 = require("@hiveio/dhive");
|
|
12
|
+
const utils_2 = require("./utils");
|
|
13
|
+
const config_1 = require("./config");
|
|
14
|
+
const sscjs_1 = __importDefault(require("sscjs"));
|
|
15
|
+
class Streamer {
|
|
16
|
+
customJsonSubscriptions = [];
|
|
17
|
+
customJsonIdSubscriptions = [];
|
|
18
|
+
customJsonHiveEngineSubscriptions = [];
|
|
19
|
+
commentSubscriptions = [];
|
|
20
|
+
postSubscriptions = [];
|
|
21
|
+
transferSubscriptions = [];
|
|
22
|
+
attempts = 0;
|
|
23
|
+
config = config_1.Config;
|
|
24
|
+
client;
|
|
25
|
+
hive;
|
|
26
|
+
username;
|
|
27
|
+
postingKey;
|
|
28
|
+
activeKey;
|
|
29
|
+
blockNumberTimeout = null;
|
|
30
|
+
latestBlockTimer = null;
|
|
31
|
+
lastBlockNumber = 0;
|
|
32
|
+
blockId;
|
|
33
|
+
previousBlockId;
|
|
34
|
+
transactionId;
|
|
35
|
+
blockTime;
|
|
36
|
+
latestBlockchainTime;
|
|
37
|
+
disableAllProcessing = false;
|
|
38
|
+
contracts = [];
|
|
39
|
+
adapter;
|
|
40
|
+
actions = [];
|
|
41
|
+
// Performance optimization properties
|
|
42
|
+
lastStateSave = Date.now();
|
|
43
|
+
stateSaveInterval = 5000; // Save state every 5 seconds instead of every block
|
|
44
|
+
blockProcessingQueue = [];
|
|
45
|
+
isProcessingQueue = false;
|
|
46
|
+
// Memory management
|
|
47
|
+
maxSubscriptions = 1000;
|
|
48
|
+
subscriptionCleanupInterval = null;
|
|
49
|
+
// Action processing optimization
|
|
50
|
+
actionFrequencyMap = new Map([
|
|
51
|
+
['3s', 3], ['block', 3], ['10s', 10], ['30s', 30],
|
|
52
|
+
['1m', 60], ['5m', 300], ['minute', 60], ['15m', 900], ['quarter', 900],
|
|
53
|
+
['30m', 1800], ['halfhour', 1800], ['hourly', 3600], ['1h', 3600],
|
|
54
|
+
['12h', 43200], ['halfday', 43200], ['24h', 86400], ['day', 86400], ['daily', 86400],
|
|
55
|
+
['week', 604800], ['weekly', 604800]
|
|
56
|
+
]);
|
|
57
|
+
contractCache = new Map();
|
|
58
|
+
// Data caching for performance
|
|
59
|
+
blockCache = new Map();
|
|
60
|
+
transactionCache = new Map();
|
|
61
|
+
accountCache = new Map();
|
|
62
|
+
cacheTimeout = 300000; // 5 minutes
|
|
63
|
+
maxCacheSize = 1000;
|
|
64
|
+
utils = utils_2.Utils;
|
|
65
|
+
constructor(userConfig = {}) {
|
|
66
|
+
this.config = Object.assign(config_1.Config, userConfig);
|
|
67
|
+
this.lastBlockNumber = this.config.LAST_BLOCK_NUMBER;
|
|
68
|
+
this.username = this.config.USERNAME;
|
|
69
|
+
this.postingKey = this.config.POSTING_KEY;
|
|
70
|
+
this.activeKey = this.config.ACTIVE_KEY;
|
|
71
|
+
this.hive = new sscjs_1.default(this.config.HIVE_ENGINE_API);
|
|
72
|
+
this.client = new dhive_1.Client(this.config.API_NODES);
|
|
73
|
+
if (process?.env?.NODE_ENV !== 'test') {
|
|
74
|
+
this._initializeAdapter(new sqlite_adapter_1.SqliteAdapter());
|
|
75
|
+
new api_1.Api(this);
|
|
76
|
+
}
|
|
77
|
+
// Start subscription cleanup interval
|
|
78
|
+
this.subscriptionCleanupInterval = setInterval(() => {
|
|
79
|
+
this.cleanupSubscriptions();
|
|
80
|
+
}, 60000); // Cleanup every minute
|
|
81
|
+
}
|
|
82
|
+
_initializeAdapter(adapter) {
|
|
83
|
+
this.adapter = adapter;
|
|
84
|
+
if (this?.adapter?.create) {
|
|
85
|
+
this.adapter.create();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async registerAdapter(adapter) {
|
|
89
|
+
if (this.adapter && this.adapter.destroy) {
|
|
90
|
+
try {
|
|
91
|
+
await this.adapter.destroy();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.warn('[Streamer] Error destroying existing adapter:', error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.adapter = adapter;
|
|
98
|
+
if (this?.adapter?.create) {
|
|
99
|
+
await this.adapter.create();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
getAdapter() {
|
|
103
|
+
return this.adapter;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Register a new action with improved validation and persistence
|
|
107
|
+
*/
|
|
108
|
+
async registerAction(action) {
|
|
109
|
+
if (!action || !(action instanceof actions_1.TimeAction)) {
|
|
110
|
+
throw new Error('Invalid action: must be an instance of TimeAction');
|
|
111
|
+
}
|
|
112
|
+
const loadedActions = await this.adapter.loadActions();
|
|
113
|
+
for (const a of loadedActions) {
|
|
114
|
+
const exists = this.actions.find(i => i.id === a.id);
|
|
115
|
+
if (!exists) {
|
|
116
|
+
try {
|
|
117
|
+
const restoredAction = actions_1.TimeAction.fromJSON(a);
|
|
118
|
+
this.actions.push(restoredAction);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.warn(`[Streamer] Failed to restore action ${a.id}:`, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const exists = this.actions.find(a => a.id === action.id);
|
|
126
|
+
if (!exists) {
|
|
127
|
+
this.validateActionContract(action);
|
|
128
|
+
this.actions.push(action);
|
|
129
|
+
await this.saveActionsToDisk();
|
|
130
|
+
if (this.config.DEBUG_MODE) {
|
|
131
|
+
console.log(`[Streamer] Registered time-based action: ${action.id} (${action.timeValue})`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
if (this.config.DEBUG_MODE) {
|
|
136
|
+
console.warn(`[Streamer] Action with ID ${action.id} already exists, skipping registration`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Validate that the contract and method exist for the action
|
|
142
|
+
*/
|
|
143
|
+
validateActionContract(action) {
|
|
144
|
+
const contract = this.contractCache.get(action.contractName) ||
|
|
145
|
+
this.contracts.find(c => c.name === action.contractName);
|
|
146
|
+
if (!contract) {
|
|
147
|
+
throw new Error(`Contract '${action.contractName}' not found for action '${action.id}'`);
|
|
148
|
+
}
|
|
149
|
+
if (!contract.contract[action.contractMethod] || typeof contract.contract[action.contractMethod] !== 'function') {
|
|
150
|
+
throw new Error(`Method '${action.contractMethod}' not found in contract '${action.contractName}' for action '${action.id}'`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Remove an action by ID
|
|
155
|
+
*/
|
|
156
|
+
async removeAction(actionId) {
|
|
157
|
+
const index = this.actions.findIndex(a => a.id === actionId);
|
|
158
|
+
if (index >= 0) {
|
|
159
|
+
const removedAction = this.actions.splice(index, 1)[0];
|
|
160
|
+
await this.saveActionsToDisk();
|
|
161
|
+
if (this.config.DEBUG_MODE) {
|
|
162
|
+
console.log(`[Streamer] Removed time-based action: ${actionId}`);
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get all registered actions
|
|
170
|
+
*/
|
|
171
|
+
getActions() {
|
|
172
|
+
return [...this.actions];
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get action by ID
|
|
176
|
+
*/
|
|
177
|
+
getAction(actionId) {
|
|
178
|
+
return this.actions.find(a => a.id === actionId);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Enable/disable an action
|
|
182
|
+
*/
|
|
183
|
+
async setActionEnabled(actionId, enabled) {
|
|
184
|
+
const action = this.actions.find(a => a.id === actionId);
|
|
185
|
+
if (action) {
|
|
186
|
+
if (enabled) {
|
|
187
|
+
action.enable();
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
action.disable();
|
|
191
|
+
}
|
|
192
|
+
await this.saveActionsToDisk();
|
|
193
|
+
if (this.config.DEBUG_MODE) {
|
|
194
|
+
console.log(`[Streamer] Action ${actionId} ${enabled ? 'enabled' : 'disabled'}`);
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Save actions to disk asynchronously
|
|
202
|
+
*/
|
|
203
|
+
async saveActionsToDisk() {
|
|
204
|
+
try {
|
|
205
|
+
if (this.adapter?.saveState) {
|
|
206
|
+
await this.adapter.saveState({
|
|
207
|
+
lastBlockNumber: this.lastBlockNumber,
|
|
208
|
+
actions: this.actions.map(a => a.toJSON())
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (error?.code !== 'SQLITE_MISUSE') {
|
|
214
|
+
console.error('[Streamer] Failed to save actions to disk:', error);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Resets a specific action time value
|
|
220
|
+
*/
|
|
221
|
+
async resetAction(id) {
|
|
222
|
+
const action = this.actions.find(i => i.id === id);
|
|
223
|
+
if (action) {
|
|
224
|
+
action.reset();
|
|
225
|
+
await this.saveActionsToDisk();
|
|
226
|
+
if (this.config.DEBUG_MODE) {
|
|
227
|
+
console.log(`[Streamer] Reset action: ${id}`);
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
registerContract(name, contract) {
|
|
234
|
+
// Store an instance of the streamer
|
|
235
|
+
contract['_instance'] = this;
|
|
236
|
+
// Call the contract create lifecycle method if it exists
|
|
237
|
+
if (contract && typeof contract['create'] !== 'undefined') {
|
|
238
|
+
contract.create();
|
|
239
|
+
}
|
|
240
|
+
const storedReference = { name, contract };
|
|
241
|
+
// Push the contract reference to be called later on
|
|
242
|
+
this.contracts.push(storedReference);
|
|
243
|
+
// Cache the contract for faster lookups
|
|
244
|
+
this.contractCache.set(name, storedReference);
|
|
245
|
+
return this;
|
|
246
|
+
}
|
|
247
|
+
unregisterContract(name) {
|
|
248
|
+
// Find the registered contract by it's ID
|
|
249
|
+
const contractIndex = this.contracts.findIndex(c => c.name === name);
|
|
250
|
+
if (contractIndex >= 0) {
|
|
251
|
+
// Get the contract itself
|
|
252
|
+
const contract = this.contracts.find(c => c.name === name);
|
|
253
|
+
// Call the contract destroy lifecycle method if it exists
|
|
254
|
+
if (contract && typeof contract.contract['destroy'] !== 'undefined') {
|
|
255
|
+
contract.contract.destroy();
|
|
256
|
+
}
|
|
257
|
+
// Remove the contract
|
|
258
|
+
this.contracts.splice(contractIndex, 1);
|
|
259
|
+
// Remove from cache
|
|
260
|
+
this.contractCache.delete(name);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* setConfig
|
|
265
|
+
*
|
|
266
|
+
* Allows specific configuration settings to be overridden
|
|
267
|
+
*
|
|
268
|
+
* @param config
|
|
269
|
+
*/
|
|
270
|
+
setConfig(config) {
|
|
271
|
+
Object.assign(this.config, config);
|
|
272
|
+
// Set keys and username incase they have changed
|
|
273
|
+
this.username = this.config.USERNAME;
|
|
274
|
+
this.postingKey = this.config.POSTING_KEY;
|
|
275
|
+
this.activeKey = this.config.ACTIVE_KEY;
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Start
|
|
280
|
+
*
|
|
281
|
+
* Starts the streamer bot to get blocks from the Hive API
|
|
282
|
+
*
|
|
283
|
+
*/
|
|
284
|
+
async start() {
|
|
285
|
+
if (this.config.DEBUG_MODE) {
|
|
286
|
+
console.log('Starting to stream the Hive blockchain');
|
|
287
|
+
}
|
|
288
|
+
this.disableAllProcessing = false;
|
|
289
|
+
const state = await this.adapter.loadState();
|
|
290
|
+
if (this.config.DEBUG_MODE) {
|
|
291
|
+
console.log(`Restoring state from file`);
|
|
292
|
+
}
|
|
293
|
+
if (!this.config.LAST_BLOCK_NUMBER && state?.lastBlockNumber) {
|
|
294
|
+
if (state.lastBlockNumber) {
|
|
295
|
+
this.lastBlockNumber = state.lastBlockNumber;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Kicks off the blockchain streaming and operation parsing
|
|
299
|
+
this.getBlock();
|
|
300
|
+
this.latestBlockTimer = setInterval(() => { this.getLatestBlock(); }, this.config.BLOCK_CHECK_INTERVAL);
|
|
301
|
+
return this;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Stop
|
|
305
|
+
*
|
|
306
|
+
* Stops the streamer from running
|
|
307
|
+
*/
|
|
308
|
+
async stop() {
|
|
309
|
+
this.disableAllProcessing = true;
|
|
310
|
+
if (this.blockNumberTimeout) {
|
|
311
|
+
clearTimeout(this.blockNumberTimeout);
|
|
312
|
+
}
|
|
313
|
+
if (this.latestBlockTimer) {
|
|
314
|
+
clearInterval(this.latestBlockTimer);
|
|
315
|
+
}
|
|
316
|
+
if (this.subscriptionCleanupInterval) {
|
|
317
|
+
clearInterval(this.subscriptionCleanupInterval);
|
|
318
|
+
}
|
|
319
|
+
if (this?.adapter?.destroy) {
|
|
320
|
+
this.adapter.destroy();
|
|
321
|
+
}
|
|
322
|
+
await (0, utils_1.sleep)(800);
|
|
323
|
+
}
|
|
324
|
+
async getLatestBlock() {
|
|
325
|
+
try {
|
|
326
|
+
const props = await this.client.database.getDynamicGlobalProperties();
|
|
327
|
+
if (props) {
|
|
328
|
+
this.latestBlockchainTime = new Date(`${props.time}Z`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
console.error('[Streamer] Error getting latest block:', error);
|
|
333
|
+
// Continue with cached time if available
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async getBlock() {
|
|
337
|
+
try {
|
|
338
|
+
// Load global properties from the Hive API
|
|
339
|
+
const props = await this.client.database.getDynamicGlobalProperties();
|
|
340
|
+
// We have no props, so try loading them again.
|
|
341
|
+
if (!props && !this.disableAllProcessing) {
|
|
342
|
+
this.blockNumberTimeout = setTimeout(() => {
|
|
343
|
+
this.getBlock();
|
|
344
|
+
}, this.config.BLOCK_CHECK_INTERVAL);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
// If the block number we've got is zero
|
|
348
|
+
// set it to the last irreversible block number
|
|
349
|
+
if (this.lastBlockNumber === 0) {
|
|
350
|
+
this.lastBlockNumber = props.head_block_number - 1;
|
|
351
|
+
}
|
|
352
|
+
if (this.config.DEBUG_MODE) {
|
|
353
|
+
console.log(`Head block number: `, props.head_block_number);
|
|
354
|
+
console.log(`Last block number: `, this.lastBlockNumber);
|
|
355
|
+
}
|
|
356
|
+
const BLOCKS_BEHIND = parseInt(this.config.BLOCKS_BEHIND_WARNING, 10);
|
|
357
|
+
if (!this.disableAllProcessing) {
|
|
358
|
+
await this.loadBlock(this.lastBlockNumber + 1);
|
|
359
|
+
}
|
|
360
|
+
// We are more than 25 blocks behind, uh oh, we gotta catch up
|
|
361
|
+
if (props.head_block_number >= (this.lastBlockNumber + BLOCKS_BEHIND) && this.config.DEBUG_MODE) {
|
|
362
|
+
console.log(`We are more than ${BLOCKS_BEHIND} blocks behind ${props.head_block_number}, ${(this.lastBlockNumber + BLOCKS_BEHIND)}`);
|
|
363
|
+
if (!this.disableAllProcessing) {
|
|
364
|
+
this.getBlock();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Storing timeout allows us to clear it, as this just calls itself
|
|
369
|
+
if (!this.disableAllProcessing) {
|
|
370
|
+
this.blockNumberTimeout = setTimeout(() => { this.getBlock(); }, this.config.BLOCK_CHECK_INTERVAL);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
375
|
+
console.error(`[Streamer] Block processing error: ${error.message}`, {
|
|
376
|
+
stack: error.stack,
|
|
377
|
+
blockNumber: this.lastBlockNumber + 1
|
|
378
|
+
});
|
|
379
|
+
// Retry after a longer delay on error
|
|
380
|
+
if (!this.disableAllProcessing) {
|
|
381
|
+
this.blockNumberTimeout = setTimeout(() => { this.getBlock(); }, this.config.BLOCK_CHECK_INTERVAL * 2);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Takes the block from Hive and allows us to work with it
|
|
386
|
+
async loadBlock(blockNumber) {
|
|
387
|
+
// Check cache first
|
|
388
|
+
let block = this.blockCache.get(blockNumber);
|
|
389
|
+
if (!block) {
|
|
390
|
+
// Load the block itself from the Hive API
|
|
391
|
+
block = await this.client.database.getBlock(blockNumber);
|
|
392
|
+
// Cache the block for potential reuse
|
|
393
|
+
if (block) {
|
|
394
|
+
this.blockCache.set(blockNumber, block);
|
|
395
|
+
// Cleanup old cache entries
|
|
396
|
+
if (this.blockCache.size > this.maxCacheSize) {
|
|
397
|
+
const oldestKey = this.blockCache.keys().next().value;
|
|
398
|
+
this.blockCache.delete(oldestKey);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// The block doesn't exist, wait and try again
|
|
403
|
+
if (!block) {
|
|
404
|
+
await utils_2.Utils.sleep(this.config.BLOCK_CHECK_INTERVAL);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
// Get the block date and time
|
|
408
|
+
const blockTime = new Date(`${block.timestamp}Z`);
|
|
409
|
+
if (this.lastBlockNumber !== blockNumber) {
|
|
410
|
+
this.processActions().catch(error => {
|
|
411
|
+
console.error('[Streamer] Error processing actions:', error);
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
this.blockId = block.block_id;
|
|
415
|
+
this.previousBlockId = block.previous;
|
|
416
|
+
this.transactionId = block.transaction_ids[1];
|
|
417
|
+
this.blockTime = blockTime;
|
|
418
|
+
if (this.adapter?.processBlock) {
|
|
419
|
+
this.adapter.processBlock(block);
|
|
420
|
+
}
|
|
421
|
+
// Process transactions with improved concurrency
|
|
422
|
+
const transactions = block.transactions;
|
|
423
|
+
const transactionIds = block.transaction_ids;
|
|
424
|
+
// Create operation processing promises for better concurrency
|
|
425
|
+
const operationPromises = [];
|
|
426
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
427
|
+
const transaction = transactions[i];
|
|
428
|
+
const operations = transaction.operations;
|
|
429
|
+
// Process operations in batch for better performance
|
|
430
|
+
for (let opIndex = 0; opIndex < operations.length; opIndex++) {
|
|
431
|
+
const op = operations[opIndex];
|
|
432
|
+
// Create promise for each operation (but don't await yet)
|
|
433
|
+
const operationPromise = this.processOperation(op, blockNumber, block.block_id, block.previous, transactionIds[i], blockTime).catch(error => {
|
|
434
|
+
console.error('[Streamer] Operation processing error:', error, {
|
|
435
|
+
blockNumber,
|
|
436
|
+
transactionIndex: i,
|
|
437
|
+
operationIndex: opIndex
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
operationPromises.push(operationPromise);
|
|
441
|
+
// Process in batches to avoid overwhelming the system
|
|
442
|
+
if (operationPromises.length >= 50) {
|
|
443
|
+
await Promise.all(operationPromises);
|
|
444
|
+
operationPromises.length = 0; // Clear array
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// Process any remaining operations
|
|
449
|
+
if (operationPromises.length > 0) {
|
|
450
|
+
await Promise.all(operationPromises);
|
|
451
|
+
}
|
|
452
|
+
this.lastBlockNumber = blockNumber;
|
|
453
|
+
this.saveStateThrottled();
|
|
454
|
+
}
|
|
455
|
+
async processOperation(op, blockNumber, blockId, prevBlockId, trxId, blockTime) {
|
|
456
|
+
if (this.adapter?.processOperation) {
|
|
457
|
+
this.adapter.processOperation(op, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
458
|
+
}
|
|
459
|
+
// Operation is a "comment" which could either be a post or comment
|
|
460
|
+
if (op[0] === 'comment') {
|
|
461
|
+
// This is a post
|
|
462
|
+
if (op[1].parent_author === '') {
|
|
463
|
+
this.postSubscriptions.forEach(sub => {
|
|
464
|
+
sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
465
|
+
});
|
|
466
|
+
// This is a comment
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
this.commentSubscriptions.forEach(sub => {
|
|
470
|
+
sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// This is a transfer
|
|
475
|
+
if (op[0] === 'transfer') {
|
|
476
|
+
const sender = op[1]?.from;
|
|
477
|
+
const amount = op[1]?.amount;
|
|
478
|
+
const json = utils_2.Utils.jsonParse(op[1].memo);
|
|
479
|
+
if (json?.[this.config.PAYLOAD_IDENTIFIER] && json?.[this.config.PAYLOAD_IDENTIFIER]?.id === this.config.JSON_ID) {
|
|
480
|
+
// Pull out details of contract
|
|
481
|
+
const { name, action, payload } = json[this.config.PAYLOAD_IDENTIFIER];
|
|
482
|
+
// Do we have a contract that matches the name in the payload?
|
|
483
|
+
const contract = this.contracts.find(c => c.name === name);
|
|
484
|
+
if (contract) {
|
|
485
|
+
if (this?.adapter?.processTransfer) {
|
|
486
|
+
this.adapter.processTransfer(op[1], { name, action, payload }, { sender, amount });
|
|
487
|
+
}
|
|
488
|
+
if (contract?.contract?.updateBlockInfo) {
|
|
489
|
+
contract.contract.updateBlockInfo(blockNumber, blockId, prevBlockId, trxId);
|
|
490
|
+
}
|
|
491
|
+
if (contract?.contract[action]) {
|
|
492
|
+
contract.contract[action](payload, { sender, amount });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
this.transferSubscriptions.forEach(sub => {
|
|
497
|
+
if (sub.account === op[1].to) {
|
|
498
|
+
sub.callback(op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
// This is a custom JSON operation
|
|
503
|
+
if (op[0] === 'custom_json') {
|
|
504
|
+
let isSignedWithActiveKey = false;
|
|
505
|
+
let sender;
|
|
506
|
+
const id = op[1]?.id;
|
|
507
|
+
if (op[1]?.required_auths?.length > 0) {
|
|
508
|
+
sender = op[1].required_auths[0];
|
|
509
|
+
isSignedWithActiveKey = true;
|
|
510
|
+
}
|
|
511
|
+
else if (op[1]?.required_posting_auths?.length > 0) {
|
|
512
|
+
sender = op[1].required_posting_auths[0];
|
|
513
|
+
isSignedWithActiveKey = false;
|
|
514
|
+
}
|
|
515
|
+
const json = utils_2.Utils.jsonParse(op[1].json);
|
|
516
|
+
if (json && json?.[this.config.PAYLOAD_IDENTIFIER] && id === this.config.JSON_ID) {
|
|
517
|
+
// Pull out details of contract
|
|
518
|
+
const { name, action, payload } = json[this.config.PAYLOAD_IDENTIFIER];
|
|
519
|
+
// Do we have a contract that matches the name in the payload?
|
|
520
|
+
const contract = this.contracts.find(c => c.name === name);
|
|
521
|
+
if (contract) {
|
|
522
|
+
this.adapter.processCustomJson(op[1], { name, action, payload }, { sender, isSignedWithActiveKey });
|
|
523
|
+
if (contract?.contract?.updateBlockInfo) {
|
|
524
|
+
contract.contract.updateBlockInfo(blockNumber, blockId, prevBlockId, trxId);
|
|
525
|
+
}
|
|
526
|
+
if (contract?.contract[action]) {
|
|
527
|
+
contract.contract[action](payload, { sender, isSignedWithActiveKey }, id);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
this.customJsonSubscriptions.forEach(sub => {
|
|
532
|
+
sub.callback(op[1], { sender, isSignedWithActiveKey }, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
533
|
+
});
|
|
534
|
+
this.customJsonIdSubscriptions.forEach(sub => {
|
|
535
|
+
const byId = this.customJsonIdSubscriptions.find(s => s.id === op[1].id);
|
|
536
|
+
if (byId) {
|
|
537
|
+
sub.callback(op[1], { sender, isSignedWithActiveKey }, blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
utils_2.Utils.asyncForEach(this.customJsonHiveEngineSubscriptions, async (sub) => {
|
|
541
|
+
let isSignedWithActiveKey = null;
|
|
542
|
+
let sender;
|
|
543
|
+
if (op[1].required_auths.length > 0) {
|
|
544
|
+
sender = op[1].required_auths[0];
|
|
545
|
+
isSignedWithActiveKey = true;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
sender = op[1].required_posting_auths[0];
|
|
549
|
+
isSignedWithActiveKey = false;
|
|
550
|
+
}
|
|
551
|
+
const id = op[1].id;
|
|
552
|
+
const json = utils_2.Utils.jsonParse(op[1].json);
|
|
553
|
+
// Hive Engine JSON operation
|
|
554
|
+
if (id === this.config.HIVE_ENGINE_ID) {
|
|
555
|
+
const { contractName, contractAction, contractPayload } = json;
|
|
556
|
+
try {
|
|
557
|
+
// Attempt to get the transaction from Hive Engine itself
|
|
558
|
+
const txInfo = await this.hive.getTransactionInfo(trxId);
|
|
559
|
+
const logs = txInfo && txInfo.logs ? utils_2.Utils.jsonParse(txInfo.logs) : null;
|
|
560
|
+
// Do we have a valid transaction and are there no errors? It's a real transaction
|
|
561
|
+
if (txInfo && logs && typeof logs.errors === 'undefined') {
|
|
562
|
+
sub.callback(contractName, contractAction, contractPayload, sender, op[1], blockNumber, blockId, prevBlockId, trxId, blockTime);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
catch (e) {
|
|
566
|
+
console.error(e);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async processActions() {
|
|
574
|
+
if (!this.latestBlockchainTime || this.actions.length === 0) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const currentTime = this.latestBlockchainTime.getTime();
|
|
578
|
+
const executedActions = [];
|
|
579
|
+
// Process actions in batch with optimized time calculations
|
|
580
|
+
for (let i = 0; i < this.actions.length; i++) {
|
|
581
|
+
const action = this.actions[i];
|
|
582
|
+
// Skip disabled actions or actions that have reached max executions
|
|
583
|
+
if (!action.enabled || action.hasReachedMaxExecutions()) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
// Get contract from cache or find and cache it
|
|
587
|
+
let contract = this.contractCache.get(action.contractName);
|
|
588
|
+
if (!contract) {
|
|
589
|
+
contract = this.contracts.find(c => c.name === action.contractName);
|
|
590
|
+
if (contract) {
|
|
591
|
+
this.contractCache.set(action.contractName, contract);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// Contract doesn't exist or method doesn't exist, log warning and skip
|
|
595
|
+
if (!contract) {
|
|
596
|
+
console.warn(`[Streamer] Contract '${action.contractName}' not found for action '${action.id}'`);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (!contract?.contract?.[action.contractMethod] || typeof contract.contract[action.contractMethod] !== 'function') {
|
|
600
|
+
console.warn(`[Streamer] Method '${action.contractMethod}' not found in contract '${action.contractName}' for action '${action.id}'`);
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
// Get frequency in seconds from optimized map
|
|
604
|
+
const frequencySeconds = this.actionFrequencyMap.get(action.timeValue);
|
|
605
|
+
if (!frequencySeconds) {
|
|
606
|
+
console.warn(`[Streamer] Invalid time value '${action.timeValue}' for action '${action.id}'`);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
// Optimized time difference calculation using timestamps
|
|
610
|
+
const actionTime = action.date.getTime();
|
|
611
|
+
const differenceSeconds = (currentTime - actionTime) / 1000;
|
|
612
|
+
// Check if enough time has passed
|
|
613
|
+
if (differenceSeconds >= frequencySeconds) {
|
|
614
|
+
try {
|
|
615
|
+
// Execute the action with error isolation
|
|
616
|
+
await this.executeAction(action, contract);
|
|
617
|
+
// Reset the action timer and increment execution count
|
|
618
|
+
action.reset();
|
|
619
|
+
action.incrementExecutionCount();
|
|
620
|
+
executedActions.push(action.id);
|
|
621
|
+
if (this.config.DEBUG_MODE) {
|
|
622
|
+
console.log(`[Streamer] Executed action: ${action.id} (execution #${action.executionCount})`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
627
|
+
console.error(`[Streamer] Action execution error for ${action.contractName}.${action.contractMethod}:`, {
|
|
628
|
+
actionId: action.id,
|
|
629
|
+
error: err.message,
|
|
630
|
+
stack: err.stack,
|
|
631
|
+
payload: action.payload
|
|
632
|
+
});
|
|
633
|
+
// Optionally disable action after repeated failures
|
|
634
|
+
// This could be configurable in the future
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
// Save state if any actions were executed
|
|
639
|
+
if (executedActions.length > 0) {
|
|
640
|
+
await this.saveActionsToDisk();
|
|
641
|
+
}
|
|
642
|
+
// Clean up disabled or completed actions periodically
|
|
643
|
+
this.cleanupActions();
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Execute a single action with proper isolation
|
|
647
|
+
*/
|
|
648
|
+
async executeAction(action, contract) {
|
|
649
|
+
const method = contract.contract[action.contractMethod];
|
|
650
|
+
if (method.constructor.name === 'AsyncFunction') {
|
|
651
|
+
await method.call(contract.contract, action.payload);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
method.call(contract.contract, action.payload);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Clean up completed or disabled actions to prevent memory leaks
|
|
659
|
+
*/
|
|
660
|
+
cleanupActions() {
|
|
661
|
+
const beforeCount = this.actions.length;
|
|
662
|
+
// Remove actions that have reached their max executions
|
|
663
|
+
this.actions = this.actions.filter(action => {
|
|
664
|
+
if (action.hasReachedMaxExecutions()) {
|
|
665
|
+
if (this.config.DEBUG_MODE) {
|
|
666
|
+
console.log(`[Streamer] Removing completed action: ${action.id} (${action.executionCount}/${action.maxExecutions} executions)`);
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
return true;
|
|
671
|
+
});
|
|
672
|
+
const afterCount = this.actions.length;
|
|
673
|
+
if (beforeCount !== afterCount) {
|
|
674
|
+
// Save state if we removed any actions
|
|
675
|
+
this.saveActionsToDisk().catch(error => {
|
|
676
|
+
console.error('[Streamer] Failed to save state after action cleanup:', error);
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
async saveStateToDisk() {
|
|
681
|
+
if (this.adapter?.saveState) {
|
|
682
|
+
await this.adapter.saveState({ lastBlockNumber: this.lastBlockNumber, actions: this.actions });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// Throttled state saving for performance
|
|
686
|
+
saveStateThrottled() {
|
|
687
|
+
const now = Date.now();
|
|
688
|
+
if (now - this.lastStateSave > this.stateSaveInterval) {
|
|
689
|
+
this.lastStateSave = now;
|
|
690
|
+
// Save state asynchronously without blocking block processing
|
|
691
|
+
this.saveStateToDisk().catch(error => {
|
|
692
|
+
console.error('[Streamer] State save error:', error);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
saveToHiveApi(from, data) {
|
|
697
|
+
return utils_2.Utils.transferHiveTokens(this.client, this.config, from, 'hiveapi', '0.001', 'HIVE', data);
|
|
698
|
+
}
|
|
699
|
+
getAccountTransfers(account, from = -1, limit = 100) {
|
|
700
|
+
return utils_2.Utils.getAccountTransfers(this.client, account, from, limit);
|
|
701
|
+
}
|
|
702
|
+
transferHiveTokens(from, to, amount, symbol, memo = '') {
|
|
703
|
+
return utils_2.Utils.transferHiveTokens(this.client, this.config, from, to, amount, symbol, memo);
|
|
704
|
+
}
|
|
705
|
+
transferHiveTokensMultiple(from, accounts = [], amount = '0', symbol, memo = '') {
|
|
706
|
+
return utils_2.Utils.transferHiveTokensMultiple(this.client, this.config, from, accounts, amount, symbol, memo);
|
|
707
|
+
}
|
|
708
|
+
transferHiveEngineTokens(from, to, symbol, quantity, memo = '') {
|
|
709
|
+
return utils_2.Utils.transferHiveEngineTokens(this.client, this.config, from, to, symbol, quantity, memo);
|
|
710
|
+
}
|
|
711
|
+
transferHiveEngineTokensMultiple(from, accounts = [], symbol, memo = '', amount = '0') {
|
|
712
|
+
return utils_2.Utils.transferHiveEngineTokensMultiple(this.client, this.config, from, accounts, symbol, memo, amount);
|
|
713
|
+
}
|
|
714
|
+
issueHiveEngineTokens(from, to, symbol, quantity, memo = '') {
|
|
715
|
+
return utils_2.Utils.issueHiveEngineTokens(this.client, this.config, from, to, symbol, quantity, memo);
|
|
716
|
+
}
|
|
717
|
+
issueHiveEngineTokensMultiple(from, accounts = [], symbol, memo = '', amount = '0') {
|
|
718
|
+
return utils_2.Utils.issueHiveEngineTokensMultiple(this.client, this.config, from, accounts, symbol, memo, amount);
|
|
719
|
+
}
|
|
720
|
+
upvote(votePercentage = '100.0', username, permlink) {
|
|
721
|
+
return utils_2.Utils.upvote(this.client, this.config, this.username, votePercentage, username, permlink);
|
|
722
|
+
}
|
|
723
|
+
downvote(votePercentage = '100.0', username, permlink) {
|
|
724
|
+
return utils_2.Utils.downvote(this.client, this.config, this.username, votePercentage, username, permlink);
|
|
725
|
+
}
|
|
726
|
+
getTransaction(blockNumber, transactionId) {
|
|
727
|
+
return utils_2.Utils.getTransaction(this.client, blockNumber, transactionId);
|
|
728
|
+
}
|
|
729
|
+
verifyTransfer(transaction, from, to, amount) {
|
|
730
|
+
return utils_2.Utils.verifyTransfer(transaction, from, to, amount);
|
|
731
|
+
}
|
|
732
|
+
onComment(callback) {
|
|
733
|
+
this.commentSubscriptions.push({
|
|
734
|
+
callback
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
onPost(callback) {
|
|
738
|
+
this.postSubscriptions.push({
|
|
739
|
+
callback
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
onTransfer(account, callback) {
|
|
743
|
+
this.transferSubscriptions.push({
|
|
744
|
+
account,
|
|
745
|
+
callback
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
onCustomJson(callback) {
|
|
749
|
+
this.customJsonSubscriptions.push({ callback });
|
|
750
|
+
}
|
|
751
|
+
onCustomJsonId(callback, id) {
|
|
752
|
+
this.customJsonIdSubscriptions.push({ callback, id });
|
|
753
|
+
}
|
|
754
|
+
onHiveEngine(callback) {
|
|
755
|
+
this.customJsonHiveEngineSubscriptions.push({ callback });
|
|
756
|
+
}
|
|
757
|
+
// Memory management: cleanup subscriptions
|
|
758
|
+
cleanupSubscriptions() {
|
|
759
|
+
// Limit subscription arrays to prevent memory leaks
|
|
760
|
+
if (this.customJsonSubscriptions.length > this.maxSubscriptions) {
|
|
761
|
+
this.customJsonSubscriptions = this.customJsonSubscriptions.slice(-this.maxSubscriptions);
|
|
762
|
+
console.warn(`[Streamer] Trimmed customJsonSubscriptions to ${this.maxSubscriptions} items`);
|
|
763
|
+
}
|
|
764
|
+
if (this.customJsonIdSubscriptions.length > this.maxSubscriptions) {
|
|
765
|
+
this.customJsonIdSubscriptions = this.customJsonIdSubscriptions.slice(-this.maxSubscriptions);
|
|
766
|
+
console.warn(`[Streamer] Trimmed customJsonIdSubscriptions to ${this.maxSubscriptions} items`);
|
|
767
|
+
}
|
|
768
|
+
if (this.customJsonHiveEngineSubscriptions.length > this.maxSubscriptions) {
|
|
769
|
+
this.customJsonHiveEngineSubscriptions = this.customJsonHiveEngineSubscriptions.slice(-this.maxSubscriptions);
|
|
770
|
+
console.warn(`[Streamer] Trimmed customJsonHiveEngineSubscriptions to ${this.maxSubscriptions} items`);
|
|
771
|
+
}
|
|
772
|
+
if (this.commentSubscriptions.length > this.maxSubscriptions) {
|
|
773
|
+
this.commentSubscriptions = this.commentSubscriptions.slice(-this.maxSubscriptions);
|
|
774
|
+
console.warn(`[Streamer] Trimmed commentSubscriptions to ${this.maxSubscriptions} items`);
|
|
775
|
+
}
|
|
776
|
+
if (this.postSubscriptions.length > this.maxSubscriptions) {
|
|
777
|
+
this.postSubscriptions = this.postSubscriptions.slice(-this.maxSubscriptions);
|
|
778
|
+
console.warn(`[Streamer] Trimmed postSubscriptions to ${this.maxSubscriptions} items`);
|
|
779
|
+
}
|
|
780
|
+
if (this.transferSubscriptions.length > this.maxSubscriptions) {
|
|
781
|
+
this.transferSubscriptions = this.transferSubscriptions.slice(-this.maxSubscriptions);
|
|
782
|
+
console.warn(`[Streamer] Trimmed transferSubscriptions to ${this.maxSubscriptions} items`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
// Add method to remove specific subscriptions
|
|
786
|
+
removeTransferSubscription(account) {
|
|
787
|
+
this.transferSubscriptions = this.transferSubscriptions.filter(sub => sub.account !== account);
|
|
788
|
+
}
|
|
789
|
+
removeCustomJsonIdSubscription(id) {
|
|
790
|
+
this.customJsonIdSubscriptions = this.customJsonIdSubscriptions.filter(sub => sub.id !== id);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
exports.Streamer = Streamer;
|
|
546
794
|
//# sourceMappingURL=streamer.js.map
|