domotz-remote-pawn
Version:
Domotz Agent
163 lines (130 loc) • 4.26 kB
JavaScript
var nat = require('../nat-upnp'),
request = require('request'),
url = require('url'),
xml2js = require('xml2js'),
Buffer = require('buffer').Buffer;
var device = exports;
function Device(url) {
this.description = url;
this.services = [
'urn:schemas-upnp-org:service:WANIPConnection:1',
'urn:schemas-upnp-org:service:WANPPPConnection:1'
];
};
device.create = function create(url) {
return new Device(url);
};
Device.prototype._getXml = function _getXml(url, callback) {
var once = false;
function respond(err, body) {
if (once) return;
once = true;
callback(err, body);
}
request(url, function(err, res, body) {
if (err) return callback(err);
if (res.statusCode !== 200) {
respond(Error('Failed to lookup device description'));
return;
}
var parser = new xml2js.Parser();
parser.parseString(body, function(err, body) {
if (err) return respond(err);
respond(null, body);
});
});
};
Device.prototype.getService= function getService(types, callback) {
var self = this;
this._getXml(this.description, function(err, info) {
if (err) return callback(err);
var s = self.parseDescription(info).services.filter(function(service) {
return types.indexOf(service.serviceType) !== -1;
});
if (s.length === 0 || !s[0].controlURL || !s[0].SCPDURL) {
return callback(Error('Service not found'));
}
var base = url.parse(info.baseURL || self.description);
function prefix(u) {
var uri = url.parse(u);
uri.host = uri.host || base.host;
uri.protocol = uri.protocol || base.protocol;
return url.format(uri);
}
callback(null,{
service: s[0].serviceType,
SCPDURL: prefix(s[0].SCPDURL),
controlURL: prefix(s[0].controlURL)
});
});
};
Device.prototype.parseDescription = function parseDescription(info) {
var services = [],
devices = [];
function toArray(item) {
return Array.isArray(item) ? item : [ item ];
};
function traverseServices(service) {
if (!service) return;
services.push(service);
}
function traverseDevices(device) {
if (!device) return;
devices.push(device);
if (device.deviceList && device.deviceList.device) {
toArray(device.deviceList.device).forEach(traverseDevices);
}
if (device.serviceList && device.serviceList.service) {
toArray(device.serviceList.service).forEach(traverseServices);
}
}
traverseDevices(info.device);
return {
services: services,
devices: devices
};
};
Device.prototype.run = function run(action, args, callback) {
var self = this;
this.getService(this.services, function(err, info) {
if (err) return callback(err);
var body = '<?xml version="1.0"?>' +
'<s:Envelope ' +
'xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" ' +
's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
'<s:Body>' +
'<u:' + action + ' xmlns:u=' +
JSON.stringify(info.service) + '>' +
args.map(function(args) {
return '<' + args[0]+ '>' +
(args[1] === undefined ? '' : args[1]) +
'</' + args[0] + '>';
}).join('') +
'</u:' + action + '>' +
'</s:Body>' +
'</s:Envelope>';
request({
method: 'POST',
url: info.controlURL,
headers: {
'Content-Type': 'text/xml; charset="utf-8"',
'Content-Length': Buffer.byteLength(body),
'Connection': 'close',
'SOAPAction': JSON.stringify(info.service + '#' + action)
},
body: body
}, function(err, res, body) {
if (err) return callback(err);
var parser = new xml2js.Parser();
parser.parseString(body, function(err, body) {
if (res.statusCode !== 200) {
return callback(Error('Request failed: ' + res.statusCode));
}
var soapns = nat.utils.getNamespace(
body,
'http://schemas.xmlsoap.org/soap/envelope/');
callback(null, body[soapns + 'Body']);
});
});
});
};