/** * PublishSubscribe * A simple publish-subscribe implementation for PHP, Python, Node/XPCOM/JS * * @version: 1.1.0 * https://github.com/foo123/PublishSubscribe * **/ !function( root, name, factory ){ "use strict"; if ( ('undefined'!==typeof Components)&&('object'===typeof Components.classes)&&('object'===typeof Components.classesByID)&&Components.utils&&('function'===typeof Components.utils['import']) ) /* XPCOM */ (root.$deps = root.$deps||{}) && (root.EXPORTED_SYMBOLS = [name]) && (root[name] = root.$deps[name] = factory.call(root)); else if ( ('object'===typeof module)&&module.exports ) /* CommonJS */ (module.$deps = module.$deps||{}) && (module.exports = module.$deps[name] = factory.call(root)); else if ( ('undefined'!==typeof System)&&('function'===typeof System.register)&&('function'===typeof System['import']) ) /* ES6 module */ System.register(name,[],function($__export){$__export(name, factory.call(root));}); else if ( ('function'===typeof define)&&define.amd&&('function'===typeof require)&&('function'===typeof require.specified)&&require.specified(name) /*&& !require.defined(name)*/ ) /* AMD */ define(name,['module'],function(module){factory.moduleUri = module.uri; return factory.call(root);}); else if ( !(name in root) ) /* Browser/WebWorker/.. */ (root[name] = factory.call(root)||1)&&('function'===typeof(define))&&define.amd&&define(function(){return root[name];} ); }( /* current root */ 'undefined' !== typeof self ? self : this, /* module name */ "PublishSubscribe", /* module factory */ function ModuleFactory__PublishSubscribe( undef ){ "use strict"; var __version__ = "1.1.0", PROTO = 'prototype', HAS = Object[PROTO].hasOwnProperty, TOPIC_SEP = '/', TAG_SEP = '#', NS_SEP = '@', OTOPIC_SEP = '/', OTAG_SEP = '#', ONS_SEP = '@', KEYS = Object.keys, NOW = Date.now ? Date.now : function( ){ return new Date().getTime(); } ; function PublishSubscribeData( props ) { if ( props ) { for (var k in props) { if ( HAS.call(props,k) ) this[ k ] = props[ k ]; } } } PublishSubscribeData[PROTO] = { constructor: PublishSubscribeData, dispose: function( props ) { if ( props ) { for (var k=0; k 0; } function parse_topic( seps, topic ) { var nspos, tagspos, tags, namespaces; topic = String( topic ); nspos = topic.indexOf( seps[2] ); tagspos = topic.indexOf( seps[1] ); if ( -1 < nspos ) { namespaces = topic .slice( nspos ) .split( seps[2] ) .filter( not_empty ) .sort( ) ; topic = topic.slice( 0, nspos ); } else { namespaces = [ ]; } if ( -1 < tagspos ) { tags = topic .slice( tagspos ) .split( seps[1] ) .filter( not_empty ) .sort( ) ; topic = topic.slice( 0, tagspos ); } else { tags = [ ]; } topic = topic.split( seps[0] ).filter( not_empty ); return [topic, tags, namespaces]; } function get_all_topics( seps, topic ) { var topics = [ ], tags = [ ], namespaces/* = [ ]*/, ttags, tns, l, i, j, jj, tmp, combinations; topic = parse_topic( seps, topic ); //tns = topic[2]; namespaces = topic[2]; ttags = topic[1]; topic = topic[0]; l = topic.length; while ( l ) { topics.push( topic.join( OTOPIC_SEP ) ); topic.pop( ); l--; } l = ttags.length; if ( l > 1 ) { combinations = (1 << l); for (i=combinations-1; i>=1; i--) { tmp = [ ]; for (j=0,jj=1; j 1 ) { combinations = (1 << l); for (i=combinations-1; i>=1; i--) { tmp = [ ]; for (j=0,jj=1; j= pbns[ ns ]) ) return false; } return true; } function check_is_subscribed( pubsub, subscribedTopics, topic, tag, namespaces, nl ) { var _topic = !!topic ? 'tp_' + topic : false, _tag = !!tag ? 'tg_' + tag : false; if ( _topic && HAS.call(pubsub.topics,_topic) ) { if ( _tag && HAS.call(pubsub.topics[ _topic ].tags,_tag) ) { if ( pubsub.topics[ _topic ].tags[ _tag ].list.length && (nl <= 0 || match_namespace( pubsub.topics[ _topic ].tags[ _tag ].namespaces, namespaces, nl )) ) { subscribedTopics.push( [topic, tag, nl > 0, pubsub.topics[ _topic ].tags[ _tag ]] ); return true; } } else { if ( pubsub.topics[ _topic ].notags.list.length && (nl <= 0 || match_namespace( pubsub.topics[ _topic ].notags.namespaces, namespaces, nl )) ) { subscribedTopics.push( [topic, null, nl > 0, pubsub.topics[ _topic ].notags] ); return true; } } } else { if ( _tag && HAS.call(pubsub.notopics.tags,_tag) ) { if ( pubsub.notopics.tags[ _tag ].list.length && (nl <= 0 || match_namespace( pubsub.notopics.tags[ _tag ].namespaces, namespaces, nl )) ) { subscribedTopics.push( [null, tag, nl > 0, pubsub.notopics.tags[ _tag ]] ); return true; } } else { if ( pubsub.notopics.notags.list.length && (nl <= 0 || match_namespace( pubsub.notopics.notags.namespaces, namespaces, nl )) ) { subscribedTopics.push( [null, null, true, pubsub.notopics.notags] ); return true; } /* else no topics no tags no namespaces, do nothing */ } } return false; } function get_subscribed_topics( seps, pubsub, atopic ) { var all = get_all_topics( seps, atopic ), l, topic, tag, ns, //_topic, _tag, t, n, tl, nl, topics = all[ 1 ], tags = all[ 2 ], namespaces = all[ 3 ], topTopic = all[ 0 ], subscribedTopics = [ ] ; tl = tags.length; nl = namespaces.length; l = topics.length; if ( l ) { while ( l ) { topic = topics[ 0 ]; //_topic = 'tp_' + topic; if ( HAS.call(pubsub.topics, 'tp_' + topic) ) { if ( tl > 0 ) { for (t=0; t 0 ) { for (t=0; t 0 ) { for (n=0; n 0 ) { for (s=sl-1; s>=0; s--) { subscriber = subs[ s ]; if ( subscriber[1] && subscriber[4] > 0 ) { subs.splice( s, 1 ); subscribers.oneOffs = subscribers.oneOffs > 0 ? (subscribers.oneOffs-1) : 0; } } } else { subscribers.oneOffs = 0; } } } return subscribers; } function publish( target, seps, pubsub, topic, data ) { if ( pubsub ) { var topics = get_subscribed_topics( seps, pubsub, topic ), t, s, tl, sl, subs, subscribers, subscriber, topTopic, subTopic, tags, namespaces, hasNamespace, nl, evt, res = false, pos, nskeys ; topTopic = topics[ 0 ]; namespaces = topics[ 2 ]; nl = namespaces.length; topics = topics[ 1 ]; tl = topics.length; evt = null; if ( tl > 0 ) { evt = new PublishSubscribeEvent( target ); evt.data = data; evt.originalTopic = topTopic ? topTopic.split( OTOPIC_SEP ) : [ ]; } for (t=0; t 0 ) continue; // oneoff subscriber already called if ( hasNamespace ) evt.namespaces = subscriber[ 3 ].slice( 0 ); else evt.namespaces = [ ]; subscriber[ 4 ] = 1; // subscriber called res = subscriber[ 0 ]( evt ); // stop event propagation if ( (false === res) || evt.stopped() || evt.aborted() ) break; } // unsubscribeOneOffs unsubscribe_oneoffs( subscribers ); // stop event bubble propagation if ( evt.aborted() || !evt.propagates() ) break; } if ( evt ) { evt.dispose( ); evt = null; } } } function create_pipeline_loop( evt, topics, abort, finish ) { var topTopic = topics[ 0 ], namespaces = topics[ 2 ], topics = topics[ 1 ]; evt.non_local = new PublishSubscribeData({ 't': 0, 's': 0, 'start_topic': true, 'subscribers': null, 'topics': topics, 'namespaces': namespaces, 'hasNamespace': false, 'abort': abort, 'finish': finish }); evt.originalTopic = topTopic ? topTopic.split( OTOPIC_SEP ) : [ ]; var pipeline_loop = function pipeline_loop( evt ) { if ( !evt.non_local ) return; var res, non_local = evt.non_local, subTopic, tags, subscriber, done, abort, finish; if (non_local.t < non_local.topics.length) { if (non_local.start_topic) { // unsubscribeOneOffs unsubscribe_oneoffs( non_local.subscribers ); // stop event propagation if ( evt.aborted() || !evt.propagates() ) { if ( evt.aborted() && 'function' === typeof non_local.abort ) { abort = non_local.abort; non_local.abort = null; abort( evt ); if ( 'function' === typeof non_local.finish ) { finish = non_local.finish; non_local.finish = null; finish( evt ); } } return false; } subTopic = non_local.topics[ non_local.t ][ 0 ]; tags = non_local.topics[ non_local.t ][ 1 ]; evt.topic = subTopic ? subTopic.split( OTOPIC_SEP ) : [ ]; evt.tags = tags ? tags.split( OTAG_SEP ) : [ ]; non_local.hasNamespace = non_local.topics[ non_local.t ][ 2 ]; non_local.subscribers = non_local.topics[ non_local.t ][ 3 ]; non_local.s = 0; non_local.start_topic = false; } //if (non_local.subscribers) non_local.sl = non_local.subscribers.list.length; if (non_local.s < non_local.subscribers.list.length) { // stop event propagation if ( evt.aborted() || evt.stopped() ) { // unsubscribeOneOffs unsubscribe_oneoffs( non_local.subscribers ); if ( evt.aborted() && 'function' === typeof non_local.abort ) { abort = non_local.abort; non_local.abort = null; abort( evt ); if ( 'function' === typeof non_local.finish ) { finish = non_local.finish; non_local.finish = null; finish( evt ); } } return false; } done = false; while ( non_local.s < non_local.subscribers.list.length && !done ) { subscriber = non_local.subscribers.list[ non_local.s ]; if ( (!subscriber[ 1 ] || !subscriber[ 4 ]) && (!non_local.hasNamespace || (subscriber[ 2 ] && match_namespace(subscriber[ 2 ], non_local.namespaces, non_local.namespaces.length))) ) { done = true; } non_local.s += 1; } if (non_local.s >= non_local.subscribers.list.length) { non_local.t += 1; non_local.start_topic = true; } if ( done ) { if ( non_local.hasNamespace ) evt.namespaces = subscriber[ 3 ].slice( 0 ); else evt.namespaces = [ ]; subscriber[ 4 ] = 1; // subscriber called res = subscriber[ 0 ]( evt ); } } else { non_local.t += 1; non_local.start_topic = true; } } if ( !evt.non_local ) return; if (non_local.t >= non_local.topics.length) { // unsubscribeOneOffs unsubscribe_oneoffs( non_local.subscribers ); if ( 'function' === typeof non_local.finish ) { finish = non_local.finish; non_local.finish = null; finish( evt ); } if ( evt ) { evt.non_local.dispose([ 't', 's', 'start_topic', 'subscribers', 'topics', 'namespaces', 'hasNamespace', 'abort', 'finish' ]); evt.non_local = null; evt.dispose(); evt = null; } } }; return pipeline_loop; } function pipeline( target, seps, pubsub, topic, data, abort, finish ) { if ( pubsub ) { var topics = get_subscribed_topics( seps, pubsub, topic ), evt = null, pipeline_loop; if ( topics[ 1 ].length > 0 ) { evt = new PublishSubscribeEvent( target ); evt.data = data; evt.pipeline( pipeline_loop = create_pipeline_loop( evt, topics, abort, finish ) ); pipeline_loop( evt ); } } } function subscribe( seps, pubsub, topic, subscriber, oneOff, on1 ) { if ( pubsub && "function" === typeof(subscriber) ) { topic = parse_topic( seps, topic ); var tags = topic[1].join( OTAG_SEP ), tagslen = tags.length, entry, queue, _topic, _tag, namespaces = topic[2], nshash, namespaces_ref, n, nslen = namespaces.length; topic = topic[0].join( OTOPIC_SEP ); oneOff = (true === oneOff); on1 = (true === on1); nshash = { }; if ( nslen ) { for (n=0; n 0) ) { while ( --pos >= 0 ) { if ( subscriber === pb.list[pos][0] ) { if ( nslen && pb.list[pos][2] && match_namespace( pb.list[pos][2], namespaces, nslen ) ) { nskeys = KEYS(pb.list[pos][2]); remove_namespaces( pb.namespaces, nskeys, nskeys.length ); if ( pb.list[pos][1] ) pb.oneOffs = pb.oneOffs > 0 ? (pb.oneOffs-1) : 0; pb.list.splice( pos, 1 ); } else if ( !nslen ) { if ( pb.list[pos][2] ) { nskeys = KEYS(pb.list[pos][2]); remove_namespaces( pb.namespaces, nskeys, nskeys.length ); } if ( pb.list[pos][1] ) pb.oneOffs = pb.oneOffs > 0 ? (pb.oneOffs-1) : 0; pb.list.splice( pos, 1 ); } } } } } else if ( !hasSubscriber && (nslen > 0) && (pos > 0) ) { while ( --pos >= 0 ) { if ( pb.list[pos][2] && match_namespace( pb.list[pos][2], namespaces, nslen ) ) { nskeys = KEYS(pb.list[pos][2]); remove_namespaces( pb.namespaces, nskeys, nskeys.length ); if ( pb.list[pos][1] ) pb.oneOffs = pb.oneOffs > 0 ? (pb.oneOffs-1) : 0; pb.list.splice( pos, 1 ); } } } else if ( !hasSubscriber && (pos > 0) ) { pb.list = [ ]; pb.oneOffs = 0; pb.namespaces = { }; } } function unsubscribe( seps, pubsub, topic, subscriber ) { if ( pubsub ) { topic = parse_topic( seps, topic ); var t, t2, tags = topic[1].join( OTAG_SEP ), namespaces = topic[2], _topic, _tag, tagslen = tags.length, nslen = namespaces.length, topiclen, hasSubscriber ; topic = topic[0].join( OTOPIC_SEP ); topiclen = topic.length; _topic = topiclen ? 'tp_' + topic : false; _tag = tagslen ? 'tg_' + tags : false; hasSubscriber = !!(subscriber && ("function" === typeof( subscriber ))); if ( !hasSubscriber ) subscriber = null; if ( topiclen && HAS.call(pubsub.topics,_topic) ) { if ( tagslen && HAS.call(pubsub.topics[ _topic ].tags,_tag) ) { remove_subscriber( pubsub.topics[ _topic ].tags[ _tag ], hasSubscriber, subscriber, namespaces, nslen ); if ( !pubsub.topics[ _topic ].tags[ _tag ].list.length ) delete pubsub.topics[ _topic ].tags[ _tag ]; } else if ( !tagslen ) { remove_subscriber( pubsub.topics[ _topic ].notags, hasSubscriber, subscriber, namespaces, nslen ); } if ( !pubsub.topics[ _topic ].notags.list.length && !KEYS(pubsub.topics[ _topic ].tags).length ) delete pubsub.topics[ _topic ]; } else if ( !topiclen && (tagslen || nslen) ) { if ( tagslen ) { if ( HAS.call(pubsub.notopics.tags,_tag) ) { remove_subscriber( pubsub.notopics.tags[ _tag ], hasSubscriber, subscriber, namespaces, nslen ); if ( !pubsub.notopics.tags[ _tag ].list.length ) delete pubsub.notopics.tags[ _tag ]; } // remove from any topics as well for ( t in pubsub.topics ) { if ( HAS.call(pubsub.topics,t) && HAS.call(pubsub.topics[ t ].tags,_tag) ) { remove_subscriber( pubsub.topics[ t ].tags[ _tag ], hasSubscriber, subscriber, namespaces, nslen ); if ( !pubsub.topics[ t ].tags[ _tag ].list.length ) delete pubsub.topics[ t ].tags[ _tag ]; } } } else { remove_subscriber( pubsub.notopics.notags, hasSubscriber, subscriber, namespaces, nslen ); // remove from any tags as well for ( t2 in pubsub.notopics.tags ) { if ( HAS.call(pubsub.notopics.tags,t2) ) { remove_subscriber( pubsub.notopics.tags[ t2 ], hasSubscriber, subscriber, namespaces, nslen ); if ( !pubsub.notopics.tags[ t2 ].list.length ) delete pubsub.notopics.tags[ t2 ]; } } // remove from any topics and tags as well for ( t in pubsub.topics ) { if ( HAS.call(pubsub.topics,t) ) { remove_subscriber( pubsub.topics[ t ].notags, hasSubscriber, subscriber, namespaces, nslen ); for ( t2 in pubsub.topics[ t ].tags ) { if ( HAS.call(pubsub.topics[ t ].tags,t2) ) { remove_subscriber( pubsub.topics[ t ].tags[ t2 ], hasSubscriber, subscriber, namespaces, nslen ); if ( !pubsub.topics[ t ].tags[ t2 ].list.length ) delete pubsub.topics[ t ].tags[ t2 ]; } } } } } } } } // // PublishSubscribe (Interface) var PublishSubscribe = function( ) { if ( !(this instanceof PublishSubscribe) ) return new PublishSubscribe( ); this.initPubSub( ); }; PublishSubscribe.VERSION = __version__; PublishSubscribe.Event = PublishSubscribeEvent; PublishSubscribe.Data = function( props ) { return new PublishSubscribeData(props); }; PublishSubscribe[PROTO] = { constructor: PublishSubscribe ,_seps: null ,_pubsub$: null ,initPubSub: function( ) { var self = this; self._seps = [TOPIC_SEP, TAG_SEP, NS_SEP]; self._pubsub$ = get_pubsub( ); return self; } ,disposePubSub: function( ) { var self = this; self._seps = null; self._pubsub$ = null; return self; } ,setSeparators: function( seps ) { var self = this, l; if ( seps && (l=seps.length) ) { if ( l > 0 && seps[0] ) self._seps[0] = seps[0]; if ( l > 1 && seps[1] ) self._seps[1] = seps[1]; if ( l > 2 && seps[2] ) self._seps[2] = seps[2]; } return self; } ,trigger: function( message, data, delay ) { var self = this; if ( 3 > arguments.length ) delay = 0; delay = +delay; if ( 2 > arguments.length ) data = { }; if ( delay > 0 ) { setTimeout(function( ) { publish( self, self._seps, self._pubsub$, message, data ); }, delay); } else { //console.log(JSON.stringify(self._pubsub$, null, 4)); publish( self, self._seps, self._pubsub$, message, data ); //console.log(JSON.stringify(self._pubsub$, null, 4)); } return self; } ,pipeline: function( message, data, abort, finish, delay ) { var self = this; if ( 5 > arguments.length ) delay = 0; delay = +delay; if ( 2 > arguments.length ) data = { }; if ( delay > 0 ) { setTimeout(function( ) { pipeline( self, self._seps, self._pubsub$, message, data, abort||null, finish||null ); }, delay); } else { //console.log(JSON.stringify(self._pubsub$, null, 4)); pipeline( self, self._seps, self._pubsub$, message, data, abort||null, finish||null ); //console.log(JSON.stringify(self._pubsub$, null, 4)); } return self; } ,on: function( message, callback ) { var self = this; if ( callback && "function" === typeof(callback) ) { //console.log(JSON.stringify(self._pubsub$, null, 4)); subscribe( self._seps, self._pubsub$, message, callback ); //console.log(JSON.stringify(self._pubsub$, null, 4)); } return self; } ,one: function( message, callback ) { var self = this; if ( callback && "function" === typeof(callback) ) { //console.log(JSON.stringify(self._pubsub$, null, 4)); subscribe( self._seps, self._pubsub$, message, callback, true ); //console.log(JSON.stringify(self._pubsub$, null, 4)); } return self; } ,on1: function( message, callback ) { var self = this; if ( callback && "function" === typeof(callback) ) { //console.log(JSON.stringify(self._pubsub$, null, 4)); subscribe( self._seps, self._pubsub$, message, callback, false, true ); //console.log(JSON.stringify(self._pubsub$, null, 4)); } return self; } ,one1: function( message, callback ) { var self = this; if ( callback && "function" === typeof(callback) ) { //console.log(JSON.stringify(self._pubsub$, null, 4)); subscribe( self._seps, self._pubsub$, message, callback, true, true ); //console.log(JSON.stringify(self._pubsub$, null, 4)); } return self; } ,off: function( message, callback ) { var self = this; //console.log(JSON.stringify(self._pubsub$, null, 4)); unsubscribe( self._seps, self._pubsub$, message, callback || null ); //console.log(JSON.stringify(self._pubsub$, null, 4)); return self; } }; // export it return PublishSubscribe; });