nat-upnp-rejetto 2.1.0 → 2.1.2
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.
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
@@ -20,124 +11,112 @@ class Client {
|
|
|
20
11
|
this.ssdp = new ssdp_1.default();
|
|
21
12
|
this.timeout = options.timeout || 1800;
|
|
22
13
|
}
|
|
23
|
-
createMapping(options) {
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
["NewLeaseDuration", (_a = options.ttl) !== null && _a !== void 0 ? _a : 60 * 30],
|
|
40
|
-
]);
|
|
41
|
-
});
|
|
14
|
+
async createMapping(options) {
|
|
15
|
+
return this.getGateway().then(({ gateway, address }) => {
|
|
16
|
+
const ports = normalizeOptions(options);
|
|
17
|
+
return gateway.run("AddPortMapping", [
|
|
18
|
+
["NewRemoteHost", ports.remote.host + ""],
|
|
19
|
+
["NewExternalPort", ports.remote.port + ""],
|
|
20
|
+
[
|
|
21
|
+
"NewProtocol",
|
|
22
|
+
options.protocol ? options.protocol.toUpperCase() : "TCP",
|
|
23
|
+
],
|
|
24
|
+
["NewInternalPort", ports.internal.port + ""],
|
|
25
|
+
["NewInternalClient", ports.internal.host || address],
|
|
26
|
+
["NewEnabled", 1],
|
|
27
|
+
["NewPortMappingDescription", options.description || "node:nat:upnp"],
|
|
28
|
+
["NewLeaseDuration", options.ttl ?? 60 * 30],
|
|
29
|
+
]);
|
|
42
30
|
});
|
|
43
31
|
}
|
|
44
|
-
removeMapping(options) {
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
]);
|
|
56
|
-
});
|
|
32
|
+
async removeMapping(options) {
|
|
33
|
+
return this.getGateway().then(({ gateway }) => {
|
|
34
|
+
const ports = normalizeOptions(options);
|
|
35
|
+
return gateway.run("DeletePortMapping", [
|
|
36
|
+
["NewRemoteHost", ports.remote.host + ""],
|
|
37
|
+
["NewExternalPort", ports.remote.port + ""],
|
|
38
|
+
[
|
|
39
|
+
"NewProtocol",
|
|
40
|
+
options.protocol ? options.protocol.toUpperCase() : "TCP",
|
|
41
|
+
],
|
|
42
|
+
]);
|
|
57
43
|
});
|
|
58
44
|
}
|
|
59
|
-
getMappings(options = {}) {
|
|
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
|
-
|
|
45
|
+
async getMappings(options = {}) {
|
|
46
|
+
const { gateway, address } = await this.getGateway();
|
|
47
|
+
let i = 0;
|
|
48
|
+
const results = [];
|
|
49
|
+
while (true) {
|
|
50
|
+
const data = await gateway.run("GetGenericPortMappingEntry", [["NewPortMappingIndex", i++]])
|
|
51
|
+
.catch(() => { });
|
|
52
|
+
if (!data)
|
|
53
|
+
break; // finished
|
|
54
|
+
const key = Object.keys(data).find((k) => k.startsWith('GetGenericPortMappingEntryResponse'));
|
|
55
|
+
if (!key) {
|
|
56
|
+
throw new Error("Incorrect response");
|
|
57
|
+
}
|
|
58
|
+
const res = data[key];
|
|
59
|
+
const result = {
|
|
60
|
+
public: {
|
|
61
|
+
host: res.NewRemoteHost || "",
|
|
62
|
+
port: Number(res.NewExternalPort),
|
|
63
|
+
},
|
|
64
|
+
private: {
|
|
65
|
+
host: res.NewInternalClient,
|
|
66
|
+
port: Number(res.NewInternalPort),
|
|
67
|
+
},
|
|
68
|
+
protocol: res.NewProtocol.toLowerCase(),
|
|
69
|
+
enabled: res.NewEnabled == 1,
|
|
70
|
+
description: res.NewPortMappingDescription,
|
|
71
|
+
ttl: Number(res.NewLeaseDuration),
|
|
72
|
+
// temporary, so typescript will compile
|
|
73
|
+
local: false,
|
|
74
|
+
};
|
|
75
|
+
result.local = result.private.host === address;
|
|
76
|
+
if (options.local && !result.local) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (options.description) {
|
|
80
|
+
if (typeof result.description !== "string")
|
|
92
81
|
continue;
|
|
82
|
+
if (options.description instanceof RegExp) {
|
|
83
|
+
if (!options.description.test(result.description))
|
|
84
|
+
continue;
|
|
93
85
|
}
|
|
94
|
-
|
|
95
|
-
if (
|
|
86
|
+
else {
|
|
87
|
+
if (result.description.indexOf(options.description) === -1)
|
|
96
88
|
continue;
|
|
97
|
-
if (options.description instanceof RegExp) {
|
|
98
|
-
if (!options.description.test(result.description))
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
if (result.description.indexOf(options.description) === -1)
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
89
|
}
|
|
106
|
-
results.push(result);
|
|
107
90
|
}
|
|
108
|
-
|
|
109
|
-
}
|
|
91
|
+
results.push(result);
|
|
92
|
+
}
|
|
93
|
+
return results;
|
|
110
94
|
}
|
|
111
|
-
getPublicIp() {
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
throw new Error("Incorrect response");
|
|
119
|
-
return ((_a = data[key]) === null || _a === void 0 ? void 0 : _a.NewExternalIPAddress) + "";
|
|
120
|
-
}));
|
|
95
|
+
async getPublicIp() {
|
|
96
|
+
return this.getGateway().then(async ({ gateway, address }) => {
|
|
97
|
+
const data = await gateway.run("GetExternalIPAddress", []);
|
|
98
|
+
const key = Object.keys(data || {}).find((k) => /^GetExternalIPAddressResponse$/.test(k));
|
|
99
|
+
if (!key)
|
|
100
|
+
throw new Error("Incorrect response");
|
|
101
|
+
return data[key]?.NewExternalIPAddress + "";
|
|
121
102
|
});
|
|
122
103
|
}
|
|
123
|
-
getGateway() {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
s({ gateway: new device_1.default(info.location), address });
|
|
140
|
-
});
|
|
104
|
+
async getGateway() {
|
|
105
|
+
let timeouted = false;
|
|
106
|
+
const p = this.ssdp.search("urn:schemas-upnp-org:device:InternetGatewayDevice:1");
|
|
107
|
+
return new Promise((s, r) => {
|
|
108
|
+
const timeout = setTimeout(() => {
|
|
109
|
+
timeouted = true;
|
|
110
|
+
p.emit("end");
|
|
111
|
+
r(new Error("Connection timed out while searching for the gateway."));
|
|
112
|
+
}, this.timeout);
|
|
113
|
+
p.on("device", (info, address) => {
|
|
114
|
+
if (timeouted)
|
|
115
|
+
return;
|
|
116
|
+
p.emit("end");
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
// Create gateway
|
|
119
|
+
s({ gateway: new device_1.default(info.location), address });
|
|
141
120
|
});
|
|
142
121
|
});
|
|
143
122
|
}
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
@@ -27,64 +18,55 @@ class Device {
|
|
|
27
18
|
"urn:schemas-upnp-org:service:WANPPPConnection:1",
|
|
28
19
|
];
|
|
29
20
|
}
|
|
30
|
-
getXML(url) {
|
|
31
|
-
return
|
|
32
|
-
return httpRequest(url).then(consumers_1.text).then(data => new fast_xml_parser_1.XMLParser().parse(data))
|
|
33
|
-
.catch(() => new Error("Failed to lookup device description"));
|
|
34
|
-
});
|
|
21
|
+
async getXML(url) {
|
|
22
|
+
return httpRequest(url).then(consumers_1.text).then(data => new fast_xml_parser_1.XMLParser().parse(data));
|
|
35
23
|
}
|
|
36
|
-
getService(types) {
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
52
|
-
});
|
|
24
|
+
async getService(types) {
|
|
25
|
+
return this.getXML(this.description).then(({ root: xml }) => {
|
|
26
|
+
const services = this.parseDescription(xml).services.filter(({ serviceType }) => types.includes(serviceType));
|
|
27
|
+
if (services.length === 0 ||
|
|
28
|
+
!services[0].controlURL ||
|
|
29
|
+
!services[0].SCPDURL) {
|
|
30
|
+
throw new Error("Service not found");
|
|
31
|
+
}
|
|
32
|
+
const baseUrl = new url_1.URL(xml.baseURL, this.description);
|
|
33
|
+
const prefix = (url) => new url_1.URL(url, baseUrl.toString()).toString();
|
|
34
|
+
return {
|
|
35
|
+
service: services[0].serviceType,
|
|
36
|
+
SCPDURL: prefix(services[0].SCPDURL),
|
|
37
|
+
controlURL: prefix(services[0].controlURL),
|
|
38
|
+
};
|
|
53
39
|
});
|
|
54
40
|
}
|
|
55
|
-
run(action, args) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<s:Body><u:${action} xmlns:u=${JSON.stringify(info.service)}>${args.reduce((p, [a, b]) => p + `<${a !== null && a !== void 0 ? a : ''}>${b !== null && b !== void 0 ? b : ''}</${a !== null && a !== void 0 ? a : ''}>`, '')}</u:${action}>
|
|
41
|
+
async run(action, args) {
|
|
42
|
+
const info = await this.getService(this.services);
|
|
43
|
+
const body = `<?xml version="1.0"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
44
|
+
<s:Body><u:${action} xmlns:u=${JSON.stringify(info.service)}>${args.reduce((p, [a, b]) => p + `<${a ?? ''}>${b ?? ''}</${a ?? ''}>`, '')}</u:${action}>
|
|
60
45
|
</s:Body></s:Envelope>`;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return res;
|
|
76
|
-
});
|
|
46
|
+
return httpRequest(info.controlURL, {
|
|
47
|
+
method: 'post',
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": 'text/xml; charset="utf-8"',
|
|
50
|
+
"Content-Length": "" + Buffer.byteLength(body),
|
|
51
|
+
Connection: "close",
|
|
52
|
+
SOAPAction: JSON.stringify(info.service + "#" + action),
|
|
53
|
+
},
|
|
54
|
+
}, body)
|
|
55
|
+
.then(consumers_1.text, consumers_1.text).then(data => {
|
|
56
|
+
const res = new fast_xml_parser_1.XMLParser({ removeNSPrefix: true }).parse(data).Envelope.Body;
|
|
57
|
+
if (res.Fault)
|
|
58
|
+
throw res.Fault.detail?.UPnPError || res.Fault;
|
|
59
|
+
return res;
|
|
77
60
|
});
|
|
78
61
|
}
|
|
79
62
|
parseDescription(info) {
|
|
80
63
|
const services = [];
|
|
81
64
|
const devices = [];
|
|
82
65
|
function traverseDevices(device) {
|
|
83
|
-
var _a, _b, _c, _d;
|
|
84
66
|
if (!device)
|
|
85
67
|
return;
|
|
86
|
-
const serviceList =
|
|
87
|
-
const deviceList =
|
|
68
|
+
const serviceList = device.serviceList?.service ?? [];
|
|
69
|
+
const deviceList = device.deviceList?.device ?? [];
|
|
88
70
|
devices.push(device);
|
|
89
71
|
if (Array.isArray(serviceList)) {
|
|
90
72
|
services.push(...serviceList);
|
|
@@ -109,5 +91,5 @@ class Device {
|
|
|
109
91
|
exports.Device = Device;
|
|
110
92
|
exports.default = Device;
|
|
111
93
|
function httpRequest(url, options = {}, body = '') {
|
|
112
|
-
return new Promise((resolve, reject) => (url.startsWith('https:') ? node_https_1.default : node_http_1.default).request(url, options, (res) =>
|
|
94
|
+
return new Promise((resolve, reject) => (url.startsWith('https:') ? node_https_1.default : node_http_1.default).request(url, options, async (res) => !res.statusCode || res.statusCode >= 400 ? reject(res) : resolve(res)).on('error', reject).end(body));
|
|
113
95
|
}
|
|
@@ -9,9 +9,8 @@ const os_1 = __importDefault(require("os"));
|
|
|
9
9
|
const events_1 = __importDefault(require("events"));
|
|
10
10
|
class Ssdp {
|
|
11
11
|
constructor(options) {
|
|
12
|
-
var _a;
|
|
13
12
|
this.options = options;
|
|
14
|
-
this.sourcePort =
|
|
13
|
+
this.sourcePort = this.options?.sourcePort || 0;
|
|
15
14
|
this.bound = false;
|
|
16
15
|
this.boundCount = 0;
|
|
17
16
|
this.closed = false;
|
|
@@ -21,10 +20,9 @@ class Ssdp {
|
|
|
21
20
|
this.ssdpEmitter = new events_1.default();
|
|
22
21
|
// Create sockets on all external interfaces
|
|
23
22
|
const interfaces = os_1.default.networkInterfaces();
|
|
24
|
-
this.sockets = Object.keys(interfaces).reduce((arr, key) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}, []);
|
|
23
|
+
this.sockets = Object.keys(interfaces).reduce((arr, key) => arr.concat(interfaces[key]
|
|
24
|
+
?.filter((item) => !item.internal)
|
|
25
|
+
.map((item) => this.createSocket(item)) ?? []), []);
|
|
28
26
|
}
|
|
29
27
|
createSocket(iface) {
|
|
30
28
|
const socket = dgram_1.default.createSocket(iface.family === "IPv4" ? "udp4" : "udp6");
|
|
@@ -116,8 +114,7 @@ function parseMimeHeader(headerStr) {
|
|
|
116
114
|
const lines = headerStr.split(/\r\n/g);
|
|
117
115
|
// Parse headers from lines to hashmap
|
|
118
116
|
return lines.reduce((headers, line) => {
|
|
119
|
-
|
|
120
|
-
const [_, key, value] = (_a = line.match(/^([^:]*)\s*:\s*(.*)$/)) !== null && _a !== void 0 ? _a : [];
|
|
117
|
+
const [_, key, value] = line.match(/^([^:]*)\s*:\s*(.*)$/) ?? [];
|
|
121
118
|
if (key && value) {
|
|
122
119
|
headers[key.toLowerCase()] = value;
|
|
123
120
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nat-upnp-rejetto",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"main": "build/src/index",
|
|
5
5
|
"author": "Fedor Indutny <fedor@indutny.com>, SimplyLinn <https://github.com/SimplyLinn>, Kaden Sharpin <http://github.com/kaden-sharpin>, Massimo Melina <a@rejetto.com>",
|
|
6
6
|
"homepage": "https://github.com/kaden-sharpin/nat-upnp-ts",
|
|
@@ -19,6 +19,6 @@
|
|
|
19
19
|
"test": "tsc && node ./build/test/index.test.js"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"fast-xml-parser": "^
|
|
22
|
+
"fast-xml-parser": "^5.3.4"
|
|
23
23
|
}
|
|
24
24
|
}
|