websocket-rack-noodles 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +61 -0
- data/Gemfile +7 -0
- data/README.md +196 -0
- data/Rakefile +11 -0
- data/example/example.ru +33 -0
- data/example/html/FABridge.js +604 -0
- data/example/html/WebSocketMain.swf +0 -0
- data/example/html/index.html +76 -0
- data/example/html/swfobject.js +4 -0
- data/example/html/web_socket.js +388 -0
- data/lib/rack/websocket/application.rb +56 -0
- data/lib/rack/websocket/extensions/common.rb +67 -0
- data/lib/rack/websocket/extensions/thin/connection.rb +24 -0
- data/lib/rack/websocket/extensions/thin.rb +16 -0
- data/lib/rack/websocket/extensions.rb +14 -0
- data/lib/rack/websocket/handler/base/connection.rb +83 -0
- data/lib/rack/websocket/handler/base.rb +68 -0
- data/lib/rack/websocket/handler/stub.rb +15 -0
- data/lib/rack/websocket/handler/thin.rb +58 -0
- data/lib/rack/websocket/handler.rb +21 -0
- data/lib/rack/websocket/version.rb +5 -0
- data/lib/rack/websocket.rb +14 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/all_drafts.rb +44 -0
- data/spec/support/all_handlers.rb +80 -0
- data/spec/support/masked_messages.rb +9 -0
- data/spec/support/requests.rb +249 -0
- data/spec/thin_spec.rb +48 -0
- data/websocket-rack.gemspec +24 -0
- metadata +137 -0
@@ -0,0 +1,4 @@
|
|
1
|
+
/* SWFObject v2.2 <http://code.google.com/p/swfobject/>
|
2
|
+
is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
|
3
|
+
*/
|
4
|
+
var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}();
|
@@ -0,0 +1,388 @@
|
|
1
|
+
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
2
|
+
// License: New BSD License
|
3
|
+
// Reference: http://dev.w3.org/html5/websockets/
|
4
|
+
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
|
5
|
+
|
6
|
+
(function() {
|
7
|
+
|
8
|
+
if (window.WebSocket) return;
|
9
|
+
|
10
|
+
var console = window.console;
|
11
|
+
if (!console) console = {log: function(){ }, error: function(){ }};
|
12
|
+
|
13
|
+
if (!swfobject.hasFlashPlayerVersion("9.0.0")) {
|
14
|
+
console.error("Flash Player is not installed.");
|
15
|
+
return;
|
16
|
+
}
|
17
|
+
if (location.protocol == "file:") {
|
18
|
+
console.error(
|
19
|
+
"WARNING: web-socket-js doesn't work in file:///... URL " +
|
20
|
+
"unless you set Flash Security Settings properly. " +
|
21
|
+
"Open the page via Web server i.e. http://...");
|
22
|
+
}
|
23
|
+
|
24
|
+
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
|
25
|
+
var self = this;
|
26
|
+
self.readyState = WebSocket.CONNECTING;
|
27
|
+
self.bufferedAmount = 0;
|
28
|
+
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
|
29
|
+
// Otherwise, when onopen fires immediately, onopen is called before it is set.
|
30
|
+
setTimeout(function() {
|
31
|
+
WebSocket.__addTask(function() {
|
32
|
+
self.__createFlash(url, protocol, proxyHost, proxyPort, headers);
|
33
|
+
});
|
34
|
+
}, 1);
|
35
|
+
}
|
36
|
+
|
37
|
+
WebSocket.prototype.__createFlash = function(url, protocol, proxyHost, proxyPort, headers) {
|
38
|
+
var self = this;
|
39
|
+
self.__flash =
|
40
|
+
WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null);
|
41
|
+
|
42
|
+
self.__flash.addEventListener("open", function(fe) {
|
43
|
+
try {
|
44
|
+
self.readyState = self.__flash.getReadyState();
|
45
|
+
if (self.__timer) clearInterval(self.__timer);
|
46
|
+
if (window.opera) {
|
47
|
+
// Workaround for weird behavior of Opera which sometimes drops events.
|
48
|
+
self.__timer = setInterval(function () {
|
49
|
+
self.__handleMessages();
|
50
|
+
}, 500);
|
51
|
+
}
|
52
|
+
if (self.onopen) self.onopen();
|
53
|
+
} catch (e) {
|
54
|
+
console.error(e.toString());
|
55
|
+
}
|
56
|
+
});
|
57
|
+
|
58
|
+
self.__flash.addEventListener("close", function(fe) {
|
59
|
+
try {
|
60
|
+
self.readyState = self.__flash.getReadyState();
|
61
|
+
if (self.__timer) clearInterval(self.__timer);
|
62
|
+
if (self.onclose) self.onclose();
|
63
|
+
} catch (e) {
|
64
|
+
console.error(e.toString());
|
65
|
+
}
|
66
|
+
});
|
67
|
+
|
68
|
+
self.__flash.addEventListener("message", function() {
|
69
|
+
try {
|
70
|
+
self.__handleMessages();
|
71
|
+
} catch (e) {
|
72
|
+
console.error(e.toString());
|
73
|
+
}
|
74
|
+
});
|
75
|
+
|
76
|
+
self.__flash.addEventListener("error", function(fe) {
|
77
|
+
try {
|
78
|
+
if (self.__timer) clearInterval(self.__timer);
|
79
|
+
if (self.onerror) self.onerror();
|
80
|
+
} catch (e) {
|
81
|
+
console.error(e.toString());
|
82
|
+
}
|
83
|
+
});
|
84
|
+
|
85
|
+
self.__flash.addEventListener("stateChange", function(fe) {
|
86
|
+
try {
|
87
|
+
self.readyState = self.__flash.getReadyState();
|
88
|
+
self.bufferedAmount = fe.getBufferedAmount();
|
89
|
+
} catch (e) {
|
90
|
+
console.error(e.toString());
|
91
|
+
}
|
92
|
+
});
|
93
|
+
|
94
|
+
//console.log("[WebSocket] Flash object is ready");
|
95
|
+
};
|
96
|
+
|
97
|
+
WebSocket.prototype.send = function(data) {
|
98
|
+
if (this.__flash) {
|
99
|
+
this.readyState = this.__flash.getReadyState();
|
100
|
+
}
|
101
|
+
if (!this.__flash || this.readyState == WebSocket.CONNECTING) {
|
102
|
+
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
|
103
|
+
}
|
104
|
+
// We use encodeURIComponent() here, because FABridge doesn't work if
|
105
|
+
// the argument includes some characters. We don't use escape() here
|
106
|
+
// because of this:
|
107
|
+
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
|
108
|
+
// But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
|
109
|
+
// preserve all Unicode characters either e.g. "\uffff" in Firefox.
|
110
|
+
var result = this.__flash.send(encodeURIComponent(data));
|
111
|
+
if (result < 0) { // success
|
112
|
+
return true;
|
113
|
+
} else {
|
114
|
+
this.bufferedAmount = result;
|
115
|
+
return false;
|
116
|
+
}
|
117
|
+
};
|
118
|
+
|
119
|
+
WebSocket.prototype.close = function() {
|
120
|
+
var self = this;
|
121
|
+
if (!self.__flash) return;
|
122
|
+
self.readyState = self.__flash.getReadyState();
|
123
|
+
if (self.readyState == WebSocket.CLOSED || self.readyState == WebSocket.CLOSING) return;
|
124
|
+
self.__flash.close();
|
125
|
+
// Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events
|
126
|
+
// which causes weird error:
|
127
|
+
// > You are trying to call recursively into the Flash Player which is not allowed.
|
128
|
+
self.readyState = WebSocket.CLOSED;
|
129
|
+
if (self.__timer) clearInterval(self.__timer);
|
130
|
+
if (self.onclose) {
|
131
|
+
// Make it asynchronous so that it looks more like an actual
|
132
|
+
// close event
|
133
|
+
setTimeout(self.onclose, 1);
|
134
|
+
}
|
135
|
+
};
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
139
|
+
*
|
140
|
+
* @param {string} type
|
141
|
+
* @param {function} listener
|
142
|
+
* @param {boolean} useCapture !NB Not implemented yet
|
143
|
+
* @return void
|
144
|
+
*/
|
145
|
+
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
|
146
|
+
if (!('__events' in this)) {
|
147
|
+
this.__events = {};
|
148
|
+
}
|
149
|
+
if (!(type in this.__events)) {
|
150
|
+
this.__events[type] = [];
|
151
|
+
if ('function' == typeof this['on' + type]) {
|
152
|
+
this.__events[type].defaultHandler = this['on' + type];
|
153
|
+
this['on' + type] = this.__createEventHandler(this, type);
|
154
|
+
}
|
155
|
+
}
|
156
|
+
this.__events[type].push(listener);
|
157
|
+
};
|
158
|
+
|
159
|
+
/**
|
160
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
161
|
+
*
|
162
|
+
* @param {string} type
|
163
|
+
* @param {function} listener
|
164
|
+
* @param {boolean} useCapture NB! Not implemented yet
|
165
|
+
* @return void
|
166
|
+
*/
|
167
|
+
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
|
168
|
+
if (!('__events' in this)) {
|
169
|
+
this.__events = {};
|
170
|
+
}
|
171
|
+
if (!(type in this.__events)) return;
|
172
|
+
for (var i = this.__events.length; i > -1; --i) {
|
173
|
+
if (listener === this.__events[type][i]) {
|
174
|
+
this.__events[type].splice(i, 1);
|
175
|
+
break;
|
176
|
+
}
|
177
|
+
}
|
178
|
+
};
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
182
|
+
*
|
183
|
+
* @param {WebSocketEvent} event
|
184
|
+
* @return void
|
185
|
+
*/
|
186
|
+
WebSocket.prototype.dispatchEvent = function(event) {
|
187
|
+
if (!('__events' in this)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
|
188
|
+
if (!(event.type in this.__events)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
|
189
|
+
|
190
|
+
for (var i = 0, l = this.__events[event.type].length; i < l; ++ i) {
|
191
|
+
this.__events[event.type][i](event);
|
192
|
+
if (event.cancelBubble) break;
|
193
|
+
}
|
194
|
+
|
195
|
+
if (false !== event.returnValue &&
|
196
|
+
'function' == typeof this.__events[event.type].defaultHandler)
|
197
|
+
{
|
198
|
+
this.__events[event.type].defaultHandler(event);
|
199
|
+
}
|
200
|
+
};
|
201
|
+
|
202
|
+
WebSocket.prototype.__handleMessages = function() {
|
203
|
+
// Gets data using readSocketData() instead of getting it from event object
|
204
|
+
// of Flash event. This is to make sure to keep message order.
|
205
|
+
// It seems sometimes Flash events don't arrive in the same order as they are sent.
|
206
|
+
var arr = this.__flash.readSocketData();
|
207
|
+
for (var i = 0; i < arr.length; i++) {
|
208
|
+
var data = decodeURIComponent(arr[i]);
|
209
|
+
try {
|
210
|
+
if (this.onmessage) {
|
211
|
+
var e;
|
212
|
+
if (window.MessageEvent && !window.opera) {
|
213
|
+
e = document.createEvent("MessageEvent");
|
214
|
+
e.initMessageEvent("message", false, false, data, null, null, window, null);
|
215
|
+
} else { // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes
|
216
|
+
e = {data: data};
|
217
|
+
}
|
218
|
+
this.onmessage(e);
|
219
|
+
}
|
220
|
+
} catch (e) {
|
221
|
+
console.error(e.toString());
|
222
|
+
}
|
223
|
+
}
|
224
|
+
};
|
225
|
+
|
226
|
+
/**
|
227
|
+
* @param {object} object
|
228
|
+
* @param {string} type
|
229
|
+
*/
|
230
|
+
WebSocket.prototype.__createEventHandler = function(object, type) {
|
231
|
+
return function(data) {
|
232
|
+
var event = new WebSocketEvent();
|
233
|
+
event.initEvent(type, true, true);
|
234
|
+
event.target = event.currentTarget = object;
|
235
|
+
for (var key in data) {
|
236
|
+
event[key] = data[key];
|
237
|
+
}
|
238
|
+
object.dispatchEvent(event, arguments);
|
239
|
+
};
|
240
|
+
}
|
241
|
+
|
242
|
+
/**
|
243
|
+
* Basic implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface">DOM 2 EventInterface</a>}
|
244
|
+
*
|
245
|
+
* @class
|
246
|
+
* @constructor
|
247
|
+
*/
|
248
|
+
function WebSocketEvent(){}
|
249
|
+
|
250
|
+
/**
|
251
|
+
*
|
252
|
+
* @type boolean
|
253
|
+
*/
|
254
|
+
WebSocketEvent.prototype.cancelable = true;
|
255
|
+
|
256
|
+
/**
|
257
|
+
*
|
258
|
+
* @type boolean
|
259
|
+
*/
|
260
|
+
WebSocketEvent.prototype.cancelBubble = false;
|
261
|
+
|
262
|
+
/**
|
263
|
+
*
|
264
|
+
* @return void
|
265
|
+
*/
|
266
|
+
WebSocketEvent.prototype.preventDefault = function() {
|
267
|
+
if (this.cancelable) {
|
268
|
+
this.returnValue = false;
|
269
|
+
}
|
270
|
+
};
|
271
|
+
|
272
|
+
/**
|
273
|
+
*
|
274
|
+
* @return void
|
275
|
+
*/
|
276
|
+
WebSocketEvent.prototype.stopPropagation = function() {
|
277
|
+
this.cancelBubble = true;
|
278
|
+
};
|
279
|
+
|
280
|
+
/**
|
281
|
+
*
|
282
|
+
* @param {string} eventTypeArg
|
283
|
+
* @param {boolean} canBubbleArg
|
284
|
+
* @param {boolean} cancelableArg
|
285
|
+
* @return void
|
286
|
+
*/
|
287
|
+
WebSocketEvent.prototype.initEvent = function(eventTypeArg, canBubbleArg, cancelableArg) {
|
288
|
+
this.type = eventTypeArg;
|
289
|
+
this.cancelable = cancelableArg;
|
290
|
+
this.timeStamp = new Date();
|
291
|
+
};
|
292
|
+
|
293
|
+
|
294
|
+
WebSocket.CONNECTING = 0;
|
295
|
+
WebSocket.OPEN = 1;
|
296
|
+
WebSocket.CLOSING = 2;
|
297
|
+
WebSocket.CLOSED = 3;
|
298
|
+
|
299
|
+
WebSocket.__tasks = [];
|
300
|
+
|
301
|
+
WebSocket.__initialize = function() {
|
302
|
+
if (WebSocket.__swfLocation) {
|
303
|
+
// For backword compatibility.
|
304
|
+
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
|
305
|
+
}
|
306
|
+
if (!window.WEB_SOCKET_SWF_LOCATION) {
|
307
|
+
console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
308
|
+
return;
|
309
|
+
}
|
310
|
+
var container = document.createElement("div");
|
311
|
+
container.id = "webSocketContainer";
|
312
|
+
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
|
313
|
+
// Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
|
314
|
+
// But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
|
315
|
+
// Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
|
316
|
+
// the best we can do as far as we know now.
|
317
|
+
container.style.position = "absolute";
|
318
|
+
if (WebSocket.__isFlashLite()) {
|
319
|
+
container.style.left = "0px";
|
320
|
+
container.style.top = "0px";
|
321
|
+
} else {
|
322
|
+
container.style.left = "-100px";
|
323
|
+
container.style.top = "-100px";
|
324
|
+
}
|
325
|
+
var holder = document.createElement("div");
|
326
|
+
holder.id = "webSocketFlash";
|
327
|
+
container.appendChild(holder);
|
328
|
+
document.body.appendChild(container);
|
329
|
+
// See this article for hasPriority:
|
330
|
+
// http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
|
331
|
+
swfobject.embedSWF(
|
332
|
+
WEB_SOCKET_SWF_LOCATION, "webSocketFlash",
|
333
|
+
"1" /* width */, "1" /* height */, "9.0.0" /* SWF version */,
|
334
|
+
null, {bridgeName: "webSocket"}, {hasPriority: true, allowScriptAccess: "always"}, null,
|
335
|
+
function(e) {
|
336
|
+
if (!e.success) console.error("[WebSocket] swfobject.embedSWF failed");
|
337
|
+
}
|
338
|
+
);
|
339
|
+
FABridge.addInitializationCallback("webSocket", function() {
|
340
|
+
try {
|
341
|
+
//console.log("[WebSocket] FABridge initializad");
|
342
|
+
WebSocket.__flash = FABridge.webSocket.root();
|
343
|
+
WebSocket.__flash.setCallerUrl(location.href);
|
344
|
+
WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
|
345
|
+
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
|
346
|
+
WebSocket.__tasks[i]();
|
347
|
+
}
|
348
|
+
WebSocket.__tasks = [];
|
349
|
+
} catch (e) {
|
350
|
+
console.error("[WebSocket] " + e.toString());
|
351
|
+
}
|
352
|
+
});
|
353
|
+
};
|
354
|
+
|
355
|
+
WebSocket.__addTask = function(task) {
|
356
|
+
if (WebSocket.__flash) {
|
357
|
+
task();
|
358
|
+
} else {
|
359
|
+
WebSocket.__tasks.push(task);
|
360
|
+
}
|
361
|
+
};
|
362
|
+
|
363
|
+
WebSocket.__isFlashLite = function() {
|
364
|
+
if (!window.navigator || !window.navigator.mimeTypes) return false;
|
365
|
+
var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
|
366
|
+
if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) return false;
|
367
|
+
return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
|
368
|
+
};
|
369
|
+
|
370
|
+
// called from Flash
|
371
|
+
window.webSocketLog = function(message) {
|
372
|
+
console.log(decodeURIComponent(message));
|
373
|
+
};
|
374
|
+
|
375
|
+
// called from Flash
|
376
|
+
window.webSocketError = function(message) {
|
377
|
+
console.error(decodeURIComponent(message));
|
378
|
+
};
|
379
|
+
|
380
|
+
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
|
381
|
+
if (window.addEventListener) {
|
382
|
+
window.addEventListener("load", WebSocket.__initialize, false);
|
383
|
+
} else {
|
384
|
+
window.attachEvent("onload", WebSocket.__initialize);
|
385
|
+
}
|
386
|
+
}
|
387
|
+
|
388
|
+
})();
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
class Application
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = {}
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :websocket_handler
|
9
|
+
end
|
10
|
+
|
11
|
+
# Standard WebSocket calls
|
12
|
+
def on_open(env); end
|
13
|
+
def on_message(env, msg); end
|
14
|
+
def on_close(env); end
|
15
|
+
def on_error(env, error); end
|
16
|
+
|
17
|
+
# Initializer
|
18
|
+
def initialize(options = {})
|
19
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Detect handler and duplicate it's instance
|
23
|
+
def call(env)
|
24
|
+
detect_handler(env)
|
25
|
+
dup._call(env)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Forward call to duplicated handler
|
29
|
+
def _call(env)
|
30
|
+
websocket_handler.call(env)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Forward all missing methods to handler
|
34
|
+
def method_missing(sym, *args, &block)
|
35
|
+
if websocket_handler && websocket_handler.respond_to?(sym)
|
36
|
+
websocket_handler.send sym, *args, &block
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Detect handler
|
45
|
+
def detect_handler(env)
|
46
|
+
self.class.websocket_handler ||= Handler.detect(env)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create and cache handler for current server
|
50
|
+
def websocket_handler
|
51
|
+
@websocket_handler ||= self.class.websocket_handler.new(self, @options || {})
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
module Extensions
|
4
|
+
module Common
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
alias :receive_data_without_websocket :receive_data
|
9
|
+
alias :receive_data :receive_data_with_websocket
|
10
|
+
|
11
|
+
alias :unbind_without_websocket :unbind
|
12
|
+
alias :unbind :unbind_with_websocket
|
13
|
+
|
14
|
+
alias :receive_data_without_flash_policy_file :receive_data
|
15
|
+
alias :receive_data :receive_data_with_flash_policy_file
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :websocket
|
20
|
+
|
21
|
+
# Is this connection WebSocket?
|
22
|
+
def websocket?
|
23
|
+
!self.websocket.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Skip default receive_data if this is
|
27
|
+
# WebSocket connection
|
28
|
+
def receive_data_with_websocket(data)
|
29
|
+
if self.websocket?
|
30
|
+
self.websocket.receive_data(data)
|
31
|
+
else
|
32
|
+
receive_data_without_websocket(data)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Skip standard unbind it this is
|
37
|
+
# WebSocket connection
|
38
|
+
def unbind_with_websocket
|
39
|
+
if self.websocket?
|
40
|
+
self.websocket.unbind
|
41
|
+
else
|
42
|
+
unbind_without_websocket
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Send flash policy file if requested
|
47
|
+
def receive_data_with_flash_policy_file(data)
|
48
|
+
# thin require data to be proper http request - in it's not
|
49
|
+
# then @request.parse raises exception and data isn't parsed
|
50
|
+
# by futher methods. Here we only check if it is flash
|
51
|
+
# policy file request ("<policy-file-request/>\000") and
|
52
|
+
# if so then flash policy file is returned. if not then
|
53
|
+
# rest of request is handled.
|
54
|
+
if (data == "<policy-file-request/>\000")
|
55
|
+
file = '<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>'
|
56
|
+
# ignore errors - we will close this anyway
|
57
|
+
send_data(file) rescue nil
|
58
|
+
close_connection_after_writing
|
59
|
+
else
|
60
|
+
receive_data_without_flash_policy_file(data)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
module Extensions
|
4
|
+
module Thin
|
5
|
+
module Connection
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
alias :pre_process_without_websocket :pre_process
|
10
|
+
alias :pre_process :pre_process_with_websocket
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set 'async.connection' Rack env
|
15
|
+
def pre_process_with_websocket
|
16
|
+
@request.env['async.connection'] = self
|
17
|
+
pre_process_without_websocket
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
module Extensions
|
4
|
+
module Thin
|
5
|
+
|
6
|
+
autoload :Connection, "#{::File.dirname(__FILE__)}/thin/connection"
|
7
|
+
|
8
|
+
def self.apply!
|
9
|
+
::Thin::Connection.send(:include, ::Rack::WebSocket::Extensions::Common)
|
10
|
+
::Thin::Connection.send(:include, ::Rack::WebSocket::Extensions::Thin::Connection)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rack
|
2
|
+
module WebSocket
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
autoload :Common, "#{ROOT_PATH}/websocket/extensions/common"
|
6
|
+
autoload :Thin, "#{ROOT_PATH}/websocket/extensions/thin"
|
7
|
+
|
8
|
+
def self.apply!
|
9
|
+
Thin.apply! if defined?(::Thin)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module WebSocket
|
5
|
+
module Handler
|
6
|
+
class Base
|
7
|
+
class Connection < ::EventMachine::WebSocket::Connection
|
8
|
+
|
9
|
+
#########################
|
10
|
+
### EventMachine part ###
|
11
|
+
#########################
|
12
|
+
|
13
|
+
# Overwrite new from EventMachine
|
14
|
+
# we need to skip standard procedure called
|
15
|
+
# when socket is created - this is just a stub
|
16
|
+
def self.new(*args)
|
17
|
+
instance = allocate
|
18
|
+
instance.__send__(:initialize, *args)
|
19
|
+
instance
|
20
|
+
end
|
21
|
+
|
22
|
+
# Overwrite send_data from EventMachine
|
23
|
+
# delegate send_data to rack server
|
24
|
+
def send_data(*args)
|
25
|
+
EM.next_tick do
|
26
|
+
@socket.send_data(*args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Overwrite close_connection from EventMachine
|
31
|
+
# delegate close_connection to rack server
|
32
|
+
def close_connection(*args)
|
33
|
+
EM.next_tick do
|
34
|
+
@socket.close_connection(*args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#########################
|
39
|
+
### EM-WebSocket part ###
|
40
|
+
#########################
|
41
|
+
|
42
|
+
# Overwrite triggers from em-websocket
|
43
|
+
def trigger_on_message(msg); @app.on_message(msg); end
|
44
|
+
def trigger_on_open; @app.on_open; end
|
45
|
+
def trigger_on_close; @app.on_close; end
|
46
|
+
def trigger_on_error(error); @app.on_error(error); true; end
|
47
|
+
|
48
|
+
# Overwrite initialize from em-websocket
|
49
|
+
# set all standard options and disable
|
50
|
+
# EM connection inactivity timeout
|
51
|
+
def initialize(app, socket, options = {})
|
52
|
+
@app = app
|
53
|
+
@socket = socket
|
54
|
+
@options = options
|
55
|
+
@debug = options[:debug] || false
|
56
|
+
@ssl = socket.backend.respond_to?(:ssl?) && socket.backend.ssl?
|
57
|
+
|
58
|
+
socket.websocket = self
|
59
|
+
socket.comm_inactivity_timeout = 0
|
60
|
+
|
61
|
+
debug [:initialize]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Overwrite dispath from em-websocket
|
65
|
+
# we already have request headers parsed so
|
66
|
+
# we can skip it and call build_with_request
|
67
|
+
def dispatch(data)
|
68
|
+
return false if data.nil?
|
69
|
+
debug [:inbound_headers, data]
|
70
|
+
@handler = EventMachine::WebSocket::HandlerFactory.build_with_request(self, data, data['Body'], @ssl, @debug)
|
71
|
+
unless @handler
|
72
|
+
# The whole header has not been received yet.
|
73
|
+
return false
|
74
|
+
end
|
75
|
+
@handler.run
|
76
|
+
return true
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|