/** * Project: Open Vehicle Monitor System * Date: 14th Jan 2019 * Based on: https://github.com/mroderick/PubSubJS * +merged: https://github.com/mroderick/PubSubJS/issues/115 * Copyright (c) 2010,2011,2012,2013,2014 Morgan Roderick http://roderick.dk * License: MIT - http://mrgnrdrck.mit-license.org */ 'use strict'; var messages = {}, lastUid = -1; function hasKeys(obj) { var key; for (key in obj) { if ( Object.prototype.hasOwnProperty.call(obj, key) ) { return true; } } return false; } function callSubscriberWithImmediateExceptions( subscriber, message, data ) { subscriber( message, data ); } function deliverMessage( originalMessage, matchedMessage, data ) { var subscribers = messages[matchedMessage], s; if ( !Object.prototype.hasOwnProperty.call(messages, matchedMessage ) ) { return; } for (s in subscribers) { if ( Object.prototype.hasOwnProperty.call(subscribers, s)) { callSubscriberWithImmediateExceptions( subscribers[s], originalMessage, data ); } } } function createDeliveryFunction( message, data ) { return function deliverNamespaced() { var topic = String( message ), position = topic.lastIndexOf( '.' ); // deliver the message as it is now deliverMessage(message, message, data); // trim the hierarchy and deliver message to each level while( position !== -1 ) { topic = topic.substr( 0, position ); position = topic.lastIndexOf('.'); deliverMessage( message, topic, data ); } }; } function messageHasSubscribers( message ) { var topic = String( message ), found = Boolean(Object.prototype.hasOwnProperty.call(messages, topic) && hasKeys(messages[topic])), position = topic.lastIndexOf( '.' ); while ( !found && position !== -1 ) { topic = topic.substr( 0, position ); position = topic.lastIndexOf( '.' ); found = Boolean(Object.prototype.hasOwnProperty.call(messages, topic) && hasKeys(messages[topic])); } return found; } function publish( message, data ) { message = (typeof message === 'symbol') ? message.toString() : message; var deliver = createDeliveryFunction( message, data ), hasSubscribers = messageHasSubscribers( message ); if ( !hasSubscribers ) { return false; } deliver(); return true; } /** * Publishes the message, passing the data to it's subscribers * @function * @alias publish * @param { String } message The message to publish * @param {} data The data to pass to subscribers * @return { Boolean } */ exports.publish = function( message, data ) { return publish( message, data ); }; /** * Subscribes the passed function to the passed message. Every returned token is unique and should be stored if you need to unsubscribe * @function * @alias subscribe * @param { String } message The message to subscribe to * @param { Function } func The function to call when a new message is published * @return { String } */ exports.subscribe = function( message, func ) { if ( typeof func !== 'function') { return false; } message = (typeof message === 'symbol') ? message.toString() : message; // message is not registered yet if ( !Object.prototype.hasOwnProperty.call(messages, message) ) { messages[message] = {}; } // forcing token as String, to allow for future expansions without breaking usage // and allow for easy use as key names for the 'messages' object var token = 'uid_' + String(++lastUid); messages[message][token] = func; // return token for unsubscribing return token; }; /** * Clears all subscriptions * @function * @public * @alias clearAllSubscriptions */ exports.clearAllSubscriptions = function clearAllSubscriptions() { messages = {}; }; /** * Clear subscriptions by the topic * @function * @public * @alias clearAllSubscriptions */ exports.clearSubscriptions = function clearSubscriptions(topic) { var m; for (m in messages) { if (Object.prototype.hasOwnProperty.call(messages, m) && m.indexOf(topic) === 0) { delete messages[m]; } } }; /** * Removes subscriptions * * - When passed a token, removes a specific subscription. * * - When passed a function, removes all subscriptions for that function * * - When passed a topic, removes all subscriptions for that topic (hierarchy) * @function * @public * @alias subscribeOnce * @param { String | Function } value A token, function or topic to unsubscribe from * @example // Unsubscribing with a token * var token = PubSub.subscribe('mytopic', myFunc); * PubSub.unsubscribe(token); * @example // Unsubscribing with a function * PubSub.unsubscribe(myFunc); * @example // Unsubscribing from a topic * PubSub.unsubscribe('mytopic'); */ exports.unsubscribe = function(value) { var descendantTopicExists = function(topic) { var m; for ( m in messages ) { if ( Object.prototype.hasOwnProperty.call(messages, m) && m.indexOf(topic) === 0 ) { // a descendant of the topic exists: return true; } } return false; }, isTopic = typeof value === 'string' && ( Object.prototype.hasOwnProperty.call(messages, value) || descendantTopicExists(value) ), isToken = !isTopic && typeof value === 'string', isFunction = typeof value === 'function', result = false, m, message, t; if (isTopic) { exports.clearSubscriptions(value); return; } for ( m in messages ) { if ( Object.prototype.hasOwnProperty.call(messages, m) ) { message = messages[m]; if ( isToken && message[value] ) { delete message[value]; result = value; // tokens are unique, so we can just stop here break; } if (isFunction) { for ( t in message ) { if (Object.prototype.hasOwnProperty.call(message, t) && message[t] === value) { delete message[t]; result = true; } } } } } return result; }; /** * Debugging: output / return subscriptions */ exports.dump = function () { JSON.print(messages); }; exports.data = function () { return messages; };