diff --git a/src/RT.BOSH.js b/src/RT.BOSH.js index 5082986..695d7d3 100644 --- a/src/RT.BOSH.js +++ b/src/RT.BOSH.js @@ -12,63 +12,106 @@ if ( 'object' === typeof exports ) factory( require('./RT.js') ); else - factory( root['RT'] ); + factory( root['RT'] ) && ('function' === typeof define) && define.amd && define(function( ){ return root['RT']; }); }(this, function( RT ) { "use strict"; var PROTO = 'prototype', HAS = 'hasOwnProperty', toString = Object[PROTO].toString, - __super__ = RT.Client[PROTO], Util = RT.Util + __super__ = RT.Client[PROTO], U = RT.Util, XHR = RT.XHR ; -function XHR( ) -{ - return window.XMLHttpRequest - // code for IE7+, Firefox, Chrome, Opera, Safari - ? new XMLHttpRequest( ) - // code for IE6, IE5 - : new ActiveXObject('Microsoft.XMLHTTP') // or ActiveXObject('Msxml2.XMLHTTP'); ?? - ; -} -function ajax( xhr, url, headers, data, cb ) -{ - if ( xhr ) - { - try{ xhr.abort( ); }catch(e){ } - xhr = null; - } - xhr = xhr || XHR( ); - xhr.open('POST', url, true); - xhr.responseType = 'text'; - xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf8'); - xhr.overrideMimeType('text/plain; charset=utf8'); - if ( headers ) Util.Header.encode( headers, xhr ); - xhr.onload = function( ) { - var err = 200 !== xhr.status, - response = err ? xhr.statusText : xhr.responseText; - if ( cb ) cb( err, response, xhr.getAllResponseHeaders( ), xhr.status, xhr.statusText ); - }; - xhr.send( data ); - return xhr; -} - -RT.Client.BOSH = function Client_BOSH( config ) { +RT.Client.BOSH = function Client_Bosh( cfg ) { var self = this; - if ( !(self instanceof Client_BOSH) ) return new Client_BOSH(config); - __super__.constructor.call( self, config ); - self._xhr = null; + if ( !(self instanceof Client_Bosh) ) return new Client_Bosh(cfg); + __super__.constructor.call( self, cfg ); + self.$xhr$ = null; }; -RT.Client.Impl['bosh'] = RT.Client.BOSH; +RT.Client.Impl['bosh'] = RT.Client.Impl['long-poll'] = RT.Client.BOSH; /* extends RT.Client class */ RT.Client.BOSH[PROTO] = Object.create( __super__ ); RT.Client.BOSH[PROTO].constructor = RT.Client.BOSH; -RT.Client.BOSH[PROTO]._xhr = null; +RT.Client.BOSH[PROTO].$xhr$ = null; RT.Client.BOSH[PROTO].dispose = function( ){ var self = this; - if ( self._xhr ) try{ self._xhr.abort( ); }catch(e){ } - self._xhr = null; + self.abort( ); return __super__.dispose.call( self ); }; +RT.Client.BOSH[PROTO].abort = function( trigger ){ + var self = this; + if ( self.$xhr$ ) { self.$xhr$.abort( true===trigger ); self.$xhr$ = null; } + return self; +}; +RT.Client.BOSH[PROTO].$poll$ = function( immediate ){ + var self = this; + var poll = function poll( ) { + var headers = { + 'Content-Type' : 'application/x-www-form-urlencoded; charset=utf8', + 'X-RT-Receive' : '1', // receive incoming message(s) + 'X-RT-Timestamp' : self.$timestamp$ + }; + var rt_msg = null, msgs = null; + if ( self.$queue$.length ) + { + // send message(s) on same request + headers['X-RT-Send'] = '1'; + headers['X-RT-Message'] = rt_msg = RT.UUID('----------------------'); + msgs = self.$queue$.slice( ); + } + self.$xhr$ = XHR.create({ + url : self.$cfg$.url + (-1 < self.$cfg$.url.indexOf('?') ? '&' : '?') + '__NOCACHE__='+(new Date().getTime()), + method : 'POST', + responseType : 'text', + //mimeType : 'text/plain; charset=utf8', + headers : headers, + onError : function( xhr ) { + self.emit('error', xhr.statusText); + }, + onTimeout : function( xhr ) { + self.$timer$ = setTimeout(poll, self.$cfg$.pollInterval); + }, + onComplete : function( xhr ) { + var rt_msg = xhr.responseHeader( 'X-RT-Message' ), + rt_close = xhr.responseHeader( 'X-RT-Close' ), + rt_error = xhr.responseHeader( 'X-RT-Error' ), + rt_timestamp = xhr.responseHeader( 'X-RT-Timestamp' ) + ; + if ( rt_error ) + { + self.emit( 'error', rt_error ); + return; + } + if ( rt_close ) + { + self.close( ); + return; + } + if ( rt_msg ) + { + // at the same time, handle incoming message(s) + var msgs = (xhr.responseText||'').split( rt_msg ), i, l; + for(i=0,l=msgs.length; i 0 ? name : '' - ,value : argslen > 1 ? value : '' - ,domain : argslen > 2 ? domain : '' - ,path : argslen > 3 ? path : '/' - ,expires : argslen > 4 ? expires : new Date(Date.now() + 31536000000) - ,secure : argslen > 5 ? !!secure : false - ,httponly : argslen > 6 ? !!httponly : false - }; - }, - encode: function( cookie ) { - if ( !cookie || !cookie.name ) return; - var cookieString = String(cookie.name) + '=' + String(cookie.value); - cookieString += '; Domain=' + String(cookie.domain); - cookieString += '; Path=' + String(cookie.path); - cookieString += '; Expires=' + String(cookie.expires); - if ( cookie.secure ) cookieString += '; Secure'; - if ( cookie.httponly ) cookieString += '; HttpOnly'; - return cookieString; - }, - decode: function( cookieString ) { - var cookie = RT.Util.Cookie.create( ), - /* parse value/name */ - equalsSplit = RT.Const.COOKIE_RE, - cookieParams = ("" + cookieString).split("; "), - cookieParam, attr, i, len - ; - if ( null == (cookieParam = cookieParams.shift().match(equalsSplit)) ) return; - - cookie.name = cookieParam[1]; - cookie.value = cookieParam[2]; - - /* parse remaining attributes */ - for (i=0,len=cookieParams.length; i 1 ) - { - key = trim(parts.shift()); - if ( lowercase ) key = key.toLowerCase(); - header[key] = parts.join(':'); - } - else if ( key ) - { - header[key] += CRLF + parts[0]; - } - } - } - return header; - } - } -}; - -RT.Client = function Client( config ) { - var self = this; - if ( !(self instanceof Client) ) return new Client( config ); - self._config = config; - self._event = { }; - self.status = RT.Client.CREATED; -}; - -RT.Client.Impl = { }; - -RT.Client.OPENED = 2; -RT.Client.CLOSED = 4; -RT.Client.PENDING = 8; -RT.Client.CREATED = 1; -RT.Client.DESTROYED = 0; - -RT.Client[PROTO] = { - constructor: RT.Client - ,status: RT.Client.CREATED - ,_config: null - ,_event: null - ,dispose: function( ){ - var self = this; - self.status = RT.Client.DESTROYED; - self._config = null; - self._event = null; - return self; - } - ,addEventListener: function( event, handler ){ - var self = this; - if ( !event || !handler ) return self; - if ( !self._event[HAS](event) ) self._event[event] = [handler]; - else self._event[event].push(handler); - return self; - } - ,removeEventListener: function( event, handler ){ - var self = this; - if ( !event || !self._event[HAS](event) ) return self; - if ( null == handler ) - { - delete self._event[event]; - } - else - { - for(var handle=self._event[event],i=handle.length-1; i>=0; i--) - if ( handle[i] === handler ) handler.splice(i, 1); - if ( !handle.length ) delete self._event[event]; - } - return self; - } - ,trigger: function( event, data ){ - var self = this; - if ( !event || !self._event[HAS](event) ) return self; - var handler = self._event[event].slice( ), i, l = handler.length; - for(i=0; is?[[o=C[s++],t[o]]]:[];n.length;){if(u=n.shift(),o=u[0],l=u[1],d=i.call(l),"[object Array]"===d)for(o+="[]",c=0,f=l.length;f>c;c++)n.unshift([o,l[c]]);else if("[object Object]"===d)for(a=r(l),c=0,f=a.length;f>c;c++)n.unshift([o+"["+a[c]+"]",l[a[c]]]);else p.push(g(o)+"="+g(l));!n.length&&h>s&&n.unshift([o=C[s++],t[o]])}return p.join("&")},rawencode:function(e){return encodeURIComponent(""+e).split("!").join("%21").split("'").join("%27").split("(").join("%28").split(")").join("%29").split("*").join("%2A")},rawdecode:function(e){return decodeURIComponent(""+e)},encode:function(t){return e.Util.Url.rawencode(t).split("%20").join("+")},decode:function(t){return e.Util.Url.rawdecode((""+t).split("+").join("%20"))}},Cookie:{create:function(e,t,n,r,i,o,l){var s=arguments.length;return{name:s>0?e:"",value:s>1?t:"",domain:s>2?n:"",path:s>3?r:"/",expires:s>4?i:new Date(Date.now()+31536e6),secure:s>5?!!o:!1,httponly:s>6?!!l:!1}},encode:function(e){if(e&&e.name){var t=String(e.name)+"="+String(e.value);return t+="; Domain="+String(e.domain),t+="; Path="+String(e.path),t+="; Expires="+String(e.expires),e.secure&&(t+="; Secure"),e.httponly&&(t+="; HttpOnly"),t}},decode:function(t){var n,r,i,o,l=e.Util.Cookie.create(),s=e.Const.COOKIE_RE,u=(""+t).split("; ");if(null!=(n=u.shift().match(s))){for(l.name=n[1],l.value=n[2],i=0,o=u.length;o>i;i++)n=u[i].match(s),null!=n&&n.length&&(r=n[1].toLowerCase(),"undefined"!=typeof l[r]&&(l[r]="string"==typeof n[2]?n[2]:!0));return"string"==typeof l.expires&&(l.expires=new Date(l.expires)),l}}},Header:{encode:function(t,n){var o="";if(!t)return n?n:o;var l,s,u,c,f,a=r(t),d=e.Const.CRLF;if(n){for(s=0,u=a.length;u>s;s++)if(l=a[s],"[object Array]"===i.call(t[l]))for(c=0,f=t[l].length;f>c;c++)n.setRequestHeader(l,t[l][c]);else n.setRequestHeader(l,t[l]);return n}for(s=0,u=a.length;u>s;s++)if(l=a[s],"[object Array]"===i.call(t[l]))for(c=0,f=t[l].length;f>c;c++)o+=d+String(t[l][c]);else o+=d+String(t[l]);return o},decode:function(t,n){var r,i,l,s,u={},c=null,f=e.Const.CRLF;if(t)for(n=!0===n,t=t.split(f),i=0,l=t.length;l>i;i++)s=t[i],r=s.split(":"),r.length>1?(c=o(r.shift()),n&&(c=c.toLowerCase()),u[c]=r.join(":")):c&&(u[c]+=f+r[0]);return u}}},e.Client=function l(t){var n=this;return n instanceof l?(n._config=t,n._event={},void(n.status=e.Client.CREATED)):new l(t)},e.Client.Impl={},e.Client.OPENED=2,e.Client.CLOSED=4,e.Client.PENDING=8,e.Client.CREATED=1,e.Client.DESTROYED=0,e.Client[t]={constructor:e.Client,status:e.Client.CREATED,_config:null,_event:null,dispose:function(){var t=this;return t.status=e.Client.DESTROYED,t._config=null,t._event=null,t},addEventListener:function(e,t){var r=this;return e&&t?(r._event[n](e)?r._event[e].push(t):r._event[e]=[t],r):r},removeEventListener:function(e,t){var r=this;if(!e||!r._event[n](e))return r;if(null==t)delete r._event[e];else{for(var i=r._event[e],o=i.length-1;o>=0;o--)i[o]===t&&t.splice(o,1);i.length||delete r._event[e]}return r},trigger:function(e,t){var r=this;if(!e||!r._event[n](e))return r;var i,o=r._event[e].slice(),l=o.length;for(i=0;l>i;i++)o[i](t);return r},open:function(){},close:function(){},send:function(){},listen:function(){}},e.Client[t].on=e.Client[t].addEventListener,e.Client[t].off=e.Client[t].removeEventListener,e}); - -/** -* RT -* unified client-side real-time communication using (xhr) polling / bosh / (web)sockets -* RT Poll Client -* -* @version: 0.1.0 -* https://github.com/foo123/RT -* -**/ -!function(e,t){"use strict";t("object"==typeof exports?require("./RT.js"):e.RT)}(this,function(e){"use strict";function t(){return window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP")}function l(e,l,n,r,o){if(e){try{e.abort()}catch(u){}e=null}return e=e||t(),e.open("POST",l,!0),e.responseType="text",e.setRequestHeader("Content-Type","text/plain; charset=utf8"),e.overrideMimeType("text/plain; charset=utf8"),n&&i.Header.encode(n,e),e.onload=function(){var t=200!==e.status,l=t?e.statusText:e.responseText;o&&o(t,l,e.getAllResponseHeaders(),e.status,e.statusText)},e.send(r),e}var n="prototype",r=(Object[n].toString,e.Client[n]),i=e.Util;return e.Client.Poll=function o(e){var t=this;return t instanceof o?(r.constructor.call(t,e),t._interval=e.pollInterval||500,t._timer=null,t._xhr=null,void(t._queue=[])):new o(e)},e.Client.Impl.poll=e.Client.Impl.ajax=e.Client.Impl.xhr=e.Client.Impl["ajax-poll"]=e.Client.Impl["xhr-poll"]=e.Client.Poll,e.Client.Poll[n]=Object.create(r),e.Client.Poll[n].constructor=e.Client.Poll,e.Client.Poll[n]._interval=500,e.Client.Poll[n]._timer=null,e.Client.Poll[n]._xhr=null,e.Client.Poll[n].dispose=function(){var e=this;if(e._timer&&clearTimeout(e._timer),e._xhr)try{e._xhr.abort()}catch(t){}return e._timer=null,e._xhr=null,e._interval=null,e._queue=null,r.dispose.call(e)},e.Client.Poll[n]._poll=function u(e){var t=this,n=t._headers||{};n["X-RT-POLL"]=1,t._headers={},t._xhr=l(t._xhr,e,n,t._queue.shift(),function(l,n,r){r=i.Header.decode(r,!0);r["x-rt-type"];l?t.trigger("error",n):n&&t.trigger(open?"open":"message",n),t._timer=setTimeout(function(){u(e)},t._interval)})},e.Client.Poll[n].send=function(e){var t=this;return t._queue.push(e),t},e.Client.Poll[n].listen=function(e){var t=this;return t._poll(e,!0),t},e.Client.Poll[n].open=function(e){var t=this,n=t._headers||{};n["X-RT-POLL"]=1,t._headers={},t._xhr=l(t._xhr,e,n,t._queue.shift(),function(l,n,r){r=i.Header.decode(r,!0);r["x-rt-type"];l?t.trigger("error",n):n&&t.trigger(open?"open":"message",n),t._timer=setTimeout(function(){poll(e)},t._interval)})},e.Client.Poll[n].close=function(){var e=this;return e._queue=[],e},e}); - -/** -* RT -* unified client-side real-time communication using (xhr) polling / bosh / (web)sockets -* RT WebSocket Client (w/ websocket shim) -* -* @version: 0.1.0 -* https://github.com/foo123/RT -* -**/ -!function(t,e){"use strict";e("object"==typeof exports?require("./RT.js"):t.RT)}(this,function(t){"use strict";function e(t){var e,n,i=document.getElementsByTagName("scripts"),o=i[i.length-1].src.split("/").slice(0,-1).join("/"),s=document.getElementsByTagName("head")[0];window.swfobject||(e=document.createElement("script"),e.setAttribute("type","text/javascript"),e.setAttribute("language","javascript"),e.setAttribute("src",o+"/websocket/swfobject.js"),s.appendChild(e)),window.WEB_SOCKET_SWF_LOCATION=o+"/websocket/WebSocketMain.swf",window.WEB_SOCKET_FORCE_FLASH=!1,window.WEB_SOCKET_DEBUG=!1,n=document.createElement("script"),n.setAttribute("type","text/javascript"),n.setAttribute("language","javascript"),n.setAttribute("src",o+"/websocket/web_socket.js"),n.onload=n.onreadystatechange=function(){("loaded"==n.readyState||"complete"==n.readyState)&&(n.onload=n.onreadystatechange=null,t&&t())},s.appendChild(n)}var n="prototype",i=(Object[n].toString,t.Client[n]),o=(t.Util,window.WebSocket||window.MozWebSocket);return o||e(function(){o=window.WebSocket}),t.Client.WS=function s(t){var e=this;return e instanceof s?(i.constructor.call(e,t),void(e._ws=null)):new s(t)},t.Client.Impl.ws=t.Client.Impl.websocket=t.Client.Impl["web-socket"]=t.Client.WS,t.Client.WS[n]=Object.create(i),t.Client.WS[n].constructor=t.Client.WS,t.Client.WS[n]._ws=null,t.Client.WS[n].dispose=function(){var t=this;return t._ws=null,i.dispose.call(t)},t}); - -/** -* RT -* unified client-side real-time communication using (xhr) polling / bosh / (web)sockets -* RT BOSH Client -* -* @version: 0.1.0 -* https://github.com/foo123/RT -* -**/ -!function(t,n){"use strict";n("object"==typeof exports?require("./RT.js"):t.RT)}(this,function(t){"use strict";{var n="prototype",e=(Object[n].toString,t.Client[n]);t.Util}return t.Client.BOSH=function r(t){var n=this;return n instanceof r?(e.constructor.call(n,t),void(n._xhr=null)):new r(t)},t.Client.Impl.bosh=t.Client.BOSH,t.Client.BOSH[n]=Object.create(e),t.Client.BOSH[n].constructor=t.Client.BOSH,t.Client.BOSH[n]._xhr=null,t.Client.BOSH[n].dispose=function(){var t=this;if(t._xhr)try{t._xhr.abort()}catch(n){}return t._xhr=null,e.dispose.call(t)},t}); diff --git a/src/RT.js b/src/RT.js index b95b598..c5b452d 100644 --- a/src/RT.js +++ b/src/RT.js @@ -11,26 +11,124 @@ if ( 'object' === typeof exports ) module.exports = factory( ); else - (root[name] = factory( )) && ('function' === typeof define) && define.amd && define(function( req ) { return root[name]; }); + (root[name] = factory( )) && ('function' === typeof define) && define.amd && define(function( ){ return root[name]; }); }(this, 'RT', function( ) { "use strict"; var PROTO = 'prototype', HAS = 'hasOwnProperty', - KEYS = Object.keys, toString = Object[PROTO].toString, - trim = String[PROTO].trim + KEYS = Object.keys, toString = Object[PROTO].toString, + CC = String.fromCharCode, + trim_re = /^\s+|\s+$/g, + trim = String[PROTO].trim ? function( s ) { return s.trim( ); } - : function( s ) { return s.replace(/^\s+|\s+$/g, ''); } + : function( s ) { return s.replace(trim_re, ''); }, + a2b_re = /[^A-Za-z0-9\+\/\=]/g, hex_re = /\x0d\x0a/g, + XHR = window.XMLHttpRequest + ? function( ){ return new XMLHttpRequest( ); } + : function( ){ return new ActiveXObject('Microsoft.XMLHTTP'); /* or ActiveXObject('Msxml2.XMLHTTP'); ??*/ }, + UUID = 0 ; -function RT( config ) +function RT( cfg ) { - config = config || { }; - var type = (config.type || 'default').toLowerCase( ); - return RT.Client.Impl[HAS](type) ? new RT.Client.Impl[type]( config ) : new RT.Client( config ); + cfg = cfg || { }; + var type = (cfg.rt_type || 'default').toLowerCase( ); + return RT.Client.Impl[HAS](type) ? new RT.Client.Impl[type]( cfg ) : new RT.Client( cfg ); } RT.VERSION = '0.1.0'; +RT.XHR = { + create: function( o, payload ) { + o = o || {}; + var xhr = XHR( ), trigger_abort = true; + xhr._abort = xhr.abort; + xhr._aborted = false; + xhr.abort = function( and_trigger ){ + if ( xhr._aborted ) return; + if ( false === and_trigger ) trigger_abort = false; + try{ xhr._abort( ); }catch(e){ } + xhr._aborted = true; + trigger_abort = true; + }; + xhr.__headers__ = null; + xhr.responseHeader = function( key ) { + if ( (null == key) || (4/*DONE*/ !== xhr.readyState) ) return null; + var headers = xhr.getAllResponseHeaders( ) || ''; + if ( null == xhr.__headers__ ) xhr.__headers__ = RT.Util.Header.decode( headers ); + return xhr.__headers__[HAS](key) ? xhr.__headers__[key] : null; + }; + xhr.responseHeaders = function( decoded ) { + if ( 4/*DONE*/ !== xhr.readyState ) return null; + var headers = xhr.getAllResponseHeaders( ) || ''; + if ( null == xhr.__headers__ ) xhr.__headers__ = RT.Util.Header.decode( headers ); + return true===decoded ? xhr.__headers__ : headers; + }; + if ( !o.url ) return xhr; + xhr.open( o.method||'GET', o.url, !o.sync ); + xhr.responseType = o.responseType || 'text'; + if ( o.headers ) RT.Util.Header.encode( o.headers, xhr ); + if ( o.mimeType ) xhr.overrideMimeType( o.mimeType ); + //xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf8'); + //xhr.overrideMimeType('text/plain; charset=utf8'); + xhr.timeout = o.timeout || 30000; // 30 secs default timeout + if ( o.onProgress ) + { + xhr.onprogress = function( ) { + o.onProgress( xhr ); + }; + } + if ( o.onLoadStart ) + { + xhr.onloadstart = function( ) { + o.onLoadStart( xhr ); + }; + } + if ( o.onLoadEnd ) + { + xhr.onloadend = function( ) { + o.onLoadEnd( xhr ); + }; + } + if ( !o.sync && o.onStateChange ) + { + xhr.onreadystatechange = function( ) { + o.onStateChange( xhr ); + }; + } + xhr.onload = function( ) { + if ( (4/*DONE*/ === xhr.readyState) ) + { + if ( 200 === xhr.status ) + { + if ( o.onComplete ) o.onComplete( xhr ); + } + else + { + if ( o.onRequestError ) o.onRequestError( xhr ); + else if ( o.onError ) o.onError( xhr ); + } + } + }; + xhr.onabort = function( ) { + if ( trigger_abort && o.onAbort ) o.onAbort( xhr ); + }; + xhr.onerror = function( ) { + if ( o.onError ) o.onError( xhr ); + }; + xhr.ontimeout = function( ) { + if ( o.onTimeout ) o.onTimeout( xhr ); + }; + if ( arguments.length > 1 ) xhr.send( payload ); + return xhr; + } +}; + +RT.UUID = function( PREFIX, SUFFIX ) { + return (PREFIX||'') + (++UUID) + '_' + (Date.now()) + '_' + Math.floor((1000*Math.random())) + (SUFFIX||''); +}; + RT.Const = { + BASE64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", CRLF: "\r\n", CRLF_RE: /(\r\n)|\r|\n/g, COOKIE_RE: /([^=]+)(?:=(.*))?/ @@ -41,9 +139,104 @@ RT.Util = { trim: trim }, + // adapted from jquery.base64 + Utf8: { + encode: function( string ) { + string = string.replace(hex_re, "\x0a"); + var output = '', n, l, c; + for (n=0,l=string.length; n 127) && (c < 2048) ) + output += CC((c >> 6) | 192) + CC((c & 63) | 128); + else + output += CC((c >> 12) | 224) + CC(((c >> 6) & 63) | 128) + CC((c & 63) | 128); + } + return output; + }, + decode: function( input ) { + var string = '', i = 0, c = c1 = c2 = 0, l = input.length; + while (i < l) + { + c = input.charCodeAt(i); + if ( c < 128 ) + { + string += CC(c); + i++; + } + else if ( (c > 191) && (c < 224) ) + { + c2 = input.charCodeAt(i+1); + string += CC(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else + { + c2 = input.charCodeAt(i+1); + c3 = input.charCodeAt(i+2); + string += CC(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; + } + }, + + // adapted from jquery.base64 Base64: { - encode: btoa, - decode: atob + encode: function( input ) { + input = RT.Util.Utf8.encode( input ); + var output = '', chr1, chr2, chr3, + enc1, enc2, enc3, enc4, i = 0, l = input.length, + keyString = RT.Const.BASE64; + while ( i < l ) + { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + if ( isNaN(chr2) ) + { + enc3 = enc4 = 64; + } + else if ( isNaN(chr3) ) + { + enc4 = 64; + } + output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4); + } + return output; + }, + decode: function( input ) { + input = input.replace(a2b_re, ''); + var output = '', chr1, chr2, chr3, enc1, enc2, enc3, enc4, i = 0, l = input.length; + while ( i < l ) + { + enc1 = keyString.indexOf(input.charAt(i++)); + enc2 = keyString.indexOf(input.charAt(i++)); + enc3 = keyString.indexOf(input.charAt(i++)); + enc4 = keyString.indexOf(input.charAt(i++)); + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + output = output + CC(chr1); + if ( 64 != enc3 ) + { + output += CC(chr2); + } + if ( 64 != enc4 ) + { + output += CC(chr3); + } + } + output = RT.Util.Utf8.decode( output ); + return output; + } }, Json: { @@ -130,7 +323,7 @@ RT.Util = { var cookie = RT.Util.Cookie.create( ), /* parse value/name */ equalsSplit = RT.Const.COOKIE_RE, - cookieParams = ("" + cookieString).split("; "), + cookieParams = String(cookieString).split('; '), cookieParam, attr, i, len ; if ( null == (cookieParam = cookieParams.shift().match(equalsSplit)) ) return; @@ -145,7 +338,7 @@ RT.Util = { if ( null != cookieParam && cookieParam.length) { attr = cookieParam[1].toLowerCase(); - if ( 'undefined' !== typeof cookie[attr] ) + if ( cookie[HAS](attr) ) cookie[attr] = 'string' === typeof cookieParam[2] ? cookieParam[2] : true; } } @@ -156,11 +349,21 @@ RT.Util = { }, Header: { - encode: function( headers, xhr ) { + encode: function( headers, xmlHttpRequest, httpServerResponse ) { var header = ''; if ( !headers ) return xhr ? xhr : header; var keys = KEYS(headers), key, i, l, k, kl, CRLF = RT.Const.CRLF; - if ( xhr ) + if ( httpServerResponse ) + { + for(i=0,l=keys.length; i 1 ) + { + cfg[key] = val; + return self; + } + else + { + return cfg[key]; + } + } + } + ,on: function( event, handler, once ){ var self = this; if ( !event || !handler ) return self; - if ( !self._event[HAS](event) ) self._event[event] = [handler]; - else self._event[event].push(handler); + if ( !self.$event$[HAS](event) ) self.$event$[event] = [[handler, true===once, 0]]; + else self.$event$[event].push([handler, true===once, 0]); return self; } - ,removeEventListener: function( event, handler ){ + ,one: function( event, handler ){ + return this.on( event, handler, true ); + } + ,off: function( event, handler ){ var self = this; - if ( !event || !self._event[HAS](event) ) return self; + if ( !event || !self.$event$[HAS](event) ) return self; if ( null == handler ) { - delete self._event[event]; + delete self.$event$[event]; } else { - for(var handle=self._event[event],i=handle.length-1; i>=0; i--) - if ( handle[i] === handler ) handler.splice(i, 1); - if ( !handle.length ) delete self._event[event]; + for(var handle=self.$event$[event],i=handle.length-1; i>=0; i--) + if ( handle[i][0] === handler ) handler.splice(i, 1); + if ( !handle.length ) delete self.$event$[event]; } return self; } - ,trigger: function( event, data ){ + ,emit: function( event, data ){ var self = this; - if ( !event || !self._event[HAS](event) ) return self; - var handler = self._event[event].slice( ), i, l = handler.length; - for(i=0; i=0; i--) handler.splice( rem[i], 1 ); + if ( !handler.length ) delete self.$event$[event]; return self; } - ,open: function( ){ } - ,close: function( ){ } - ,send: function( ){ } - ,listen: function( ){ } + ,abort: function( trigger ){ + return true === trigger ? this.emit('abort') : this; + } + ,open: function( ){ + return this.emit('open'); + } + ,close: function( ){ + return this.abort( false ).emit('close'); + } + ,send: function( payload ){ + return this; + } + ,listen: function( ){ + return this; + } }; // aliases -RT.Client[PROTO].on = RT.Client[PROTO].addEventListener; -RT.Client[PROTO].off = RT.Client[PROTO].removeEventListener; +RT.Client[PROTO].addEventListener = RT.Client[PROTO].on; +RT.Client[PROTO].removeEventListener = RT.Client[PROTO].off; +RT.Client[PROTO].trigger = RT.Client[PROTO].dispatchEvent = RT.Client[PROTO].emit; // export it return RT; diff --git a/src/websocket/WebSocketMain.swf b/src/lib/ws/WebSocketMain.swf similarity index 100% rename from src/websocket/WebSocketMain.swf rename to src/lib/ws/WebSocketMain.swf diff --git a/src/websocket/swfobject.js b/src/lib/ws/swfobject.js similarity index 100% rename from src/websocket/swfobject.js rename to src/lib/ws/swfobject.js diff --git a/src/websocket/web_socket.dev.js b/src/lib/ws/web_socket.dev.js similarity index 100% rename from src/websocket/web_socket.dev.js rename to src/lib/ws/web_socket.dev.js diff --git a/src/websocket/web_socket.js b/src/lib/ws/web_socket.js similarity index 100% rename from src/websocket/web_socket.js rename to src/lib/ws/web_socket.js diff --git a/test/chat.html b/test/chat.html new file mode 100644 index 0000000..6ab781c --- /dev/null +++ b/test/chat.html @@ -0,0 +1,92 @@ + + + + + RT Chat Test + + + + + +

Simple Chat Test

+
+
+ + + + + + \ No newline at end of file diff --git a/test/chat.php b/test/chat.php new file mode 100644 index 0000000..fd6fdcf --- /dev/null +++ b/test/chat.php @@ -0,0 +1,45 @@ +array()); +$HEADER = (array)getallheaders(); + +$timestamp = time( ); +$channel = empty($_GET['channel']) ? 'default' : $_GET['channel']; +if ( !isset($_SESSION['channel'][$channel]) ) $_SESSION['channel'][$channel] = array( ); + +if ( !empty($HEADER['X-RT-Send']) && !empty($_POST['rt_payload']) ) +{ + $msgs = $_POST['rt_payload']; + if ( !empty($HEADER['X-RT-Message']) ) $msgs = explode($HEADER['X-RT-Message'], $msgs); + else $msgs = (array)$msgs; + + foreach($msgs as $msg) + { + $msg = json_decode($msg, true); + if ( empty($msg['user']) ) continue; + $_SESSION['channel'][$channel][] = array( + 'user' => $msg['user'], + 'message' => $msg['message'], + 'timestamp' => $timestamp + ); + } +} +header('X-RT-Timestamp: '.($timestamp)); +if ( !empty($HEADER['X-RT-Receive']) ) +{ + $receive_timestamp = empty($HEADER['X-RT-Timestamp']) ? 0 : (int)$HEADER['X-RT-Timestamp']; + $rt_msg = '----------------------' . '_rt_msg_' . $timestamp . '_' . mt_rand(1,1000); + $msgs = array(); + foreach($_SESSION['channel'][$channel] as $msg) + { + if ( $msg['timestamp'] > $receive_timestamp ) + $msgs[] = json_encode($msg); + } + if ( !empty($msgs) ) + { + header('X-RT-Message: '.$rt_msg); + echo implode($rt_msg, $msgs); + } +} diff --git a/test/post.html b/test/post.html new file mode 100644 index 0000000..0458d29 --- /dev/null +++ b/test/post.html @@ -0,0 +1,41 @@ + + + + + RT XHR Post Test + + + + +
+

+
+

+
+
\ No newline at end of file
diff --git a/test/post.php b/test/post.php
new file mode 100644
index 0000000..e22d1fc
--- /dev/null
+++ b/test/post.php
@@ -0,0 +1,11 @@
+