@attocash/n8n-nodes-atto 0.3.2 → 0.4.1
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 +16 -12
- package/THIRD_PARTY_NOTICES.md +32 -0
- package/dist/credentials/AttoApi.credentials.js +1 -1
- package/dist/nodes/Atto/Atto.node.js +4 -17
- package/dist/nodes/Atto/Atto.node.json +2 -2
- 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/nodes/AttoTrigger/AttoTrigger.node.json +2 -2
- package/dist/package.json +6 -7
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @attocash/n8n-nodes-atto
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@attocash/n8n-nodes-atto)
|
|
4
|
-
[](https://github.com/attocash/integrations/actions/workflows/n8n-node-package.yml)
|
|
5
|
-
[](https://github.com/attocash/integrations/blob/main/LICENSE)
|
|
4
|
+
[](https://github.com/attocash/integrations-n8n/actions/workflows/n8n-node-package.yml)
|
|
5
|
+
[](https://github.com/attocash/integrations-n8n/blob/main/LICENSE)
|
|
6
6
|
|
|
7
7
|
Use Atto from n8n workflows.
|
|
8
8
|
|
|
@@ -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):
|
|
@@ -99,7 +101,7 @@ After importing an example, attach your **Atto API** credential and replace any
|
|
|
99
101
|
Install dependencies and run the checks:
|
|
100
102
|
|
|
101
103
|
```bash
|
|
102
|
-
npm install
|
|
104
|
+
npm install --ignore-scripts
|
|
103
105
|
npm run build
|
|
104
106
|
npm test
|
|
105
107
|
npm run lint
|
|
@@ -107,6 +109,8 @@ npm run lint
|
|
|
107
109
|
|
|
108
110
|
`npm test` builds the package and runs unit, smoke, and integration tests. The integration test uses `AttoNodeMockAsyncBuilder` and `AttoWorkerMockAsyncBuilder` from `@attocash/commons-test`. It uses Docker when available and falls back to a local Podman socket.
|
|
109
111
|
|
|
112
|
+
Dependencies ship prebuilt, so lifecycle scripts are disabled to avoid transitive package-manager guard scripts.
|
|
113
|
+
|
|
110
114
|
To require the mock-container integration path:
|
|
111
115
|
|
|
112
116
|
```bash
|
|
@@ -139,8 +143,8 @@ If you have shell access inside the n8n container:
|
|
|
139
143
|
|
|
140
144
|
```bash
|
|
141
145
|
cd /tmp
|
|
142
|
-
git clone https://github.com/attocash/integrations.git
|
|
143
|
-
cd integrations
|
|
146
|
+
git clone https://github.com/attocash/integrations-n8n.git
|
|
147
|
+
cd integrations-n8n
|
|
144
148
|
npm run install:n8n
|
|
145
149
|
```
|
|
146
150
|
|
|
@@ -157,17 +161,17 @@ Restart n8n after the script finishes.
|
|
|
157
161
|
|
|
158
162
|
## Maintainers
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
Use conventional commits to select the next release version. Release-producing changes use the `n8n-node` scope:
|
|
161
165
|
|
|
162
166
|
```bash
|
|
163
|
-
|
|
164
|
-
npm version patch --no-git-tag-version
|
|
165
|
-
git add package.json package-lock.json
|
|
166
|
-
git commit -m "Release n8n Atto node vX.Y.Z"
|
|
167
|
-
git push origin main
|
|
167
|
+
git commit -m "fix(n8n-node): describe the fix"
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
On pushes to `main`, GitHub Actions
|
|
170
|
+
On pushes to `main`, GitHub Actions calculates the next version from the latest `n8n-node-vX.Y.Z` tag and tests the attempted package. When the manifests need updating, CI commits both files on `release/n8n-node-vX.Y.Z` and opens a release PR. After that protected PR is reviewed and merged, the next `main` run verifies the committed version, waits for approval in the `release` environment, creates the matching tag and GitHub release, and publishes the tested `.tgz` to npm.
|
|
171
|
+
|
|
172
|
+
Do not bump `package.json` manually. Snapshot versions exist only in their workflow runners; successful releases commit the version through the protected release PR so the repository and npm stay synchronized.
|
|
173
|
+
|
|
174
|
+
Allow GitHub Actions to create pull requests under **Settings → Actions → General → Workflow permissions**. A version PR created with `GITHUB_TOKEN` may require a maintainer to approve its workflow run before the required `test / test` check starts.
|
|
171
175
|
|
|
172
176
|
Configure npm Trusted Publishing for `.github/workflows/n8n-node-package.yml`.
|
|
173
177
|
|
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.
|
|
@@ -8,7 +8,7 @@ class AttoApi {
|
|
|
8
8
|
light: 'file:../nodes/Atto/atto.svg',
|
|
9
9
|
dark: 'file:../nodes/Atto/atto.dark.svg',
|
|
10
10
|
};
|
|
11
|
-
documentationUrl = 'https://github.com/attocash/integrations/blob/main/
|
|
11
|
+
documentationUrl = 'https://github.com/attocash/integrations-n8n/blob/main/USAGE.md';
|
|
12
12
|
properties = [
|
|
13
13
|
{
|
|
14
14
|
displayName: 'Node Base URL',
|
|
@@ -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',
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"resources": {
|
|
7
7
|
"credentialDocumentation": [
|
|
8
8
|
{
|
|
9
|
-
"url": "https://github.com/attocash/integrations
|
|
9
|
+
"url": "https://github.com/attocash/integrations-n8n#credentials"
|
|
10
10
|
}
|
|
11
11
|
],
|
|
12
12
|
"primaryDocumentation": [
|
|
13
13
|
{
|
|
14
|
-
"url": "https://github.com/attocash/integrations/blob/main/
|
|
14
|
+
"url": "https://github.com/attocash/integrations-n8n/blob/main/USAGE.md"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
|
@@ -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
|