UNPKG

domotz-remote-pawn

Version:

Domotz Agent

165 lines (148 loc) 7.29 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) { 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;