-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathipinfo.js
More file actions
135 lines (118 loc) · 5.97 KB
/
ipinfo.js
File metadata and controls
135 lines (118 loc) · 5.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
const { IPinfoWrapper } = require("node-ipinfo");
/**
* Node-RED module for retrieving IP geolocation information using the IPInfo service.
*
* @module IPInfoNode
* @description A Node-RED node that looks up geolocation details for a given IP address.
* Supports Standard, Lite, Core, Plus APIs and Residential Proxy lookups.
* @requires node-ipinfo
*/
module.exports = function (RED) {
// Pre-compiled regex patterns for IP validation (avoids re-creation per call)
var IPV4_RE = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
var IPV6_RE = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d)|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))$/;
/**
* Validates whether a string is a valid IP address (IPv4, IPv6) or the
* special "me" keyword supported by the IPInfo API.
*
* @param {string} ip - The IP address to validate
* @returns {boolean} True when the value is a valid lookup target
*/
function isValidIP(ip) {
if (!ip || typeof ip !== "string") {
return false;
}
return ip === "me" || IPV4_RE.test(ip) || IPV6_RE.test(ip);
}
/**
* Creates an IPInfo node for Node-RED.
*
* @constructor
* @param {Object} config - Node configuration
* @param {string} [config.property="payload"] - Input message property containing the IP
* @param {string} [config.propertyout="payload"] - Output message property for the result
* @param {string} [config.lookupType="ip"] - Lookup type: "ip" or "resproxy"
* @param {Object} config.config - Reference to the IPInfo configuration node
*/
function IPInfoNode(config) {
RED.nodes.createNode(this, config);
var node = this;
// Input / output property names, defaulting to "payload"
this.property = config.property || "payload";
this.propertyout = config.propertyout || "payload";
this.lookupType = config.lookupType || "ip";
// Resolve the shared configuration node
var ipinfoconfig = RED.nodes.getNode(config.config);
if (!ipinfoconfig) {
node.error(RED._("ipinfo.errors.missing-config"));
node.status({ fill: "red", shape: "ring", text: "ipinfo.status.missing-config" });
return;
}
// Residential Proxy lookup is only available with the Standard API wrapper
if (this.lookupType === "resproxy" && ipinfoconfig.apiType !== "standard") {
node.warn(RED._("ipinfo.errors.resproxy-requires-standard"));
}
/**
* Handles incoming messages and performs IP lookup.
*/
node.on("input", function (msg, nodeSend, nodeDone) {
// Extract IP address from the specified input property
var value = RED.util.getMessageProperty(msg, node.property);
// Validate input exists
if (value === undefined || value === null || value === "") {
node.error(RED._("ipinfo.errors.no-ip"), msg);
node.status({ fill: "red", shape: "ring", text: "ipinfo.status.no-ip" });
if (nodeDone) { nodeDone(); }
return;
}
// Coerce to trimmed string
value = String(value).trim();
// Validate IP address format
if (!isValidIP(value)) {
node.warn(RED._("ipinfo.errors.invalid-ip", { ip: value }));
node.status({ fill: "yellow", shape: "ring", text: "ipinfo.status.invalid-ip" });
if (nodeDone) { nodeDone(); }
return;
}
// Indicate lookup in progress
var statusText = node.lookupType === "resproxy"
? "ipinfo.status.lookupresproxy"
: "ipinfo.status.lookupip";
node.status({ fill: "blue", shape: "dot", text: statusText });
node.debug(RED._("ipinfo.debug.lookup-start", { ip: value }));
// Select the appropriate lookup method
var lookupPromise;
if (node.lookupType === "resproxy" && typeof ipinfoconfig.client.lookupResproxy === "function") {
lookupPromise = ipinfoconfig.client.lookupResproxy(value);
} else {
lookupPromise = ipinfoconfig.client.lookupIp(value);
}
// Perform lookup
lookupPromise.then(function (result) {
node.debug(RED._("ipinfo.debug.lookup-success", { ip: value }));
node.status({});
try {
RED.util.setMessageProperty(msg, node.propertyout, result);
nodeSend(msg);
if (nodeDone) { nodeDone(); }
} catch (err) {
node.error(RED._("ipinfo.errors.property-error", { error: err.message }), msg);
node.status({ fill: "red", shape: "ring", text: "ipinfo.status.error" });
if (nodeDone) { nodeDone(err); }
}
}).catch(function (err) {
node.error(RED._("ipinfo.errors.lookup-failed", { error: err.message }), msg);
node.status({ fill: "red", shape: "dot", text: "ipinfo.status.error" });
if (nodeDone) { nodeDone(err); }
});
});
/**
* Cleanup on node close.
*/
node.on("close", function () {
node.status({});
});
}
// Register the IPInfo node type with Node-RED
RED.nodes.registerType("ipinfo", IPInfoNode);
};