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.
@@ -0,0 +1,214 @@
1
+ /**
2
+ * lib/node-helpers.js
3
+ *
4
+ * Utility functions shared across all IPGeolocation.io nodes.
5
+ *
6
+ * Contents
7
+ * ─────────
8
+ * • setStatus / clearStatus — node status shorthands
9
+ * • resolveValue — reads msg / flow / global / str values
10
+ * • setOutputProperty — writes to any msg / flow / global path
11
+ * • handleNodeError — standard error handler (status + done)
12
+ * • buildCommonParams — assembles shared query params (apiKey, lang, output, fields, excludes)
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ // ─── Status Helpers ──────────────────────────────────────────────────────────
18
+
19
+ const STATUS = {
20
+ IDLE: { fill: 'grey', shape: 'ring', text: '' },
21
+ WORKING: { fill: 'blue', shape: 'dot', text: 'requesting…' },
22
+ OK: { fill: 'green', shape: 'dot', text: 'ok' },
23
+ ERROR: { fill: 'red', shape: 'ring', text: 'error' },
24
+ NO_KEY: { fill: 'red', shape: 'ring', text: 'no API key' },
25
+ RATE_LIM: { fill: 'yellow', shape: 'ring', text: 'rate limited' },
26
+ };
27
+
28
+ /**
29
+ * Set a node status using a named key from STATUS, or a raw status object.
30
+ * @param {object} node
31
+ * @param {string|object} state Key from STATUS or raw { fill, shape, text }
32
+ * @param {string} [suffix] Optional text appended to STATUS[state].text
33
+ */
34
+ function setStatus(node, state, suffix) {
35
+ if (typeof state === 'string' && STATUS[state]) {
36
+ const s = { ...STATUS[state] };
37
+ if (suffix) s.text = suffix;
38
+ node.status(s);
39
+ } else {
40
+ node.status(state);
41
+ }
42
+ }
43
+
44
+ /** Clear a node's status indicator. */
45
+ function clearStatus(node) {
46
+ node.status({});
47
+ }
48
+
49
+ // ─── Value Resolution ────────────────────────────────────────────────────────
50
+
51
+ /**
52
+ * Resolve a typed value from the message context.
53
+ * Supports Node-RED's standard { value, type } descriptor types:
54
+ * msg, flow, global, str, num, bool, json, env
55
+ *
56
+ * @param {object} RED Node-RED runtime
57
+ * @param {object} node The node instance (for flow/global context access)
58
+ * @param {object} msg The current message
59
+ * @param {object} descriptor { value: string, type: string }
60
+ * @returns {*} Resolved value or undefined
61
+ */
62
+ function resolveValue(RED, node, msg, descriptor) {
63
+ if (!descriptor) return undefined;
64
+ const { value, type } = descriptor;
65
+
66
+ switch (type) {
67
+ case 'msg':
68
+ return RED.util.getMessageProperty(msg, value);
69
+
70
+ case 'flow':
71
+ return node.context().flow.get(value);
72
+
73
+ case 'global':
74
+ return node.context().global.get(value);
75
+
76
+ case 'str':
77
+ return String(value);
78
+
79
+ case 'num':
80
+ return Number(value);
81
+
82
+ case 'bool':
83
+ return value === true || value === 'true';
84
+
85
+ case 'json':
86
+ try { return JSON.parse(value); } catch { return undefined; }
87
+
88
+ case 'env':
89
+ return process.env[value];
90
+
91
+ default:
92
+ return value;
93
+ }
94
+ }
95
+
96
+ // ─── Output Property Setter ──────────────────────────────────────────────────
97
+
98
+ /**
99
+ * Write a value to a target on msg / flow / global.
100
+ *
101
+ * @param {object} RED
102
+ * @param {object} node
103
+ * @param {object} msg
104
+ * @param {object} descriptor { value: string, type: 'msg'|'flow'|'global' }
105
+ * @param {*} data Value to write
106
+ */
107
+ function setOutputProperty(RED, node, msg, descriptor, data) {
108
+ if (!descriptor) return;
109
+ const { value, type } = descriptor;
110
+
111
+ switch (type) {
112
+ case 'msg':
113
+ RED.util.setMessageProperty(msg, value, data);
114
+ break;
115
+ case 'flow':
116
+ node.context().flow.set(value, data);
117
+ break;
118
+ case 'global':
119
+ node.context().global.set(value, data);
120
+ break;
121
+ default:
122
+ RED.util.setMessageProperty(msg, value, data);
123
+ }
124
+ }
125
+
126
+ // ─── Error Handler ───────────────────────────────────────────────────────────
127
+
128
+ /**
129
+ * Standard error handler called from within input event listeners.
130
+ * Sets an appropriate status, logs to the Node-RED debug panel, and
131
+ * calls done(err) / node.error(err, msg) for Catch-node compatibility.
132
+ *
133
+ * @param {object} node
134
+ * @param {Error} err
135
+ * @param {object} msg
136
+ * @param {Function} done Node-RED 1.0+ done callback
137
+ * @param {Function} send Node-RED send function (used for error port if configured)
138
+ * @param {boolean} [twoOutputs] Whether the node uses a second (error) output
139
+ */
140
+ function handleNodeError(node, err, msg, done, send, twoOutputs) {
141
+ const code = (err.code || 'ERROR').toUpperCase();
142
+
143
+ // Choose the most informative status text
144
+ let statusText = err.message || 'unknown error';
145
+ if (statusText.length > 50) statusText = statusText.substring(0, 47) + '…';
146
+
147
+ if (code === 'RATE_LIMITED') {
148
+ setStatus(node, 'RATE_LIM');
149
+ } else {
150
+ setStatus(node, { fill: 'red', shape: 'ring', text: statusText });
151
+ }
152
+
153
+ // Attach error metadata to the message for downstream nodes
154
+ msg.error = {
155
+ code: err.code || 'ERROR',
156
+ message: err.message,
157
+ status: err.status || null,
158
+ body: err.body || null,
159
+ };
160
+
161
+ // If the node has a second output, emit error message there instead of throwing
162
+ if (twoOutputs && send) {
163
+ send([null, msg]);
164
+ if (done) done();
165
+ return;
166
+ }
167
+
168
+ // Standard Node-RED error handling (triggers Catch nodes)
169
+ if (done) {
170
+ done(err);
171
+ } else {
172
+ node.error(err.message, msg);
173
+ }
174
+ }
175
+
176
+ // ─── Common Parameter Builder ────────────────────────────────────────────────
177
+
178
+ /**
179
+ * Build the common query parameters shared by most IPGeo API calls.
180
+ *
181
+ * @param {string} apiKey
182
+ * @param {object} nodeConfig Node configuration properties
183
+ * @returns {object} params object ready for Axios
184
+ */
185
+ function buildCommonParams(apiKey, nodeConfig) {
186
+ const params = { apiKey };
187
+
188
+ if (nodeConfig.lang && nodeConfig.lang !== 'en') {
189
+ params.lang = nodeConfig.lang;
190
+ }
191
+ if (nodeConfig.output && nodeConfig.output !== 'json') {
192
+ params.output = nodeConfig.output;
193
+ }
194
+ if (nodeConfig.fields && nodeConfig.fields.trim()) {
195
+ params.fields = nodeConfig.fields.trim();
196
+ }
197
+ if (nodeConfig.excludes && nodeConfig.excludes.trim()) {
198
+ params.excludes = nodeConfig.excludes.trim();
199
+ }
200
+
201
+ return params;
202
+ }
203
+
204
+ // ─── Exports ─────────────────────────────────────────────────────────────────
205
+
206
+ module.exports = {
207
+ STATUS,
208
+ setStatus,
209
+ clearStatus,
210
+ resolveValue,
211
+ setOutputProperty,
212
+ handleNodeError,
213
+ buildCommonParams,
214
+ };
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "node-red-contrib-ipgeolocation",
3
+ "version": "1.0.0",
4
+ "description": "Node-RED nodes for the IPGeolocation.io v3 API: IP geolocation, security and threat intelligence (VPN, proxy, and TOR detection), abuse contacts, ASN lookup, bulk IP processing, timezone, astronomy, and user-agent parsing.",
5
+ "keywords": [
6
+ "node-red",
7
+ "ipgeolocation",
8
+ "geolocation",
9
+ "ip-lookup",
10
+ "timezone",
11
+ "astronomy",
12
+ "user-agent",
13
+ "security",
14
+ "ip-intelligence"
15
+ ],
16
+ "author": {
17
+ "name": "IPGeolocation.io"
18
+ },
19
+ "license": "MIT",
20
+ "homepage": "https://github.com/IPGeolocation/node-red-contrib-ipgeolocation",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/IPGeolocation/node-red-contrib-ipgeolocation.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/IPGeolocation/node-red-contrib-ipgeolocation/issues"
27
+ },
28
+ "engines": {
29
+ "node": ">=14.0.0"
30
+ },
31
+ "node-red": {
32
+ "version": ">=2.0.0",
33
+ "nodes": {
34
+ "ipgeo-config": "nodes/ipgeo-config.js",
35
+ "ipgeo-lookup": "nodes/ipgeo-lookup.js",
36
+ "ipgeo-bulk": "nodes/ipgeo-bulk.js",
37
+ "ipgeo-security": "nodes/ipgeo-security.js",
38
+ "ipgeo-bulk-security": "nodes/ipgeo-bulk-security.js",
39
+ "ipgeo-abuse": "nodes/ipgeo-abuse.js",
40
+ "ipgeo-asn": "nodes/ipgeo-asn.js",
41
+ "ipgeo-timezone": "nodes/ipgeo-timezone.js",
42
+ "ipgeo-astronomy": "nodes/ipgeo-astronomy.js",
43
+ "ipgeo-useragent": "nodes/ipgeo-useragent.js"
44
+ }
45
+ },
46
+ "dependencies": {
47
+ "axios": "^1.18.0"
48
+ },
49
+ "devDependencies": {
50
+ "@node-red/editor-client": "^3.1.9",
51
+ "mocha": "^10.8.2",
52
+ "nock": "^13.5.6",
53
+ "node-red": "^3.1.15",
54
+ "node-red-node-test-helper": "^0.3.6",
55
+ "nyc": "^15.1.0",
56
+ "should": "^13.2.3",
57
+ "sinon": "^17.0.1"
58
+ },
59
+ "main": "index.js",
60
+ "files": [
61
+ "nodes/",
62
+ "examples/",
63
+ "index.js",
64
+ "README.md"
65
+ ],
66
+ "scripts": {
67
+ "test": "mocha",
68
+ "test:watch": "mocha --watch",
69
+ "coverage": "nyc --reporter=text --reporter=html mocha",
70
+ "prepublishOnly": "npm test"
71
+ }
72
+ }