diff --git a/app-showcase/directors-console/director.js b/app-showcase/directors-console/director.js
index 6c15b5e2..a8f46cc3 100644
--- a/app-showcase/directors-console/director.js
+++ b/app-showcase/directors-console/director.js
@@ -1,7 +1,7 @@
(function(){
var p = PUBNUB.init({ publish_key: 'demo', subscribe_key : 'demo' })
- , channel = 'my_channel';
+ , channel = 'my_directors_channel';
p.bind( 'mousedown,touchstart', p.$('buttons'), function(e) {
var target = e.target || e.srcElement;
diff --git a/javascript/3.4/README.md b/javascript/3.4/README.md
new file mode 100644
index 00000000..df605a0e
--- /dev/null
+++ b/javascript/3.4/README.md
@@ -0,0 +1,153 @@
+# YOU MUST HAVE A PUBNUB ACCOUNT TO USE THE API.
+http://www.pubnub.com/account
+
+## TESTLING - (OPTIONAL)
+PubNub JavaScript API for Web Browsers
+uses Testling Cloud Service for QA and Deployment.
+http://www.testling.com/
+
+You need this to run './test.sh' unit test.
+This is completely optional, however we love Testling.
+
+
+## PubNub 3.3.1 Real-time Cloud Push API - JAVASCRIPT
+http://www.pubnub.com - PubNub Real-time Push Service in the Cloud.
+http://www.pubnub.com/tutorial/javascript-push-api
+
+PubNub is a blazingly fast cloud-hosted messaging service for building
+real-time web and mobile apps. Hundreds of apps and thousands of developers
+rely on PubNub for delivering human-perceptive real-time
+experiences that scale to millions of users worldwide. PubNub delivers
+the infrastructure needed to build amazing MMO games, social apps,
+business collaborative solutions, and more.
+
+## SIMPLE EXAMPLE
+```html
+
+
+
+```
+
+## ADVANCED STYLE
+```html
+
+
+
+
+Click Me for Here Now! // here_now() example (see console for logged output.)
+
+Click Me for History! // detailedHistory() example (see console for logged output.)
+
+
+
+```
+
+## SSL MODE
+
+```html
+
+
+
+```
+
+## Using the PUBNUB init() Function
+
+Sometimes you want to use create a PubNub Instance directly in JavaScript
+and pass the PubNub API Keys without using a DOM element.
+To do this, simply follow this `init` example:
+
+```html
+
+
+```
\ No newline at end of file
diff --git a/javascript/3.4/comet.swf b/javascript/3.4/comet.swf
new file mode 100644
index 00000000..1209542d
Binary files /dev/null and b/javascript/3.4/comet.swf differ
diff --git a/javascript/3.4/crossdomain.xml b/javascript/3.4/crossdomain.xml
new file mode 100644
index 00000000..8fb3c522
--- /dev/null
+++ b/javascript/3.4/crossdomain.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/javascript/3.4/crypto.js b/javascript/3.4/crypto.js
new file mode 100644
index 00000000..e69de29b
diff --git a/javascript/3.4/json.js b/javascript/3.4/json.js
new file mode 100644
index 00000000..5802af65
--- /dev/null
+++ b/javascript/3.4/json.js
@@ -0,0 +1,155 @@
+/* =-====================================================================-= */
+/* =-====================================================================-= */
+/* =-========================= JSON =============================-= */
+/* =-====================================================================-= */
+/* =-====================================================================-= */
+
+(window['JSON'] && window['JSON']['stringify']) || (function () {
+ window['JSON'] || (window['JSON'] = {});
+
+ if (typeof String.prototype.toJSON !== 'function') {
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+ function quote(string) {
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+ return String(value);
+
+ case 'object':
+
+ if (!value) {
+ return 'null';
+ }
+
+ gap += indent;
+ partial = [];
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+ if (typeof JSON['stringify'] !== 'function') {
+ JSON['stringify'] = function (value, replacer, space) {
+ var i;
+ gap = '';
+ indent = '';
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+ return str('', {'': value});
+ };
+ }
+
+ if (typeof JSON['parse'] !== 'function') {
+ // JSON is parsed on the server for security.
+ JSON['parse'] = function (text) {return eval('('+text+')')};
+ }
+}());
diff --git a/javascript/3.4/pubnub-3.4.js b/javascript/3.4/pubnub-3.4.js
new file mode 100644
index 00000000..12b0dfcb
--- /dev/null
+++ b/javascript/3.4/pubnub-3.4.js
@@ -0,0 +1,946 @@
+/* ---------------------------------------------------------------------------
+WAIT! - This file depends on instructions from the PUBNUB Cloud.
+http://www.pubnub.com/account-javascript-api-include
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks
+Copyright (c) 2011 PubNub Inc.
+http://www.pubnub.com/
+http://www.pubnub.com/terms
+--------------------------------------------------------------------------- */
+
+/* ---------------------------------------------------------------------------
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+--------------------------------------------------------------------------- */
+
+/* =-====================================================================-= */
+/* =-====================================================================-= */
+/* =-========================= UTIL =============================-= */
+/* =-====================================================================-= */
+/* =-====================================================================-= */
+
+window['PUBNUB'] || (function() {
+
+/**
+ * UTIL LOCALS
+ */
+var NOW = 1
+, SWF = 'https://dh15atwfs066y.cloudfront.net/pubnub.swf'
+, REPL = /{([\w\-]+)}/g
+, ASYNC = 'async'
+, URLBIT = '/'
+, PARAMSBIT = '&'
+, SUB_TIMEOUT = 310000
+, DEF_TIMEOUT = 10000
+, SECOND = 1000
+, PRESENCE_SUFFIX = '-pnpres'
+, UA = navigator.userAgent
+, XORIGN = UA.indexOf('MSIE 6') == -1;
+
+/**
+ * CONSOLE COMPATIBILITY
+ */
+window.console||(window.console=window.console||{});
+console.log||(console.log=((window.opera||{}).postError||function(){}));
+
+/**
+ * UTILITIES
+ */
+function unique() { return'x'+ ++NOW+''+(+new Date) }
+function rnow() { return+new Date }
+
+/**
+ * LOCAL STORAGE OR COOKIE
+ */
+var db = (function(){
+ var ls = window['localStorage'];
+ return {
+ 'get' : function(key) {
+ try {
+ if (ls) return ls.getItem(key);
+ if (document.cookie.indexOf(key) == -1) return null;
+ return ((document.cookie||'').match(
+ RegExp(key+'=([^;]+)')
+ )||[])[1] || null;
+ } catch(e) { return }
+ },
+ 'set' : function( key, value ) {
+ try {
+ if (ls) return ls.setItem( key, value ) && 0;
+ document.cookie = key + '=' + value +
+ '; expires=Thu, 1 Aug 2030 20:00:00 UTC; path=/';
+ } catch(e) { return }
+ }
+ };
+})();
+
+/**
+ * NEXTORIGIN
+ * ==========
+ * var next_origin = nextorigin();
+ */
+var nextorigin = (function() {
+ var max = 100
+ , ori = Math.floor(Math.random() * max);
+ return function(origin) {
+ return origin.indexOf('pubsub') > 0
+ && origin.replace(
+ 'pubsub', 'ps' + (++ori < max? ori : ori=1)
+ ) || origin;
+ }
+})();
+
+/**
+ * UPDATER
+ * ======
+ * var timestamp = unique();
+ */
+function updater( fun, rate ) {
+ var timeout
+ , last = 0
+ , runnit = function() {
+ if (last + rate > rnow()) {
+ clearTimeout(timeout);
+ timeout = setTimeout( runnit, rate );
+ }
+ else {
+ last = rnow();
+ fun();
+ }
+ };
+
+ return runnit;
+}
+
+/**
+ * $
+ * =
+ * var div = $('divid');
+ */
+function $(id) { return document.getElementById(id) }
+
+/**
+ * LOG
+ * ===
+ * log('message');
+ */
+function log(message) { console['log'](message) }
+
+/**
+ * SEARCH
+ * ======
+ * var elements = search('a div span');
+ */
+function search( elements, start ) {
+ var list = [];
+ each( elements.split(/\s+/), function(el) {
+ each( (start || document).getElementsByTagName(el), function(node) {
+ list.push(node);
+ } );
+ } );
+ return list;
+}
+
+/**
+ * EACH
+ * ====
+ * each( [1,2,3], function(item) { console.log(item) } )
+ */
+function each( o, f ) {
+ if ( !o || !f ) return;
+
+ if ( typeof o[0] != 'undefined' )
+ for ( var i = 0, l = o.length; i < l; )
+ f.call( o[i], o[i], i++ );
+ else
+ for ( var i in o )
+ o.hasOwnProperty &&
+ o.hasOwnProperty(i) &&
+ f.call( o[i], i, o[i] );
+}
+
+/**
+ * MAP
+ * ===
+ * var list = map( [1,2,3], function(item) { return item + 1 } )
+ */
+function map( list, fun ) {
+ var fin = [];
+ each( list || [], function( k, v ) { fin.push(fun( k, v )) } );
+ return fin;
+}
+
+/**
+ * GREP
+ * ====
+ * var list = grep( [1,2,3], function(item) { return item % 2 } )
+ */
+function grep( list, fun ) {
+ var fin = [];
+ each( list || [], function(l) { fun(l) && fin.push(l) } );
+ return fin
+}
+
+/**
+ * SUPPLANT
+ * ========
+ * var text = supplant( 'Hello {name}!', { name : 'John' } )
+ */
+function supplant( str, values ) {
+ return str.replace( REPL, function( _, match ) {
+ return values[match] || _
+ } );
+}
+
+/**
+ * BIND
+ * ====
+ * bind( 'keydown', search('a')[0], function(element) {
+ * console.log( element, '1st anchor' )
+ * } );
+ */
+function bind( type, el, fun ) {
+ each( type.split(','), function(etype) {
+ var rapfun = function(e) {
+ if (!e) e = window.event;
+ if (!fun(e)) {
+ e.cancelBubble = true;
+ e.returnValue = false;
+ e.preventDefault && e.preventDefault();
+ e.stopPropagation && e.stopPropagation();
+ }
+ };
+
+ if ( el.addEventListener ) el.addEventListener( etype, rapfun, false );
+ else if ( el.attachEvent ) el.attachEvent( 'on' + etype, rapfun );
+ else el[ 'on' + etype ] = rapfun;
+ } );
+}
+
+/**
+ * UNBIND
+ * ======
+ * unbind( 'keydown', search('a')[0] );
+ */
+function unbind( type, el, fun ) {
+ if ( el.removeEventListener ) el.removeEventListener( type, false );
+ else if ( el.detachEvent ) el.detachEvent( 'on' + type, false );
+ else el[ 'on' + type ] = null;
+}
+
+/**
+ * HEAD
+ * ====
+ * head().appendChild(elm);
+ */
+function head() { return search('head')[0] }
+
+/**
+ * ATTR
+ * ====
+ * var attribute = attr( node, 'attribute' );
+ */
+function attr( node, attribute, value ) {
+ if (value) node.setAttribute( attribute, value );
+ else return node && node.getAttribute && node.getAttribute(attribute);
+}
+
+/**
+ * CSS
+ * ===
+ * var obj = create('div');
+ */
+function css( element, styles ) {
+ for (var style in styles) if (styles.hasOwnProperty(style))
+ try {element.style[style] = styles[style] + (
+ '|width|height|top|left|'.indexOf(style) > 0 &&
+ typeof styles[style] == 'number'
+ ? 'px' : ''
+ )}catch(e){}
+}
+
+/**
+ * CREATE
+ * ======
+ * var obj = create('div');
+ */
+function create(element) { return document.createElement(element) }
+
+/**
+ * timeout
+ * =======
+ * timeout( function(){}, 100 );
+ */
+function timeout( fun, wait ) {
+ return setTimeout( fun, wait );
+}
+
+/**
+ * jsonp_cb
+ * ========
+ * var callback = jsonp_cb();
+ */
+function jsonp_cb() { return XORIGN || FDomainRequest() ? 0 : unique() }
+
+/**
+ * ENCODE
+ * ======
+ * var encoded_path = encode('path');
+ */
+function encode(path) {
+ return map( (encodeURIComponent(path)).split(''), function(chr) {
+ return "-_.!~*'()".indexOf(chr) < 0 ? chr :
+ "%"+chr.charCodeAt(0).toString(16).toUpperCase()
+ } ).join('');
+}
+
+/**
+ * EVENTS
+ * ======
+ * PUBNUB.events.bind( 'you-stepped-on-flower', function(message) {
+ * // Do Stuff with message
+ * } );
+ *
+ * PUBNUB.events.fire( 'you-stepped-on-flower', "message-data" );
+ * PUBNUB.events.fire( 'you-stepped-on-flower', {message:"data"} );
+ * PUBNUB.events.fire( 'you-stepped-on-flower', [1,2,3] );
+ *
+ */
+var events = {
+ 'list' : {},
+ 'unbind' : function( name ) { events.list[name] = [] },
+ 'bind' : function( name, fun ) {
+ (events.list[name] = events.list[name] || []).push(fun);
+ },
+ 'fire' : function( name, data ) {
+ each(
+ events.list[name] || [],
+ function(fun) { fun(data) }
+ );
+ }
+};
+
+/**
+ * XDR Cross Domain Request
+ * ========================
+ * xdr({
+ * url : ['http://www.blah.com/url'],
+ * success : function(response) {},
+ * fail : function() {}
+ * });
+ */
+function xdr( setup ) {
+ if (XORIGN || FDomainRequest()) return ajax(setup);
+
+ var script = create('script')
+ , callback = setup.callback
+ , id = unique()
+ , finished = 0
+ , xhrtme = setup.timeout || DEF_TIMEOUT
+ , timer = timeout( function(){done(1)}, xhrtme )
+ , fail = setup.fail || function(){}
+ , success = setup.success || function(){}
+
+ , append = function() {
+ head().appendChild(script);
+ }
+
+ , done = function( failed, response ) {
+ if (finished) return;
+ finished = 1;
+
+ failed || success(response);
+ script.onerror = null;
+ clearTimeout(timer);
+
+ timeout( function() {
+ failed && fail();
+ var s = $(id)
+ , p = s && s.parentNode;
+ p && p.removeChild(s);
+ }, SECOND );
+ };
+
+ window[callback] = function(response) {
+ done( 0, response );
+ };
+
+ if (!setup.blocking) script[ASYNC] = ASYNC;
+
+ script.onerror = function() { done(1) };
+ script.src = setup.url.join(URLBIT);
+
+ if (setup.data) {
+ var params = [];
+ script.src += "?";
+ for (key in setup.data) {
+ params.push(key+"="+setup.data[key]);
+ }
+ script.src += params.join(PARAMSBIT);
+ }
+ attr( script, 'id', id );
+
+ append();
+ return done;
+}
+
+/**
+ * CORS XHR Request
+ * ================
+ * xdr({
+ * url : ['http://www.blah.com/url'],
+ * success : function(response) {},
+ * fail : function() {}
+ * });
+ */
+function ajax( setup ) {
+ var xhr, response
+ , finished = function() {
+ if (loaded) return;
+ loaded = 1;
+
+ clearTimeout(timer);
+
+ try { response = JSON['parse'](xhr.responseText); }
+ catch (r) { return done(1); }
+
+ success(response);
+ }
+ , complete = 0
+ , loaded = 0
+ , xhrtme = setup.timeout || DEF_TIMEOUT
+ , timer = timeout( function(){done(1)}, xhrtme )
+ , fail = setup.fail || function(){}
+ , success = setup.success || function(){}
+ , done = function(failed) {
+ if (complete) return;
+ complete = 1;
+
+ clearTimeout(timer);
+
+ if (xhr) {
+ xhr.onerror = xhr.onload = null;
+ xhr.abort && xhr.abort();
+ xhr = null;
+ }
+
+ failed && fail();
+ };
+
+ // Send
+ try {
+ xhr = FDomainRequest() ||
+ window.XDomainRequest &&
+ new XDomainRequest() ||
+ new XMLHttpRequest();
+
+ xhr.onerror = xhr.onabort = function(){ done(1) };
+ xhr.onload = xhr.onloadend = finished;
+ xhr.timeout = xhrtme;
+
+ var url = setup.url.join(URLBIT);
+ if (setup.data) {
+ var params = [];
+ var key;
+ url += "?";
+ for (key in setup.data) params.push(key+"="+setup.data[key]);
+ url += params.join(PARAMSBIT);
+ }
+
+ xhr.open( 'GET', url, typeof setup.blocking == 'undefined' );
+ xhr.send();
+ }
+ catch(eee) {
+ done(0);
+ XORIGN = 0;
+ return xdr(setup);
+ }
+
+ // Return 'done'
+ return done;
+}
+
+/**
+ * Generate Subscription Channel List
+ * ==================================
+ * generate_channel_list(channels_object);
+ */
+function generate_channel_list(channels) {
+ var list = [];
+ each( channels, function( channel, status ) {
+ if (status.connected) list.push(channel);
+ } );
+ return list.sort().join(',');
+}
+
+/* =-====================================================================-= */
+/* =-====================================================================-= */
+/* =-========================= PUBNUB ===========================-= */
+/* =-====================================================================-= */
+/* =-====================================================================-= */
+
+var PDIV = $('pubnub') || {}
+, READY = 0
+, READY_BUFFER = []
+, CREATE_PUBNUB = function(setup) {
+ var CHANNELS = {}
+ , SUB_RECEIVER = null
+ , TIMETOKEN = 0
+ , DISCONNECTED = 0
+ , CONNECTED = 0
+ , PUBLISH_KEY = setup['publish_key'] || ''
+ , SUBSCRIBE_KEY = setup['subscribe_key'] || ''
+ , SSL = setup['ssl'] ? 's' : ''
+ , UUID = setup['uuid'] || db.get(SUBSCRIBE_KEY+'uuid') || ''
+ , ORIGIN = 'http'+SSL+'://'+(setup['origin']||'pubsub.pubnub.com')
+ , LEAVE = function(){}
+ , CONNECT = function(){}
+ , SELF = {
+ /*
+ PUBNUB.history({
+ channel : 'my_chat_channel',
+ limit : 100,
+ callback : function(messages) { console.log(messages) }
+ });
+ */
+ 'history' : function( args, callback ) {
+ var callback = args['callback'] || callback
+ , count = args['count'] || args['limit'] || 100
+ , reverse = args['reverse'] || "false"
+ , channel = args['channel']
+ , start = args['start']
+ , end = args['end']
+ , params = {}
+ , jsonp = jsonp_cb();
+
+ // Make sure we have a Channel
+ if (!channel) return log('Missing Channel');
+ if (!callback) return log('Missing Callback');
+ if (!SUBSCRIBE_KEY) return log('Missing Subscribe Key');
+
+ params["count"] = count;
+ params["reverse"] = reverse;
+
+ if (start) params["start"] = start;
+ if (end) params["end"] = end;
+
+ // Send Message
+ xdr({
+ callback : jsonp,
+ data : params,
+ success : function(response) { callback(response) },
+ fail : function(response) { log(response) },
+ url : [
+ ORIGIN, 'v2', 'history',
+ 'sub-key', SUBSCRIBE_KEY, 'channel', encode(channel)
+ ]
+ });
+ },
+
+ /*
+ PUBNUB.time(function(time){ console.log(time) });
+ */
+ 'time' : function(callback) {
+ var jsonp = jsonp_cb()
+ , origin = nextorigin(ORIGIN);
+
+ xdr({
+ callback : jsonp,
+ url : [origin, 'time', jsonp],
+ success : function(response) { callback(response[0]) },
+ fail : function() { callback(0) }
+ });
+ },
+
+ /*
+ PUBNUB.uuid(function(uuid) { console.log(uuid) });
+ */
+ 'uuid' : function(callback) {
+ var u = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
+ function(c) {
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+ if (callback) callback(u);
+ return u;
+ },
+
+ /*
+ PUBNUB.publish({
+ channel : 'my_chat_channel',
+ message : 'hello!'
+ });
+ */
+ 'publish' : function( args, callback ) {
+ var callback = callback || args['callback'] || function(){}
+ , message = args['message']
+ , channel = args['channel']
+ , jsonp = jsonp_cb()
+ , origin = nextorigin(ORIGIN)
+ , url;
+
+ if (!message) return log('Missing Message');
+ if (!channel) return log('Missing Channel');
+ if (!PUBLISH_KEY) return log('Missing Publish Key');
+ if (!SUBSCRIBE_KEY) return log('Missing Subscribe Key');
+
+ // If trying to send Object
+ message = JSON['stringify'](message);
+
+ // Create URL
+ url = [
+ origin, 'publish',
+ PUBLISH_KEY, SUBSCRIBE_KEY,
+ 0, encode(channel),
+ jsonp, encode(message)
+ ];
+
+ // Send Message
+ xdr({
+ callback : jsonp,
+ success : function(response) { callback(response) },
+ fail : function() { callback([ 0, 'Disconnected' ]) },
+ url : url,
+ data : { uuid: UUID }
+ });
+ },
+ /*
+ PUBNUB.unsubscribe({ channel : 'my_chat' });
+ */
+ 'unsubscribe' : function(args) {
+ // Unsubscribe from both the Channel and the Presence Channel
+ unsubscribe(args['channel']);
+ unsubscribe(args['channel'] + PRESENCE_SUFFIX);
+
+ // Announce Leave
+ LEAVE(args['channel']);
+
+ // Open Connection if Any Channels Left
+ CONNECT();
+
+ function unsubscribe(channel) {
+ // Leave if there never was a channel.
+ if (!(channel in CHANNELS)) return;
+
+ // Disable Channel
+ CHANNELS[channel].connected = 0;
+
+ // Abort and/or Remove Script
+ SUB_RECEIVER && SUB_RECEIVER()
+ }
+ },
+
+ /*
+ PUBNUB.subscribe({
+ channel : 'my_chat'
+ callback : function(message) { console.log(message) }
+ });
+ */
+ 'subscribe' : function( args, callback ) {
+ var channel = args['channel']
+ , callback = callback || args['callback']
+ , subscribe_key = args['subscribe_key'] || SUBSCRIBE_KEY
+ , error = args['error'] || function(){}
+ , connect = args['connect'] || function(){}
+ , reconnect = args['reconnect'] || function(){}
+ , disconnect = args['disconnect'] || function(){}
+ , presence = args['presence'] || function(){}
+ , restore = args['restore']
+ , origin = nextorigin(ORIGIN);
+
+ // Reduce Status Flicker
+ if (!READY) return READY_BUFFER.push([ args, callback, SELF ]);
+
+ // Make sure we have a Channel
+ if (!channel) return log('Missing Channel');
+ if (!callback) return log('Missing Callback');
+ if (!SUBSCRIBE_KEY) return log('Missing Subscribe Key');
+
+ // If is an Array
+ if (channel.join) channel = channel.join(',');
+
+ // If User used Commas in Channels
+ if (channel.indexOf(',') > 0) {
+ each( channel.split(','), function(channel) {
+ args['channel'] = channel;
+ SELF['subscribe']( args, callback );
+ } );
+ return;
+ }
+
+ if (!(channel in CHANNELS)) CHANNELS[channel] = {};
+
+ // Make sure we have a Channel
+ if (CHANNELS[channel].connected) return log('Already Connected');
+ CHANNELS[channel].connected = 1;
+
+ // Register Callback
+ CHANNELS[channel].callback = callback;
+
+ // Evented Subscribe
+ function _connect() {
+ var jsonp = jsonp_cb();
+
+ // Stop Connection
+ if (!CHANNELS[channel].connected) return;
+
+ // Connect to PubNub Subscribe Servers
+ SUB_RECEIVER = xdr({
+ timeout : SUB_TIMEOUT,
+ callback : jsonp,
+ data : { uuid: UUID },
+ url : [
+ origin,
+ 'subscribe',
+ subscribe_key,
+ encode(generate_channel_list(CHANNELS)),
+ jsonp,
+ TIMETOKEN
+ ],
+ fail : function() {
+ // Disconnect
+ if (!DISCONNECTED) {
+ DISCONNECTED = 1;
+ disconnect();
+ leave();
+ }
+ timeout( _connect, SECOND );
+ SELF['time'](function(success){
+ // Reconnect
+ if (success && DISCONNECTED) {
+ DISCONNECTED = 0;
+ reconnect();
+ }
+ else {
+ error();
+ }
+ });
+ },
+ success : function(messages) {
+ if (!CHANNELS[channel].connected) return;
+
+ // Connect
+ if (!CONNECTED) {
+ CONNECTED = 1;
+ connect();
+ }
+
+ // Reconnect
+ if (DISCONNECTED) {
+ DISCONNECTED = 0;
+ reconnect();
+ }
+
+ // Restore Previous Connection Point if Needed
+ // Also Update Timetoken
+ restore = db.set(
+ SUBSCRIBE_KEY,
+ TIMETOKEN = restore && db.get(
+ subscribe_key + channel
+ ) || messages[1]
+ );
+
+ var next_callback = (function() {
+ var channels = (messages.length > 2 ? messages[2] : '')
+ , list = channels.split(',');
+
+ return function() {
+ var channel = list.shift();
+ return channel in CHANNELS &&
+ CHANNELS[channel].callback ||
+ callback;
+ };
+ })();
+
+ each( messages[0], function(msg) {
+ next_callback()( msg, messages );
+ } );
+
+ timeout( _connect, 10 );
+ }
+ });
+ }
+
+ // Announce Leave Event
+ function leave(chan) {
+ var data = { uuid : UUID }
+ , jsonp = jsonp_cb();
+
+ // Prevent Leaving a Presence Channel
+ if ( channel.indexOf(PRESENCE_SUFFIX) ===
+ channel.length - PRESENCE_SUFFIX.length
+ ) return true;
+
+ if (jsonp != '0') data['callback'] = jsonp;
+
+ xdr({
+ blocking : 1,
+ timeout : 2000,
+ callback : jsonp,
+ data : data,
+ url : [
+ origin, 'v2', 'presence',
+ 'sub_key', SUBSCRIBE_KEY,
+ 'channel', encode(channel),
+ 'leave'
+ ]
+ });
+
+ return true;
+ }
+
+ LEAVE = leave;
+
+ // onBeforeUnload
+ bind( 'beforeunload', window, leave );
+
+ // Presence Subscribe
+ if (args['presence']) SELF.subscribe({
+ channel : args['channel'] + PRESENCE_SUFFIX,
+ callback : presence,
+ restore : args['restore'],
+ disconnect : leave
+ });
+
+ // Close Previous Subscribe Connection
+ SUB_RECEIVER && SUB_RECEIVER();
+
+ // Begin Recursive Subscribe
+ (CONNECT = _connect)();
+ },
+
+ 'here_now' : function( args, callback ) {
+ var callback = args['callback'] || callback
+ , channel = args['channel']
+ , jsonp = jsonp_cb()
+ , data = {}
+ , origin = nextorigin(ORIGIN);
+
+ // Make sure we have a Channel
+ if (!channel) return log('Missing Channel');
+ if (!callback) return log('Missing Callback');
+ if (!SUBSCRIBE_KEY) return log('Missing Subscribe Key');
+
+ if (jsonp != '0') data['callback'] = jsonp;
+
+ xdr({
+ callback : jsonp,
+ data : data,
+ success : function(response) { callback(response) },
+ fail : function(response) { log(response) },
+ url : [
+ origin, 'v2', 'presence',
+ 'sub_key', SUBSCRIBE_KEY,
+ 'channel', encode(channel)
+ ]
+ });
+ },
+
+ // Expose PUBNUB Functions
+ 'xdr' : xdr,
+ 'ready' : ready,
+ 'db' : db,
+ 'each' : each,
+ 'map' : map,
+ 'grep' : grep,
+ 'css' : css,
+ '$' : $,
+ 'create' : create,
+ 'bind' : bind,
+ 'supplant' : supplant,
+ 'head' : head,
+ 'search' : search,
+ 'attr' : attr,
+ 'now' : rnow,
+ 'unique' : unique,
+ 'events' : events,
+ 'updater' : updater,
+ 'init' : CREATE_PUBNUB
+ };
+
+ if (!UUID) UUID = SELF.uuid();
+ db.set( SUBSCRIBE_KEY + 'uuid', UUID );
+
+ return SELF;
+};
+
+// CREATE A PUBNUB GLOBAL OBJECT
+PUBNUB = CREATE_PUBNUB({
+ 'publish_key' : attr( PDIV, 'pub-key' ),
+ 'subscribe_key' : attr( PDIV, 'sub-key' ),
+ 'ssl' : !document.location.href.indexOf('https') || attr( PDIV, 'ssl' ) == 'on',
+ 'origin' : attr( PDIV, 'origin' ),
+ 'uuid' : attr( PDIV, 'uuid' )
+});
+
+// PUBNUB Flash Socket
+css( PDIV, { 'position' : 'absolute', 'top' : -SECOND } );
+
+if ('opera' in window || attr( PDIV, 'flash' )) PDIV['innerHTML'] =
+ '';
+
+var pubnubs = $('pubnubs') || {};
+
+// PUBNUB READY TO CONNECT
+function ready() { PUBNUB['time'](rnow);
+PUBNUB['time'](function(t){ timeout( function() {
+ if (READY) return;
+ READY = 1;
+ each( READY_BUFFER, function(sub) {
+ sub[2]['subscribe']( sub[0], sub[1] )
+ } );
+}, SECOND ); }); }
+
+// Bind for PUBNUB Readiness to Subscribe
+bind( 'load', window, function(){ timeout( ready, 0 ) } );
+
+// Create Interface for Opera Flash
+PUBNUB['rdx'] = function( id, data ) {
+ if (!data) return FDomainRequest[id]['onerror']();
+ FDomainRequest[id]['responseText'] = unescape(data);
+ FDomainRequest[id]['onload']();
+};
+
+function FDomainRequest() {
+ if (!pubnubs['get']) return 0;
+
+ var fdomainrequest = {
+ 'id' : FDomainRequest['id']++,
+ 'send' : function() {},
+ 'abort' : function() { fdomainrequest['id'] = {} },
+ 'open' : function( method, url ) {
+ FDomainRequest[fdomainrequest['id']] = fdomainrequest;
+ pubnubs['get']( fdomainrequest['id'], url );
+ }
+ };
+
+ return fdomainrequest;
+}
+FDomainRequest['id'] = SECOND;
+
+// jQuery Interface
+window['jQuery'] && (window['jQuery']['PUBNUB'] = PUBNUB);
+
+// For Testling.js - http://testling.com/
+typeof module !== 'undefined' && (module.exports = PUBNUB) && ready();
+
+})();
diff --git a/javascript/3.4/pubnub.as b/javascript/3.4/pubnub.as
new file mode 100644
index 00000000..a10e9985
--- /dev/null
+++ b/javascript/3.4/pubnub.as
@@ -0,0 +1,47 @@
+package {
+ import flash.external.ExternalInterface;
+ import flash.display.Sprite;
+ import flash.net.URLLoader;
+ import flash.net.URLRequest;
+ import flash.events.Event;
+ import flash.events.IOErrorEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.system.Security;
+ import flash.utils.setTimeout;
+
+ public class pubnub extends Sprite {
+
+ Security.allowDomain("*");
+ Security.allowInsecureDomain("*");
+
+ ExternalInterface.addCallback( "get", function(
+ id:Number,
+ url:String
+ ):void {
+ function handler(e:Event):void {
+ var loader:URLLoader = URLLoader(e.target)
+ , data:String = loader.data
+ , timeout:int = 1;
+
+ if (e.type == 'securityError') {
+ data = '[1,"S"]';
+ timeout = 1000;
+ }
+
+ setTimeout( function delayed():void {
+ ExternalInterface.call( "PUBNUB.rdx", id, escape(data) );
+ loader.close();
+ }, timeout );
+ }
+
+ var loader:URLLoader = new URLLoader();
+
+ loader.addEventListener( Event.COMPLETE, handler );
+ loader.addEventListener( IOErrorEvent.IO_ERROR, handler );
+ loader.addEventListener(
+ SecurityErrorEvent.SECURITY_ERROR, handler
+ );
+ loader.load(new URLRequest(url));
+ });
+ }
+}
diff --git a/javascript/3.4/pubnub.swf b/javascript/3.4/pubnub.swf
new file mode 100644
index 00000000..1fce400b
Binary files /dev/null and b/javascript/3.4/pubnub.swf differ
diff --git a/javascript/3.4/tests/head-test.htm b/javascript/3.4/tests/head-test.htm
new file mode 100644
index 00000000..36f1e257
--- /dev/null
+++ b/javascript/3.4/tests/head-test.htm
@@ -0,0 +1,40 @@
+
+
+
+ PubNub JavaScript HEAD INIT TEST
+
+
+
+
+
+
+
Head Test
+
Wait here for an alert() message.
+
+
+
+
diff --git a/javascript/3.4/tests/head.load.min.js b/javascript/3.4/tests/head.load.min.js
new file mode 100644
index 00000000..6242b0fa
--- /dev/null
+++ b/javascript/3.4/tests/head.load.min.js
@@ -0,0 +1,8 @@
+/**
+ Head JS The only script in your
+ Copyright Tero Piirainen (tipiirai)
+ License MIT / http://bit.ly/mit-license
+ Version 0.96
+
+ http://headjs.com
+*/(function(a){function z(){d||(d=!0,s(e,function(a){p(a)}))}function y(c,d){var e=a.createElement("script");e.type="text/"+(c.type||"javascript"),e.src=c.src||c,e.async=!1,e.onreadystatechange=e.onload=function(){var a=e.readyState;!d.done&&(!a||/loaded|complete/.test(a))&&(d.done=!0,d())},(a.body||b).appendChild(e)}function x(a,b){if(a.state==o)return b&&b();if(a.state==n)return k.ready(a.name,b);if(a.state==m)return a.onpreload.push(function(){x(a,b)});a.state=n,y(a.url,function(){a.state=o,b&&b(),s(g[a.name],function(a){p(a)}),u()&&d&&s(g.ALL,function(a){p(a)})})}function w(a,b){a.state===undefined&&(a.state=m,a.onpreload=[],y({src:a.url,type:"cache"},function(){v(a)}))}function v(a){a.state=l,s(a.onpreload,function(a){a.call()})}function u(a){a=a||h;var b;for(var c in a){if(a.hasOwnProperty(c)&&a[c].state!=o)return!1;b=!0}return b}function t(a){return Object.prototype.toString.call(a)=="[object Function]"}function s(a,b){if(!!a){typeof a=="object"&&(a=[].slice.call(a));for(var c=0;c
+
+
+ PubNub JavaScript MailTO Fix
+
+
+