@attocash/n8n-nodes-atto 0.3.2 → 0.4.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/README.md +2 -0
- package/THIRD_PARTY_NOTICES.md +32 -0
- package/dist/nodes/Atto/Atto.node.js +4 -17
- package/dist/nodes/Atto/operations.js +265 -62
- package/dist/nodes/Atto/protocol.js +13 -13
- package/dist/nodes/AttoTrigger/AttoTrigger.node.js +5 -35
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,6 +83,8 @@ Trigger events:
|
|
|
83
83
|
- **Transaction**: watches by hash, address stream, or all supported transactions.
|
|
84
84
|
- **Account Entry**: watches by hash, address stream, or all supported account entries.
|
|
85
85
|
|
|
86
|
+
Triggers stay connected to the selected Atto NDJSON endpoint. If a stream closes, the node reconnects with exponential backoff from 1 to 30 seconds; a received event resets the delay.
|
|
87
|
+
|
|
86
88
|
## Example Workflows
|
|
87
89
|
|
|
88
90
|
Importable examples live in [`examples`](./examples):
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -31,6 +31,38 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
|
31
31
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
32
32
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
33
33
|
|
|
34
|
+
## js-joda
|
|
35
|
+
|
|
36
|
+
`@js-joda/core` is licensed under the BSD 3-Clause License.
|
|
37
|
+
|
|
38
|
+
Copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
|
|
39
|
+
|
|
40
|
+
All rights reserved.
|
|
41
|
+
|
|
42
|
+
Redistribution and use in source and binary forms, with or without
|
|
43
|
+
modification, are permitted provided that the following conditions are met:
|
|
44
|
+
|
|
45
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
46
|
+
this list of conditions and the following disclaimer.
|
|
47
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
48
|
+
this list of conditions and the following disclaimer in the documentation
|
|
49
|
+
and/or other materials provided with the distribution.
|
|
50
|
+
3. Neither the name of js-joda nor the names of its contributors may be used
|
|
51
|
+
to endorse or promote products derived from this software without specific
|
|
52
|
+
prior written permission.
|
|
53
|
+
|
|
54
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
55
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
56
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
57
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
58
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
59
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
60
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
61
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
62
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
63
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
64
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
65
|
+
|
|
34
66
|
## Noble Hashes
|
|
35
67
|
|
|
36
68
|
`@noble/hashes` is licensed under the MIT License.
|
|
@@ -7,12 +7,12 @@ const SECRET_PARAMETER_NAMES = ['secretSource', 'walletSecretType', 'walletSecre
|
|
|
7
7
|
const OPERATION_PARAMETER_NAMES = {
|
|
8
8
|
deriveAddress: SECRET_PARAMETER_NAMES,
|
|
9
9
|
deriveAccount: SECRET_PARAMETER_NAMES,
|
|
10
|
-
getAccount: ['address'
|
|
11
|
-
getReceivables: ['addressSource', 'addresses', 'minAmount', 'minAmountUnit', 'maxItems', 'timeoutMs'
|
|
12
|
-
getTransactions: ['queryMode', 'addresses', 'hash', 'fromHeight', 'toHeight', 'maxItems', 'timeoutMs'
|
|
10
|
+
getAccount: ['address'],
|
|
11
|
+
getReceivables: ['addressSource', 'addresses', 'minAmount', 'minAmountUnit', 'maxItems', 'timeoutMs'],
|
|
12
|
+
getTransactions: ['queryMode', 'addresses', 'hash', 'fromHeight', 'toHeight', 'maxItems', 'timeoutMs'],
|
|
13
13
|
sendTransaction: [...SECRET_PARAMETER_NAMES, 'destinationAddress', 'amount', 'amountUnit', 'timeoutMs'],
|
|
14
14
|
receivePending: [...SECRET_PARAMETER_NAMES, 'representativeAddress', 'timeoutMs'],
|
|
15
|
-
getAccountEntries: ['queryMode', 'addresses', 'hash', 'fromHeight', 'toHeight', 'maxItems', 'timeoutMs'
|
|
15
|
+
getAccountEntries: ['queryMode', 'addresses', 'hash', 'fromHeight', 'toHeight', 'maxItems', 'timeoutMs'],
|
|
16
16
|
changeRepresentative: [...SECRET_PARAMETER_NAMES, 'representativeAddress'],
|
|
17
17
|
};
|
|
18
18
|
const DEFAULT_OPERATION_BY_RESOURCE = {
|
|
@@ -51,7 +51,6 @@ const DEFAULT_PARAMETER_VALUES = {
|
|
|
51
51
|
representativeAddress: '',
|
|
52
52
|
timeoutMs: 5000,
|
|
53
53
|
maxItems: 25,
|
|
54
|
-
simplify: true,
|
|
55
54
|
};
|
|
56
55
|
const AMOUNT_UNITS = [
|
|
57
56
|
{
|
|
@@ -553,18 +552,6 @@ class Atto {
|
|
|
553
552
|
},
|
|
554
553
|
description: 'New representative address',
|
|
555
554
|
},
|
|
556
|
-
{
|
|
557
|
-
displayName: 'Simplify',
|
|
558
|
-
name: 'simplify',
|
|
559
|
-
type: 'boolean',
|
|
560
|
-
default: true,
|
|
561
|
-
displayOptions: {
|
|
562
|
-
show: {
|
|
563
|
-
operation: ['getAccount', 'getReceivables', 'getTransactions', 'getAccountEntries'],
|
|
564
|
-
},
|
|
565
|
-
},
|
|
566
|
-
description: 'Whether to return a compact set of commonly used fields instead of the complete Atto API response',
|
|
567
|
-
},
|
|
568
555
|
{
|
|
569
556
|
displayName: 'Max Items',
|
|
570
557
|
name: 'maxItems',
|
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.executeAttoOperation = executeAttoOperation;
|
|
4
|
-
exports.
|
|
4
|
+
exports.attoStreamRequest = attoStreamRequest;
|
|
5
|
+
exports.attoHttpStreamRequest = attoHttpStreamRequest;
|
|
6
|
+
exports.startAttoEventStream = startAttoEventStream;
|
|
5
7
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
6
8
|
const protocol_1 = require("./protocol");
|
|
7
9
|
const DEFAULT_STREAM_TIMEOUT_MS = 5000;
|
|
8
10
|
const DEFAULT_PUBLISH_TIMEOUT_MS = 60000;
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
11
|
+
const INITIAL_RECONNECT_DELAY_MS = 1000;
|
|
12
|
+
const MAX_RECONNECT_DELAY_MS = 30000;
|
|
13
|
+
function scheduleTimeout(callback, delayMs) {
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const timeout = AbortSignal.timeout(delayMs);
|
|
16
|
+
const onTimeout = () => {
|
|
17
|
+
if (!controller.signal.aborted)
|
|
18
|
+
callback();
|
|
19
|
+
};
|
|
20
|
+
timeout.addEventListener('abort', onTimeout, { once: true });
|
|
21
|
+
controller.signal.addEventListener('abort', () => timeout.removeEventListener('abort', onTimeout), { once: true });
|
|
22
|
+
return controller;
|
|
23
|
+
}
|
|
24
|
+
const DEFAULT_STREAM_RUNTIME = {
|
|
25
|
+
open: openAttoEventStream,
|
|
26
|
+
setTimeout: scheduleTimeout,
|
|
27
|
+
clearTimeout: (timer) => timer.abort(),
|
|
28
|
+
};
|
|
12
29
|
function credentials(value) {
|
|
13
30
|
return (value ?? {});
|
|
14
31
|
}
|
|
@@ -39,13 +56,14 @@ function positiveHeight(value, fieldName) {
|
|
|
39
56
|
throw new Error(`${fieldName} must be a positive integer`);
|
|
40
57
|
return valueText;
|
|
41
58
|
}
|
|
42
|
-
function simplifyOutput(parameters) {
|
|
43
|
-
return parameters.simplify !== false;
|
|
44
|
-
}
|
|
45
59
|
function requireNodeUrl(value) {
|
|
46
60
|
const url = text(value.nodeUrl);
|
|
47
61
|
if (!url)
|
|
48
62
|
throw new Error('Atto credentials must include a Node Base URL');
|
|
63
|
+
const parsed = URL.canParse(url) ? new URL(url) : undefined;
|
|
64
|
+
if (!parsed || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
|
|
65
|
+
throw new Error('Atto credentials must include a valid HTTP Node Base URL');
|
|
66
|
+
}
|
|
49
67
|
return url.replace(/\/+$/, '');
|
|
50
68
|
}
|
|
51
69
|
function requireWorkerUrl(value) {
|
|
@@ -189,6 +207,24 @@ async function requestStream(context, value, request) {
|
|
|
189
207
|
throw apiError(context, error, `Atto API stream failed: ${request.method} /${request.path}`);
|
|
190
208
|
}
|
|
191
209
|
}
|
|
210
|
+
async function openAttoEventStream(context, value, request, signal) {
|
|
211
|
+
const response = await context.helpers.httpRequest({
|
|
212
|
+
url: `${requireNodeUrl(value)}/${request.path}`,
|
|
213
|
+
method: request.method,
|
|
214
|
+
headers: {
|
|
215
|
+
...requestHeaders(value, 'application/x-ndjson'),
|
|
216
|
+
...(request.body ? { 'Content-Type': 'application/json' } : {}),
|
|
217
|
+
},
|
|
218
|
+
encoding: 'stream',
|
|
219
|
+
json: false,
|
|
220
|
+
abortSignal: signal,
|
|
221
|
+
...(request.body ? { body: (0, protocol_1.stringifyAttoJson)(request.body) } : {}),
|
|
222
|
+
});
|
|
223
|
+
if (!response || typeof response !== 'object' || !(Symbol.asyncIterator in response)) {
|
|
224
|
+
throw new Error('Atto stream response is not readable');
|
|
225
|
+
}
|
|
226
|
+
return response;
|
|
227
|
+
}
|
|
192
228
|
function contextRequired(context) {
|
|
193
229
|
if (!context)
|
|
194
230
|
throw new Error('This Atto operation requires an n8n execution context');
|
|
@@ -410,7 +446,6 @@ async function receivableFromInput(context, value, parameters) {
|
|
|
410
446
|
}
|
|
411
447
|
async function executeAttoOperation(context, operation, parameters, credentialData) {
|
|
412
448
|
const value = credentials(credentialData);
|
|
413
|
-
const simplify = simplifyOutput(parameters);
|
|
414
449
|
if (operation === 'deriveAddress' || operation === 'deriveAccount') {
|
|
415
450
|
const derived = await (0, protocol_1.deriveAddressFromSecret)(parameters, value);
|
|
416
451
|
return { address: derived.value, publicKey: derived.publicKey, keyIndex: derived.keyIndex, secretType: derived.secretType };
|
|
@@ -419,21 +454,21 @@ async function executeAttoOperation(context, operation, parameters, credentialDa
|
|
|
419
454
|
if (operation === 'getAccount') {
|
|
420
455
|
const address = (0, protocol_1.parseAddress)(parameters.address ?? parameters.lookupAddress);
|
|
421
456
|
const account = await accountByAddress(execution, value, address);
|
|
422
|
-
return account ? (0, protocol_1.accountOutput)(account
|
|
457
|
+
return account ? (0, protocol_1.accountOutput)(account) : { found: false, address: address.value };
|
|
423
458
|
}
|
|
424
459
|
if (operation === 'getReceivables') {
|
|
425
|
-
return (await requestStream(execution, value, await receivableStreamRequest(parameters, value))).map((item) => (0, protocol_1.receivableOutput)(item
|
|
460
|
+
return (await requestStream(execution, value, await receivableStreamRequest(parameters, value))).map((item) => (0, protocol_1.receivableOutput)(item));
|
|
426
461
|
}
|
|
427
462
|
if (operation === 'getTransactions') {
|
|
428
463
|
if (queryMode(parameters.queryMode) === 'hash') {
|
|
429
464
|
const hash = (0, protocol_1.parseHash)(parameters.hash);
|
|
430
465
|
const transaction = (await requestJson(execution, value, requireNodeUrl(value), 'GET', `transactions/${hash}`));
|
|
431
|
-
return [(0, protocol_1.transactionOutput)(transaction
|
|
466
|
+
return [(0, protocol_1.transactionOutput)(transaction)];
|
|
432
467
|
}
|
|
433
|
-
return (await requestStream(execution, value, await transactionStreamRequest(parameters, value))).map((item) => (0, protocol_1.transactionOutput)(item
|
|
468
|
+
return (await requestStream(execution, value, await transactionStreamRequest(parameters, value))).map((item) => (0, protocol_1.transactionOutput)(item));
|
|
434
469
|
}
|
|
435
470
|
if (operation === 'getAccountEntries') {
|
|
436
|
-
return (await requestStream(execution, value, await accountEntryStreamRequest(parameters, value))).map((item) => (0, protocol_1.accountEntryOutput)(item
|
|
471
|
+
return (await requestStream(execution, value, await accountEntryStreamRequest(parameters, value))).map((item) => (0, protocol_1.accountEntryOutput)(item));
|
|
437
472
|
}
|
|
438
473
|
const derived = await (0, protocol_1.deriveAddressFromSecret)(parameters, value);
|
|
439
474
|
const account = await accountByAddress(execution, value, derived);
|
|
@@ -447,7 +482,7 @@ async function executeAttoOperation(context, operation, parameters, credentialDa
|
|
|
447
482
|
const block = (0, protocol_1.createSendBlock)(account, destination, amount, timestamp);
|
|
448
483
|
const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
|
|
449
484
|
return {
|
|
450
|
-
...(0, protocol_1.transactionOutput)(transaction,
|
|
485
|
+
...(0, protocol_1.transactionOutput)(transaction, 'published'),
|
|
451
486
|
fromAddress: derived.value,
|
|
452
487
|
destinationAddress: destination.value,
|
|
453
488
|
amount: (0, protocol_1.amountOutput)(amount),
|
|
@@ -464,7 +499,7 @@ async function executeAttoOperation(context, operation, parameters, credentialDa
|
|
|
464
499
|
const block = (0, protocol_1.createReceiveBlock)(account, receivable, representative, timestamp);
|
|
465
500
|
const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
|
|
466
501
|
return {
|
|
467
|
-
...(0, protocol_1.transactionOutput)(transaction,
|
|
502
|
+
...(0, protocol_1.transactionOutput)(transaction, 'received'),
|
|
468
503
|
address: derived.value,
|
|
469
504
|
representativeAddress: representative.value,
|
|
470
505
|
amount: (0, protocol_1.amountOutput)(receivable.amount),
|
|
@@ -477,66 +512,234 @@ async function executeAttoOperation(context, operation, parameters, credentialDa
|
|
|
477
512
|
const block = (0, protocol_1.createChangeBlock)(account, representative, timestamp);
|
|
478
513
|
const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
|
|
479
514
|
return {
|
|
480
|
-
...(0, protocol_1.transactionOutput)(transaction,
|
|
515
|
+
...(0, protocol_1.transactionOutput)(transaction, 'representative_changed'),
|
|
481
516
|
address: derived.value,
|
|
482
517
|
representativeAddress: representative.value,
|
|
483
518
|
};
|
|
484
519
|
}
|
|
485
520
|
throw new Error(`Unsupported Atto operation: ${operation}`);
|
|
486
521
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
maxItems: POLL_MAX_ITEMS,
|
|
498
|
-
timeoutMs: POLL_TIMEOUT_MS,
|
|
499
|
-
})).map((item) => (0, protocol_1.accountOutput)(item, true));
|
|
500
|
-
}
|
|
501
|
-
function pollItemKey(event, item) {
|
|
502
|
-
if (event === 'account')
|
|
503
|
-
return `${String(item.address)}:${String(item.frontier)}:${String(item.balance?.raw ?? '')}`;
|
|
504
|
-
return String(item.hash ?? JSON.stringify(item));
|
|
505
|
-
}
|
|
506
|
-
function newPollItems(context, event, items) {
|
|
507
|
-
const staticData = context.getWorkflowStaticData('node');
|
|
508
|
-
const stateKey = `seen_${event}`;
|
|
509
|
-
const existing = Array.isArray(staticData[stateKey]) ? staticData[stateKey].map(String) : [];
|
|
510
|
-
const seen = new Set(existing);
|
|
511
|
-
const fresh = items.filter((item) => !seen.has(pollItemKey(event, item)));
|
|
512
|
-
const updated = [...existing, ...items.map((item) => pollItemKey(event, item))].slice(-MAX_SEEN_POLL_ITEMS);
|
|
513
|
-
staticData[stateKey] = updated;
|
|
514
|
-
if (existing.length === 0 && context.getMode() !== 'manual')
|
|
515
|
-
return [];
|
|
516
|
-
return fresh;
|
|
517
|
-
}
|
|
518
|
-
async function pollAttoEvent(context, event, parameters, credentialData) {
|
|
522
|
+
function addressStreamRoute(event, addresses) {
|
|
523
|
+
if (!addresses)
|
|
524
|
+
return { event, route: 'all' };
|
|
525
|
+
return {
|
|
526
|
+
event,
|
|
527
|
+
route: addresses.length === 1 ? 'publicKey' : 'addresses',
|
|
528
|
+
addresses,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
async function attoStreamRequest(event, parameters, credentialData) {
|
|
519
532
|
const value = credentials(credentialData);
|
|
520
|
-
let items;
|
|
521
533
|
if (event === 'receivable') {
|
|
522
|
-
|
|
534
|
+
const request = addressStreamRoute(event, await addressesForSource(parameters, value));
|
|
535
|
+
return {
|
|
536
|
+
...request,
|
|
537
|
+
minAmount: optionalText(parameters.minAmount)
|
|
538
|
+
? (0, protocol_1.parseAmount)(parameters.minAmount, parameters.minAmountUnit ?? 'RAW', 'Minimum Amount').toString()
|
|
539
|
+
: '0',
|
|
540
|
+
};
|
|
523
541
|
}
|
|
524
|
-
|
|
525
|
-
|
|
542
|
+
if (event === 'account') {
|
|
543
|
+
return addressStreamRoute(event, await addressesForSource(parameters, value, true));
|
|
526
544
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
545
|
+
const mode = queryMode(parameters.queryMode);
|
|
546
|
+
if (mode === 'hash') {
|
|
547
|
+
return {
|
|
548
|
+
event,
|
|
549
|
+
route: 'hash',
|
|
550
|
+
hash: (0, protocol_1.parseHash)(parameters.hash),
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const request = addressStreamRoute(event, await addressesForQuery(parameters, value));
|
|
554
|
+
if (request.route === 'all')
|
|
555
|
+
return request;
|
|
556
|
+
return {
|
|
557
|
+
...request,
|
|
558
|
+
fromHeight: positiveHeight(parameters.fromHeight, 'From Height') ?? '1',
|
|
559
|
+
toHeight: positiveHeight(parameters.toHeight, 'To Height'),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function requiredStreamAddresses(request) {
|
|
563
|
+
const addresses = request.addresses ?? [];
|
|
564
|
+
if (addresses.length === 0)
|
|
565
|
+
throw new Error('Atto stream route requires at least one address');
|
|
566
|
+
return addresses;
|
|
567
|
+
}
|
|
568
|
+
function streamHeightQuery(request) {
|
|
569
|
+
const fromHeight = request.fromHeight ?? '1';
|
|
570
|
+
return `fromHeight=${encodeURIComponent(fromHeight)}${request.toHeight ? `&toHeight=${encodeURIComponent(request.toHeight)}` : ''}`;
|
|
571
|
+
}
|
|
572
|
+
function streamHeightSearch(request) {
|
|
573
|
+
return {
|
|
574
|
+
search: requiredStreamAddresses(request).map((address) => ({
|
|
575
|
+
address: address.value,
|
|
576
|
+
fromHeight: request.fromHeight ?? '1',
|
|
577
|
+
...(request.toHeight ? { toHeight: request.toHeight } : {}),
|
|
578
|
+
})),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function attoHttpStreamRequest(request) {
|
|
582
|
+
if (request.event === 'account') {
|
|
583
|
+
if (request.route === 'all')
|
|
584
|
+
return { method: 'GET', path: 'accounts/stream' };
|
|
585
|
+
const addresses = requiredStreamAddresses(request);
|
|
586
|
+
if (request.route === 'publicKey') {
|
|
587
|
+
return { method: 'GET', path: `accounts/${addresses[0].publicKey}/stream` };
|
|
532
588
|
}
|
|
533
|
-
|
|
534
|
-
|
|
589
|
+
if (request.route === 'addresses') {
|
|
590
|
+
return {
|
|
591
|
+
method: 'POST',
|
|
592
|
+
path: 'accounts/stream',
|
|
593
|
+
body: { addresses: addresses.map((address) => address.value) },
|
|
594
|
+
};
|
|
535
595
|
}
|
|
536
596
|
}
|
|
537
|
-
|
|
538
|
-
|
|
597
|
+
if (request.event === 'receivable') {
|
|
598
|
+
const addresses = requiredStreamAddresses(request);
|
|
599
|
+
const path = `accounts/receivables/stream?minAmount=${encodeURIComponent(request.minAmount ?? '0')}`;
|
|
600
|
+
if (request.route === 'publicKey') {
|
|
601
|
+
return {
|
|
602
|
+
method: 'GET',
|
|
603
|
+
path: `accounts/${addresses[0].publicKey}/receivables/stream?minAmount=${encodeURIComponent(request.minAmount ?? '0')}`,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
if (request.route === 'addresses') {
|
|
607
|
+
return {
|
|
608
|
+
method: 'POST',
|
|
609
|
+
path,
|
|
610
|
+
body: { addresses: addresses.map((address) => address.value) },
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (request.event === 'transaction') {
|
|
615
|
+
if (request.route === 'all')
|
|
616
|
+
return { method: 'GET', path: 'transactions/stream' };
|
|
617
|
+
if (request.route === 'hash' && request.hash) {
|
|
618
|
+
return { method: 'GET', path: `transactions/${request.hash}/stream` };
|
|
619
|
+
}
|
|
620
|
+
const addresses = requiredStreamAddresses(request);
|
|
621
|
+
if (request.route === 'publicKey') {
|
|
622
|
+
return {
|
|
623
|
+
method: 'GET',
|
|
624
|
+
path: `accounts/${addresses[0].publicKey}/transactions/stream?${streamHeightQuery(request)}`,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
if (request.route === 'addresses') {
|
|
628
|
+
return {
|
|
629
|
+
method: 'POST',
|
|
630
|
+
path: 'accounts/transactions/stream',
|
|
631
|
+
body: streamHeightSearch(request),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (request.event === 'accountEntry') {
|
|
636
|
+
if (request.route === 'all')
|
|
637
|
+
return { method: 'GET', path: 'accounts/entries/stream' };
|
|
638
|
+
if (request.route === 'hash' && request.hash) {
|
|
639
|
+
return { method: 'GET', path: `accounts/entries/${request.hash}/stream` };
|
|
640
|
+
}
|
|
641
|
+
const addresses = requiredStreamAddresses(request);
|
|
642
|
+
if (request.route === 'publicKey') {
|
|
643
|
+
return {
|
|
644
|
+
method: 'GET',
|
|
645
|
+
path: `accounts/${addresses[0].publicKey}/entries/stream?${streamHeightQuery(request)}`,
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (request.route === 'addresses') {
|
|
649
|
+
return {
|
|
650
|
+
method: 'POST',
|
|
651
|
+
path: 'accounts/entries/stream',
|
|
652
|
+
body: streamHeightSearch(request),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
539
655
|
}
|
|
540
|
-
|
|
656
|
+
throw new Error(`Unsupported Atto ${request.event} stream route: ${request.route}`);
|
|
657
|
+
}
|
|
658
|
+
async function startAttoEventStream(context, event, parameters, credentialData, onEvent, runtime = DEFAULT_STREAM_RUNTIME) {
|
|
659
|
+
const value = credentials(credentialData);
|
|
660
|
+
requireNodeUrl(value);
|
|
661
|
+
requestHeaders(value, 'application/x-ndjson');
|
|
662
|
+
const request = attoHttpStreamRequest(await attoStreamRequest(event, parameters, credentialData));
|
|
663
|
+
let activeController;
|
|
664
|
+
let activeConnection;
|
|
665
|
+
let reconnectTimer;
|
|
666
|
+
let reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
|
|
667
|
+
let generation = 0;
|
|
668
|
+
let closed = false;
|
|
669
|
+
const scheduleReconnect = () => {
|
|
670
|
+
if (closed || activeController || reconnectTimer !== undefined)
|
|
671
|
+
return;
|
|
672
|
+
const delayMs = reconnectDelayMs;
|
|
673
|
+
reconnectDelayMs = Math.min(reconnectDelayMs * 2, MAX_RECONNECT_DELAY_MS);
|
|
674
|
+
reconnectTimer = runtime.setTimeout(() => {
|
|
675
|
+
reconnectTimer = undefined;
|
|
676
|
+
connect();
|
|
677
|
+
}, delayMs);
|
|
678
|
+
};
|
|
679
|
+
const connect = () => {
|
|
680
|
+
if (closed || activeController)
|
|
681
|
+
return;
|
|
682
|
+
const streamGeneration = ++generation;
|
|
683
|
+
const controller = new AbortController();
|
|
684
|
+
activeController = controller;
|
|
685
|
+
activeConnection = (async () => {
|
|
686
|
+
try {
|
|
687
|
+
const response = await runtime.open(context, value, request, controller.signal);
|
|
688
|
+
let pending = '';
|
|
689
|
+
for await (const chunk of response) {
|
|
690
|
+
if (closed || streamGeneration !== generation)
|
|
691
|
+
return;
|
|
692
|
+
pending += streamChunk(chunk);
|
|
693
|
+
const lines = pending.split('\n');
|
|
694
|
+
pending = lines.pop() ?? '';
|
|
695
|
+
for (const line of lines) {
|
|
696
|
+
if (!line.trim())
|
|
697
|
+
continue;
|
|
698
|
+
const parsed = (0, protocol_1.parseAttoJson)(line);
|
|
699
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
700
|
+
throw new Error(`Atto ${event} stream item must contain a JSON object`);
|
|
701
|
+
}
|
|
702
|
+
onEvent((0, protocol_1.streamEventOutput)(event, parsed));
|
|
703
|
+
reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (pending.trim() && !closed && streamGeneration === generation) {
|
|
707
|
+
const parsed = (0, protocol_1.parseAttoJson)(pending);
|
|
708
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
709
|
+
throw new Error(`Atto ${event} stream item must contain a JSON object`);
|
|
710
|
+
}
|
|
711
|
+
onEvent((0, protocol_1.streamEventOutput)(event, parsed));
|
|
712
|
+
reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
catch {
|
|
716
|
+
}
|
|
717
|
+
finally {
|
|
718
|
+
if (streamGeneration === generation) {
|
|
719
|
+
activeController = undefined;
|
|
720
|
+
activeConnection = undefined;
|
|
721
|
+
scheduleReconnect();
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
})();
|
|
725
|
+
};
|
|
726
|
+
connect();
|
|
727
|
+
return {
|
|
728
|
+
async close() {
|
|
729
|
+
if (closed)
|
|
730
|
+
return;
|
|
731
|
+
closed = true;
|
|
732
|
+
generation++;
|
|
733
|
+
if (reconnectTimer !== undefined) {
|
|
734
|
+
runtime.clearTimeout(reconnectTimer);
|
|
735
|
+
reconnectTimer = undefined;
|
|
736
|
+
}
|
|
737
|
+
const connection = activeConnection;
|
|
738
|
+
activeController?.abort();
|
|
739
|
+
activeController = undefined;
|
|
740
|
+
activeConnection = undefined;
|
|
741
|
+
await connection;
|
|
742
|
+
},
|
|
743
|
+
};
|
|
541
744
|
}
|
|
542
745
|
//# sourceMappingURL=operations.js.map
|