domotz-remote-pawn
Version:
Domotz Agent
203 lines (166 loc) • 5.53 kB
JavaScript
var nat = require('../nat-upnp');
var async = require('async');
var client = exports;
function Client() {
this.ssdp = nat.ssdp.create();
this.timeout = 1800;
}
client.create = function create() {
return new Client();
};
function normalizeOptions(options) {
function toObject(addr) {
if (typeof addr === 'number') return { port: addr };
if (typeof addr === 'string' && !isNaN(addr)) return { port: Number(addr) };
if (typeof addr === 'object') return addr;
return {};
}
return {
remote: toObject(options.public),
internal: toObject(options.private)
};
}
Client.prototype.portMapping = function portMapping(options, callback) {
if (!callback) callback = function() {};
this.findGateway(function(err, gateway, address) {
if (err) return callback(err);
var ports = normalizeOptions(options);
var ttl = 60 * 30;
if (typeof options.ttl === 'number') { ttl = options.ttl; }
if (typeof options.ttl === 'string' && !isNaN(options.ttl)) { ttl = Number(options.ttl); }
gateway.run('AddPortMapping', [
[ 'NewRemoteHost', ports.remote.host ],
[ 'NewExternalPort', ports.remote.port ],
[ 'NewProtocol', options.protocol ?
options.protocol.toUpperCase() : 'TCP' ],
[ 'NewInternalPort', ports.internal.port ],
[ 'NewInternalClient', ports.internal.host || address ],
[ 'NewEnabled', 1 ],
[ 'NewPortMappingDescription', options.description || 'node:nat:upnp' ],
[ 'NewLeaseDuration', ttl ]
], callback);
});
};
Client.prototype.portUnmapping = function portMapping(options, callback) {
if (!callback) callback = function() {};
this.findGateway(function(err, gateway/*, address*/) {
if (err) return callback(err);
var ports = normalizeOptions(options);
gateway.run('DeletePortMapping', [
[ 'NewRemoteHost', ports.remote.host ],
[ 'NewExternalPort', ports.remote.port ],
[ 'NewProtocol', options.protocol ?
options.protocol.toUpperCase() : 'TCP' ]
], callback);
});
};
Client.prototype.getMappings = function getMappings(options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options) options = {};
this.findGateway(function(err, gateway, address) {
if (err) return callback(err);
var i = 0;
var end = false;
var results = [];
async.whilst(function() {
return !end;
}, function(callback) {
gateway.run('GetGenericPortMappingEntry', [
[ 'NewPortMappingIndex', i++ ]
], function(err, data) {
if (err) {
// If we got an error on index 0, ignore it in case this router starts indicies on 1
if (i !== 1) {
end = true;
}
return callback(null);
}
var key;
Object.keys(data).some(function(k) {
if (!/:GetGenericPortMappingEntryResponse/.test(k)) return false;
key = k;
return true;
});
data = data[key];
var result = {
public: {
host: typeof data.NewRemoteHost === 'string' &&
data.NewRemoteHost || '',
port: parseInt(data.NewExternalPort, 10)
},
private: {
host: data.NewInternalClient,
port: parseInt(data.NewInternalPort, 10)
},
protocol: data.NewProtocol.toLowerCase(),
enabled: data.NewEnabled === '1',
description: data.NewPortMappingDescription,
ttl: parseInt(data.NewLeaseDuration, 10)
};
result.local = result.private.host === address;
results.push(result);
callback(null);
});
}, function(err) {
if (err) return callback(err);
if (options.local) {
results = results.filter(function(item) {
return item.local;
});
}
if (options.description) {
results = results.filter(function(item) {
if (typeof item.description !== 'string')
return;
if (options.description instanceof RegExp) {
return item.description.match(options.description) !== null;
} else {
return item.description.indexOf(options.description) !== -1;
}
});
}
callback(null, results);
});
});
};
Client.prototype.externalIp = function externalIp(callback) {
this.findGateway(function(err, gateway/*, address*/) {
if (err) return callback(err);
gateway.run('GetExternalIPAddress', [], function(err, data) {
if (err) return callback(err);
var key;
Object.keys(data).some(function(k) {
if (!/:GetExternalIPAddressResponse$/.test(k)) return false;
key = k;
return true;
});
if (!key) return callback(Error('Incorrect response'));
callback(null, data[key].NewExternalIPAddress);
});
});
};
Client.prototype.findGateway = function findGateway(callback) {
var timeout;
var timeouted = false;
var p = this.ssdp.search(
'urn:schemas-upnp-org:device:InternetGatewayDevice:1'
);
timeout = setTimeout(function() {
timeouted = true;
p.emit('end');
callback(new Error('timeout'));
}, this.timeout);
p.on('device', function (info, address) {
if (timeouted) return;
p.emit('end');
clearTimeout(timeout);
// Create gateway
callback(null, nat.device.create(info.location), address);
});
};
Client.prototype.close = function close() {
this.ssdp.close();
};