node-red-contrib-ipgeolocation 1.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/index.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * node-red-contrib-ipgeolocation
3
+ * Entry point — registers all nodes with the Node-RED runtime.
4
+ *
5
+ * Note: Node-RED loads nodes from the `node-red.nodes` map in package.json,
6
+ * so this file is primarily a convenience for `require()`-ing the package
7
+ * directly (e.g. in tests). It must stay in sync with package.json.
8
+ */
9
+ 'use strict';
10
+
11
+ module.exports = function (RED) {
12
+ require('./nodes/ipgeo-config')(RED);
13
+ require('./nodes/ipgeo-lookup')(RED);
14
+ require('./nodes/ipgeo-bulk')(RED);
15
+ require('./nodes/ipgeo-security')(RED);
16
+ require('./nodes/ipgeo-bulk-security')(RED);
17
+ require('./nodes/ipgeo-abuse')(RED);
18
+ require('./nodes/ipgeo-asn')(RED);
19
+ require('./nodes/ipgeo-timezone')(RED);
20
+ require('./nodes/ipgeo-astronomy')(RED);
21
+ require('./nodes/ipgeo-useragent')(RED);
22
+ };
@@ -0,0 +1,203 @@
1
+ <!-- ============================================================
2
+ ipgeo-abuse.html
3
+ Single-IP abuse contact lookup node — editor template + help
4
+ ============================================================ -->
5
+
6
+ <script type="text/javascript">
7
+ (function () {
8
+ 'use strict';
9
+
10
+ RED.nodes.registerType('ipgeo-abuse', {
11
+ category: 'IPGeolocation',
12
+ color: '#c2554f',
13
+ icon: 'font-awesome/fa-envelope',
14
+ paletteLabel:'ipgeo abuse',
15
+
16
+ defaults: {
17
+ name: { value: '' },
18
+ server: { value: '', type: 'ipgeo-config', required: true },
19
+ ipType: { value: 'str' },
20
+ ipValue: { value: '' },
21
+ fields: { value: '' },
22
+ excludes: { value: '' },
23
+ outputProp: { value: 'payload' },
24
+ outputType: { value: 'msg' },
25
+ twoOutputs: { value: true },
26
+ },
27
+
28
+ inputs: 1,
29
+ outputs: 2, // [success, error]
30
+
31
+ outputLabels: function (idx) {
32
+ return idx === 0 ? 'success' : 'error';
33
+ },
34
+
35
+ label: function () {
36
+ return this.name || 'ipgeo abuse';
37
+ },
38
+
39
+ labelStyle: function () {
40
+ return this.name ? 'node_label_italic' : '';
41
+ },
42
+
43
+ oneditprepare: function () {
44
+ const node = this;
45
+
46
+ // ── IP input — typed input widget ──────────────────────────────────
47
+ $('#node-input-ipValue').typedInput({
48
+ default: 'str',
49
+ typeField: '#node-input-ipType',
50
+ types: ['msg', 'flow', 'global', 'str'],
51
+ });
52
+
53
+ // ── Output property typed input ────────────────────────────────────
54
+ $('#node-input-outputProp').typedInput({
55
+ default: 'msg',
56
+ typeField: '#node-input-outputType',
57
+ types: ['msg', 'flow', 'global'],
58
+ });
59
+
60
+ // ── Two outputs checkbox ───────────────────────────────────────────
61
+ $('#node-input-twoOutputs').prop('checked', node.twoOutputs !== false);
62
+ },
63
+
64
+ oneditsave: function () {
65
+ this.twoOutputs = $('#node-input-twoOutputs').prop('checked');
66
+ },
67
+ });
68
+ }());
69
+ </script>
70
+
71
+ <!-- ── Edit template ── -->
72
+ <script type="text/html" data-template-name="ipgeo-abuse">
73
+ <!-- Name -->
74
+ <div class="form-row">
75
+ <label for="node-input-name"><i class="fa fa-tag"></i>&nbsp;Name</label>
76
+ <input type="text" id="node-input-name" placeholder="ipgeo abuse" style="width:70%"/>
77
+ </div>
78
+
79
+ <!-- Config node (shared with ipgeo-lookup / ipgeo-security) -->
80
+ <div class="form-row">
81
+ <label for="node-input-server"><i class="fa fa-key"></i>&nbsp;API Config</label>
82
+ <input type="text" id="node-input-server" style="width:70%"/>
83
+ <input type="hidden" id="node-input-ipType"/>
84
+ </div>
85
+
86
+ <hr/>
87
+
88
+ <!-- IP address input -->
89
+ <div class="form-row">
90
+ <label for="node-input-ipValue"><i class="fa fa-globe"></i>&nbsp;IP Address</label>
91
+ <input type="text" id="node-input-ipValue" placeholder="Leave empty to detect caller IP" style="width:70%"/>
92
+ <div style="margin-top:4px;font-size:0.82em;color:#888;">
93
+ IPv4 or IPv6 only. Leave empty to auto-detect the caller's IP. You
94
+ can also point this to a msg/flow/global property.
95
+ </div>
96
+ </div>
97
+
98
+ <hr/>
99
+
100
+ <!-- Fields & Excludes -->
101
+ <div class="form-row">
102
+ <label for="node-input-fields"><i class="fa fa-filter"></i>&nbsp;Fields</label>
103
+ <input type="text" id="node-input-fields" placeholder="e.g. abuse.emails,abuse.phone_numbers" style="width:70%"/>
104
+ </div>
105
+ <div class="form-row">
106
+ <label for="node-input-excludes"><i class="fa fa-minus-square"></i>&nbsp;Excludes</label>
107
+ <input type="text" id="node-input-excludes" placeholder="e.g. abuse.address" style="width:70%"/>
108
+ </div>
109
+
110
+ <hr/>
111
+
112
+ <!-- Output property -->
113
+ <div class="form-row">
114
+ <label for="node-input-outputProp"><i class="fa fa-sign-out"></i>&nbsp;Output to</label>
115
+ <input type="text" id="node-input-outputProp" style="width:70%"/>
116
+ <input type="hidden" id="node-input-outputType"/>
117
+ </div>
118
+
119
+ <!-- Two outputs toggle -->
120
+ <div class="form-row">
121
+ <label><i class="fa fa-random"></i>&nbsp;Outputs</label>
122
+ <input type="checkbox" id="node-input-twoOutputs" style="width:auto;margin-right:6px"/>
123
+ <label for="node-input-twoOutputs" style="display:inline;font-weight:normal">
124
+ Use second output for errors (instead of throwing to Catch node)
125
+ </label>
126
+ </div>
127
+ </script>
128
+
129
+ <!-- ── Help panel ── -->
130
+ <script type="text/html" data-help-name="ipgeo-abuse">
131
+ <p>
132
+ Looks up abuse-reporting contact details for a single IPv4 or IPv6
133
+ address via <strong>IPGeolocation.io v3 Abuse Contact API</strong>:
134
+ responsible organization, abuse-handling role, contact emails, phone
135
+ numbers, and registered address.
136
+ </p>
137
+
138
+ <h3>Inputs</h3>
139
+ <dl class="message-properties">
140
+ <dt>payload <span class="property-type">any</span></dt>
141
+ <dd>Passed through unchanged.</dd>
142
+ </dl>
143
+
144
+ <h3>Outputs</h3>
145
+ <ol class="node-ports">
146
+ <li>
147
+ <strong>Success</strong>
148
+ <dl class="message-properties">
149
+ <dt>payload <span class="property-type">object</span></dt>
150
+ <dd>
151
+ <code>{ ip, abuse }</code> where <code>abuse</code> contains
152
+ <code>route</code>, <code>country</code>, <code>name</code>,
153
+ <code>organization</code>, <code>kind</code>
154
+ (<code>group</code> | <code>individual</code>),
155
+ <code>address</code>, <code>emails</code> (array),
156
+ and <code>phone_numbers</code> (array).
157
+ </dd>
158
+ <dt class="optional">ipgeo_credits <span class="property-type">number</span></dt>
159
+ <dd>Credits consumed by this request (flat 1 credit per lookup).</dd>
160
+ </dl>
161
+ </li>
162
+ <li>
163
+ <strong>Error</strong> (when <em>Use second output</em> is checked)
164
+ <dl class="message-properties">
165
+ <dt>error <span class="property-type">object</span></dt>
166
+ <dd>
167
+ <code>{ code, message, status, body }</code> — structured error object.
168
+ Error codes include <code>AUTH_FAILED</code>, <code>RATE_LIMITED</code>,
169
+ <code>TIMEOUT</code>, <code>BAD_REQUEST</code>, etc.
170
+ </dd>
171
+ </dl>
172
+ </li>
173
+ </ol>
174
+
175
+ <h3>Details</h3>
176
+ <ul>
177
+ <li>Leave <em>IP Address</em> empty to look up the caller's IP address.</li>
178
+ <li>Every successful lookup costs a flat <strong>1 credit</strong>.</li>
179
+ <li>
180
+ <em>Fields</em> restricts the response to named fields
181
+ (e.g. <code>abuse.emails</code>).
182
+ </li>
183
+ <li>
184
+ <em>Excludes</em> removes fields from the <code>abuse</code> object
185
+ (e.g. <code>abuse.address</code>). The top-level <code>ip</code>
186
+ field cannot be excluded.
187
+ </li>
188
+ <li>Any field may legitimately come back empty if the registry has no data for it.</li>
189
+ <li>
190
+ Bogon/private IPs are rejected with HTTP 423 and are not billed.
191
+ </li>
192
+ <li>Failed requests are retried up to 3 times with exponential back-off.</li>
193
+ </ul>
194
+
195
+ <h3>References</h3>
196
+ <ul>
197
+ <li>
198
+ <a href="https://ipgeolocation.io/documentation/ip-abuse-contact-api.html" target="_blank">
199
+ IP Abuse Contact API documentation
200
+ </a>
201
+ </li>
202
+ </ul>
203
+ </script>
@@ -0,0 +1,148 @@
1
+ /**
2
+ * ipgeo-abuse.js
3
+ *
4
+ * Node-RED node: "ipgeo abuse"
5
+ *
6
+ * Performs a single-IP abuse contact lookup via the IPGeolocation.io
7
+ * /v3/abuse endpoint (GET).
8
+ *
9
+ * Features
10
+ * ─────────
11
+ * • IPv4 or IPv6 input, or empty (caller IP auto-detect)
12
+ * • `fields` and `excludes` for bandwidth optimisation (dot-notation,
13
+ * e.g. abuse.emails / abuse.phone_numbers)
14
+ * • IP can be sourced from node config OR msg property (dynamic)
15
+ * • Two outputs: [success, error] — error output is optional
16
+ * • Exposes credits consumed on msg.ipgeo_credits (flat 1 credit/lookup)
17
+ * • Full retry + exponential backoff via shared api-client
18
+ *
19
+ * Shares the same `ipgeo-config` config node (API key) as ipgeo-lookup
20
+ * and ipgeo-security.
21
+ */
22
+
23
+ 'use strict';
24
+
25
+ const { request, IPGeoError } = require('./lib/api-client');
26
+ const {
27
+ setStatus,
28
+ resolveValue,
29
+ setOutputProperty,
30
+ handleNodeError,
31
+ buildCommonParams,
32
+ } = require('./lib/node-helpers');
33
+
34
+ module.exports = function (RED) {
35
+
36
+ function IPGeoAbuseNode(config) {
37
+ RED.nodes.createNode(this, config);
38
+
39
+ // ── Config node (API key) ─────────────────────────────────────────────
40
+ const configNode = RED.nodes.getNode(config.server);
41
+
42
+ // ── Node configuration ────────────────────────────────────────────────
43
+ this.name = config.name;
44
+ this.ipType = config.ipType || 'str'; // 'str' | 'msg' | 'flow' | 'global'
45
+ this.ipValue = config.ipValue || ''; // value or msg/flow/global prop path
46
+ this.fields = config.fields || '';
47
+ this.excludes = config.excludes || '';
48
+ this.outputProp = config.outputProp || 'payload'; // output property
49
+ this.outputType = config.outputType || 'msg';
50
+ this.twoOutputs = config.twoOutputs !== false; // default true
51
+
52
+ const node = this;
53
+
54
+ // ── Pre-flight: warn if no config node ───────────────────────────────
55
+ if (!configNode) {
56
+ setStatus(node, 'NO_KEY');
57
+ node.warn('No IPGeolocation.io config node selected — configure an API key first.');
58
+ } else {
59
+ setStatus(node, 'IDLE');
60
+ }
61
+
62
+ // ── Input handler ─────────────────────────────────────────────────────
63
+ node.on('input', async function (msg, send, done) {
64
+ // Node-RED 0.x compat
65
+ send = send || function () { node.send.apply(node, arguments); };
66
+
67
+ // ── Validate API key availability ─────────────────────────────────
68
+ const apiKey = configNode && configNode.credentials
69
+ ? configNode.credentials.apiKey
70
+ : null;
71
+
72
+ if (!apiKey) {
73
+ setStatus(node, 'NO_KEY');
74
+ const err = new IPGeoError('No API key configured', 'NO_API_KEY');
75
+ handleNodeError(node, err, msg, done, send, node.twoOutputs);
76
+ return;
77
+ }
78
+
79
+ // ── Resolve IP from msg/flow/global or static config ──────────────
80
+ let ip = '';
81
+ if (node.ipType === 'msg') {
82
+ ip = resolveValue(RED, node, msg,
83
+ { value: node.ipValue, type: 'msg' }) || '';
84
+ } else if (node.ipType === 'flow') {
85
+ ip = resolveValue(RED, node, msg,
86
+ { value: node.ipValue, type: 'flow' }) || '';
87
+ } else if (node.ipType === 'global') {
88
+ ip = resolveValue(RED, node, msg,
89
+ { value: node.ipValue, type: 'global' }) || '';
90
+ } else {
91
+ // 'str' — static value from node config
92
+ ip = (node.ipValue || '').trim();
93
+ }
94
+
95
+ ip = String(ip).trim();
96
+
97
+ // ── Build query parameters ────────────────────────────────────────
98
+ // Abuse endpoint has no `lang` or `include` params — just
99
+ // apiKey / ip / fields / excludes. Like /ipgeo and /security, an
100
+ // omitted ip auto-detects the caller's IP.
101
+ const params = buildCommonParams(apiKey, {
102
+ fields: node.fields,
103
+ excludes: node.excludes,
104
+ });
105
+
106
+ if (ip) params.ip = ip;
107
+
108
+ // ── Set working status ────────────────────────────────────────────
109
+ setStatus(node, 'WORKING');
110
+
111
+ // ── API call ──────────────────────────────────────────────────────
112
+ try {
113
+ const { data, credits } = await request({
114
+ method: 'GET',
115
+ path: '/abuse',
116
+ params,
117
+ });
118
+
119
+ // ── Success ────────────────────────────────────────────────────
120
+ setStatus(node, 'OK', data.abuse?.organization || data.abuse?.name || data.ip || 'ok');
121
+
122
+ // Write result to configured output property
123
+ setOutputProperty(RED, node, msg,
124
+ { value: node.outputProp, type: node.outputType }, data);
125
+
126
+ // Expose credits consumed (informational) — flat 1 credit/lookup
127
+ if (credits !== null) msg.ipgeo_credits = credits;
128
+
129
+ // Clear error property if previously set
130
+ delete msg.error;
131
+
132
+ send(node.twoOutputs ? [msg, null] : msg);
133
+ if (done) done();
134
+
135
+ } catch (err) {
136
+ handleNodeError(node, err, msg, done, send, node.twoOutputs);
137
+ }
138
+ });
139
+
140
+ // ── Clean up on node removal/redeploy ─────────────────────────────────
141
+ node.on('close', function () {
142
+ setStatus(node, 'IDLE');
143
+ });
144
+ }
145
+
146
+ RED.nodes.registerType('ipgeo-abuse', IPGeoAbuseNode);
147
+
148
+ };
@@ -0,0 +1,297 @@
1
+ <!-- ============================================================
2
+ ipgeo-asn.html
3
+ ASN lookup node (by IP or by ASN number) — editor template + help
4
+ ============================================================ -->
5
+
6
+ <script type="text/javascript">
7
+ (function () {
8
+ 'use strict';
9
+
10
+ // ── Include module list ────────────────────────────────────────────────────
11
+ const INCLUDE_MODULES = [
12
+ { id: 'inc-peers', val: 'peers', label: 'peers' },
13
+ { id: 'inc-downstreams', val: 'downstreams', label: 'downstreams' },
14
+ { id: 'inc-upstreams', val: 'upstreams', label: 'upstreams' },
15
+ { id: 'inc-routes', val: 'routes', label: 'routes' },
16
+ { id: 'inc-whois', val: 'whois_response', label: 'whois_response' },
17
+ ];
18
+
19
+ RED.nodes.registerType('ipgeo-asn', {
20
+ category: 'IPGeolocation',
21
+ color: '#7aa874',
22
+ icon: 'font-awesome/fa-sitemap',
23
+ paletteLabel: 'ipgeo asn',
24
+
25
+ defaults: {
26
+ name: { value: '' },
27
+ server: { value: '', type: 'ipgeo-config', required: true },
28
+ queryType: { value: 'auto' },
29
+ ipType: { value: 'str' },
30
+ ipValue: { value: '' },
31
+ asnType: { value: 'str' },
32
+ asnValue: { value: '' },
33
+ include: { value: '' },
34
+ fields: { value: '' },
35
+ excludes: { value: '' },
36
+ outputProp: { value: 'payload' },
37
+ outputType: { value: 'msg' },
38
+ twoOutputs: { value: true },
39
+ },
40
+
41
+ inputs: 1,
42
+ outputs: 2, // [success, error]
43
+
44
+ outputLabels: function (idx) {
45
+ return idx === 0 ? 'success' : 'error';
46
+ },
47
+
48
+ label: function () {
49
+ return this.name || 'ipgeo asn';
50
+ },
51
+
52
+ labelStyle: function () {
53
+ return this.name ? 'node_label_italic' : '';
54
+ },
55
+
56
+ oneditprepare: function () {
57
+ const node = this;
58
+
59
+ // ── IP input — typed input widget ──────────────────────────────────
60
+ $('#node-input-ipValue').typedInput({
61
+ default: 'str',
62
+ typeField: '#node-input-ipType',
63
+ types: ['msg', 'flow', 'global', 'str'],
64
+ });
65
+
66
+ // ── ASN input — typed input widget ─────────────────────────────────
67
+ $('#node-input-asnValue').typedInput({
68
+ default: 'str',
69
+ typeField: '#node-input-asnType',
70
+ types: ['msg', 'flow', 'global', 'str'],
71
+ });
72
+
73
+ // ── Query type — show/hide the relevant row ─────────────────────────
74
+ function toggleQueryRows() {
75
+ const qt = $('#node-input-queryType').val();
76
+ $('#ipgeo-asn-ip-row').toggle(qt === 'ip');
77
+ $('#ipgeo-asn-asn-row').toggle(qt === 'asn');
78
+ }
79
+ $('#node-input-queryType').on('change', toggleQueryRows);
80
+ toggleQueryRows();
81
+
82
+ // ── Include checkboxes — encode/decode comma list ──────────────────
83
+ const incList = (node.include || '').split(',').map(s => s.trim()).filter(Boolean);
84
+ INCLUDE_MODULES.forEach(function (mod) {
85
+ const $cb = $('<input type="checkbox" style="margin-right:6px">')
86
+ .prop('id', mod.id)
87
+ .prop('checked', incList.includes(mod.val))
88
+ .data('val', mod.val);
89
+ const $label = $('<label>').attr('for', mod.id).text(mod.label)
90
+ .css({ display: 'inline', fontWeight: 'normal', marginRight: '12px' });
91
+ $('#ipgeo-asn-include-container').append($('<div>').css('display', 'inline-block').append($cb).append($label));
92
+ });
93
+
94
+ // ── Output property typed input ────────────────────────────────────
95
+ $('#node-input-outputProp').typedInput({
96
+ default: 'msg',
97
+ typeField: '#node-input-outputType',
98
+ types: ['msg', 'flow', 'global'],
99
+ });
100
+
101
+ // ── Two outputs checkbox ───────────────────────────────────────────
102
+ $('#node-input-twoOutputs').prop('checked', node.twoOutputs !== false);
103
+ },
104
+
105
+ oneditsave: function () {
106
+ // Collect include checkboxes back into a comma-separated string
107
+ const chosen = [];
108
+ INCLUDE_MODULES.forEach(function (mod) {
109
+ if ($('#' + mod.id).prop('checked')) chosen.push(mod.val);
110
+ });
111
+ this.include = chosen.join(',');
112
+ this.twoOutputs = $('#node-input-twoOutputs').prop('checked');
113
+ },
114
+ });
115
+ }());
116
+ </script>
117
+
118
+ <!-- ── Edit template ── -->
119
+ <script type="text/html" data-template-name="ipgeo-asn">
120
+ <!-- Name -->
121
+ <div class="form-row">
122
+ <label for="node-input-name"><i class="fa fa-tag"></i>&nbsp;Name</label>
123
+ <input type="text" id="node-input-name" placeholder="ipgeo asn" style="width:70%" />
124
+ </div>
125
+
126
+ <!-- Config node (shared with other ipgeo-* nodes) -->
127
+ <div class="form-row">
128
+ <label for="node-input-server"><i class="fa fa-key"></i>&nbsp;API Config</label>
129
+ <input type="text" id="node-input-server" style="width:70%" />
130
+ </div>
131
+
132
+ <hr />
133
+
134
+ <!-- Query mode -->
135
+ <div class="form-row">
136
+ <label for="node-input-queryType"><i class="fa fa-search"></i>&nbsp;Lookup by</label>
137
+ <select id="node-input-queryType" style="width:70%">
138
+ <option value="auto">Auto-detect (caller's IP)</option>
139
+ <option value="ip">IP address</option>
140
+ <option value="asn">ASN number</option>
141
+ </select>
142
+ </div>
143
+
144
+ <!-- IP row (shown when queryType === 'ip') -->
145
+ <div class="form-row" id="ipgeo-asn-ip-row">
146
+ <label for="node-input-ipValue" style="margin-left:20px"><i class="fa fa-globe"></i>&nbsp;IP Address</label>
147
+ <input type="text" id="node-input-ipValue" placeholder="e.g. 49.12.0.0 or msg.ip" style="width:60%" />
148
+ <input type="hidden" id="node-input-ipType" />
149
+ </div>
150
+
151
+ <!-- ASN row (shown when queryType === 'asn') -->
152
+ <div class="form-row" id="ipgeo-asn-asn-row">
153
+ <label for="node-input-asnValue" style="margin-left:20px"><i class="fa fa-hashtag"></i>&nbsp;ASN</label>
154
+ <input type="text" id="node-input-asnValue" placeholder="e.g. 24940 or AS24940" style="width:60%" />
155
+ <input type="hidden" id="node-input-asnType" />
156
+ </div>
157
+
158
+ <hr />
159
+
160
+ <!-- Include modules (checkboxes) -->
161
+ <div class="form-row">
162
+ <label><i class="fa fa-plus-square"></i>&nbsp;Include</label>
163
+ <div id="ipgeo-asn-include-container" style="display:inline-block;width:70%;vertical-align:top;line-height:2em">
164
+ </div>
165
+ </div>
166
+ <!-- Hidden field intentionally omitted — include is built/read entirely
167
+ via the checkboxes above in oneditprepare/oneditsave. -->
168
+
169
+ <hr />
170
+
171
+ <!-- Fields & Excludes -->
172
+ <div class="form-row">
173
+ <label for="node-input-fields"><i class="fa fa-filter"></i>&nbsp;Fields</label>
174
+ <input type="text" id="node-input-fields" placeholder="e.g. asn.organization,asn.country" style="width:70%" />
175
+ </div>
176
+ <div class="form-row">
177
+ <label for="node-input-excludes"><i class="fa fa-minus-square"></i>&nbsp;Excludes</label>
178
+ <input type="text" id="node-input-excludes" placeholder="e.g. asn.date_allocated,asn.allocation_status"
179
+ style="width:70%" />
180
+ </div>
181
+
182
+ <hr />
183
+
184
+ <!-- Output property -->
185
+ <div class="form-row">
186
+ <label for="node-input-outputProp"><i class="fa fa-sign-out"></i>&nbsp;Output to</label>
187
+ <input type="text" id="node-input-outputProp" style="width:70%" />
188
+ <input type="hidden" id="node-input-outputType" />
189
+ </div>
190
+
191
+ <!-- Two outputs toggle -->
192
+ <div class="form-row">
193
+ <label><i class="fa fa-random"></i>&nbsp;Outputs</label>
194
+ <input type="checkbox" id="node-input-twoOutputs" style="width:auto;margin-right:6px" />
195
+ <label for="node-input-twoOutputs" style="display:inline;font-weight:normal">
196
+ Use second output for errors (instead of throwing to Catch node)
197
+ </label>
198
+ </div>
199
+ </script>
200
+
201
+ <!-- ── Help panel ── -->
202
+ <script type="text/html" data-help-name="ipgeo-asn">
203
+ <p>
204
+ Looks up Autonomous System Number (ASN) details — organization, country,
205
+ type, allocation date, RIR, and (optionally) peers, upstreams,
206
+ downstreams, routes, and raw WHOIS text — via
207
+ <strong>IPGeolocation.io v3 ASN API</strong>.
208
+ </p>
209
+
210
+ <h3>Inputs</h3>
211
+ <dl class="message-properties">
212
+ <dt>payload <span class="property-type">any</span></dt>
213
+ <dd>Passed through unchanged.</dd>
214
+ <dt class="optional">ipgeo_include <span class="property-type">string</span></dt>
215
+ <dd>
216
+ Override the node's <em>Include</em> setting per-message.
217
+ Comma-separated list, e.g. <code>"peers,routes"</code>.
218
+ </dd>
219
+ </dl>
220
+
221
+ <h3>Outputs</h3>
222
+ <ol class="node-ports">
223
+ <li>
224
+ <strong>Success</strong>
225
+ <dl class="message-properties">
226
+ <dt>payload <span class="property-type">object</span></dt>
227
+ <dd>
228
+ <code>{ asn, ip? }</code> — <code>ip</code> is present only when
229
+ looking up by IP address or auto-detected caller IP; it is
230
+ omitted when looking up directly by ASN number.
231
+ <code>asn</code> contains <code>as_number</code>,
232
+ <code>organization</code>, <code>country</code>,
233
+ <code>asn_name</code>, <code>type</code>, <code>domain</code>,
234
+ <code>date_allocated</code>, <code>rir</code>,
235
+ <code>allocation_status</code>, <code>num_of_ipv4_routes</code>,
236
+ <code>num_of_ipv6_routes</code>, and (when included)
237
+ <code>peers</code>, <code>downstreams</code>,
238
+ <code>upstreams</code>, <code>routes</code>,
239
+ <code>whois_response</code>.
240
+ </dd>
241
+ <dt class="optional">ipgeo_credits <span class="property-type">number</span></dt>
242
+ <dd>Credits consumed by this request (flat 1 credit per lookup).</dd>
243
+ </dl>
244
+ </li>
245
+ <li>
246
+ <strong>Error</strong> (when <em>Use second output</em> is checked)
247
+ <dl class="message-properties">
248
+ <dt>error <span class="property-type">object</span></dt>
249
+ <dd>
250
+ <code>{ code, message, status, body }</code> — structured error object.
251
+ Error codes include <code>AUTH_FAILED</code>, <code>RATE_LIMITED</code>,
252
+ <code>TIMEOUT</code>, <code>BAD_REQUEST</code>, etc.
253
+ </dd>
254
+ </dl>
255
+ </li>
256
+ </ol>
257
+
258
+ <h3>Details</h3>
259
+ <ul>
260
+ <li>
261
+ <em>Lookup by</em> chooses which single parameter is sent:
262
+ <strong>Auto-detect</strong> sends neither <code>ip</code> nor
263
+ <code>asn</code> and the API resolves the caller's IP;
264
+ <strong>IP address</strong> sends <code>ip</code>;
265
+ <strong>ASN number</strong> sends <code>asn</code> (the
266
+ <code>AS</code> prefix, if typed, is stripped automatically).
267
+ </li>
268
+ <li>
269
+ Use <em>Include</em> checkboxes to add <code>peers</code>,
270
+ <code>downstreams</code>, <code>upstreams</code>, <code>routes</code>,
271
+ and/or <code>whois_response</code> to the response. None of these
272
+ add extra credit cost — the lookup is always flat-rate.
273
+ </li>
274
+ <li>
275
+ <em>Fields</em> restricts the response to named fields
276
+ (e.g. <code>asn.organization,asn.country</code>). If both
277
+ <em>Include</em> and <em>Fields</em> are set, only <em>Include</em>
278
+ is applied by the API.
279
+ </li>
280
+ <li>
281
+ <em>Excludes</em> removes fields from the response
282
+ (e.g. <code>asn.date_allocated,asn.allocation_status</code>). The
283
+ top-level <code>ip</code> field cannot be excluded.
284
+ </li>
285
+ <li>Every successful lookup costs a flat <strong>1 credit</strong>.</li>
286
+ <li>Failed requests are retried up to 3 times with exponential back-off.</li>
287
+ </ul>
288
+
289
+ <h3>References</h3>
290
+ <ul>
291
+ <li>
292
+ <a href="https://ipgeolocation.io/documentation/asn-api.html" target="_blank">
293
+ ASN API documentation
294
+ </a>
295
+ </li>
296
+ </ul>
297
+ </script>