UNPKG

domotz-remote-pawn

Version:

Domotz Agent

163 lines (130 loc) 4.26 kB
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']); }); }); }); };