UNPKG

domotz-remote-pawn

Version:

Domotz Agent

171 lines (154 loc) 7.24 kB
/** 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) { 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) { console.log(correlationId + " - Empty response, probable connection error - " + JSON.stringify(error)); } else if (parseInt(response.statusCode, 10) !== 200) { console.error(correlationId + " - Subscription failed - Got response - code: " + response.statusCode); } else { var headers = response.headers; console.log(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) { console.log(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) { console.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;