UNPKG

webp2p

Version:

The server-less signaling channel for WebRTC

1,492 lines (1,309 loc) 104 kB
// Version: 3.6.4 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.6.4' , 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 && (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(); } // PUBNUB READY TO CONNECT function ready() { timeout( function() { if (READY) return; READY = 1; each( READY_BUFFER, function(connect) { connect() } ); }, SECOND ); } 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 , 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 = [] , TIME_DRIFT = 0 , SUB_CALLBACK = 0 , SUB_CHANNEL = 0 , SUB_RECEIVER = 0 , SUB_RESTORE = 0 , SUB_BUFF_WAIT = 0 , TIMETOKEN = 0 , RESUMED = false , CHANNELS = {} , 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'] || ( db && db['get'](SUBSCRIBE_KEY+'uuid') || ''); 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 ) { 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){ 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(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'] && response['message'] && response['payload']) { err({'message' : response['message'], 'payload' : response['payload']}); return; } if (response['payload']) { callback(response['payload']); return; } } callback(response); } function _invoke_error(response,err) { if (typeof response == 'object' && response['error'] && response['message'] && response['payload']) { err({'message' : response['message'], 'payload' : response['payload']}); } else err(response); } // Announce Leave Event var SELF = { 'LEAVE' : function( channel, blocking, callback, error ) { var data = { 'uuid' : UUID, 'auth' : 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; }, '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; }, '_add_param' : function(key,val) { params[key] = val; }, /* 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'] , start = args['start'] , end = args['end'] , include_token = args['include_token'] , params = {} , jsonp = jsonp_cb(); // Make sure we have a Channel if (!channel) 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 (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 callback = callback || args['callback'] || function(){} , msg = args['message'] , channel = args['channel'] , auth_key = args['auth_key'] || AUTH_KEY , cipher_key = args['cipher_key'] , err = args['error'] || function() {} , post = args['post'] || false , jsonp = jsonp_cb() , add_msg = 'push' , url; if (args['prepend']) add_msg = 'unshift' if (!msg) return error('Missing Message'); if (!channel) return error('Missing Channel'); if (!PUBLISH_KEY) return error('Missing Publish Key'); if (!SUBSCRIBE_KEY) return error('Missing Subscribe Key'); // 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) ]; // Queue Message Send PUB_QUEUE[add_msg]({ callback : jsonp, timeout : SECOND * 5, url : url, data : _get_url_params({ 'uuid' : UUID, 'auth' : auth_key }), 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'] , callback = callback || args['callback'] || function(){} , err = args['error'] || function(){}; TIMETOKEN = 0; SUB_RESTORE = 1; // 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(channel) { var CB_CALLED = true; if (!channel) return; if (READY) { CB_CALLED = SELF['LEAVE']( channel, 0 , callback, err); } if (!CB_CALLED) callback({action : "leave"}); CHANNELS[channel] = 0; if (channel in STATE) delete STATE[channel]; } ); // Reset Connection if Count Less CONNECT(); }, /* PUBNUB.subscribe({ channel : 'my_chat' callback : function(message) { } }); */ 'subscribe' : function( args, callback ) { var channel = args['channel'] , 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']; // Restore Enabled? SUB_RESTORE = restore; // Always Reset the TT TIMETOKEN = timetoken; // Make sure we have a Channel if (!channel) 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) 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' : rnow(), 'occupancy' : here['occupancy'] || 1 }, here, channel ); } ); } }); } ); // 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); } }); } // Evented Subscribe function _connect() { var jsonp = jsonp_cb() , channels = generate_channel_list(CHANNELS).join(','); // Stop Connection if (!channels) return; // Connect to PubNub Subscribe Servers _reset_offline(); var data = _get_url_params({ 'uuid' : UUID, 'auth' : auth_key }); 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) { _invoke_error(response, errcb); //SUB_RECEIVER = null; SELF['time'](_test_connection); }, data : _get_url_params(data), url : [ SUB_ORIGIN, 'subscribe', SUBSCRIBE_KEY, encode(channels), jsonp, TIMETOKEN ], success : function(messages) { //SUB_RECEIVER = null; // 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(function(channel){ if (channel.connected) return; channel.connected = 1; channel.connect(channel.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 = (messages.length>2?messages[2]:map( generate_channel_list(CHANNELS), function(chan) { return map( Array(messages[0].length) .join(',').split(','), function() { return chan; } ) }).join(',')); var list = channels.split(','); return function() { var channel = list.shift()||SUB_CHANNEL; return [ (CHANNELS[channel]||{}) .callback||SUB_CALLBACK, channel.split(PRESENCE_SUFFIX)[0] ]; }; })(); var latency = detect_latency(+messages[1]); each( messages[0], function(msg) { var next = next_callback(); var decrypted_msg = decrypt(msg,CHANNELS[next[1]]['cipher_key']); next[0]( decrypted_msg, messages, next[1], latency); }); 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'] , 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; } 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'] , 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) return error('Missing Channel'); if (jsonp != '0') { data['callback'] = jsonp; } if (CHANNELS[channel] && CHANNELS[channel].subscribed) STATE[channel] = state; data['state'] = JSON.stringify(state); if (state) { url = [ STD_ORIGIN, 'v2', 'presence', 'sub-key', SUBSCRIBE_KEY, 'channel', encode(channel), 'uuid', uuid, 'data' ] } else { url = [ STD_ORIGIN, 'v2', 'presence', 'sub-key', SUBSCRIBE_KEY, 'channel', encode(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'] , jsonp = jsonp_cb() , ttl = args['ttl'] , r = (args['read'] )?"1":"0" , w = (args['write'])?"1":"0" , auth_key = args['auth_key']; // Make sure we have a Channel if (!channel) return error('Missing 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" + "grant" + "\n"; var data = { 'w' : w, 'r' : r, 'channel' : channel, 'timestamp' : timestamp }; 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) 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.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'] , 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 (channel) data['channel'] = channel; if (auth_key) data['auth'] = auth_key; data = _get_url_params(data) 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; }, '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; xdr({ callback : jsonp, data : _get_url_params(data), timeout : SECOND * 5, url : [ STD_ORIGIN, 'v2', 'presence', 'sub-key', SUBSCRIBE_KEY, 'channel' , encode(generate_channel_list(CHANNELS, true)['join'](',')), 'heartbeat' ], success : function(response) { _invoke_callback(response, callback, err); }, fail : function(response) { _invoke_error(response, err); } }); }, // 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. " }); timeout( _poll_online, SECOND ); } function _poll_online2() { 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." }); timeout( _poll_online2, KEEPALIVE ); }); } function _reset_offline(err, msg) { SUB_RECEIVER && SUB_RECEIVER(err, msg); SUB_RECEIVER = null; } if (!UUID) UUID = SELF['uuid'](); db['set']( SUBSCRIBE_KEY + 'uuid', UUID ); timeout( _poll_online, SECOND ); 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(