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-ietf-hybi-thewebsocketprotocol-10
8 if (window
.WebSocket
&& !window
.WEB_SOCKET_FORCE_FLASH
) return;
11 if (window
.WEB_SOCKET_LOGGER
) {
12 logger
= WEB_SOCKET_LOGGER
;
13 } else if (window
.console
&& window
.console
.log
&& window
.console
.error
) {
14 // In some environment, console is defined but console.log or console.error is missing.
15 logger
= window
.console
;
17 logger
= {log: function(){ }, error: function(){ }};
20 // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
21 if (swfobject
.getFlashPlayerVersion().major
< 10) {
22 logger
.error("Flash Player >= 10.0.0 is required.");
25 if (location
.protocol
== "file:") {
27 "WARNING: web-socket-js doesn't work in file:///... URL " +
28 "unless you set Flash Security Settings properly. " +
29 "Open the page via Web server i.e. http://...");
33 * This class represents a faux web socket.
35 * @param {array or string} protocols
36 * @param {string} proxyHost
37 * @param {int} proxyPort
38 * @param {string} headers
40 WebSocket = function(url
, protocols
, proxyHost
, proxyPort
, headers
) {
42 self
.__id
= WebSocket
.__nextId
++;
43 WebSocket
.__instances
[self
.__id
] = self
;
44 self
.readyState
= WebSocket
.CONNECTING
;
45 self
.bufferedAmount
= 0;
49 } else if (typeof protocols
== "string") {
50 protocols
= [protocols
];
52 // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
53 // Otherwise, when onopen fires immediately, onopen is called before it is set.
54 self
.__createTask
= setTimeout(function() {
55 WebSocket
.__addTask(function() {
56 self
.__createTask
= null;
57 WebSocket
.__flash
.create(
58 self
.__id
, url
, protocols
, proxyHost
|| null, proxyPort
|| 0, headers
|| null);
64 * Send data to the web socket.
65 * @param {string} data The data to send to the socket.
66 * @return {boolean} True for success, false for failure.
68 WebSocket
.prototype.send = function(data
) {
69 if (this.readyState
== WebSocket
.CONNECTING
) {
70 throw "INVALID_STATE_ERR: Web Socket connection has not been established";
72 // We use encodeURIComponent() here, because FABridge doesn't work if
73 // the argument includes some characters. We don't use escape() here
75 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
76 // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
77 // preserve all Unicode characters either e.g. "\uffff" in Firefox.
78 // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
79 // additional testing.
80 var result
= WebSocket
.__flash
.send(this.__id
, encodeURIComponent(data
));
81 if (result
< 0) { // success
84 this.bufferedAmount
+= result
;
90 * Close this web socket gracefully.
92 WebSocket
.prototype.close = function() {
93 if (this.__createTask
) {
94 clearTimeout(this.__createTask
);
95 this.__createTask
= null;
96 this.readyState
= WebSocket
.CLOSED
;
99 if (this.readyState
== WebSocket
.CLOSED
|| this.readyState
== WebSocket
.CLOSING
) {
102 this.readyState
= WebSocket
.CLOSING
;
103 WebSocket
.__flash
.close(this.__id
);
107 * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
109 * @param {string} type
110 * @param {function} listener
111 * @param {boolean} useCapture
114 WebSocket
.prototype.addEventListener = function(type
, listener
, useCapture
) {
115 if (!(type
in this.__events
)) {
116 this.__events
[type
] = [];
118 this.__events
[type
].push(listener
);
122 * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
124 * @param {string} type
125 * @param {function} listener
126 * @param {boolean} useCapture
129 WebSocket
.prototype.removeEventListener = function(type
, listener
, useCapture
) {
130 if (!(type
in this.__events
)) return;
131 var events
= this.__events
[type
];
132 for (var i
= events
.length
- 1; i
>= 0; --i
) {
133 if (events
[i
] === listener
) {
141 * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
143 * @param {Event} event
146 WebSocket
.prototype.dispatchEvent = function(event
) {
147 var events
= this.__events
[event
.type
] || [];
148 for (var i
= 0; i
< events
.length
; ++i
) {
151 var handler
= this["on" + event
.type
];
152 if (handler
) handler
.apply(this, [event
]);
156 * Handles an event from Flash.
157 * @param {Object} flashEvent
159 WebSocket
.prototype.__handleEvent = function(flashEvent
) {
161 if ("readyState" in flashEvent
) {
162 this.readyState
= flashEvent
.readyState
;
164 if ("protocol" in flashEvent
) {
165 this.protocol
= flashEvent
.protocol
;
169 if (flashEvent
.type
== "open" || flashEvent
.type
== "error") {
170 jsEvent
= this.__createSimpleEvent(flashEvent
.type
);
171 } else if (flashEvent
.type
== "close") {
172 jsEvent
= this.__createSimpleEvent("close");
173 jsEvent
.wasClean
= flashEvent
.wasClean
? true : false;
174 jsEvent
.code
= flashEvent
.code
;
175 jsEvent
.reason
= flashEvent
.reason
;
176 } else if (flashEvent
.type
== "message") {
177 var data
= decodeURIComponent(flashEvent
.message
);
178 jsEvent
= this.__createMessageEvent("message", data
);
180 throw "unknown event type: " + flashEvent
.type
;
183 this.dispatchEvent(jsEvent
);
187 WebSocket
.prototype.__createSimpleEvent = function(type
) {
188 if (document
.createEvent
&& window
.Event
) {
189 var event
= document
.createEvent("Event");
190 event
.initEvent(type
, false, false);
193 return {type: type
, bubbles: false, cancelable: false};
197 WebSocket
.prototype.__createMessageEvent = function(type
, data
) {
198 if (document
.createEvent
&& window
.MessageEvent
&& !window
.opera
) {
199 var event
= document
.createEvent("MessageEvent");
200 event
.initMessageEvent("message", false, false, data
, null, null, window
, null);
203 // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
204 return {type: type
, data: data
, bubbles: false, cancelable: false};
209 * Define the WebSocket readyState enumeration.
211 WebSocket
.CONNECTING
= 0;
213 WebSocket
.CLOSING
= 2;
214 WebSocket
.CLOSED
= 3;
216 WebSocket
.__flash
= null;
217 WebSocket
.__instances
= {};
218 WebSocket
.__tasks
= [];
219 WebSocket
.__nextId
= 0;
222 * Load a new flash security policy file.
223 * @param {string} url
225 WebSocket
.loadFlashPolicyFile = function(url
){
226 WebSocket
.__addTask(function() {
227 WebSocket
.__flash
.loadManualPolicyFile(url
);
232 * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
234 WebSocket
.__initialize = function() {
235 if (WebSocket
.__flash
) return;
237 if (WebSocket
.__swfLocation
) {
238 // For backword compatibility.
239 window
.WEB_SOCKET_SWF_LOCATION
= WebSocket
.__swfLocation
;
241 if (!window
.WEB_SOCKET_SWF_LOCATION
) {
242 logger
.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
245 if (!window
.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR
&&
246 !WEB_SOCKET_SWF_LOCATION
.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
247 WEB_SOCKET_SWF_LOCATION
.match(/^\w+:\/\/([^\/]+)/)) {
248 var swfHost
= RegExp
.$1;
249 if (location
.host
!= swfHost
) {
251 "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
252 "('" + location
.host
+ "' != '" + swfHost
+ "'). " +
253 "See also 'How to host HTML file and SWF file in different domains' section " +
254 "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
255 "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
258 var container
= document
.createElement("div");
259 container
.id
= "webSocketContainer";
260 // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
261 // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
262 // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
263 // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
264 // the best we can do as far as we know now.
265 container
.style
.position
= "absolute";
266 if (WebSocket
.__isFlashLite()) {
267 container
.style
.left
= "0px";
268 container
.style
.top
= "0px";
270 container
.style
.left
= "-100px";
271 container
.style
.top
= "-100px";
273 var holder
= document
.createElement("div");
274 holder
.id
= "webSocketFlash";
275 container
.appendChild(holder
);
276 document
.body
.appendChild(container
);
277 // See this article for hasPriority:
278 // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
280 WEB_SOCKET_SWF_LOCATION
,
284 "10.0.0" /* SWF version */,
287 {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
291 logger
.error("[WebSocket] swfobject.embedSWF failed");
297 * Called by Flash to notify JS that it's fully loaded and ready
300 WebSocket
.__onFlashInitialized = function() {
301 // We need to set a timeout here to avoid round-trip calls
302 // to flash during the initialization process.
303 setTimeout(function() {
304 WebSocket
.__flash
= document
.getElementById("webSocketFlash");
305 WebSocket
.__flash
.setCallerUrl(location
.href
);
306 WebSocket
.__flash
.setDebug(!!window
.WEB_SOCKET_DEBUG
);
307 for (var i
= 0; i
< WebSocket
.__tasks
.length
; ++i
) {
308 WebSocket
.__tasks
[i
]();
310 WebSocket
.__tasks
= [];
315 * Called by Flash to notify WebSockets events are fired.
317 WebSocket
.__onFlashEvent = function() {
318 setTimeout(function() {
320 // Gets events using receiveEvents() instead of getting it from event object
321 // of Flash event. This is to make sure to keep message order.
322 // It seems sometimes Flash events don't arrive in the same order as they are sent.
323 var events
= WebSocket
.__flash
.receiveEvents();
324 for (var i
= 0; i
< events
.length
; ++i
) {
325 WebSocket
.__instances
[events
[i
].webSocketId
].__handleEvent(events
[i
]);
335 WebSocket
.__log = function(message
) {
336 logger
.log(decodeURIComponent(message
));
340 WebSocket
.__error = function(message
) {
341 logger
.error(decodeURIComponent(message
));
344 WebSocket
.__addTask = function(task
) {
345 if (WebSocket
.__flash
) {
348 WebSocket
.__tasks
.push(task
);
353 * Test if the browser is running flash lite.
354 * @return {boolean} True if flash lite is running, false otherwise.
356 WebSocket
.__isFlashLite = function() {
357 if (!window
.navigator
|| !window
.navigator
.mimeTypes
) {
360 var mimeType
= window
.navigator
.mimeTypes
["application/x-shockwave-flash"];
361 if (!mimeType
|| !mimeType
.enabledPlugin
|| !mimeType
.enabledPlugin
.filename
) {
364 return mimeType
.enabledPlugin
.filename
.match(/flashlite/i) ? true : false;
367 if (!window
.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION
) {
368 if (window
.addEventListener
) {
369 window
.addEventListener("load", function(){
370 WebSocket
.__initialize();
373 window
.attachEvent("onload", function(){
374 WebSocket
.__initialize();