@attocash/n8n-nodes-atto 0.4.2 → 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 CHANGED
@@ -5,7 +5,8 @@
5
5
  <p align="center">
6
6
  <a href="https://www.npmjs.com/package/@attocash/n8n-nodes-atto"><img src="https://img.shields.io/npm/v/@attocash/n8n-nodes-atto.svg" alt="npm version"></a>
7
7
  <a href="https://www.npmjs.com/package/@attocash/n8n-nodes-atto"><img src="https://img.shields.io/npm/dw/@attocash/n8n-nodes-atto.svg" alt="npm weekly downloads"></a>
8
- <a href="https://github.com/attocash/integrations-n8n/actions/workflows/n8n-node-package.yml"><img src="https://github.com/attocash/integrations-n8n/actions/workflows/n8n-node-package.yml/badge.svg" alt="n8n Atto Node CI"></a>
8
+ <a href="https://github.com/attocash/integrations-n8n/actions/workflows/n8n-node-package.yml"><img src="https://github.com/attocash/integrations-n8n/actions/workflows/n8n-node-package.yml/badge.svg" alt="Pipeline status"></a>
9
+ <a href="https://atto.cash"><img src="https://img.shields.io/badge/website-atto.cash-FAB005.svg" alt="Atto website"></a>
9
10
  <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
10
11
  </p>
11
12
 
@@ -101,7 +102,7 @@ Send and receive wait up to 60 seconds for publication by default. Stream reads
101
102
  | Transaction | Hash, credential-derived address, manual addresses, or all transactions |
102
103
  | Account Entry | Hash, credential-derived address, manual addresses, or all entries |
103
104
 
104
- 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.
105
106
 
106
107
  ## Example workflows
107
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 = attoHttpStreamRequest(await attoStreamRequest(event, parameters, credentialData));
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
- 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;
754
+ emitLine(line);
704
755
  }
705
756
  }
706
757
  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;
758
+ emitLine(pending);
713
759
  }
714
760
  }
715
761
  catch {
@@ -50,7 +50,6 @@ class AttoTrigger {
50
50
  },
51
51
  inputs: [],
52
52
  outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
53
- usableAsTool: true,
54
53
  credentials: [
55
54
  {
56
55
  name: 'attoApi',
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attocash/n8n-nodes-atto",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Atto cryptocurrency wallet, transaction, account, and trigger nodes for n8n, powered by Atto Commons",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attocash/n8n-nodes-atto",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Atto cryptocurrency wallet, transaction, account, and trigger nodes for n8n, powered by Atto Commons",
5
5
  "license": "MIT",
6
6
  "keywords": [