domotz-remote-pawn
Version:
Domotz Agent
165 lines (148 loc) • 7.29 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2016 Domotz Ltd
*
* Domotz Agent is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Domotz Agent is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>.
* Created by Iacopo Papalini <ipapalini@domotz.com> on 21/04/15.
* This module implements the
* 'Unicast eventing' specification of the UPnp protocol.
* See http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf for a detailed specification
*
* When an event from a registered publisher is received, it is dispatched to a specified URL of the Domotz Engine, but
* only if the changed properties belong to the 'properties' set in the subscription description
*/
var subscriptionsManager = function (configuration) {
var myConsole = configuration.log.decorateLogs();
const defaultTimeout = 1800;
const timeoutMultiplier = 0.9; // In order to renew the subscription before it expires
var ret = {
subscribe: function (payload, correlationId) {
// Implements paragraph 4.1.2 of the specifications document
var existingSubscription = configuration.upnp.registry.getSubscriptionByDevice([payload.host, payload.port, payload.path]);
if (existingSubscription) {
updateSubscription(existingSubscription, payload, correlationId);
ret.renewSubscription(existingSubscription, true);
return;
}
configuration.request(buildNewSubscriptionRequestOptions(payload), createHandleSubscriptionResponse(payload, correlationId));
},
renewSubscription: function (subscription, justOnce) {
// Implements paragraph 4.1.3 of the specifications document
configuration.request(buildRenewSubscriptionRequestOptions(subscription), createHandleRenewSubscriptionResponse(subscription, justOnce));
},
};
function buildNewSubscriptionRequestOptions(payload) {
return {
method: 'SUBSCRIBE',
uri: 'http://' + payload.host + ':' + payload.port + payload.path,
headers: {
host: payload.host + ':' + payload.port,
callback: '<' + configuration.upnp.httpServer.getHttpServerUrl() + '>',
nt: 'upnp:event',
timeout: 'Second-' + defaultTimeout,
},
};
}
function buildRenewSubscriptionRequestOptions(subscription) {
var hostInfo = subscription.device_info;
return {
method: 'SUBSCRIBE',
uri: 'http://' + hostInfo[0] + ':' + hostInfo[1] + hostInfo[2],
headers: {
host: hostInfo[0] + ':' + hostInfo[1],
sid: subscription.id,
timeout: 'Second-' + defaultTimeout,
},
};
}
var createHandleSubscriptionResponse = function (description, correlationId) {
return function (error, response) {
if (response === undefined) {
myConsole.info(correlationId + ' - Empty response, probable connection error - ' + JSON.stringify(error));
} else if (parseInt(response.statusCode, 10) !== 200) {
myConsole.error(correlationId + ' - Subscription failed - Got response - code: ' + response.statusCode);
} else {
var headers = response.headers;
myConsole.info(correlationId + ' - Subscription successful, sid:' + headers.sid);
var subscription = createSubscription(headers, description, correlationId);
configuration.upnp.registry.insertSubscription(subscription);
scheduleSubscriptionRenew(subscription);
}
};
};
var createHandleRenewSubscriptionResponse = function (subscription, justOnce) {
return function (error, response) {
if (response === undefined) {
myConsole.info(
subscription.correlation_id + ' - Removing subscription for empty response, probable connection error - ' + JSON.stringify(error)
);
configuration.upnp.registry.removeSubscription(subscription);
} else if (parseInt(response.statusCode, 10) !== 200) {
myConsole.error(
subscription.correlation_id +
' - Subscription renew failed - Got response - code: ' +
response.statusCode +
' - recreating subscription for device:' +
JSON.stringify(subscription.device_info)
);
configuration.upnp.registry.removeSubscription(subscription);
recreateSubscription(subscription);
} else if (!justOnce) {
scheduleSubscriptionRenew(subscription);
}
};
};
function createSubscription(headers, payload, correlationId) {
return {
id: headers.sid,
timeout: extractTimeout(headers),
device_info: [payload.host, payload.port, payload.path],
properties: payload.properties,
callback_url: payload.report_to,
correlation_id: correlationId,
};
}
function recreateSubscription(subscription) {
var newPayload = {
host: subscription.device_info[0],
port: subscription.device_info[1],
path: subscription.device_info[2],
properties: subscription.properties,
report_to: subscription.callback_url,
};
ret.subscribe(newPayload, subscription.correlation_id);
}
function updateSubscription(existingSubscription, payload, correlationId) {
existingSubscription.properties = payload.properties;
existingSubscription.callback_url = payload.report_to;
existingSubscription.correlation_id = correlationId;
configuration.upnp.registry.updateSubscription(existingSubscription.id, existingSubscription);
}
function extractTimeout(headers) {
return parseInt(headers.timeout.split('-')[1], 10);
}
function scheduleSubscriptionRenew(subscription) {
var schedule = [];
var scheduler = configuration.utils.scheduler;
schedule.push([scheduler.nop, subscription.timeout * timeoutMultiplier * 1000, 'Sleep until renew subscription for sid:' + subscription.id]);
schedule.push([createSubscriptionRenewer(subscription), 0, 'Renew subscription for sid:' + subscription.id]);
scheduler.execute(schedule);
}
function createSubscriptionRenewer(subscription) {
return function () {
ret.renewSubscription(subscription);
};
}
return ret;
};
module.exports.factory = subscriptionsManager;