text: // Version: 3.7.8
  var NOW             = 1
  ,   READY           = false
  ,   READY_BUFFER    = []
  ,   PRESENCE_SUFFIX = '-pnpres'
  ,   DEF_WINDOWING   = 10     // MILLISECONDS.
  ,   DEF_TIMEOUT     = 10000  // MILLISECONDS.
  ,   DEF_SUB_TIMEOUT = 310    // SECONDS.
  ,   DEF_KEEPALIVE   = 60     // SECONDS (FOR TIMESYNC).
  ,   SECOND          = 1000   // A THOUSAND MILLISECONDS.
  ,   URLBIT          = '/'
  ,   PARAMSBIT       = '&'
  ,   PRESENCE_HB_THRESHOLD = 5
  ,   PRESENCE_HB_DEFAULT  = 30
  ,   SDK_VER         = '3.7.8'
  ,   REPL            = /{([\w\-]+)}/g;
  
  /**
   * UTILITIES
   */
  function unique() { return'x'+ ++NOW+''+(+new Date) }
  function rnow()   { return+new Date }
  
  /**
   * NEXTORIGIN
   * ==========
   * var next_origin = nextorigin();
   */
  var nextorigin = (function() {
      var max = 20
      ,   ori = Math.floor(Math.random() * max);
      return function( origin, failover ) {
          return origin.indexOf('pubsub.') > 0
              && origin.replace(
               'pubsub', 'ps' + (
                  failover ? uuid().split('-')[0] :
                  (++ori < max? ori : ori=1)
              ) ) || origin;
      }
  })();
  
  
  /**
   * Build Url
   * =======
   *
   */
  function build_url( url_components, url_params ) {
      var url    = url_components.join(URLBIT)
      ,   params = [];
  
      if (!url_params) return url;
  
      each( url_params, function( key, value ) {
          var value_str = (typeof value == 'object')?JSON['stringify'](value):value;
          (typeof value != 'undefined' &&
              value != null && encode(value_str).length > 0
          ) && params.push(key + "=" + encode(value_str));
      } );
  
      url += "?" + params.join(PARAMSBIT);
      return url;
  }
  
  /**
   * 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;
  }
  
  /**
   * 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] || _
      } );
  }
  
  /**
   * timeout
   * =======
   * timeout( function(){}, 100 );
   */
  function timeout( fun, wait ) {
      return setTimeout( fun, wait );
  }
  
  /**
   * uuid
   * ====
   * var my_uuid = uuid();
   */
  function uuid(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;
  }
  
  function isArray(arg) {
    return !!arg && typeof arg !== 'string' && (Array.isArray && Array.isArray(arg) || typeof(arg.length) === "number")
    //return !!arg && (Array.isArray && Array.isArray(arg) || typeof(arg.length) === "number")
  }
  
  /**
   * EACH
   * ====
   * each( [1,2,3], function(item) { } )
   */
  function each( o, f) {
      if ( !o || !f ) return;
  
      if ( isArray(o) )
          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;
  }
  
  /**
   * ENCODE
   * ======
   * var encoded_data = encode('path');
   */
  function encode(path) { return encodeURIComponent(path) }
  
  /**
   * Generate Subscription Channel List
   * ==================================
   * generate_channel_list(channels_object);
   */
  function generate_channel_list(channels, nopresence) {
      var list = [];
      each( channels, function( channel, status ) {
          if (nopresence) {
              if(channel.search('-pnpres') < 0) {
                  if (status.subscribed) list.push(channel);
              }
          } else {
              if (status.subscribed) list.push(channel);
          }
      });
      return list.sort();
  }
  
  /**
   * Generate Subscription Channel Groups List
   * ==================================
   * generate_channel_group_list(channels_groups object);
   */
  function generate_channel_group_list(channel_groups, nopresence) {
      var list = [];
      each(channel_groups, function( channel_group, status ) {
          if (nopresence) {
              if(channel_group.search('-pnpres') < 0) {
                  if (status.subscribed) list.push(channel_group);
              }
          } else {
              if (status.subscribed) list.push(channel_group);
          }
      });
      return list.sort();
  }
  
  // PUBNUB READY TO CONNECT
  function ready() { timeout( function() {
      if (READY) return;
      READY = 1;
      each( READY_BUFFER, function(connect) { connect() } );
  }, SECOND ); }
  
  function PNmessage(args) {
      msg = args || {'apns' : {}},
      msg['getPubnubMessage'] = function() {
          var m = {};
  
          if (Object.keys(msg['apns']).length) {
              m['pn_apns'] = {
                      'aps' : {
                          'alert' : msg['apns']['alert'] ,
                          'badge' : msg['apns']['badge']
                      }
              }
              for (var k in msg['apns']) {
                  m['pn_apns'][k] = msg['apns'][k];
              }
              var exclude1 = ['badge','alert'];
              for (var k in exclude1) {
                  delete m['pn_apns'][exclude1[k]];
              }
          }
  
  
  
          if (msg['gcm']) {
              m['pn_gcm'] = {
                  'data' : msg['gcm']
              }
          }
  
          for (var k in msg) {
              m[k] = msg[k];
          }
          var exclude = ['apns','gcm','publish', 'channel','callback','error'];
          for (var k in exclude) {
              delete m[exclude[k]];
          }
  
          return m;
      };
      msg['publish'] = function() {
  
          var m = msg.getPubnubMessage();
  
          if (msg['pubnub'] && msg['channel']) {
              msg['pubnub'].publish({
                  'message' : m,
                  'channel' : msg['channel'],
                  'callback' : msg['callback'],
                  'error' : msg['error']
              })
          }
      };
      return msg;
  }
  
  function PN_API(setup) {
      var SUB_WINDOWING =  +setup['windowing']   || DEF_WINDOWING
      ,   SUB_TIMEOUT   = (+setup['timeout']     || DEF_SUB_TIMEOUT) * SECOND
      ,   KEEPALIVE     = (+setup['keepalive']   || DEF_KEEPALIVE)   * SECOND
      ,   TIME_CHECK    = setup['timecheck']     || 0
      ,   NOLEAVE       = setup['noleave']       || 0
      ,   PUBLISH_KEY   = setup['publish_key']   || 'demo'
      ,   SUBSCRIBE_KEY = setup['subscribe_key'] || 'demo'
      ,   AUTH_KEY      = setup['auth_key']      || ''
      ,   SECRET_KEY    = setup['secret_key']    || ''
      ,   hmac_SHA256   = setup['hmac_SHA256']
      ,   SSL           = setup['ssl']            ? 's' : ''
      ,   ORIGIN        = 'http'+SSL+'://'+(setup['origin']||'pubsub.pubnub.com')
      ,   STD_ORIGIN    = nextorigin(ORIGIN)
      ,   SUB_ORIGIN    = nextorigin(ORIGIN)
      ,   CONNECT       = function(){}
      ,   PUB_QUEUE     = []
      ,   CLOAK         = true
      ,   TIME_DRIFT    = 0
      ,   SUB_CALLBACK  = 0
      ,   SUB_CHANNEL   = 0
      ,   SUB_RECEIVER  = 0
      ,   SUB_RESTORE   = setup['restore'] || 0
      ,   SUB_BUFF_WAIT = 0
      ,   TIMETOKEN     = 0
      ,   RESUMED       = false
      ,   CHANNELS      = {}
      ,   CHANNEL_GROUPS       = {}
      ,   STATE         = {}
      ,   PRESENCE_HB_TIMEOUT  = null
      ,   PRESENCE_HB          = validate_presence_heartbeat(
          setup['heartbeat'] || setup['pnexpires'] || 0, setup['error']
      )
      ,   PRESENCE_HB_INTERVAL = setup['heartbeat_interval'] || PRESENCE_HB - 3
      ,   PRESENCE_HB_RUNNING  = false
      ,   NO_WAIT_FOR_PENDING  = setup['no_wait_for_pending']
      ,   COMPATIBLE_35 = setup['compatible_3.5']  || false
      ,   xdr           = setup['xdr']
      ,   params        = setup['params'] || {}
      ,   error         = setup['error']      || function() {}
      ,   _is_online    = setup['_is_online'] || function() { return 1 }
      ,   jsonp_cb      = setup['jsonp_cb']   || function() { return 0 }
      ,   db            = setup['db']         || {'get': function(){}, 'set': function(){}}
      ,   CIPHER_KEY    = setup['cipher_key']
      ,   UUID          = setup['uuid'] || ( !setup['unique_uuid'] && db && db['get'](SUBSCRIBE_KEY+'uuid') || '')
      ,   _poll_timer
      ,   _poll_timer2;
  
      var crypto_obj    = setup['crypto_obj'] ||
          {
              'encrypt' : function(a,key){ return a},
              'decrypt' : function(b,key){return b}
          };
  
      function _get_url_params(data) {
          if (!data) data = {};
          each( params , function( key, value ) {
              if (!(key in data)) data[key] = value;
          });
          return data;
      }
  
      function _object_to_key_list(o) {
          var l = []
          each( o , function( key, value ) {
              l.push(key);
          });
          return l;
      }
      function _object_to_key_list_sorted(o) {
          return _object_to_key_list(o).sort();
      }
  
      function _get_pam_sign_input_from_params(params) {
          var si = "";
          var l = _object_to_key_list_sorted(params);
  
          for (var i in l) {
              var k = l[i]
              si += k + "=" + encode(params[k]) ;
              if (i != l.length - 1) si += "&"
          }
          return si;
      }
  
      function validate_presence_heartbeat(heartbeat, cur_heartbeat, error) {
          var err = false;
  
          if (typeof heartbeat === 'number') {
              if (heartbeat > PRESENCE_HB_THRESHOLD || heartbeat == 0)
                  err = false;
              else
                  err = true;
          } else if(typeof heartbeat === 'boolean'){
              if (!heartbeat) {
                  return 0;
              } else {
                  return PRESENCE_HB_DEFAULT;
              }
          } else {
              err = true;
          }
  
          if (err) {
              error && error("Presence Heartbeat value invalid. Valid range ( x > " + PRESENCE_HB_THRESHOLD + " or x = 0). Current Value : " + (cur_heartbeat || PRESENCE_HB_THRESHOLD));
              return cur_heartbeat || PRESENCE_HB_THRESHOLD;
          } else return heartbeat;
      }
  
      function encrypt(input, key) {
          return crypto_obj['encrypt'](input, key || CIPHER_KEY) || input;
      }
      function decrypt(input, key) {
          return crypto_obj['decrypt'](input, key || CIPHER_KEY) ||
                 crypto_obj['decrypt'](input, CIPHER_KEY) ||
                 input;
      }
  
      function error_common(message, callback) {
          callback && callback({ 'error' : message || "error occurred"});
          error && error(message);
      }
      function _presence_heartbeat() {
  
          clearTimeout(PRESENCE_HB_TIMEOUT);
  
          if (!PRESENCE_HB_INTERVAL || PRESENCE_HB_INTERVAL >= 500 ||
              PRESENCE_HB_INTERVAL < 1 ||
              (!generate_channel_list(CHANNELS,true).length  && !generate_channel_group_list(CHANNEL_GROUPS, true).length ) )
          {
              PRESENCE_HB_RUNNING = false;
              return;
          }
  
          PRESENCE_HB_RUNNING = true;
          SELF['presence_heartbeat']({
              'callback' : function(r) {
                  PRESENCE_HB_TIMEOUT = timeout( _presence_heartbeat, (PRESENCE_HB_INTERVAL) * SECOND );
              },
              'error' : function(e) {
                  error && error("Presence Heartbeat unable to reach Pubnub servers." + JSON.stringify(e));
                  PRESENCE_HB_TIMEOUT = timeout( _presence_heartbeat, (PRESENCE_HB_INTERVAL) * SECOND );
              }
          });
      }
  
      function start_presence_heartbeat() {
          !PRESENCE_HB_RUNNING && _presence_heartbeat();
      }
  
      function publish(next) {
  
          if (NO_WAIT_FOR_PENDING) {
              if (!PUB_QUEUE.length) return;
          } else {
              if (next) PUB_QUEUE.sending = 0;
              if ( PUB_QUEUE.sending || !PUB_QUEUE.length ) return;
              PUB_QUEUE.sending = 1;
          }
  
          xdr(PUB_QUEUE.shift());
      }
      function each_channel_group(callback) {
          var count = 0;
  
          each( generate_channel_group_list(CHANNEL_GROUPS), function(channel_group) {
              var chang = CHANNEL_GROUPS[channel_group];
  
              if (!chang) return;
  
              count++;
              (callback||function(){})(chang);
          } );
  
          return count;
      }
  
      function each_channel(callback) {
          var count = 0;
  
          each( generate_channel_list(CHANNELS), function(channel) {
              var chan = CHANNELS[channel];
  
              if (!chan) return;
  
              count++;
              (callback||function(){})(chan);
          } );
  
          return count;
      }
      function _invoke_callback(response, callback, err) {
          if (typeof response == 'object') {
              if (response['error']) {
                  var callback_data = {};
  
                  if (response['message']) {
                      callback_data['message'] = response['message'];
                  }
  
                  if (response['payload']) {
                      callback_data['payload'] = response['payload'];
                  }
  
                  err && err(callback_data);
                  return;
  
              }
              if (response['payload']) {
                  if (response['next_page'])
                      callback && callback(response['payload'], response['next_page']);
                  else
                      callback && callback(response['payload']);
                  return;
              }
          }
          callback && callback(response);
      }
  
      function _invoke_error(response,err) {
  
          if (typeof response == 'object' && response['error']) {
                  var callback_data = {};
  
                  if (response['message']) {
                      callback_data['message'] = response['message'];
                  }
  
                  if (response['payload']) {
                      callback_data['payload'] = response['payload'];
                  }
                  
                  err && err(callback_data);
                  return;
          } else {
              err && err(response);
          }
      }
      function CR(args, callback, url1, data) {
              var callback        = args['callback']      || callback
              ,   err             = args['error']         || error
              ,   jsonp           = jsonp_cb();
  
              data = data || {};
              
              if (!data['auth']) {
                  data['auth'] = args['auth_key'] || AUTH_KEY;
              }
              
              var url = [
                      STD_ORIGIN, 'v1', 'channel-registration',
                      'sub-key', SUBSCRIBE_KEY
                  ];
  
              url.push.apply(url,url1);
              
              if (jsonp) data['callback']              = jsonp;
              
              xdr({
                  callback : jsonp,
                  data     : _get_url_params(data),
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : url
              });
  
      }
  
      // Announce Leave Event
      var SELF = {
          'LEAVE' : function( channel, blocking, auth_key, callback, error ) {
  
              var data   = { 'uuid' : UUID, 'auth' : auth_key || AUTH_KEY }
              ,   origin = nextorigin(ORIGIN)
              ,   callback = callback || function(){}
              ,   err      = error    || function(){}
              ,   jsonp  = jsonp_cb();
  
              // Prevent Leaving a Presence Channel
              if (channel.indexOf(PRESENCE_SUFFIX) > 0) return true;
  
              if (COMPATIBLE_35) {
                  if (!SSL)         return false;
                  if (jsonp == '0') return false;
              }
  
              if (NOLEAVE)  return false;
  
              if (jsonp != '0') data['callback'] = jsonp;
  
              xdr({
                  blocking : blocking || SSL,
                  timeout  : 2000,
                  callback : jsonp,
                  data     : _get_url_params(data),
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : [
                      origin, 'v2', 'presence', 'sub_key',
                      SUBSCRIBE_KEY, 'channel', encode(channel), 'leave'
                  ]
              });
              return true;
          },
          'LEAVE_GROUP' : function( channel_group, blocking, auth_key, callback, error ) {
  
              var data   = { 'uuid' : UUID, 'auth' : auth_key || AUTH_KEY }
              ,   origin = nextorigin(ORIGIN)
              ,   callback = callback || function(){}
              ,   err      = error    || function(){}
              ,   jsonp  = jsonp_cb();
  
              // Prevent Leaving a Presence Channel Group
              if (channel_group.indexOf(PRESENCE_SUFFIX) > 0) return true;
  
              if (COMPATIBLE_35) {
                  if (!SSL)         return false;
                  if (jsonp == '0') return false;
              }
  
              if (NOLEAVE)  return false;
  
              if (jsonp != '0') data['callback'] = jsonp;
  
              if (channel_group && channel_group.length > 0) data['channel-group'] = channel_group;
  
              xdr({
                  blocking : blocking || SSL,
                  timeout  : 5000,
                  callback : jsonp,
                  data     : _get_url_params(data),
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : [
                      origin, 'v2', 'presence', 'sub_key',
                      SUBSCRIBE_KEY, 'channel', encode(','), 'leave'
                  ]
              });
              return true;
          },
          'set_resumed' : function(resumed) {
                  RESUMED = resumed;
          },
          'get_cipher_key' : function() {
              return CIPHER_KEY;
          },
          'set_cipher_key' : function(key) {
              CIPHER_KEY = key;
          },
          'raw_encrypt' : function(input, key) {
              return encrypt(input, key);
          },
          'raw_decrypt' : function(input, key) {
              return decrypt(input, key);
          },
          'get_heartbeat' : function() {
              return PRESENCE_HB;
          },
          'set_heartbeat' : function(heartbeat) {
              PRESENCE_HB = validate_presence_heartbeat(heartbeat, PRESENCE_HB_INTERVAL, error);
              PRESENCE_HB_INTERVAL = (PRESENCE_HB - 3 >= 1)?PRESENCE_HB - 3:1;
              CONNECT();
              _presence_heartbeat();
          },
          'get_heartbeat_interval' : function() {
              return PRESENCE_HB_INTERVAL;
          },
          'set_heartbeat_interval' : function(heartbeat_interval) {
              PRESENCE_HB_INTERVAL = heartbeat_interval;
              _presence_heartbeat();
          },
          'get_version' : function() {
              return SDK_VER;
          },
          'getGcmMessageObject' : function(obj) {
              return {
                  'data' : obj
              }
          },
          'getApnsMessageObject' : function(obj) {
              var x =  {
                  'aps' : { 'badge' : 1, 'alert' : ''}
              }
              for (k in obj) {
                  k[x] = obj[k];
              }
              return x;
          },
          'newPnMessage' : function() {
              var x = {};
              if (gcm) x['pn_gcm'] = gcm;
              if (apns) x['pn_apns'] = apns;
              for ( k in n ) {
                  x[k] = n[k];
              }
              return x;
          },
  
          '_add_param' : function(key,val) {
              params[key] = val;
          },
  
          'channel_group' : function(args, callback) {
              var ns_ch       = args['channel_group']
              ,   channels    = args['channels'] || args['channel']
              ,   cloak       = args['cloak']
              ,   namespace
              ,   channel_group
              ,   url = []
              ,   data = {}
              ,   mode = args['mode'] || 'add';
  
  
              if (ns_ch) {
                  var ns_ch_a = ns_ch.split(':');
  
                  if (ns_ch_a.length > 1) {
                      namespace = (ns_ch_a[0] === '*')?null:ns_ch_a[0];
  
                      channel_group = ns_ch_a[1];
                  } else {
                      channel_group = ns_ch_a[0];
                  }
              }
  
              namespace && url.push('namespace') && url.push(encode(namespace));
  
              url.push('channel-group');
  
              if (channel_group && channel_group !== '*') {
                  url.push(channel_group);
              }
  
              if (channels ) {
                  if (isArray(channels)) {
                      channels = channels.join(',');
                  }
                  data[mode] = channels;
                  data['cloak'] = (CLOAK)?'true':'false';
              } else {
                  if (mode === 'remove') url.push('remove');
              }
  
              if (typeof cloak != 'undefined') data['cloak'] = (cloak)?'true':'false';
  
              CR(args, callback, url, data);
          },
  
          'channel_group_list_groups' : function(args, callback) {
              var namespace;
  
              namespace = args['namespace'] || args['ns'] || args['channel_group'] || null;
              if (namespace) {
                  args["channel_group"] = namespace + ":*";
              }
  
              SELF['channel_group'](args, callback);
          },
  
          'channel_group_list_channels' : function(args, callback) {
              if (!args['channel_group']) return error('Missing Channel Group');
              SELF['channel_group'](args, callback);
          },
  
          'channel_group_remove_channel' : function(args, callback) {
              if (!args['channel_group']) return error('Missing Channel Group');
              if (!args['channel'] && !args['channels'] ) return error('Missing Channel');
  
              args['mode'] = 'remove';
              SELF['channel_group'](args,callback);
          },
  
          'channel_group_remove_group' : function(args, callback) {
              if (!args['channel_group']) return error('Missing Channel Group');
              if (args['channel']) return error('Use channel_group_remove_channel if you want to remove a channel from a group.');
  
              args['mode'] = 'remove';
              SELF['channel_group'](args,callback);
          },
  
          'channel_group_add_channel' : function(args, callback) {
             if (!args['channel_group']) return error('Missing Channel Group');
             if (!args['channel'] && !args['channels'] ) return error('Missing Channel');
              SELF['channel_group'](args,callback);
          },
  
          'channel_group_cloak' : function(args, callback) {
              if (typeof args['cloak'] == 'undefined') {
                  callback(CLOAK);
                  return;
              }
              CLOAK = args['cloak'];
              SELF['channel_group'](args,callback);
          },
  
          'channel_group_list_namespaces' : function(args, callback) {
              var url = ['namespace'];
              CR(args, callback, url);
          },
          'channel_group_remove_namespace' : function(args, callback) {
              var url = ['namespace',args['namespace'],'remove'];
              CR(args, callback, url);
          },
  
          /*
              PUBNUB.history({
                  channel  : 'my_chat_channel',
                  limit    : 100,
                  callback : function(history) { }
              });
          */
          'history' : function( args, callback ) {
              var callback         = args['callback'] || callback
              ,   count            = args['count']    || args['limit'] || 100
              ,   reverse          = args['reverse']  || "false"
              ,   err              = args['error']    || function(){}
              ,   auth_key         = args['auth_key'] || AUTH_KEY
              ,   cipher_key       = args['cipher_key']
              ,   channel          = args['channel']
              ,   channel_group    = args['channel_group']
              ,   start            = args['start']
              ,   end              = args['end']
              ,   include_token    = args['include_token']
              ,   params           = {}
              ,   jsonp            = jsonp_cb();
  
              // Make sure we have a Channel
              if (!channel && !channel_group) return error('Missing Channel');
              if (!callback)      return error('Missing Callback');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
  
              params['stringtoken'] = 'true';
              params['count']       = count;
              params['reverse']     = reverse;
              params['auth']        = auth_key;
  
              if (channel_group) {
                  params['channel-group'] = channel_group;
                  if (!channel) {
                      channel = ','; 
                  }
              }
              if (jsonp) params['callback']              = jsonp;
              if (start) params['start']                 = start;
              if (end)   params['end']                   = end;
              if (include_token) params['include_token'] = 'true';
  
              // Send Message
              xdr({
                  callback : jsonp,
                  data     : _get_url_params(params),
                  success  : function(response) {
                      if (typeof response == 'object' && response['error']) {
                          err({'message' : response['message'], 'payload' : response['payload']});
                          return;
                      }
                      var messages = response[0];
                      var decrypted_messages = [];
                      for (var a = 0; a < messages.length; a++) {
                          var new_message = decrypt(messages[a],cipher_key);
                          try {
                              decrypted_messages['push'](JSON['parse'](new_message));
                          } catch (e) {
                              decrypted_messages['push']((new_message));
                          }
                      }
                      callback([decrypted_messages, response[1], response[2]]);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : [
                      STD_ORIGIN, 'v2', 'history', 'sub-key',
                      SUBSCRIBE_KEY, 'channel', encode(channel)
                  ]
              });
          },
  
          /*
              PUBNUB.replay({
                  source      : 'my_channel',
                  destination : 'new_channel'
              });
          */
          'replay' : function(args, callback) {
              var callback    = callback || args['callback'] || function(){}
              ,   auth_key    = args['auth_key'] || AUTH_KEY
              ,   source      = args['source']
              ,   destination = args['destination']
              ,   stop        = args['stop']
              ,   start       = args['start']
              ,   end         = args['end']
              ,   reverse     = args['reverse']
              ,   limit       = args['limit']
              ,   jsonp       = jsonp_cb()
              ,   data        = {}
              ,   url;
  
              // Check User Input
              if (!source)        return error('Missing Source Channel');
              if (!destination)   return error('Missing Destination Channel');
              if (!PUBLISH_KEY)   return error('Missing Publish Key');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
  
              // Setup URL Params
              if (jsonp != '0') data['callback'] = jsonp;
              if (stop)         data['stop']     = 'all';
              if (reverse)      data['reverse']  = 'true';
              if (start)        data['start']    = start;
              if (end)          data['end']      = end;
              if (limit)        data['count']    = limit;
  
              data['auth'] = auth_key;
  
              // Compose URL Parts
              url = [
                  STD_ORIGIN, 'v1', 'replay',
                  PUBLISH_KEY, SUBSCRIBE_KEY,
                  source, destination
              ];
  
              // Start (or Stop) Replay!
              xdr({
                  callback : jsonp,
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function() { callback([ 0, 'Disconnected' ]) },
                  url      : url,
                  data     : _get_url_params(data)
              });
          },
  
          /*
              PUBNUB.auth('AJFLKAJSDKLA');
          */
          'auth' : function(auth) {
              AUTH_KEY = auth;
              CONNECT();
          },
  
          /*
              PUBNUB.time(function(time){ });
          */
          'time' : function(callback) {
              var jsonp = jsonp_cb();
              xdr({
                  callback : jsonp,
                  data     : _get_url_params({ 'uuid' : UUID, 'auth' : AUTH_KEY }),
                  timeout  : SECOND * 5,
                  url      : [STD_ORIGIN, 'time', jsonp],
                  success  : function(response) { callback(response[0]) },
                  fail     : function() { callback(0) }
              });
          },
  
          /*
              PUBNUB.publish({
                  channel : 'my_chat_channel',
                  message : 'hello!'
              });
          */
          'publish' : function( args, callback ) {
              var msg      = args['message'];
              if (!msg) return error('Missing Message');
  
              var callback = callback || args['callback'] || msg['callback'] || function(){}
              ,   channel  = args['channel'] || msg['channel']
              ,   auth_key = args['auth_key'] || AUTH_KEY
              ,   cipher_key = args['cipher_key']
              ,   err      = args['error'] || msg['error'] || function() {}
              ,   post     = args['post'] || false
              ,   store    = ('store_in_history' in args) ? args['store_in_history']: true
              ,   jsonp    = jsonp_cb()
              ,   add_msg  = 'push'
              ,   url;
  
              if (args['prepend']) add_msg = 'unshift'
  
              if (!channel)       return error('Missing Channel');
              if (!PUBLISH_KEY)   return error('Missing Publish Key');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
  
              if (msg['getPubnubMessage']) {
                  msg = msg['getPubnubMessage']();
              }
  
              // If trying to send Object
              msg = JSON['stringify'](encrypt(msg, cipher_key));
  
              // Create URL
              url = [
                  STD_ORIGIN, 'publish',
                  PUBLISH_KEY, SUBSCRIBE_KEY,
                  0, encode(channel),
                  jsonp, encode(msg)
              ];
  
              params = { 'uuid' : UUID, 'auth' : auth_key }
  
              if (!store) params['store'] ="0"
  
              // Queue Message Send
              PUB_QUEUE[add_msg]({
                  callback : jsonp,
                  timeout  : SECOND * 5,
                  url      : url,
                  data     : _get_url_params(params),
                  fail     : function(response){
                      _invoke_error(response, err);
                      publish(1);
                  },
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                      publish(1);
                  },
                  mode     : (post)?'POST':'GET'
              });
  
              // Send Message
              publish();
          },
  
          /*
              PUBNUB.unsubscribe({ channel : 'my_chat' });
          */
          'unsubscribe' : function(args, callback) {
              var channel       = args['channel']
              ,   channel_group = args['channel_group']
              ,   auth_key      = args['auth_key']    || AUTH_KEY
              ,   callback      = callback            || args['callback'] || function(){}
              ,   err           = args['error']       || function(){};
  
              TIMETOKEN   = 0;
              //SUB_RESTORE = 1;    REVISIT !!!!
  
              if (channel) {
                  // Prepare Channel(s)
                  channel = map( (
                      channel.join ? channel.join(',') : ''+channel
                  ).split(','), function(channel) {
                      if (!CHANNELS[channel]) return;
                      return channel + ',' + channel + PRESENCE_SUFFIX;
                  } ).join(',');
  
                  // Iterate over Channels
                  each( channel.split(','), function(ch) {
                      var CB_CALLED = true;
                      if (!ch) return;
                      CHANNELS[ch] = 0;
                      if (ch in STATE) delete STATE[ch];
                      if (READY) {
                          CB_CALLED = SELF['LEAVE']( ch, 0 , auth_key, callback, err);
                      }
                      if (!CB_CALLED) callback({action : "leave"});
  
                      
                  } );
              }
  
              if (channel_group) {
                  // Prepare channel group(s)
                  channel_group = map( (
                      channel_group.join ? channel_group.join(',') : ''+channel_group
                  ).split(','), function(channel_group) {
                      if (!CHANNEL_GROUPS[channel_group]) return;
                      return channel_group + ',' + channel_group + PRESENCE_SUFFIX;
                  } ).join(',');
  
                  // Iterate over channel groups
                  each( channel_group.split(','), function(chg) {
                      var CB_CALLED = true;
                      if (!chg) return;
                      CHANNEL_GROUPS[chg] = 0;
                      if (chg in STATE) delete STATE[chg];
                      if (READY) {
                          CB_CALLED = SELF['LEAVE_GROUP']( chg, 0 , auth_key, callback, err);
                      }
                      if (!CB_CALLED) callback({action : "leave"});
  
                  } );
              }
  
              // Reset Connection if Count Less
              CONNECT();
          },
  
          /*
              PUBNUB.subscribe({
                  channel  : 'my_chat'
                  callback : function(message) { }
              });
          */
          'subscribe' : function( args, callback ) {
              var channel         = args['channel']
              ,   channel_group   = args['channel_group']
              ,   callback        = callback            || args['callback']
              ,   callback        = callback            || args['message']
              ,   auth_key        = args['auth_key']    || AUTH_KEY
              ,   connect         = args['connect']     || function(){}
              ,   reconnect       = args['reconnect']   || function(){}
              ,   disconnect      = args['disconnect']  || function(){}
              ,   errcb           = args['error']       || function(){}
              ,   idlecb          = args['idle']        || function(){}
              ,   presence        = args['presence']    || 0
              ,   noheresync      = args['noheresync']  || 0
              ,   backfill        = args['backfill']    || 0
              ,   timetoken       = args['timetoken']   || 0
              ,   sub_timeout     = args['timeout']     || SUB_TIMEOUT
              ,   windowing       = args['windowing']   || SUB_WINDOWING
              ,   state           = args['state']
              ,   heartbeat       = args['heartbeat'] || args['pnexpires']
              ,   restore         = args['restore'] || SUB_RESTORE;
  
              // Restore Enabled?
              SUB_RESTORE = restore;
  
              // Always Reset the TT
              TIMETOKEN = timetoken;
  
              // Make sure we have a Channel
              if (!channel && !channel_group) {
                  return error('Missing Channel');
              }
  
              if (!callback)      return error('Missing Callback');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
  
              if (heartbeat || heartbeat === 0) {
                  SELF['set_heartbeat'](heartbeat);
              }
  
              // Setup Channel(s)
              if (channel) {
                  each( (channel.join ? channel.join(',') : ''+channel).split(','),
                  function(channel) {
                      var settings = CHANNELS[channel] || {};
  
                      // Store Channel State
                      CHANNELS[SUB_CHANNEL = channel] = {
                          name         : channel,
                          connected    : settings.connected,
                          disconnected : settings.disconnected,
                          subscribed   : 1,
                          callback     : SUB_CALLBACK = callback,
                          'cipher_key' : args['cipher_key'],
                          connect      : connect,
                          disconnect   : disconnect,
                          reconnect    : reconnect
                      };
  
                      if (state) {
                          if (channel in state) {
                              STATE[channel] = state[channel];
                          } else {
                              STATE[channel] = state;
                          }
                      }
  
                      // Presence Enabled?
                      if (!presence) return;
  
                      // Subscribe Presence Channel
                      SELF['subscribe']({
                          'channel'  : channel + PRESENCE_SUFFIX,
                          'callback' : presence,
                          'restore'  : restore
                      });
  
                      // Presence Subscribed?
                      if (settings.subscribed) return;
  
                      // See Who's Here Now?
                      if (noheresync) return;
                      SELF['here_now']({
                          'channel'  : channel,
                          'callback' : function(here) {
                              each( 'uuids' in here ? here['uuids'] : [],
                              function(uid) { presence( {
                                  'action'    : 'join',
                                  'uuid'      : uid,
                                  'timestamp' : Math.floor(rnow() / 1000),
                                  'occupancy' : here['occupancy'] || 1
                              }, here, channel ); } );
                          }
                      });
                  } );
              }
  
              // Setup Channel Groups
              if (channel_group) {
                  each( (channel_group.join ? channel_group.join(',') : ''+channel_group).split(','),
                  function(channel_group) {
                      var settings = CHANNEL_GROUPS[channel_group] || {};
  
                      CHANNEL_GROUPS[channel_group] = {
                          name         : channel_group,
                          connected    : settings.connected,
                          disconnected : settings.disconnected,
                          subscribed   : 1,
                          callback     : SUB_CALLBACK = callback,
                          'cipher_key' : args['cipher_key'],
                          connect      : connect,
                          disconnect   : disconnect,
                          reconnect    : reconnect
                      };
  
                      // Presence Enabled?
                      if (!presence) return;
  
                      // Subscribe Presence Channel
                      SELF['subscribe']({
                          'channel_group'  : channel_group + PRESENCE_SUFFIX,
                          'callback' : presence,
                          'restore'  : restore
                      });
  
                      // Presence Subscribed?
                      if (settings.subscribed) return;
  
                      // See Who's Here Now?
                      if (noheresync) return;
                      SELF['here_now']({
                          'channel_group'  : channel_group,
                          'callback' : function(here) {
                              each( 'uuids' in here ? here['uuids'] : [],
                              function(uid) { presence( {
                                  'action'    : 'join',
                                  'uuid'      : uid,
                                  'timestamp' : Math.floor(rnow() / 1000),
                                  'occupancy' : here['occupancy'] || 1
                              }, here, channel_group ); } );
                          }
                      });
                  } );
              }
  
  
              // Test Network Connection
              function _test_connection(success) {
                  if (success) {
                      // Begin Next Socket Connection
                      timeout( CONNECT, SECOND );
                  }
                  else {
                      // New Origin on Failed Connection
                      STD_ORIGIN = nextorigin( ORIGIN, 1 );
                      SUB_ORIGIN = nextorigin( ORIGIN, 1 );
  
                      // Re-test Connection
                      timeout( function() {
                          SELF['time'](_test_connection);
                      }, SECOND );
                  }
  
                  // Disconnect & Reconnect
                  each_channel(function(channel){
                      // Reconnect
                      if (success && channel.disconnected) {
                          channel.disconnected = 0;
                          return channel.reconnect(channel.name);
                      }
  
                      // Disconnect
                      if (!success && !channel.disconnected) {
                          channel.disconnected = 1;
                          channel.disconnect(channel.name);
                      }
                  });
                  
                  // Disconnect & Reconnect for channel groups
                  each_channel_group(function(channel_group){
                      // Reconnect
                      if (success && channel_group.disconnected) {
                          channel_group.disconnected = 0;
                          return channel_group.reconnect(channel_group.name);
                      }
  
                      // Disconnect
                      if (!success && !channel_group.disconnected) {
                          channel_group.disconnected = 1;
                          channel_group.disconnect(channel_group.name);
                      }
                  });
              }
  
              // Evented Subscribe
              function _connect() {
                  var jsonp           = jsonp_cb()
                  ,   channels        = generate_channel_list(CHANNELS).join(',')
                  ,   channel_groups  = generate_channel_group_list(CHANNEL_GROUPS).join(',');
  
                  // Stop Connection
                  if (!channels && !channel_groups) return;
  
                  if (!channels) channels = ',';
  
                  // Connect to PubNub Subscribe Servers
                  _reset_offline();
  
                  var data = _get_url_params({ 'uuid' : UUID, 'auth' : auth_key });
  
                  if (channel_groups) {
                      data['channel-group'] = channel_groups;
                  }
  
  
                  var st = JSON.stringify(STATE);
                  if (st.length > 2) data['state'] = JSON.stringify(STATE);
  
                  if (PRESENCE_HB) data['heartbeat'] = PRESENCE_HB;
  
                  start_presence_heartbeat();
                  SUB_RECEIVER = xdr({
                      timeout  : sub_timeout,
                      callback : jsonp,
                      fail     : function(response) {
                          if (response['error'] && response['service']) {
                              _invoke_error(response, errcb);
                              _test_connection(1);
                          } else {
                              SELF['time'](function(success){
                                  !success && ( _invoke_error(response, errcb));
                                  _test_connection(success);
                              });
                          }
                      },
                      data     : _get_url_params(data),
                      url      : [
                          SUB_ORIGIN, 'subscribe',
                          SUBSCRIBE_KEY, encode(channels),
                          jsonp, TIMETOKEN
                      ],
                      success : function(messages) {
  
                          // Check for Errors
                          if (!messages || (
                              typeof messages == 'object' &&
                              'error' in messages         &&
                              messages['error']
                          )) {
                              errcb(messages['error']);
                              return timeout( CONNECT, SECOND );
                          }
  
                          // User Idle Callback
                          idlecb(messages[1]);
  
                          // Restore Previous Connection Point if Needed
                          TIMETOKEN = !TIMETOKEN               &&
                                      SUB_RESTORE              &&
                                      db['get'](SUBSCRIBE_KEY) || messages[1];
  
                          /*
                          // Connect
                          each_channel_registry(function(registry){
                              if (registry.connected) return;
                              registry.connected = 1;
                              registry.connect(channel.name);
                          });
                          */
  
                          // Connect
                          each_channel(function(channel){
                              if (channel.connected) return;
                              channel.connected = 1;
                              channel.connect(channel.name);
                          });
  
                          // Connect for channel groups
                          each_channel_group(function(channel_group){
                              if (channel_group.connected) return;
                              channel_group.connected = 1;
                              channel_group.connect(channel_group.name);
                          });
  
                          if (RESUMED && !SUB_RESTORE) {
                                  TIMETOKEN = 0;
                                  RESUMED = false;
                                  // Update Saved Timetoken
                                  db['set']( SUBSCRIBE_KEY, 0 );
                                  timeout( _connect, windowing );
                                  return;
                          }
  
                          // Invoke Memory Catchup and Receive Up to 100
                          // Previous Messages from the Queue.
                          if (backfill) {
                              TIMETOKEN = 10000;
                              backfill  = 0;
                          }
  
                          // Update Saved Timetoken
                          db['set']( SUBSCRIBE_KEY, messages[1] );
  
                          // Route Channel <---> Callback for Message
                          var next_callback = (function() {
                              var channels = '';
                              var channels2 = '';
  
                              if (messages.length > 3) {
                                  channels  = messages[3];
                                  channels2 = messages[2];
                              } else if (messages.length > 2) {
                                  channels = messages[2];
                              } else {
                                  channels =  map(
                                      generate_channel_list(CHANNELS), function(chan) { return map(
                                          Array(messages[0].length)
                                          .join(',').split(','),
                                          function() { return chan; }
                                      ) }).join(',')
                              }
  
                              var list  = channels.split(',');
                              var list2 = (channels2)?channels2.split(','):[];
  
                              return function() {
                                  var channel  = list.shift()||SUB_CHANNEL;
                                  var channel2 = list2.shift();
  
                                  var chobj = {};
  
                                  if (channel2) {
                                      if (channel && channel.indexOf('-pnpres') >= 0 
                                          && channel2.indexOf('-pnpres') < 0) {
                                          channel2 += '-pnpres';
                                      }
                                      chobj = CHANNEL_GROUPS[channel2] || CHANNELS[channel2] || {'callback' : function(){}};
                                  } else {
                                      chobj = CHANNELS[channel];
                                  }
  
                                  var r = [
                                      chobj
                                      .callback||SUB_CALLBACK,
                                      channel.split(PRESENCE_SUFFIX)[0]
                                  ];
                                  channel2 && r.push(channel2.split(PRESENCE_SUFFIX)[0]);
                                  return r;
                              };
                          })();
  
                          var latency = detect_latency(+messages[1]);
                          each( messages[0], function(msg) {
                              var next = next_callback();
                              var decrypted_msg = decrypt(msg,
                                  (CHANNELS[next[1]])?CHANNELS[next[1]]['cipher_key']:null);
                              next[0] && next[0]( decrypted_msg, messages, next[2] || next[1], latency, next[1]);
                          });
  
                          timeout( _connect, windowing );
                      }
                  });
              }
  
              CONNECT = function() {
                  _reset_offline();
                  timeout( _connect, windowing );
              };
  
              // Reduce Status Flicker
              if (!READY) return READY_BUFFER.push(CONNECT);
  
              // Connect Now
              CONNECT();
          },
  
          /*
              PUBNUB.here_now({ channel : 'my_chat', callback : fun });
          */
          'here_now' : function( args, callback ) {
              var callback = args['callback'] || callback
              ,   err      = args['error']    || function(){}
              ,   auth_key = args['auth_key'] || AUTH_KEY
              ,   channel  = args['channel']
              ,   channel_group = args['channel_group']
              ,   jsonp    = jsonp_cb()
              ,   uuids    = ('uuids' in args) ? args['uuids'] : true
              ,   state    = args['state']
              ,   data     = { 'uuid' : UUID, 'auth' : auth_key };
  
              if (!uuids) data['disable_uuids'] = 1;
              if (state) data['state'] = 1;
  
              // Make sure we have a Channel
              if (!callback)      return error('Missing Callback');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
  
              var url = [
                      STD_ORIGIN, 'v2', 'presence',
                      'sub_key', SUBSCRIBE_KEY
                  ];
  
              channel && url.push('channel') && url.push(encode(channel));
  
              if (jsonp != '0') { data['callback'] = jsonp; }
  
              if (channel_group) {
                  data['channel-group'] = channel_group;
                  !channel && url.push('channel') && url.push(','); 
              }
  
  
              xdr({
                  callback : jsonp,
                  data     : _get_url_params(data),
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : url
              });
          },
  
          /*
              PUBNUB.current_channels_by_uuid({ channel : 'my_chat', callback : fun });
          */
          'where_now' : function( args, callback ) {
              var callback = args['callback'] || callback
              ,   err      = args['error']    || function(){}
              ,   auth_key = args['auth_key'] || AUTH_KEY
              ,   jsonp    = jsonp_cb()
              ,   uuid     = args['uuid']     || UUID
              ,   data     = { 'auth' : auth_key };
  
              // Make sure we have a Channel
              if (!callback)      return error('Missing Callback');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
  
              if (jsonp != '0') { data['callback'] = jsonp; }
  
              xdr({
                  callback : jsonp,
                  data     : _get_url_params(data),
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : [
                      STD_ORIGIN, 'v2', 'presence',
                      'sub_key', SUBSCRIBE_KEY,
                      'uuid', encode(uuid)
                  ]
              });
          },
  
          'state' : function(args, callback) {
              var callback = args['callback'] || callback || function(r) {}
              ,   err      = args['error']    || function(){}
              ,   auth_key = args['auth_key'] || AUTH_KEY
              ,   jsonp    = jsonp_cb()
              ,   state    = args['state']
              ,   uuid     = args['uuid'] || UUID
              ,   channel  = args['channel']
              ,   channel_group = args['channel_group']
              ,   url
              ,   data     = _get_url_params({ 'auth' : auth_key });
  
              // Make sure we have a Channel
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
              if (!uuid) return error('Missing UUID');
              if (!channel && !channel_group) return error('Missing Channel');
  
              if (jsonp != '0') { data['callback'] = jsonp; }
  
              if (typeof channel != 'undefined'
                  && CHANNELS[channel] && CHANNELS[channel].subscribed ) {
                  if (state) STATE[channel] = state;
              }
  
              if (typeof channel_group != 'undefined'
                  && CHANNEL_GROUPS[channel_group]
                  && CHANNEL_GROUPS[channel_group].subscribed
                  ) {
                  if (state) STATE[channel_group] = state;
                  data['channel-group'] = channel_group;
  
                  if (!channel) {
                      channel = ',';
                  }
              }
  
              data['state'] = JSON.stringify(state);
  
              if (state) {
                  url      = [
                      STD_ORIGIN, 'v2', 'presence',
                      'sub-key', SUBSCRIBE_KEY,
                      'channel', channel,
                      'uuid', uuid, 'data'
                  ]
              } else {
                  url      = [
                      STD_ORIGIN, 'v2', 'presence',
                      'sub-key', SUBSCRIBE_KEY,
                      'channel', channel,
                      'uuid', encode(uuid)
                  ]
              }
  
              xdr({
                  callback : jsonp,
                  data     : _get_url_params(data),
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : url
  
              });
  
          },
  
          /*
              PUBNUB.grant({
                  channel  : 'my_chat',
                  callback : fun,
                  error    : fun,
                  ttl      : 24 * 60, // Minutes
                  read     : true,
                  write    : true,
                  auth_key : '3y8uiajdklytowsj'
              });
          */
          'grant' : function( args, callback ) {
              var callback        = args['callback'] || callback
              ,   err             = args['error']    || function(){}
              ,   channel         = args['channel']
              ,   channel_group   = args['channel_group']
              ,   jsonp           = jsonp_cb()
              ,   ttl             = args['ttl']
              ,   r               = (args['read'] )?"1":"0"
              ,   w               = (args['write'])?"1":"0"
              ,   m               = (args['manage'])?"1":"0"
              ,   auth_key        = args['auth_key'];
  
              if (!callback)      return error('Missing Callback');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
              if (!PUBLISH_KEY)   return error('Missing Publish Key');
              if (!SECRET_KEY)    return error('Missing Secret Key');
  
              var timestamp  = Math.floor(new Date().getTime() / 1000)
              ,   sign_input = SUBSCRIBE_KEY + "\n" + PUBLISH_KEY + "\n"
                      + "grant" + "\n";
  
              var data = {
                  'w'         : w,
                  'r'         : r,
                  'timestamp' : timestamp
              };
              if (args['manage']) {
                  data['m'] = m;
              }
              if (typeof channel != 'undefined' && channel != null && channel.length > 0) data['channel'] = channel;
              if (typeof channel_group != 'undefined' && channel_group != null && channel_group.length > 0) {
                  data['channel-group'] = channel_group;
              }
              if (jsonp != '0') { data['callback'] = jsonp; }
              if (ttl || ttl === 0) data['ttl'] = ttl;
  
              if (auth_key) data['auth'] = auth_key;
  
              data = _get_url_params(data)
  
              if (!auth_key) delete data['auth'];
  
              sign_input += _get_pam_sign_input_from_params(data);
  
              var signature = hmac_SHA256( sign_input, SECRET_KEY );
  
              signature = signature.replace( /\+/g, "-" );
              signature = signature.replace( /\//g, "_" );
  
              data['signature'] = signature;
  
              xdr({
                  callback : jsonp,
                  data     : data,
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : [
                      STD_ORIGIN, 'v1', 'auth', 'grant' ,
                      'sub-key', SUBSCRIBE_KEY
                  ]
              });
          },
  
          /*
           PUBNUB.mobile_gw_provision ({
           device_id: 'A655FBA9931AB',
           op       : 'add' | 'remove',
           gw_type  : 'apns' | 'gcm',
           channel  : 'my_chat',
           callback : fun,
           error    : fun,
           });
           */
  
          'mobile_gw_provision' : function( args ) {
  
              var callback = args['callback'] || function(){}
                  ,   auth_key       = args['auth_key'] || AUTH_KEY
                  ,   err            = args['error'] || function() {}
                  ,   jsonp          = jsonp_cb()
                  ,   channel        = args['channel']
                  ,   op             = args['op']
                  ,   gw_type        = args['gw_type']
                  ,   device_id      = args['device_id']
                  ,   url;
  
              if (!device_id)     return error('Missing Device ID (device_id)');
              if (!gw_type)       return error('Missing GW Type (gw_type: gcm or apns)');
              if (!op)            return error('Missing GW Operation (op: add or remove)');
              if (!channel)       return error('Missing gw destination Channel (channel)');
              if (!PUBLISH_KEY)   return error('Missing Publish Key');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
  
              // Create URL
              url = [
                  STD_ORIGIN, 'v1/push/sub-key',
                  SUBSCRIBE_KEY, 'devices', device_id
              ];
  
              params = { 'uuid' : UUID, 'auth' : auth_key, 'type': gw_type};
  
              if (op == "add") {
                  params['add'] = channel;
              } else if (op == "remove") {
                  params['remove'] = channel;
              }
  
              xdr({
                  callback : jsonp,
                  data     : params,
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : url
              });
  
          },
  
          /*
              PUBNUB.audit({
                  channel  : 'my_chat',
                  callback : fun,
                  error    : fun,
                  read     : true,
                  write    : true,
                  auth_key : '3y8uiajdklytowsj'
              });
          */
          'audit' : function( args, callback ) {
              var callback        = args['callback'] || callback
              ,   err             = args['error']    || function(){}
              ,   channel         = args['channel']
              ,   channel_group   = args['channel_group']
              ,   auth_key        = args['auth_key']
              ,   jsonp           = jsonp_cb();
  
              // Make sure we have a Channel
              if (!callback)      return error('Missing Callback');
              if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key');
              if (!PUBLISH_KEY)   return error('Missing Publish Key');
              if (!SECRET_KEY)    return error('Missing Secret Key');
  
              var timestamp  = Math.floor(new Date().getTime() / 1000)
              ,   sign_input = SUBSCRIBE_KEY + "\n"
                  + PUBLISH_KEY + "\n"
                  + "audit" + "\n";
  
              var data = {'timestamp' : timestamp };
              if (jsonp != '0') { data['callback'] = jsonp; }
              if (typeof channel != 'undefined' && channel != null && channel.length > 0) data['channel'] = channel;
              if (typeof channel_group != 'undefined' && channel_group != null && channel_group.length > 0) {
                  data['channel-group'] = channel_group;
              }
              if (auth_key) data['auth']    = auth_key;
  
              data = _get_url_params(data);
  
              if (!auth_key) delete data['auth'];
  
              sign_input += _get_pam_sign_input_from_params(data);
  
              var signature = hmac_SHA256( sign_input, SECRET_KEY );
  
              signature = signature.replace( /\+/g, "-" );
              signature = signature.replace( /\//g, "_" );
  
              data['signature'] = signature;
              xdr({
                  callback : jsonp,
                  data     : data,
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) {
                      _invoke_error(response, err);
                  },
                  url      : [
                      STD_ORIGIN, 'v1', 'auth', 'audit' ,
                      'sub-key', SUBSCRIBE_KEY
                  ]
              });
          },
  
          /*
              PUBNUB.revoke({
                  channel  : 'my_chat',
                  callback : fun,
                  error    : fun,
                  auth_key : '3y8uiajdklytowsj'
              });
          */
          'revoke' : function( args, callback ) {
              args['read']  = false;
              args['write'] = false;
              SELF['grant']( args, callback );
          },
          'set_uuid' : function(uuid) {
              UUID = uuid;
              CONNECT();
          },
          'get_uuid' : function() {
              return UUID;
          },
          'isArray'  : function(arg) {
              return isArray(arg);
          },
          'get_subscibed_channels' : function() {
              return generate_channel_list(CHANNELS, true);
          },
          'presence_heartbeat' : function(args) {
              var callback = args['callback'] || function() {}
              var err      = args['error']    || function() {}
              var jsonp    = jsonp_cb();
              var data     = { 'uuid' : UUID, 'auth' : AUTH_KEY };
  
              var st = JSON['stringify'](STATE);
              if (st.length > 2) data['state'] = JSON['stringify'](STATE);
  
              if (PRESENCE_HB > 0 && PRESENCE_HB < 320) data['heartbeat'] = PRESENCE_HB;
  
              if (jsonp != '0') { data['callback'] = jsonp; }
  
              var channels        = encode(generate_channel_list(CHANNELS, true)['join'](','));
              var channel_groups  = generate_channel_group_list(CHANNEL_GROUPS, true)['join'](',');
  
              if (!channels) channels = ',';
              if (channel_groups) data['channel-group'] = channel_groups;
  
              xdr({
                  callback : jsonp,
                  data     : _get_url_params(data),
                  timeout  : SECOND * 5,
                  url      : [
                      STD_ORIGIN, 'v2', 'presence',
                      'sub-key', SUBSCRIBE_KEY,
                      'channel' , channels,
                      'heartbeat'
                  ],
                  success  : function(response) {
                      _invoke_callback(response, callback, err);
                  },
                  fail     : function(response) { _invoke_error(response, err); }
              });
          },
          'stop_timers': function () {
              clearTimeout(_poll_timer);
              clearTimeout(_poll_timer2);
          },
  
          // Expose PUBNUB Functions
          'xdr'           : xdr,
          'ready'         : ready,
          'db'            : db,
          'uuid'          : uuid,
          'map'           : map,
          'each'          : each,
          'each-channel'  : each_channel,
          'grep'          : grep,
          'offline'       : function(){ _reset_offline(
              1, { "message" : "Offline. Please check your network settings." })
          },
          'supplant'      : supplant,
          'now'           : rnow,
          'unique'        : unique,
          'updater'       : updater
      };
  
      function _poll_online() {
          _is_online() || _reset_offline( 1, {
              "error" : "Offline. Please check your network settings. "
          });
          _poll_timer && clearTimeout(_poll_timer);
          _poll_timer = timeout( _poll_online, SECOND );
      }
  
      function _poll_online2() {
          if (!TIME_CHECK) return;
          SELF['time'](function(success){
              detect_time_detla( function(){}, success );
              success || _reset_offline( 1, {
                  "error" : "Heartbeat failed to connect to Pubnub Servers." +
                      "Please check your network settings."
                  });
              _poll_timer2 && clearTimeout(_poll_timer2);
              _poll_timer2 = timeout( _poll_online2, KEEPALIVE );
          });
      }
  
      function _reset_offline(err, msg) {
          SUB_RECEIVER && SUB_RECEIVER(err, msg);
          SUB_RECEIVER = null;
  
          clearTimeout(_poll_timer);
          clearTimeout(_poll_timer2);
      }
  
      if (!UUID) UUID = SELF['uuid']();
      db['set']( SUBSCRIBE_KEY + 'uuid', UUID );
  
      _poll_timer  = timeout( _poll_online,  SECOND    );
      _poll_timer2 = timeout( _poll_online2, KEEPALIVE );
      PRESENCE_HB_TIMEOUT = timeout(
          start_presence_heartbeat,
          ( PRESENCE_HB_INTERVAL - 3 ) * SECOND
      );
  
      // Detect Age of Message
      function detect_latency(tt) {
          var adjusted_time = rnow() - TIME_DRIFT;
          return adjusted_time - tt / 10000;
      }
  
      detect_time_detla();
      function detect_time_detla( cb, time ) {
          var stime = rnow();
  
          time && calculate(time) || SELF['time'](calculate);
  
          function calculate(time) {
              if (!time) return;
              var ptime   = time / 10000
              ,   latency = (rnow() - stime) / 2;
              TIME_DRIFT = rnow() - (ptime + latency);
              cb && cb(TIME_DRIFT);
          }
      }
  
      return SELF;
  }
  
  var CRYPTO = (function(){
      var Nr = 14,
      /* Default to 256 Bit Encryption */
      Nk = 8,
      Decrypt = false,
  
      enc_utf8 = function(s)
      {
          try {
              return unescape(encodeURIComponent(s));
          }
          catch(e) {
              throw 'Error on UTF-8 encode';
          }
      },
  
      dec_utf8 = function(s)
      {
          try {
              return decodeURIComponent(escape(s));
          }
          catch(e) {
              throw ('Bad Key');
          }
      },
  
      padBlock = function(byteArr)
      {
          var array = [], cpad, i;
          if (byteArr.length < 16) {
              cpad = 16 - byteArr.length;
              array = [cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad, cpad];
          }
          for (i = 0; i < byteArr.length; i++)
          {
              array[i] = byteArr[i];
          }
          return array;
      },
  
      block2s = function(block, lastBlock)
      {
          var string = '', padding, i;
          if (lastBlock) {
              padding = block[15];
              if (padding > 16) {
                  throw ('Decryption error: Maybe bad key');
              }
              if (padding == 16) {
                  return '';
              }
              for (i = 0; i < 16 - padding; i++) {
                  string += String.fromCharCode(block[i]);
              }
          } else {
              for (i = 0; i < 16; i++) {
                  string += String.fromCharCode(block[i]);
              }
          }
          return string;
      },
  
      a2h = function(numArr)
      {
          var string = '', i;
          for (i = 0; i < numArr.length; i++) {
              string += (numArr[i] < 16 ? '0': '') + numArr[i].toString(16);
          }
          return string;
      },
  
      h2a = function(s)
      {
          var ret = [];
          s.replace(/(..)/g,
          function(s) {
              ret.push(parseInt(s, 16));
          });
          return ret;
      },
  
      s2a = function(string, binary) {
          var array = [], i;
  
          if (! binary) {
              string = enc_utf8(string);
          }
  
          for (i = 0; i < string.length; i++)
          {
              array[i] = string.charCodeAt(i);
          }
  
          return array;
      },
  
      size = function(newsize)
      {
          switch (newsize)
          {
          case 128:
              Nr = 10;
              Nk = 4;
              break;
          case 192:
              Nr = 12;
              Nk = 6;
              break;
          case 256:
              Nr = 14;
              Nk = 8;
              break;
          default:
              throw ('Invalid Key Size Specified:' + newsize);
          }
      },
  
      randArr = function(num) {
          var result = [], i;
          for (i = 0; i < num; i++) {
              result = result.concat(Math.floor(Math.random() * 256));
          }
          return result;
      },
  
      openSSLKey = function(passwordArr, saltArr) {
          // Number of rounds depends on the size of the AES in use
          // 3 rounds for 256
          //        2 rounds for the key, 1 for the IV
          // 2 rounds for 128
          //        1 round for the key, 1 round for the IV
          // 3 rounds for 192 since it's not evenly divided by 128 bits
          var rounds = Nr >= 12 ? 3: 2,
          key = [],
          iv = [],
          md5_hash = [],
          result = [],
          data00 = passwordArr.concat(saltArr),
          i;
          md5_hash[0] = GibberishAES.Hash.MD5(data00);
          result = md5_hash[0];
          for (i = 1; i < rounds; i++) {
              md5_hash[i] = GibberishAES.Hash.MD5(md5_hash[i - 1].concat(data00));
              result = result.concat(md5_hash[i]);
          }
          key = result.slice(0, 4 * Nk);
          iv = result.slice(4 * Nk, 4 * Nk + 16);
          return {
              key: key,
              iv: iv
          };
      },
  
      rawEncrypt = function(plaintext, key, iv) {
          // plaintext, key and iv as byte arrays
          key = expandKey(key);
          var numBlocks = Math.ceil(plaintext.length / 16),
          blocks = [],
          i,
          cipherBlocks = [];
          for (i = 0; i < numBlocks; i++) {
              blocks[i] = padBlock(plaintext.slice(i * 16, i * 16 + 16));
          }
          if (plaintext.length % 16 === 0) {
              blocks.push([16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]);
              // CBC OpenSSL padding scheme
              numBlocks++;
          }
          for (i = 0; i < blocks.length; i++) {
              blocks[i] = (i === 0) ? xorBlocks(blocks[i], iv) : xorBlocks(blocks[i], cipherBlocks[i - 1]);
              cipherBlocks[i] = encryptBlock(blocks[i], key);
          }
          return cipherBlocks;
      },
  
      rawDecrypt = function(cryptArr, key, iv, binary) {
          //  cryptArr, key and iv as byte arrays
          key = expandKey(key);
          var numBlocks = cryptArr.length / 16,
          cipherBlocks = [],
          i,
          plainBlocks = [],
          string = '';
          for (i = 0; i < numBlocks; i++) {
              cipherBlocks.push(cryptArr.slice(i * 16, (i + 1) * 16));
          }
          for (i = cipherBlocks.length - 1; i >= 0; i--) {
              plainBlocks[i] = decryptBlock(cipherBlocks[i], key);
              plainBlocks[i] = (i === 0) ? xorBlocks(plainBlocks[i], iv) : xorBlocks(plainBlocks[i], cipherBlocks[i - 1]);
          }
          for (i = 0; i < numBlocks - 1; i++) {
              string += block2s(plainBlocks[i]);
          }
          string += block2s(plainBlocks[i], true);
          return binary ? string : dec_utf8(string);
      },
  
      encryptBlock = function(block, words) {
          Decrypt = false;
          var state = addRoundKey(block, words, 0),
          round;
          for (round = 1; round < (Nr + 1); round++) {
              state = subBytes(state);
              state = shiftRows(state);
              if (round < Nr) {
                  state = mixColumns(state);
              }
              //last round? don't mixColumns
              state = addRoundKey(state, words, round);
          }
  
          return state;
      },
  
      decryptBlock = function(block, words) {
          Decrypt = true;
          var state = addRoundKey(block, words, Nr),
          round;
          for (round = Nr - 1; round > -1; round--) {
              state = shiftRows(state);
              state = subBytes(state);
              state = addRoundKey(state, words, round);
              if (round > 0) {
                  state = mixColumns(state);
              }
              //last round? don't mixColumns
          }
  
          return state;
      },
  
      subBytes = function(state) {
          var S = Decrypt ? SBoxInv: SBox,
          temp = [],
          i;
          for (i = 0; i < 16; i++) {
              temp[i] = S[state[i]];
          }
          return temp;
      },
  
      shiftRows = function(state) {
          var temp = [],
          shiftBy = Decrypt ? [0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3] : [0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11],
          i;
          for (i = 0; i < 16; i++) {
              temp[i] = state[shiftBy[i]];
          }
          return temp;
      },
  
      mixColumns = function(state) {
          var t = [],
          c;
          if (!Decrypt) {
              for (c = 0; c < 4; c++) {
                  t[c * 4] = G2X[state[c * 4]] ^ G3X[state[1 + c * 4]] ^ state[2 + c * 4] ^ state[3 + c * 4];
                  t[1 + c * 4] = state[c * 4] ^ G2X[state[1 + c * 4]] ^ G3X[state[2 + c * 4]] ^ state[3 + c * 4];
                  t[2 + c * 4] = state[c * 4] ^ state[1 + c * 4] ^ G2X[state[2 + c * 4]] ^ G3X[state[3 + c * 4]];
                  t[3 + c * 4] = G3X[state[c * 4]] ^ state[1 + c * 4] ^ state[2 + c * 4] ^ G2X[state[3 + c * 4]];
              }
          }else {
              for (c = 0; c < 4; c++) {
                  t[c*4] = GEX[state[c*4]] ^ GBX[state[1+c*4]] ^ GDX[state[2+c*4]] ^ G9X[state[3+c*4]];
                  t[1+c*4] = G9X[state[c*4]] ^ GEX[state[1+c*4]] ^ GBX[state[2+c*4]] ^ GDX[state[3+c*4]];
                  t[2+c*4] = GDX[state[c*4]] ^ G9X[state[1+c*4]] ^ GEX[state[2+c*4]] ^ GBX[state[3+c*4]];
                  t[3+c*4] = GBX[state[c*4]] ^ GDX[state[1+c*4]] ^ G9X[state[2+c*4]] ^ GEX[state[3+c*4]];
              }
          }
  
          return t;
      },
  
      addRoundKey = function(state, words, round) {
          var temp = [],
          i;
          for (i = 0; i < 16; i++) {
              temp[i] = state[i] ^ words[round][i];
          }
          return temp;
      },
  
      xorBlocks = function(block1, block2) {
          var temp = [],
          i;
          for (i = 0; i < 16; i++) {
              temp[i] = block1[i] ^ block2[i];
          }
          return temp;
      },
  
      expandKey = function(key) {
          // Expects a 1d number array
          var w = [],
          temp = [],
          i,
          r,
          t,
          flat = [],
          j;
  
          for (i = 0; i < Nk; i++) {
              r = [key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]];
              w[i] = r;
          }
  
          for (i = Nk; i < (4 * (Nr + 1)); i++) {
              w[i] = [];
              for (t = 0; t < 4; t++) {
                  temp[t] = w[i - 1][t];
              }
              if (i % Nk === 0) {
                  temp = subWord(rotWord(temp));
                  temp[0] ^= Rcon[i / Nk - 1];
              } else if (Nk > 6 && i % Nk == 4) {
                  temp = subWord(temp);
              }
              for (t = 0; t < 4; t++) {
                  w[i][t] = w[i - Nk][t] ^ temp[t];
              }
          }
          for (i = 0; i < (Nr + 1); i++) {
              flat[i] = [];
              for (j = 0; j < 4; j++) {
                  flat[i].push(w[i * 4 + j][0], w[i * 4 + j][1], w[i * 4 + j][2], w[i * 4 + j][3]);
              }
          }
          return flat;
      },
  
      subWord = function(w) {
          // apply SBox to 4-byte word w
          for (var i = 0; i < 4; i++) {
              w[i] = SBox[w[i]];
          }
          return w;
      },
  
      rotWord = function(w) {
          // rotate 4-byte word w left by one byte
          var tmp = w[0],
          i;
          for (i = 0; i < 4; i++) {
              w[i] = w[i + 1];
          }
          w[3] = tmp;
          return w;
      },
  
  // jlcooke: 2012-07-12: added strhex + invertArr to compress G2X/G3X/G9X/GBX/GEX/SBox/SBoxInv/Rcon saving over 7KB, and added encString, decString
      strhex = function(str,size) {
          var ret = [];
          for (i=0; i<str.length; i+=size)
              ret[i/size] = parseInt(str.substr(i,size), 16);
          return ret;
      },
      invertArr = function(arr) {
          var ret = [];
          for (i=0; i<arr.length; i++)
              ret[arr[i]] = i;
          return ret;
      },
      Gxx = function(a, b) {
          var i, ret;
  
          ret = 0;
          for (i=0; i<8; i++) {
              ret = ((b&1)==1) ? ret^a : ret;
              /* xmult */
              a = (a>0x7f) ? 0x11b^(a<<1) : (a<<1);
              b >>>= 1;
          }
  
          return ret;
      },
      Gx = function(x) {
          var r = [];
          for (var i=0; i<256; i++)
              r[i] = Gxx(x, i);
          return r;
      },
  
      // S-box
  /*
      SBox = [
      99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171,
      118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164,
      114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113,
      216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226,
      235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214,
      179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203,
      190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69,
      249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245,
      188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68,
      23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42,
      144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73,
      6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109,
      141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37,
      46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62,
      181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225,
      248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
      140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187,
      22], //*/ SBox = strhex('637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16',2),
  
      // Precomputed lookup table for the inverse SBox
  /*    SBoxInv = [
      82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215,
      251, 124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222,
      233, 203, 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66,
      250, 195, 78, 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73,
      109, 139, 209, 37, 114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92,
      204, 93, 101, 182, 146, 108, 112, 72, 80, 253, 237, 185, 218, 94, 21,
      70, 87, 167, 141, 157, 132, 144, 216, 171, 0, 140, 188, 211, 10, 247,
      228, 88, 5, 184, 179, 69, 6, 208, 44, 30, 143, 202, 63, 15, 2,
      193, 175, 189, 3, 1, 19, 138, 107, 58, 145, 17, 65, 79, 103, 220,
      234, 151, 242, 207, 206, 240, 180, 230, 115, 150, 172, 116, 34, 231, 173,
      53, 133, 226, 249, 55, 232, 28, 117, 223, 110, 71, 241, 26, 113, 29,
      41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, 252, 86, 62, 75,
      198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, 31, 221, 168,
      51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, 96, 81,
      127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, 160,
      224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97,
      23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12,
      125], //*/ SBoxInv = invertArr(SBox),
  
      // Rijndael Rcon
  /*
      Rcon = [1, 2, 4, 8, 16, 32, 64, 128, 27, 54, 108, 216, 171, 77, 154, 47, 94,
      188, 99, 198, 151, 53, 106, 212, 179, 125, 250, 239, 197, 145],
  //*/ Rcon = strhex('01020408102040801b366cd8ab4d9a2f5ebc63c697356ad4b37dfaefc591',2),
  
  /*
      G2X = [
      0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16,
      0x18, 0x1a, 0x1c, 0x1e, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e,
      0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 0x40, 0x42, 0x44, 0x46,
      0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e,
      0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76,
      0x78, 0x7a, 0x7c, 0x7e, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e,
      0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2, 0xa4, 0xa6,
      0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe,
      0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6,
      0xd8, 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee,
      0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 0x1b, 0x19, 0x1f, 0x1d,
      0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05,
      0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d,
      0x23, 0x21, 0x27, 0x25, 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55,
      0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, 0x7b, 0x79, 0x7f, 0x7d,
      0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65,
      0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d,
      0x83, 0x81, 0x87, 0x85, 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5,
      0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 0xdb, 0xd9, 0xdf, 0xdd,
      0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5,
      0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed,
      0xe3, 0xe1, 0xe7, 0xe5
      ], //*/ G2X = Gx(2),
  
  /*    G3X = [
      0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d,
      0x14, 0x17, 0x12, 0x11, 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39,
      0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, 0x60, 0x63, 0x66, 0x65,
      0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71,
      0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d,
      0x44, 0x47, 0x42, 0x41, 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9,
      0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, 0xf0, 0xf3, 0xf6, 0xf5,
      0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1,
      0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd,
      0xb4, 0xb7, 0xb2, 0xb1, 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99,
      0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, 0x9b, 0x98, 0x9d, 0x9e,
      0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a,
      0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6,
      0xbf, 0xbc, 0xb9, 0xba, 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2,
      0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, 0xcb, 0xc8, 0xcd, 0xce,
      0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda,
      0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46,
      0x4f, 0x4c, 0x49, 0x4a, 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62,
      0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, 0x3b, 0x38, 0x3d, 0x3e,
      0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a,
      0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16,
      0x1f, 0x1c, 0x19, 0x1a
      ], //*/ G3X = Gx(3),
  
  /*
      G9X = [
      0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53,
      0x6c, 0x65, 0x7e, 0x77, 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf,
      0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, 0x3b, 0x32, 0x29, 0x20,
      0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
      0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8,
      0xc7, 0xce, 0xd5, 0xdc, 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49,
      0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, 0xe6, 0xef, 0xf4, 0xfd,
      0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91,
      0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e,
      0x21, 0x28, 0x33, 0x3a, 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2,
      0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, 0xec, 0xe5, 0xfe, 0xf7,
      0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b,
      0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f,
      0x10, 0x19, 0x02, 0x0b, 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8,
      0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, 0x47, 0x4e, 0x55, 0x5c,
      0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30,
      0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9,
      0xf6, 0xff, 0xe4, 0xed, 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35,
      0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, 0xa1, 0xa8, 0xb3, 0xba,
      0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6,
      0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62,
      0x5d, 0x54, 0x4f, 0x46
      ], //*/ G9X = Gx(9),
  
  /*    GBX = [
      0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45,
      0x74, 0x7f, 0x62, 0x69, 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81,
      0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, 0x7b, 0x70, 0x6d, 0x66,
      0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12,
      0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e,
      0xbf, 0xb4, 0xa9, 0xa2, 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7,
      0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, 0x46, 0x4d, 0x50, 0x5b,
      0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f,
      0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8,
      0xf9, 0xf2, 0xef, 0xe4, 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c,
      0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, 0xf7, 0xfc, 0xe1, 0xea,
      0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e,
      0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02,
      0x33, 0x38, 0x25, 0x2e, 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd,
      0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, 0x3c, 0x37, 0x2a, 0x21,
      0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55,
      0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44,
      0x75, 0x7e, 0x63, 0x68, 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80,
      0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, 0x7a, 0x71, 0x6c, 0x67,
      0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13,
      0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f,
      0xbe, 0xb5, 0xa8, 0xa3
      ], //*/ GBX = Gx(0xb),
  
  /*
      GDX = [
      0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f,
      0x5c, 0x51, 0x46, 0x4b, 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3,
      0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, 0xbb, 0xb6, 0xa1, 0xac,
      0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0,
      0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14,
      0x37, 0x3a, 0x2d, 0x20, 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e,
      0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, 0xbd, 0xb0, 0xa7, 0xaa,
      0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6,
      0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9,
      0x8a, 0x87, 0x90, 0x9d, 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25,
      0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, 0xda, 0xd7, 0xc0, 0xcd,
      0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91,
      0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75,
      0x56, 0x5b, 0x4c, 0x41, 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42,
      0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, 0xb1, 0xbc, 0xab, 0xa6,
      0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa,
      0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8,
      0xeb, 0xe6, 0xf1, 0xfc, 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44,
      0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, 0x0c, 0x01, 0x16, 0x1b,
      0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47,
      0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3,
      0x80, 0x8d, 0x9a, 0x97
      ], //*/ GDX = Gx(0xd),
  
  /*
      GEX = [
      0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62,
      0x48, 0x46, 0x54, 0x5a, 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca,
      0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, 0xdb, 0xd5, 0xc7, 0xc9,
      0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81,
      0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59,
      0x73, 0x7d, 0x6f, 0x61, 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87,
      0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, 0x4d, 0x43, 0x51, 0x5f,
      0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17,
      0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14,
      0x3e, 0x30, 0x22, 0x2c, 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc,
      0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, 0x41, 0x4f, 0x5d, 0x53,
      0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b,
      0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3,
      0xe9, 0xe7, 0xf5, 0xfb, 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0,
      0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, 0x7a, 0x74, 0x66, 0x68,
      0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20,
      0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e,
      0xa4, 0xaa, 0xb8, 0xb6, 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26,
      0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, 0x37, 0x39, 0x2b, 0x25,
      0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d,
      0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5,
      0x9f, 0x91, 0x83, 0x8d
      ], //*/ GEX = Gx(0xe),
  
      enc = function(string, pass, binary) {
          // string, password in plaintext
          var salt = randArr(8),
          pbe = openSSLKey(s2a(pass, binary), salt),
          key = pbe.key,
          iv = pbe.iv,
          cipherBlocks,
          saltBlock = [[83, 97, 108, 116, 101, 100, 95, 95].concat(salt)];
          string = s2a(string, binary);
          cipherBlocks = rawEncrypt(string, key, iv);
          // Spells out 'Salted__'
          cipherBlocks = saltBlock.concat(cipherBlocks);
          return Base64.encode(cipherBlocks);
      },
  
      dec = function(string, pass, binary) {
          // string, password in plaintext
          var cryptArr = Base64.decode(string),
          salt = cryptArr.slice(8, 16),
          pbe = openSSLKey(s2a(pass, binary), salt),
          key = pbe.key,
          iv = pbe.iv;
          cryptArr = cryptArr.slice(16, cryptArr.length);
          // Take off the Salted__ffeeddcc
          string = rawDecrypt(cryptArr, key, iv, binary);
          return string;
      },
  
      MD5 = function(numArr) {
  
          function rotateLeft(lValue, iShiftBits) {
              return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
          }
  
          function addUnsigned(lX, lY) {
              var lX4,
              lY4,
              lX8,
              lY8,
              lResult;
              lX8 = (lX & 0x80000000);
              lY8 = (lY & 0x80000000);
              lX4 = (lX & 0x40000000);
              lY4 = (lY & 0x40000000);
              lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
              if (lX4 & lY4) {
                  return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
              }
              if (lX4 | lY4) {
                  if (lResult & 0x40000000) {
                      return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
                  } else {
                      return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
                  }
              } else {
                  return (lResult ^ lX8 ^ lY8);
              }
          }
  
          function f(x, y, z) {
              return (x & y) | ((~x) & z);
          }
          function g(x, y, z) {
              return (x & z) | (y & (~z));
          }
          function h(x, y, z) {
              return (x ^ y ^ z);
          }
          function funcI(x, y, z) {
              return (y ^ (x | (~z)));
          }
  
          function ff(a, b, c, d, x, s, ac) {
              a = addUnsigned(a, addUnsigned(addUnsigned(f(b, c, d), x), ac));
              return addUnsigned(rotateLeft(a, s), b);
          }
  
          function gg(a, b, c, d, x, s, ac) {
              a = addUnsigned(a, addUnsigned(addUnsigned(g(b, c, d), x), ac));
              return addUnsigned(rotateLeft(a, s), b);
          }
  
          function hh(a, b, c, d, x, s, ac) {
              a = addUnsigned(a, addUnsigned(addUnsigned(h(b, c, d), x), ac));
              return addUnsigned(rotateLeft(a, s), b);
          }
  
          function ii(a, b, c, d, x, s, ac) {
              a = addUnsigned(a, addUnsigned(addUnsigned(funcI(b, c, d), x), ac));
              return addUnsigned(rotateLeft(a, s), b);
          }
  
          function convertToWordArray(numArr) {
              var lWordCount,
              lMessageLength = numArr.length,
              lNumberOfWords_temp1 = lMessageLength + 8,
              lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64,
              lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16,
              lWordArray = [],
              lBytePosition = 0,
              lByteCount = 0;
              while (lByteCount < lMessageLength) {
                  lWordCount = (lByteCount - (lByteCount % 4)) / 4;
                  lBytePosition = (lByteCount % 4) * 8;
                  lWordArray[lWordCount] = (lWordArray[lWordCount] | (numArr[lByteCount] << lBytePosition));
                  lByteCount++;
              }
              lWordCount = (lByteCount - (lByteCount % 4)) / 4;
              lBytePosition = (lByteCount % 4) * 8;
              lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
              lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
              lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
              return lWordArray;
          }
  
          function wordToHex(lValue) {
              var lByte,
              lCount,
              wordToHexArr = [];
              for (lCount = 0; lCount <= 3; lCount++) {
                  lByte = (lValue >>> (lCount * 8)) & 255;
                  wordToHexArr = wordToHexArr.concat(lByte);
               }
              return wordToHexArr;
          }
  
          /*function utf8Encode(string) {
              string = string.replace(/\r\n/g, "\n");
              var utftext = "",
              n,
              c;
  
              for (n = 0; n < string.length; n++) {
  
                  c = string.charCodeAt(n);
  
                  if (c < 128) {
                      utftext += String.fromCharCode(c);
                  }
                  else if ((c > 127) && (c < 2048)) {
                      utftext += String.fromCharCode((c >> 6) | 192);
                      utftext += String.fromCharCode((c & 63) | 128);
                  }
                  else {
                      utftext += String.fromCharCode((c >> 12) | 224);
                      utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                      utftext += String.fromCharCode((c & 63) | 128);
                  }
  
              }
  
              return utftext;
          }*/
  
          var x = [],
          k,
          AA,
          BB,
          CC,
          DD,
          a,
          b,
          c,
          d,
          rnd = strhex('67452301efcdab8998badcfe10325476d76aa478e8c7b756242070dbc1bdceeef57c0faf4787c62aa8304613fd469501698098d88b44f7afffff5bb1895cd7be6b901122fd987193a679438e49b40821f61e2562c040b340265e5a51e9b6c7aad62f105d02441453d8a1e681e7d3fbc821e1cde6c33707d6f4d50d87455a14eda9e3e905fcefa3f8676f02d98d2a4c8afffa39428771f6816d9d6122fde5380ca4beea444bdecfa9f6bb4b60bebfbc70289b7ec6eaa127fad4ef308504881d05d9d4d039e6db99e51fa27cf8c4ac5665f4292244432aff97ab9423a7fc93a039655b59c38f0ccc92ffeff47d85845dd16fa87e4ffe2ce6e0a30143144e0811a1f7537e82bd3af2352ad7d2bbeb86d391',8);
  
          x = convertToWordArray(numArr);
  
          a = rnd[0];
          b = rnd[1];
          c = rnd[2];
          d = rnd[3]
  
          for (k = 0; k < x.length; k += 16) {
              AA = a;
              BB = b;
              CC = c;
              DD = d;
              a = ff(a, b, c, d, x[k + 0], 7, rnd[4]);
              d = ff(d, a, b, c, x[k + 1], 12, rnd[5]);
              c = ff(c, d, a, b, x[k + 2], 17, rnd[6]);
              b = ff(b, c, d, a, x[k + 3], 22, rnd[7]);
              a = ff(a, b, c, d, x[k + 4], 7, rnd[8]);
              d = ff(d, a, b, c, x[k + 5], 12, rnd[9]);
              c = ff(c, d, a, b, x[k + 6], 17, rnd[10]);
              b = ff(b, c, d, a, x[k + 7], 22, rnd[11]);
              a = ff(a, b, c, d, x[k + 8], 7, rnd[12]);
              d = ff(d, a, b, c, x[k + 9], 12, rnd[13]);
              c = ff(c, d, a, b, x[k + 10], 17, rnd[14]);
              b = ff(b, c, d, a, x[k + 11], 22, rnd[15]);
              a = ff(a, b, c, d, x[k + 12], 7, rnd[16]);
              d = ff(d, a, b, c, x[k + 13], 12, rnd[17]);
              c = ff(c, d, a, b, x[k + 14], 17, rnd[18]);
              b = ff(b, c, d, a, x[k + 15], 22, rnd[19]);
              a = gg(a, b, c, d, x[k + 1], 5, rnd[20]);
              d = gg(d, a, b, c, x[k + 6], 9, rnd[21]);
              c = gg(c, d, a, b, x[k + 11], 14, rnd[22]);
              b = gg(b, c, d, a, x[k + 0], 20, rnd[23]);
              a = gg(a, b, c, d, x[k + 5], 5, rnd[24]);
              d = gg(d, a, b, c, x[k + 10], 9, rnd[25]);
              c = gg(c, d, a, b, x[k + 15], 14, rnd[26]);
              b = gg(b, c, d, a, x[k + 4], 20, rnd[27]);
              a = gg(a, b, c, d, x[k + 9], 5, rnd[28]);
              d = gg(d, a, b, c, x[k + 14], 9, rnd[29]);
              c = gg(c, d, a, b, x[k + 3], 14, rnd[30]);
              b = gg(b, c, d, a, x[k + 8], 20, rnd[31]);
              a = gg(a, b, c, d, x[k + 13], 5, rnd[32]);
              d = gg(d, a, b, c, x[k + 2], 9, rnd[33]);
              c = gg(c, d, a, b, x[k + 7], 14, rnd[34]);
              b = gg(b, c, d, a, x[k + 12], 20, rnd[35]);
              a = hh(a, b, c, d, x[k + 5], 4, rnd[36]);
              d = hh(d, a, b, c, x[k + 8], 11, rnd[37]);
              c = hh(c, d, a, b, x[k + 11], 16, rnd[38]);
              b = hh(b, c, d, a, x[k + 14], 23, rnd[39]);
              a = hh(a, b, c, d, x[k + 1], 4, rnd[40]);
              d = hh(d, a, b, c, x[k + 4], 11, rnd[41]);
              c = hh(c, d, a, b, x[k + 7], 16, rnd[42]);
              b = hh(b, c, d, a, x[k + 10], 23, rnd[43]);
              a = hh(a, b, c, d, x[k + 13], 4, rnd[44]);
              d = hh(d, a, b, c, x[k + 0], 11, rnd[45]);
              c = hh(c, d, a, b, x[k + 3], 16, rnd[46]);
              b = hh(b, c, d, a, x[k + 6], 23, rnd[47]);
              a = hh(a, b, c, d, x[k + 9], 4, rnd[48]);
              d = hh(d, a, b, c, x[k + 12], 11, rnd[49]);
              c = hh(c, d, a, b, x[k + 15], 16, rnd[50]);
              b = hh(b, c, d, a, x[k + 2], 23, rnd[51]);
              a = ii(a, b, c, d, x[k + 0], 6, rnd[52]);
              d = ii(d, a, b, c, x[k + 7], 10, rnd[53]);
              c = ii(c, d, a, b, x[k + 14], 15, rnd[54]);
              b = ii(b, c, d, a, x[k + 5], 21, rnd[55]);
              a = ii(a, b, c, d, x[k + 12], 6, rnd[56]);
              d = ii(d, a, b, c, x[k + 3], 10, rnd[57]);
              c = ii(c, d, a, b, x[k + 10], 15, rnd[58]);
              b = ii(b, c, d, a, x[k + 1], 21, rnd[59]);
              a = ii(a, b, c, d, x[k + 8], 6, rnd[60]);
              d = ii(d, a, b, c, x[k + 15], 10, rnd[61]);
              c = ii(c, d, a, b, x[k + 6], 15, rnd[62]);
              b = ii(b, c, d, a, x[k + 13], 21, rnd[63]);
              a = ii(a, b, c, d, x[k + 4], 6, rnd[64]);
              d = ii(d, a, b, c, x[k + 11], 10, rnd[65]);
              c = ii(c, d, a, b, x[k + 2], 15, rnd[66]);
              b = ii(b, c, d, a, x[k + 9], 21, rnd[67]);
              a = addUnsigned(a, AA);
              b = addUnsigned(b, BB);
              c = addUnsigned(c, CC);
              d = addUnsigned(d, DD);
          }
  
          return wordToHex(a).concat(wordToHex(b), wordToHex(c), wordToHex(d));
      },
  
      encString = function(plaintext, key, iv) {
          plaintext = s2a(plaintext);
  
          key = s2a(key);
          for (var i=key.length; i<32; i++)
              key[i] = 0;
  
          if (iv == null) {
              iv = genIV();
          } else {
              iv = s2a(iv);
              for (var i=iv.length; i<16; i++)
                  iv[i] = 0;
          }
  
          var ct = rawEncrypt(plaintext, key, iv);
          var ret = [iv];
          for (var i=0; i<ct.length; i++)
              ret[ret.length] = ct[i];
          return Base64.encode(ret);
      },
  
      decString = function(ciphertext, key) {
          var tmp = Base64.decode(ciphertext);
          var iv = tmp.slice(0, 16);
          var ct = tmp.slice(16, tmp.length);
  
          key = s2a(key);
          for (var i=key.length; i<32; i++)
              key[i] = 0;
  
          var pt = rawDecrypt(ct, key, iv, false);
          return pt;
      },
  
      Base64 = (function(){
          // Takes a Nx16x1 byte array and converts it to Base64
          var _chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
          chars = _chars.split(''),
  
          encode = function(b, withBreaks) {
              var flatArr = [],
              b64 = '',
              i,
              broken_b64;
              var totalChunks = Math.floor(b.length * 16 / 3);
              for (i = 0; i < b.length * 16; i++) {
                  flatArr.push(b[Math.floor(i / 16)][i % 16]);
              }
              for (i = 0; i < flatArr.length; i = i + 3) {
                  b64 += chars[flatArr[i] >> 2];
                  b64 += chars[((flatArr[i] & 3) << 4) | (flatArr[i + 1] >> 4)];
                  if (! (flatArr[i + 1] === undefined)) {
                      b64 += chars[((flatArr[i + 1] & 15) << 2) | (flatArr[i + 2] >> 6)];
                  } else {
                      b64 += '=';
                  }
                  if (! (flatArr[i + 2] === undefined)) {
                      b64 += chars[flatArr[i + 2] & 63];
                  } else {
                      b64 += '=';
                  }
              }
              // OpenSSL is super particular about line breaks
              broken_b64 = b64.slice(0, 64); // + '\n';
              for (i = 1; i < (Math['ceil'](b64.length / 64)); i++) {
                  broken_b64 += b64.slice(i * 64, i * 64 + 64) + (Math.ceil(b64.length / 64) == i + 1 ? '': '\n');
              }
              return broken_b64;
          },
  
          decode = function(string) {
              string = string['replace'](/\n/g, '');
              var flatArr = [],
              c = [],
              b = [],
              i;
              for (i = 0; i < string.length; i = i + 4) {
                  c[0] = _chars.indexOf(string.charAt(i));
                  c[1] = _chars.indexOf(string.charAt(i + 1));
                  c[2] = _chars.indexOf(string.charAt(i + 2));
                  c[3] = _chars.indexOf(string.charAt(i + 3));
  
                  b[0] = (c[0] << 2) | (c[1] >> 4);
                  b[1] = ((c[1] & 15) << 4) | (c[2] >> 2);
                  b[2] = ((c[2] & 3) << 6) | c[3];
                  flatArr.push(b[0], b[1], b[2]);
              }
              flatArr = flatArr.slice(0, flatArr.length - (flatArr.length % 16));
              return flatArr;
          };
  
          //internet explorer
          if(typeof Array.indexOf === "function") {
              _chars = chars;
          }
  
          /*
          //other way to solve internet explorer problem
          if(!Array.indexOf){
              Array.prototype.indexOf = function(obj){
                  for(var i=0; i<this.length; i++){
                      if(this[i]===obj){
                          return i;
                      }
                  }
                  return -1;
              }
          }
          */
  
  
          return {
              "encode": encode,
              "decode": decode
          };
      })();
  
      return {
          "size": size,
          "h2a":h2a,
          "expandKey":expandKey,
          "encryptBlock":encryptBlock,
          "decryptBlock":decryptBlock,
          "Decrypt":Decrypt,
          "s2a":s2a,
          "rawEncrypt":rawEncrypt,
          "rawDecrypt":rawDecrypt,
          "dec":dec,
          "openSSLKey":openSSLKey,
          "a2h":a2h,
          "enc":enc,
          "Hash":{"MD5":MD5},
          "Base64":Base64
      };
  
  })();
  
  function crypto_obj (){
  
  
  function SHA256(s) {
  
      var chrsz = 8;
      var hexcase = 0;
  
      function safe_add(x, y) {
          var lsw = (x & 0xFFFF) + (y & 0xFFFF);
          var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
          return (msw << 16) | (lsw & 0xFFFF);
      }
  
      function S(X, n) {
          return ( X >>> n ) | (X << (32 - n));
      }
  
      function R(X, n) {
          return ( X >>> n );
      }
  
      function Ch(x, y, z) {
          return ((x & y) ^ ((~x) & z));
      }
  
      function Maj(x, y, z) {
          return ((x & y) ^ (x & z) ^ (y & z));
      }
  
      function Sigma0256(x) {
          return (S(x, 2) ^ S(x, 13) ^ S(x, 22));
      }
  
      function Sigma1256(x) {
          return (S(x, 6) ^ S(x, 11) ^ S(x, 25));
      }
  
      function Gamma0256(x) {
          return (S(x, 7) ^ S(x, 18) ^ R(x, 3));
      }
  
      function Gamma1256(x) {
          return (S(x, 17) ^ S(x, 19) ^ R(x, 10));
      }
  
      function core_sha256(m, l) {
          var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2);
          var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
          var W = new Array(64);
          var a, b, c, d, e, f, g, h, i, j;
          var T1, T2;
  
          m[l >> 5] |= 0x80 << (24 - l % 32);
          m[((l + 64 >> 9) << 4) + 15] = l;
  
          for (var i = 0; i < m.length; i += 16) {
              a = HASH[0];
              b = HASH[1];
              c = HASH[2];
              d = HASH[3];
              e = HASH[4];
              f = HASH[5];
              g = HASH[6];
              h = HASH[7];
  
              for (var j = 0; j < 64; j++) {
                  if (j < 16) W[j] = m[j + i];
                  else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
  
                  T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
                  T2 = safe_add(Sigma0256(a), Maj(a, b, c));
  
                  h = g;
                  g = f;
                  f = e;
                  e = safe_add(d, T1);
                  d = c;
                  c = b;
                  b = a;
                  a = safe_add(T1, T2);
              }
  
              HASH[0] = safe_add(a, HASH[0]);
              HASH[1] = safe_add(b, HASH[1]);
              HASH[2] = safe_add(c, HASH[2]);
              HASH[3] = safe_add(d, HASH[3]);
              HASH[4] = safe_add(e, HASH[4]);
              HASH[5] = safe_add(f, HASH[5]);
              HASH[6] = safe_add(g, HASH[6]);
              HASH[7] = safe_add(h, HASH[7]);
          }
          return HASH;
      }
  
      function str2binb(str) {
          var bin = Array();
          var mask = (1 << chrsz) - 1;
          for (var i = 0; i < str.length * chrsz; i += chrsz) {
              bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32);
          }
          return bin;
      }
  
      function Utf8Encode(string) {
          string = string['replace'](/\r\n/g, "\n");
          var utftext = "";
  
          for (var n = 0; n < string['length']; n++) {
  
              var c = string.charCodeAt(n);
  
              if (c < 128) {
                  utftext += String.fromCharCode(c);
              }
              else if ((c > 127) && (c < 2048)) {
                  utftext += String.fromCharCode((c >> 6) | 192);
                  utftext += String.fromCharCode((c & 63) | 128);
              }
              else {
                  utftext += String.fromCharCode((c >> 12) | 224);
                  utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                  utftext += String.fromCharCode((c & 63) | 128);
              }
  
          }
  
          return utftext;
      }
  
      function binb2hex(binarray) {
          var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
          var str = "";
          for (var i = 0; i < binarray.length * 4; i++) {
              str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
                  hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8  )) & 0xF);
          }
          return str;
      }
  
      s = Utf8Encode(s);
      return binb2hex(core_sha256(str2binb(s), s.length * chrsz));
  }
      var crypto = CRYPTO;
      crypto['size'](256);
  
      var cipher_key = "";
      var iv = crypto['s2a']("0123456789012345");
  
      return {
  
          'encrypt' : function(data, key) {
              if (!key) return data;
              var cipher_key = crypto['s2a'](SHA256(key)['slice'](0,32));
              var hex_message = crypto['s2a'](JSON['stringify'](data));
              var encryptedHexArray = crypto['rawEncrypt'](hex_message, cipher_key, iv);
              var base_64_encrypted = crypto['Base64']['encode'](encryptedHexArray);
              return base_64_encrypted || data;
          } ,
  
          'decrypt' : function(data, key) {
              if (!key) return data;
              var cipher_key = crypto['s2a'](SHA256(key)['slice'](0,32));
              try {
                  var binary_enc = crypto['Base64']['decode'](data);
                  var json_plain = crypto['rawDecrypt'](binary_enc, cipher_key, iv, false);
                  var plaintext = JSON['parse'](json_plain);
                  return plaintext;
              }
              catch (e) {
                  return undefined;
              }
          }
      };
  }
  /* ---------------------------------------------------------------------------
  --------------------------------------------------------------------------- */
  
  /* ---------------------------------------------------------------------------
  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.
  --------------------------------------------------------------------------- */
  (function(){
  
  /**
   * UTIL LOCALS
   */
  var NOW        = 1
  ,    PNSDK      = 'PubNub-JS-' + 'Modern' + '/' + '3.7.8'
  ,   XHRTME     = 310000;
  
  
  
  /**
   * LOCAL STORAGE
   */
  var db = (function(){
      var ls = typeof localStorage != 'undefined' && 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 }
          }
      };
  })();
  
  
  /**
   * CORS XHR Request
   * ================
   *  xdr({
   *     url     : ['http://www.blah.com/url'],
   *     success : function(response) {},
   *     fail    : function() {}
   *  });
   */
  function xdr( setup ) {
      var xhr
      ,   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
      ,   timer    = timeout( function(){done(1)}, XHRTME )
      ,   data     = setup.data || {}
      ,   fail     = setup.fail    || function(){}
      ,   success  = setup.success || function(){}
      ,   async    = ( typeof(setup.blocking) === 'undefined' )
      ,   done     = function(failed, response) {
              if (complete) return;
                  complete = 1;
  
              clearTimeout(timer);
  
              if (xhr) {
                  xhr.onerror = xhr.onload = null;
                  xhr.abort && xhr.abort();
                  xhr = null;
              }
  
              failed && fail(response);
          };
  
      // Send
      try {
          xhr = typeof XDomainRequest !== 'undefined' &&
                new XDomainRequest()  ||
                new XMLHttpRequest();
  
          xhr.onerror = xhr.onabort   = function(){ done(1, xhr.responseText || { "error" : "Network Connection Error"}) };
          xhr.onload  = xhr.onloadend = finished;
          xhr.onreadystatechange = function() {
              if (xhr.readyState == 4) {
                  switch(xhr.status) {
                      case 401:
                      case 402:
                      case 403:
                          try {
                              response = JSON['parse'](xhr.responseText);
                              done(1,response);
                          }
                          catch (r) { return done(1, xhr.responseText); }
                          break;
                      default:
                          break;
                  }
              }
          }
          data['pnsdk'] = PNSDK;
          url = build_url(setup.url, data);
          xhr.open( 'GET', url, async);
          if (async) xhr.timeout = XHRTME;
          xhr.send();
      }
      catch(eee) {
          done(0);
          return xdr(setup);
      }
  
      // Return 'done'
      return done;
  }
  
  /**
   * BIND
   * ====
   * bind( 'keydown', search('a')[0], function(element) {
   *     ...
   * } );
   */
  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;
  }
  
  /**
   * ERROR
   * ===
   * error('message');
   */
  function error(message) { console['error'](message) }
  
  /**
   * 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) }
          );
      }
  };
  
  /**
   * 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);
  }
  
  /**
   * $
   * =
   * var div = $('divid');
   */
  function $(id) { return document.getElementById(id) }
  
  
  /**
   * 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;
  }
  
  /**
   * 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) }
  
  
  function get_hmac_SHA256(data,key) {
      var hash = CryptoJS['HmacSHA256'](data, key);
      return hash.toString(CryptoJS['enc']['Base64']);
  }
  
  /* =-====================================================================-= */
  /* =-====================================================================-= */
  /* =-=========================     PUBNUB     ===========================-= */
  /* =-====================================================================-= */
  /* =-====================================================================-= */
  
  function CREATE_PUBNUB(setup) {
  
  
      setup['db'] = db;
      setup['xdr'] = xdr;
      setup['error'] = setup['error'] || error;
      setup['hmac_SHA256']= get_hmac_SHA256;
      setup['crypto_obj'] = crypto_obj();
      setup['params']      = { 'pnsdk' : PNSDK }
  
      SELF = function(setup) {
          return CREATE_PUBNUB(setup);
      }
      var PN = PN_API(setup);
      for (var prop in PN) {
          if (PN.hasOwnProperty(prop)) {
              SELF[prop] = PN[prop];
          }
      }
  
      SELF['init'] = SELF;
      SELF['$'] = $;
      SELF['attr'] = attr;
      SELF['search'] = search;
      SELF['bind'] = bind;
      SELF['css'] = css;
      SELF['create'] = create;
  
      if (typeof(window) !== 'undefined'){
          bind( 'beforeunload', window, function() {
              SELF['each-channel'](function(ch){ SELF['LEAVE']( ch.name, 1 ) });
              return true;
          });
      }
  
      // Return without Testing
      if (setup['notest']) return SELF;
  
      if (typeof(window) !== 'undefined'){
          bind( 'offline', window,   SELF['_reset_offline'] );
      }
  
      if (typeof(document) !== 'undefined'){
          bind( 'offline', document, SELF['_reset_offline'] );
      }
  
      SELF['ready']();
      return SELF;
  }
  CREATE_PUBNUB['init'] = CREATE_PUBNUB
  CREATE_PUBNUB['secure'] = CREATE_PUBNUB
  PUBNUB = CREATE_PUBNUB({})
  typeof module  !== 'undefined' && (module.exports = CREATE_PUBNUB) ||
  typeof exports !== 'undefined' && (exports.PUBNUB = CREATE_PUBNUB) || (PUBNUB = CREATE_PUBNUB);
  
  })();
  (function(){
  
  // ---------------------------------------------------------------------------
  // WEBSOCKET INTERFACE
  // ---------------------------------------------------------------------------
  var WS = PUBNUB['ws'] = function( url, protocols ) {
      if (!(this instanceof WS)) return new WS( url, protocols );
  
      var self     = this
      ,   url      = self.url      = url || ''
      ,   protocol = self.protocol = protocols || 'Sec-WebSocket-Protocol'
      ,   bits     = url.split('/')
      ,   setup    = {
           'ssl'           : bits[0] === 'wss:'
          ,'origin'        : bits[2]
          ,'publish_key'   : bits[3]
          ,'subscribe_key' : bits[4]
          ,'channel'       : bits[5]
      };
  
      // READY STATES
      self['CONNECTING'] = 0; // The connection is not yet open.
      self['OPEN']       = 1; // The connection is open and ready to communicate.
      self['CLOSING']    = 2; // The connection is in the process of closing.
      self['CLOSED']     = 3; // The connection is closed or couldn't be opened.
  
      // CLOSE STATES
      self['CLOSE_NORMAL']         = 1000; // Normal Intended Close; completed.
      self['CLOSE_GOING_AWAY']     = 1001; // Closed Unexpecttedly.
      self['CLOSE_PROTOCOL_ERROR'] = 1002; // Server: Not Supported.
      self['CLOSE_UNSUPPORTED']    = 1003; // Server: Unsupported Protocol.
      self['CLOSE_TOO_LARGE']      = 1004; // Server: Too Much Data.
      self['CLOSE_NO_STATUS']      = 1005; // Server: No reason.
      self['CLOSE_ABNORMAL']       = 1006; // Abnormal Disconnect.
  
      // Events Default
      self['onclose']   = self['onerror'] =
      self['onmessage'] = self['onopen']  =
      self['onsend']    =  function(){};
  
      // Attributes
      self['binaryType']     = '';
      self['extensions']     = '';
      self['bufferedAmount'] = 0;
      self['trasnmitting']   = false;
      self['buffer']         = [];
      self['readyState']     = self['CONNECTING'];
  
      // Close if no setup.
      if (!url) {
          self['readyState'] = self['CLOSED'];
          self['onclose']({
              'code'     : self['CLOSE_ABNORMAL'],
              'reason'   : 'Missing URL',
              'wasClean' : true
          });
          return self;
      }
  
      // PubNub WebSocket Emulation
      self.pubnub       = PUBNUB['init'](setup);
      self.pubnub.setup = setup;
      self.setup        = setup;
  
      self.pubnub['subscribe']({
          'restore'    : false,
          'channel'    : setup['channel'],
          'disconnect' : self['onerror'],
          'reconnect'  : self['onopen'],
          'error'      : function() {
              self['onclose']({
                  'code'     : self['CLOSE_ABNORMAL'],
                  'reason'   : 'Missing URL',
                  'wasClean' : false
              });
          },
          'callback'   : function(message) {
              self['onmessage']({ 'data' : message });
          },
          'connect'    : function() {
              self['readyState'] = self['OPEN'];
              self['onopen']();
          }
      });
  };
  
  // ---------------------------------------------------------------------------
  // WEBSOCKET SEND
  // ---------------------------------------------------------------------------
  WS.prototype.send = function(data) {
      var self = this;
      self.pubnub['publish']({
          'channel'  : self.pubnub.setup['channel'],
          'message'  : data,
          'callback' : function(response) {
              self['onsend']({ 'data' : response });
          }
      });
  };
  
  // ---------------------------------------------------------------------------
  // WEBSOCKET CLOSE
  // ---------------------------------------------------------------------------
  WS.prototype.close = function() {
      var self = this;
      self.pubnub['unsubscribe']({ 'channel' : self.pubnub.setup['channel'] });
      self['readyState'] = self['CLOSED'];
      self['onclose']({});
  };
  
  })();
  