text: /*! intercom.js | https://github.com/diy/intercom.js | Apache License (v2) */
  /*! This version forked at https://github.com/JHunz/intercom.js */
  
  define(function() {	
  	
  	// --- lib/events.js ---
  	
  	var EventEmitter = function() {};
  	
  	EventEmitter.createInterface = function(space) {
  		var methods = {};
  		
  		methods.on = function(name, fn) {
  			if (typeof this[space] === 'undefined') {
  				this[space] = {};
  			}
  			if (!this[space].hasOwnProperty(name)) {
  				this[space][name] = [];
  			}
  			this[space][name].push(fn);
  		};
  		
  		methods.off = function(name, fn) {
  			if (typeof this[space] === 'undefined') return;
  			if (this[space].hasOwnProperty(name)) {
  				util.removeItem(fn, this[space][name]);
  			}
  		};
  		
  		methods.trigger = function(name) {
  			if (typeof this[space] !== 'undefined' && this[space].hasOwnProperty(name)) {
  				var args = Array.prototype.slice.call(arguments, 1);
  				for (var i = 0; i < this[space][name].length; i++) {
  					this[space][name][i].apply(this[space][name][i], args);
  				}
  			}
  		};
  		
  		return methods;
  	};
  	
  	var pvt = EventEmitter.createInterface('_handlers');
  	EventEmitter.prototype._on = pvt.on;
  	EventEmitter.prototype._off = pvt.off;
  	EventEmitter.prototype._trigger = pvt.trigger;
  	
  	var pub = EventEmitter.createInterface('handlers');
  	EventEmitter.prototype.on = function() {
  		pub.on.apply(this, arguments);
  		Array.prototype.unshift.call(arguments, 'on');
  		this._trigger.apply(this, arguments);
  	};
  	EventEmitter.prototype.off = pub.off;
  	EventEmitter.prototype.trigger = pub.trigger;
  	
  	// --- lib/localstorage.js ---
  	
  	var localStorage = window.localStorage;
  	if (typeof localStorage === 'undefined') {
  		localStorage = {
  			getItem    : function() {},
  			setItem    : function() {},
  			removeItem : function() {}
  		};
  	}
  	
  	// --- lib/util.js ---
  	
  	var util = {};
  	
  	util.guid = (function() {
  		var S4 = function() {
  			return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  		};
  		return function() {
  			return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
  		};
  	})();
  	
  	util.throttle = function(delay, fn) {
  		var last = 0;
  		return function() {
  			var now = (new Date()).getTime();
  			if (now - last > delay) {
  				last = now;
  				fn.apply(this, arguments);
  			}
  		};
  	};
  	
  	util.extend = function(a, b) {
  		if (typeof a === 'undefined' || !a) { a = {}; }
  		if (typeof b === 'object') {
  			for (var key in b) {
  				if (b.hasOwnProperty(key)) {
  					a[key] = b[key];
  				}
  			}
  		}
  		return a;
  	};
  	
  	util.removeItem = function(item, array) {
  		for (var i = array.length - 1; i >= 0; i--) {
  			if (array[i] === item) {
  				array.splice(i, 1);
  			}
  		}
  		return array;
  	};
  	
  	// --- lib/intercom.js ---
  	
  	/**
  	* A cross-window broadcast service built on top
  	* of the HTML5 localStorage API. The interface
  	* mimic socket.io in design.
  	*
  	* @author Brian Reavis <brian@thirdroute.com>
  	* @constructor
  	*/
  	
  	var Intercom = function() {
  		var self = this;
  		var now = (new Date()).getTime();
  	
  		this.origin         = util.guid();
  		this.lastMessage    = now;
  		this.bindings       = [];
  		this.receivedIDs    = {};
  		this.previousValues = {};
  	
  		var storageHandler = function() { self._onStorageEvent.apply(self, arguments); };
  		if (window.attachEvent) { document.attachEvent('onstorage', storageHandler); }
  		else { window.addEventListener('storage', storageHandler, false); };
  	};
  	
  	Intercom.prototype._transaction = function(fn) {
  		var TIMEOUT   = 1000;
  		var WAIT      = 20;
  	
  		var self      = this;
  		var executed  = false;
  		var listening = false;
  		var waitTimer = null;
  	
  		var lock = function() {
  			if (executed) return;
  	
  			var now = (new Date()).getTime();
  			var activeLock = parseInt(localStorage.getItem(INDEX_LOCK) || 0);
  			if (activeLock && now - activeLock < TIMEOUT) {
  				if (!listening) {
  					self._on('storage', lock);
  					listening = true;
  				}
  				waitTimer = window.setTimeout(lock, WAIT);
  				return;
  			}
  			executed = true;
  			localStorage.setItem(INDEX_LOCK, now);
  	
  			fn();
  			unlock();
  		};
  	
  		var unlock = function() {
  			if (listening) { self._off('storage', lock); }
  			if (waitTimer) { window.clearTimeout(waitTimer); }
  			localStorage.removeItem(INDEX_LOCK);
  		};
  	
  		lock();
  	};
  	
  	Intercom.prototype._cleanup_emit = util.throttle(100, function() {
  		var self = this;
  	
  		this._transaction(function() {
  			var now = (new Date()).getTime();
  			var threshold = now - THRESHOLD_TTL_EMIT;
  			var changed = 0;
  	
  			var messages = JSON.parse(localStorage.getItem(INDEX_EMIT) || '[]');
  			for (var i = messages.length - 1; i >= 0; i--) {
  				if (messages[i].timestamp < threshold) {
  					messages.splice(i, 1);
  					changed++;
  				}
  			}
  			if (changed > 0) {
  				localStorage.setItem(INDEX_EMIT, JSON.stringify(messages));
  			}
  		});
  	});
  	
  	Intercom.prototype._cleanup_once = util.throttle(100, function() {
  		var self = this;
  	
  		this._transaction(function() {
  			var timestamp, ttl, key;
  			var table   = JSON.parse(localStorage.getItem(INDEX_ONCE) || '{}');
  			var now     = (new Date()).getTime();
  			var changed = 0;
  	
  			for (key in table) {
  				if (self._once_expired(key, table)) {
  					delete table[key];
  					changed++;
  				}
  			}
  	
  			if (changed > 0) {
  				localStorage.setItem(INDEX_ONCE, JSON.stringify(table));
  			}
  		});
  	});
  	
  	Intercom.prototype._once_expired = function(key, table) {
  		if (!table) return true;
  		if (!table.hasOwnProperty(key)) return true;
  		if (typeof table[key] !== 'object') return true;
  		var ttl = table[key].ttl || THRESHOLD_TTL_ONCE;
  		var now = (new Date()).getTime();
  		var timestamp = table[key].timestamp;
  		return timestamp < now - ttl;
  	};
  	
  	Intercom.prototype._localStorageChanged = function(event, field) {
  		if (event && event.key) {
  			return event.key === field;
  		}
  	
  		var currentValue = localStorage.getItem(field);
  		if (currentValue === this.previousValues[field]) {
  			return false;
  		}
  		this.previousValues[field] = currentValue;
  		return true;
  	};
  	
  	Intercom.prototype._onStorageEvent = function(event) {
  		event = event || window.event;
  		var self = this;
  	
  		if (this._localStorageChanged(event, INDEX_EMIT)) {
  			this._transaction(function() {
  				var now = (new Date()).getTime();
  				var data = localStorage.getItem(INDEX_EMIT);
  				var messages = JSON.parse(data || '[]');
  				for (var i = 0; i < messages.length; i++) {
  					if (messages[i].origin === self.origin && !(messages[i].allowSelf === true)) continue;
  					if (messages[i].timestamp < self.lastMessage) continue;
  					if (messages[i].id) {
  						if (self.receivedIDs.hasOwnProperty(messages[i].id)) continue;
  						self.receivedIDs[messages[i].id] = true;
  					}
  					self.trigger(messages[i].name, messages[i].payload);
  				}
  				self.lastMessage = now;
  			});
  		}
  	
  		this._trigger('storage', event);
  	};
  	
  	Intercom.prototype._emit = function(name, message, allowSelf, id) {
  		id = (typeof id === 'string' || typeof id === 'number') ? String(id) : null;
  		if (id && id.length) {
  			if (this.receivedIDs.hasOwnProperty(id)) return;
  			this.receivedIDs[id] = true;
  		}
  	
  		var packet = {
  			id        : id,
  			name      : name,
  			origin    : this.origin,
  			timestamp : (new Date()).getTime(),
  			payload   : message,
  			allowSelf : allowSelf
  		};
  	
  		var self = this;
  		this._transaction(function() {
  			var data = localStorage.getItem(INDEX_EMIT) || '[]';
  			var delimiter = (data === '[]') ? '' : ',';
  			data = [data.substring(0, data.length - 1), delimiter, JSON.stringify(packet), ']'].join('');
  			localStorage.setItem(INDEX_EMIT, data);
  	
  			if (packet.origin != self.origin || packet.allowSelf === true) {
  				self.trigger(name, message);
  			}
  	
  			window.setTimeout(function() { self._cleanup_emit(); }, 50);
  		});
  	};
  	
  	Intercom.prototype.bind = function(object, options) {
  		for (var i = 0; i < Intercom.bindings.length; i++) {
  			var binding = Intercom.bindings[i].factory(object, options || null, this);
  			if (binding) { this.bindings.push(binding); }
  		}
  	};
  	
  	Intercom.prototype.emit = function(name, message, allowSelf) {
  		this._emit.apply(this, arguments);
  		this._trigger('emit', name, message, allowSelf);
  	};
  	
  	Intercom.prototype.once = function(key, fn, ttl) {
  		if (!Intercom.supported) return;
  	
  		var self = this;
  		this._transaction(function() {
  			var data = JSON.parse(localStorage.getItem(INDEX_ONCE) || '{}');
  			if (!self._once_expired(key, data)) return;
  	
  			data[key] = {};
  			data[key].timestamp = (new Date()).getTime();
  			if (typeof ttl === 'number') {
  				data[key].ttl = ttl * 1000;
  			}
  	
  			localStorage.setItem(INDEX_ONCE, JSON.stringify(data));
  			fn();
  	
  			window.setTimeout(function() { self._cleanup_once(); }, 50);
  		});
  	};
  	
  	util.extend(Intercom.prototype, EventEmitter.prototype);
  	
  	Intercom.bindings = [];
  	Intercom.supported = (typeof localStorage !== 'undefined');
  	
  	var INDEX_EMIT = 'intercom';
  	var INDEX_ONCE = 'intercom_once';
  	var INDEX_LOCK = 'intercom_lock';
  	
  	var THRESHOLD_TTL_EMIT = 50000;
  	var THRESHOLD_TTL_ONCE = 1000 * 3600;
  	
  	Intercom.destroy = function() {
  		localStorage.removeItem(INDEX_LOCK);
  		localStorage.removeItem(INDEX_EMIT);
  		localStorage.removeItem(INDEX_ONCE);
  	};
  	
  	Intercom.getInstance = (function() {
  		var intercom = null;
  		return function() {
  			if (!intercom) {
  				intercom = new Intercom();
  			}
  			return intercom;
  		};
  	})();
  	
  	// --- lib/bindings/socket.js ---
  	
  	/**
  	* Socket.io binding for intercom.js.
  	*
  	* - When a message is received on the socket, it's emitted on intercom.
  	* - When a message is emitted via intercom, it's sent over the socket.
  	*
  	* @author Brian Reavis <brian@thirdroute.com>
  	*/
  	
  	var SocketBinding = function(socket, options, intercom) {
  		options = util.extend({
  			id      : null,
  			send    : true,
  			receive : true
  		}, options);
  		
  		if (options.receive) {
  			var watchedEvents = [];
  			var onEventAdded = function(name, fn) {
  				if (watchedEvents.indexOf(name) === -1) {
  					watchedEvents.push(name);
  					socket.on(name, function(data) {
  						var id = (typeof options.id === 'function') ? options.id(name, data) : null;
  						var emit = (typeof options.receive === 'function') ? options.receive(name, data) : true;
  						if (emit || typeof emit !== 'boolean') {
  							intercom._emit(name, data, id);
  						}
  					});
  				}
  			};
  	
  			for (var name in intercom.handlers) {
  				for (var i = 0; i < intercom.handlers[name].length; i++) {
  					onEventAdded(name, intercom.handlers[name][i]);
  				}
  			}
  		
  			intercom._on('on', onEventAdded);
  		}
  		
  		if (options.send) {
  			intercom._on('emit', function(name, message) {
  				var emit = (typeof options.send === 'function') ? options.send(name, message) : true;
  				if (emit || typeof emit !== 'boolean') {
  					socket.emit(name, message);
  				}
  			});
  		}
  	};
  	
  	SocketBinding.factory = function(object, options, intercom) {
  		if (typeof object.socket === 'undefined') { return false };
  		return new SocketBinding(object, options, intercom);
  	};
  	
  	Intercom.bindings.push(SocketBinding);
  	return Intercom;
  });
  