@attocash/n8n-nodes-atto 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/nodes/Atto/operations.js +60 -14
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ Send and receive wait up to 60 seconds for publication by default. Stream reads
|
|
|
102
102
|
| Transaction | Hash, credential-derived address, manual addresses, or all transactions |
|
|
103
103
|
| Account Entry | Hash, credential-derived address, manual addresses, or all entries |
|
|
104
104
|
|
|
105
|
-
Triggers use Atto's NDJSON endpoints. When a stream closes or fails, the node reconnects with exponential backoff from 1 to 30 seconds. Receiving an event resets the delay.
|
|
105
|
+
Triggers use Atto's NDJSON endpoints. When a stream closes or fails, the node reconnects with exponential backoff from 1 to 30 seconds. Receiving an event resets the delay. Address-filtered transaction and account-entry triggers resume after the last emitted height for each address; a configured To Height ends reconnection after every selected address reaches it. The resume positions are held in memory for the life of the trigger.
|
|
106
106
|
|
|
107
107
|
## Example workflows
|
|
108
108
|
|
|
@@ -655,11 +655,41 @@ function attoHttpStreamRequest(request) {
|
|
|
655
655
|
}
|
|
656
656
|
throw new Error(`Unsupported Atto ${request.event} stream route: ${request.route}`);
|
|
657
657
|
}
|
|
658
|
+
function resumedHttpStreamRequest(request, lastHeights) {
|
|
659
|
+
if ((request.event !== 'transaction' && request.event !== 'accountEntry') ||
|
|
660
|
+
(request.route !== 'publicKey' && request.route !== 'addresses')) {
|
|
661
|
+
return attoHttpStreamRequest(request);
|
|
662
|
+
}
|
|
663
|
+
const fromHeight = (address) => {
|
|
664
|
+
const lastHeight = lastHeights.get(address.value);
|
|
665
|
+
return lastHeight === undefined ? (request.fromHeight ?? '1') : (lastHeight + 1n).toString();
|
|
666
|
+
};
|
|
667
|
+
const addresses = requiredStreamAddresses(request);
|
|
668
|
+
if (request.route === 'publicKey') {
|
|
669
|
+
return attoHttpStreamRequest({ ...request, fromHeight: fromHeight(addresses[0]) });
|
|
670
|
+
}
|
|
671
|
+
const activeAddresses = addresses.filter((address) => !reachedToHeight(request, address, lastHeights));
|
|
672
|
+
return {
|
|
673
|
+
...attoHttpStreamRequest(request),
|
|
674
|
+
body: {
|
|
675
|
+
search: activeAddresses.map((address) => ({
|
|
676
|
+
address: address.value,
|
|
677
|
+
fromHeight: fromHeight(address),
|
|
678
|
+
...(request.toHeight ? { toHeight: request.toHeight } : {}),
|
|
679
|
+
})),
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function reachedToHeight(request, address, lastHeights) {
|
|
684
|
+
return request.toHeight !== undefined &&
|
|
685
|
+
(lastHeights.get(address.value) ?? -1n) >= BigInt(request.toHeight);
|
|
686
|
+
}
|
|
658
687
|
async function startAttoEventStream(context, event, parameters, credentialData, onEvent, runtime = DEFAULT_STREAM_RUNTIME) {
|
|
659
688
|
const value = credentials(credentialData);
|
|
660
689
|
requireNodeUrl(value);
|
|
661
690
|
requestHeaders(value, 'application/x-ndjson');
|
|
662
|
-
const request =
|
|
691
|
+
const request = await attoStreamRequest(event, parameters, credentialData);
|
|
692
|
+
const lastHeights = new Map();
|
|
663
693
|
let activeController;
|
|
664
694
|
let activeConnection;
|
|
665
695
|
let reconnectTimer;
|
|
@@ -669,6 +699,11 @@ async function startAttoEventStream(context, event, parameters, credentialData,
|
|
|
669
699
|
const scheduleReconnect = () => {
|
|
670
700
|
if (closed || activeController || reconnectTimer !== undefined)
|
|
671
701
|
return;
|
|
702
|
+
if (request.toHeight !== undefined &&
|
|
703
|
+
(request.event === 'transaction' || request.event === 'accountEntry') &&
|
|
704
|
+
(request.route === 'publicKey' || request.route === 'addresses') &&
|
|
705
|
+
requiredStreamAddresses(request).every((address) => reachedToHeight(request, address, lastHeights)))
|
|
706
|
+
return;
|
|
672
707
|
const delayMs = reconnectDelayMs;
|
|
673
708
|
reconnectDelayMs = Math.min(reconnectDelayMs * 2, MAX_RECONNECT_DELAY_MS);
|
|
674
709
|
reconnectTimer = runtime.setTimeout(() => {
|
|
@@ -684,7 +719,28 @@ async function startAttoEventStream(context, event, parameters, credentialData,
|
|
|
684
719
|
activeController = controller;
|
|
685
720
|
activeConnection = (async () => {
|
|
686
721
|
try {
|
|
687
|
-
const response = await runtime.open(context, value, request, controller.signal);
|
|
722
|
+
const response = await runtime.open(context, value, resumedHttpStreamRequest(request, lastHeights), controller.signal);
|
|
723
|
+
const emitLine = (line) => {
|
|
724
|
+
const parsed = (0, protocol_1.parseAttoJson)(line);
|
|
725
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
726
|
+
throw new Error(`Atto ${event} stream item must contain a JSON object`);
|
|
727
|
+
}
|
|
728
|
+
const output = (0, protocol_1.streamEventOutput)(event, parsed);
|
|
729
|
+
if ((event === 'transaction' || event === 'accountEntry') &&
|
|
730
|
+
(request.route === 'publicKey' || request.route === 'addresses')) {
|
|
731
|
+
const address = String(output.address);
|
|
732
|
+
const height = BigInt(String(output.height));
|
|
733
|
+
const lastHeight = lastHeights.get(address);
|
|
734
|
+
if (lastHeight !== undefined && height <= lastHeight)
|
|
735
|
+
return;
|
|
736
|
+
onEvent(output);
|
|
737
|
+
lastHeights.set(address, height);
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
onEvent(output);
|
|
741
|
+
}
|
|
742
|
+
reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
|
|
743
|
+
};
|
|
688
744
|
let pending = '';
|
|
689
745
|
for await (const chunk of response) {
|
|
690
746
|
if (closed || streamGeneration !== generation)
|
|
@@ -695,21 +751,11 @@ async function startAttoEventStream(context, event, parameters, credentialData,
|
|
|
695
751
|
for (const line of lines) {
|
|
696
752
|
if (!line.trim())
|
|
697
753
|
continue;
|
|
698
|
-
|
|
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;
|
|
754
|
+
emitLine(line);
|
|
704
755
|
}
|
|
705
756
|
}
|
|
706
757
|
if (pending.trim() && !closed && streamGeneration === generation) {
|
|
707
|
-
|
|
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;
|
|
758
|
+
emitLine(pending);
|
|
713
759
|
}
|
|
714
760
|
}
|
|
715
761
|
catch {
|
package/dist/package.json
CHANGED
package/package.json
CHANGED