firehose 0.1.1 → 0.2.alpha.2
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.
- data/.env.sample +10 -0
- data/.gitignore +2 -0
- data/Procfile +1 -1
- data/README.md +117 -11
- data/config/rainbows.rb +20 -0
- data/firehose.gemspec +9 -6
- data/lib/assets/flash/firehose/WebSocketMain.swf +0 -0
- data/lib/assets/javascripts/firehose.js.coffee +4 -1
- data/lib/assets/javascripts/firehose/consumer.js.coffee +3 -11
- data/lib/assets/javascripts/firehose/lib/jquery.cors.headers.js.coffee +16 -0
- data/lib/assets/javascripts/firehose/lib/swfobject.js +4 -0
- data/lib/assets/javascripts/firehose/lib/web_socket.js +389 -0
- data/lib/assets/javascripts/firehose/long_poll.js.coffee +42 -39
- data/lib/assets/javascripts/firehose/transport.js.coffee +1 -1
- data/lib/assets/javascripts/firehose/web_socket.js.coffee +8 -14
- data/lib/firehose.rb +12 -17
- data/lib/firehose/channel.rb +84 -0
- data/lib/firehose/cli.rb +57 -13
- data/lib/firehose/client.rb +92 -0
- data/lib/firehose/default.rb +2 -2
- data/lib/firehose/logging.rb +35 -0
- data/lib/firehose/producer.rb +1 -0
- data/lib/firehose/publisher.rb +56 -4
- data/lib/firehose/rack.rb +37 -120
- data/lib/firehose/rack/consumer_app.rb +143 -0
- data/lib/firehose/rack/ping_app.rb +84 -0
- data/lib/firehose/rack/publisher_app.rb +40 -0
- data/lib/firehose/server.rb +48 -0
- data/lib/firehose/subscriber.rb +54 -0
- data/lib/firehose/swf_policy_request.rb +23 -0
- data/lib/firehose/version.rb +2 -2
- data/lib/rainbows_em_swf_policy.rb +33 -0
- data/lib/thin_em_swf_policy.rb +26 -0
- data/spec/integrations/integration_test_helper.rb +16 -0
- data/spec/integrations/rainbows_spec.rb +7 -0
- data/spec/integrations/shared_examples.rb +111 -0
- data/spec/integrations/thin_spec.rb +5 -79
- data/spec/lib/channel_spec.rb +164 -0
- data/spec/lib/client_spec.rb +9 -0
- data/spec/lib/default_spec.rb +2 -2
- data/spec/lib/publisher_spec.rb +82 -0
- data/spec/lib/rack/consumer_app_spec.rb +11 -0
- data/spec/lib/rack/ping_app_spec.rb +28 -0
- data/spec/lib/rack/publisher_app_spec.rb +26 -0
- data/spec/lib/subscriber_spec.rb +69 -0
- data/spec/spec_helper.rb +49 -8
- metadata +114 -45
- data/config.ru +0 -6
- data/lib/firehose/subscription.rb +0 -77
data/.env.sample
ADDED
data/.gitignore
CHANGED
data/Procfile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
firehose: firehose server
|
1
|
+
firehose: ./bin/firehose server -p $PORT
|
data/README.md
CHANGED
@@ -5,20 +5,16 @@
|
|
5
5
|
| | | | | | __/ | | | (_) \__ \ __/
|
6
6
|
|_| |_|_| \___|_| |_|\___/|___/\___|
|
7
7
|
|
8
|
-
Build
|
8
|
+
Build realtime web applications in Ruby and JS
|
9
9
|
|
10
10
|
# What is Firehose?
|
11
11
|
|
12
|
-
Firehose is both a Rack application and
|
12
|
+
Firehose is both a Rack application and JavaScript library that makes building real-time web applications possible.
|
13
13
|
|
14
14
|
# Getting Started
|
15
15
|
|
16
|
-
First, you'll need to install and run Redis.
|
17
|
-
|
18
|
-
```sh
|
19
|
-
$ apt-get install redis # Install on Ubuntu
|
20
|
-
$ brew install redis # Install on Mac Homebrew
|
21
|
-
```
|
16
|
+
First, you'll need to [install and run Redis 2.6](http://redis.io/download).
|
17
|
+
Version 2.6 is required because Firehose uses [Lua/EVAL](http://redis.io/commands/eval) for its transactions, which is not available in earlier versions of Redis.
|
22
18
|
|
23
19
|
Then install the gem.
|
24
20
|
|
@@ -30,14 +26,14 @@ $ gem install firehose
|
|
30
26
|
|
31
27
|
Now fire up the server.
|
32
28
|
|
33
|
-
```
|
29
|
+
```
|
34
30
|
$ firehose server
|
35
31
|
>> Thin web server (v1.3.1 codename Triple Espresso)
|
36
32
|
>> Maximum connections set to 1024
|
37
33
|
>> Listening on 127.0.0.1:7474, CTRL+C to stop
|
38
34
|
```
|
39
35
|
|
40
|
-
In case you're wondering, the Firehose application server runs the Rack app `Firehose::Rack::App.new` inside of Thin
|
36
|
+
In case you're wondering, the Firehose application server runs the Rack app `Firehose::Rack::App.new` inside of Thin or Rainbows!.
|
41
37
|
|
42
38
|
## Publish a message to a bunch of subscribers
|
43
39
|
|
@@ -72,6 +68,9 @@ Firehose doesn't just stop at curl; it has a full-featured JavaScript client tha
|
|
72
68
|
Still have the server running? Copy and paste the code below into Firebug or the WebKit console.
|
73
69
|
|
74
70
|
```javascript
|
71
|
+
// You'll need to set for WS to work in IE. The value here is the default.
|
72
|
+
window.WEB_SOCKET_SWF_LOCATION = '/assets/firehose/WebSocketMain.swf';
|
73
|
+
|
75
74
|
new Firehose.Consumer({
|
76
75
|
message: function(msg){
|
77
76
|
console.log(msg);
|
@@ -103,4 +102,111 @@ socket.io attempts to store connection state per node instance. Firehose makes n
|
|
103
102
|
|
104
103
|
Also, socket.io attempts to abstract a low-latency full-duplex port. Firehose assumes that its impossible to simulate this in older web browsers that don't support WebSockets. As such, Firehose focuses on low-latency server-to-client connections and encourages the use of existing HTTP transports, like POST and PUT, for client-to-server communications.
|
105
104
|
|
106
|
-
Finally, Firehose attempts to solve data consistency issues and authentication by
|
105
|
+
Finally, Firehose attempts to solve data consistency issues and authentication by encouraging the use of proxying to the web application.
|
106
|
+
|
107
|
+
# The Ruby Publisher
|
108
|
+
|
109
|
+
While you can certainly make your own PUT requests when publishing messages, Firehose includes a Ruby client for easy publishing.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
require 'firehose'
|
113
|
+
require 'json'
|
114
|
+
json = {'hello'=> 'world'}.to_json
|
115
|
+
firehose = Firehose::Producer.new('//127.0.0.1:7474')
|
116
|
+
firehose.publish(json).to("/my/messages/path")
|
117
|
+
```
|
118
|
+
|
119
|
+
# Configuration
|
120
|
+
|
121
|
+
Most configuration happens inside the `.env` file. Take a look at `.env.sample` for more info.
|
122
|
+
|
123
|
+
## Sprockets
|
124
|
+
|
125
|
+
Using Sprockets is the recommended method of including the included client-side assets in a web page.
|
126
|
+
|
127
|
+
1) Add the firehose gem in your app's Gemfile.
|
128
|
+
|
129
|
+
2) Append the firehose gem's assets to the sprockets path. In a Rails app, this is usually done in an initializer.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
gem_path = Gem.loaded_specs['firehose'].full_gem_path
|
133
|
+
sprockets.append_path File.join(gem_path, 'lib/assets/flash')
|
134
|
+
sprockets.append_path File.join(gem_path, 'lib/assets/javascripts')
|
135
|
+
```
|
136
|
+
|
137
|
+
NOTE: Some spockets setups will do this automatically. If that is your case, then you can skip this step.
|
138
|
+
|
139
|
+
3) Create a firehose config file for setting constants. A good place for this file in a Rails app is `app/assets/javascripts/lib/firehose_config.js.erb`. This gives you a way to configure Flash Web Sockets needed to make some older browser such as IE<10 and Opera<12 support web sockets. Usually this config file will just be:
|
140
|
+
|
141
|
+
```erb
|
142
|
+
window.WEB_SOCKET_SWF_LOCATION = '<%= asset_path('firehose/WebSocketMain.swf') %>'
|
143
|
+
```
|
144
|
+
|
145
|
+
The options you can set in this file come directly from https://github.com/gimite/web-socket-js (which is the flash web socket implementation used by Firehose).
|
146
|
+
|
147
|
+
4) Require your config file and the firehose gem. This would look something like this:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
#= require some/other/js/file
|
151
|
+
#= require lib/firehose_config
|
152
|
+
#= require firehose
|
153
|
+
#= require some/more/js/files
|
154
|
+
```
|
155
|
+
|
156
|
+
It is important that your config file comes first.
|
157
|
+
|
158
|
+
|
159
|
+
# Web Server
|
160
|
+
|
161
|
+
Firehose currently supports Thin and Rainbows! (which is the default). Neither is listed as a dependency in the gemspec so that you don't need to install whichever one you aren't using. You can set which server to use via the `.env` file (recommended) or with the `-s` option to `bin/firehose`.
|
162
|
+
|
163
|
+
# Exception Notification
|
164
|
+
|
165
|
+
If you'd like to be notified of exceptions, add something like this in your custom config.ru file.
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
# Use exceptional to handle anything missed by Rack::Exceptional
|
169
|
+
if exceptional_key = ENV['EXCEPTIONAL_KEY']
|
170
|
+
require 'exceptional'
|
171
|
+
EM.error_handler do |e|
|
172
|
+
Firehose.logger.error "Unhandled exception: #{e.class} #{e.message}\n#{e.backtrace.join "\n"}"
|
173
|
+
::Exceptional.handle(e)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
# Flash Policy File
|
179
|
+
|
180
|
+
This works out of the box in development if you use the included Procfile with Foreman. However, that solution doesn't work well with a lot production setups, so it is disabled in other environments.
|
181
|
+
|
182
|
+
[Here is a hack to server your policy file via Nginx](http://blog.vokle.com/index.php/2009/06/10/dealing-with-adobe-and-serving-socket-policy-servers-via-nginx-and-10-lines-of-code/)
|
183
|
+
|
184
|
+
You can also add a similar hack to HAProxy to serve your policy file.
|
185
|
+
|
186
|
+
```sh
|
187
|
+
listen swf_policy_requests 0.0.0.0:843
|
188
|
+
# Be careful here. This needs to be an absolute path.
|
189
|
+
errorfile 400 /path/to/my/crossdomain.xml
|
190
|
+
```
|
191
|
+
|
192
|
+
In either case, you'll want to be careful about using these hacks on ports that you are also using for genuine HTTP traffic.
|
193
|
+
|
194
|
+
# Deployment
|
195
|
+
|
196
|
+
The recommended method of deploying firehose is to deploy it separately from your main app.
|
197
|
+
|
198
|
+
1) Create a new project with a Gemfile such as
|
199
|
+
|
200
|
+
```
|
201
|
+
gem "firehose"
|
202
|
+
gem "airbrake"
|
203
|
+
gem "rainbows", :require => false
|
204
|
+
gem "foreman", :require => false
|
205
|
+
gem "capistrano", :require => false
|
206
|
+
```
|
207
|
+
|
208
|
+
Of course, you could use `exceptional` instead of `airbrake` and `thin` instead of `rainbows`.
|
209
|
+
|
210
|
+
2) Set up `config/deploy.rb` to your liking. You can follow most directions for using Capistrano and Foreman to deploy Rack apps, such as https://gist.github.com/1027117
|
211
|
+
|
212
|
+
3) Set up `config/rainbows.rb` (if you are using Rainbows!). The gem includes an example to get you started.
|
data/config/rainbows.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# TODO - Dunno what a lot of this stuff is... Tune with benchmarks
|
2
|
+
# http://rainbows.rubyforge.org/Rainbows/Configurator.html
|
3
|
+
|
4
|
+
Rainbows! do
|
5
|
+
use :EventMachine # concurrency model
|
6
|
+
worker_connections 400
|
7
|
+
keepalive_timeout 0 # disables keepalives
|
8
|
+
keepalive_requests 666 # default:100
|
9
|
+
client_max_body_size 5 * 1024 * 1024 # 5 megabytes
|
10
|
+
client_header_buffer_size 2 * 1024 # 2 kilobytes
|
11
|
+
end
|
12
|
+
|
13
|
+
# the rest of the Unicorn configuration...
|
14
|
+
worker_processes [ENV['WORKER_PROCESSES'].to_i, 1].max # Default to 1
|
15
|
+
working_directory ENV['WORKING_DIRECTORY'] if ENV['WORKING_DIRECTORY']
|
16
|
+
logger Firehose.logger
|
17
|
+
|
18
|
+
after_fork do |server, worker|
|
19
|
+
require 'rainbows_em_swf_policy'
|
20
|
+
end if ENV['RACK_ENV'] == 'development'
|
data/firehose.gemspec
CHANGED
@@ -5,8 +5,8 @@ require "firehose/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "firehose"
|
7
7
|
s.version = Firehose::VERSION
|
8
|
-
s.authors = ["Brad Gessler", "Steel Fu"]
|
9
|
-
s.email = ["brad@polleverywhere.com", "steel@polleverywhere.com"]
|
8
|
+
s.authors = ["Brad Gessler", "Steel Fu", "Paul Cortens"]
|
9
|
+
s.email = ["brad@polleverywhere.com", "steel@polleverywhere.com", "paul@polleverywhere.com"]
|
10
10
|
s.homepage = "http://firehose.io/"
|
11
11
|
s.summary = %q{Build realtime Ruby web applications}
|
12
12
|
s.description = %q{Firehose is a realtime web application toolkit for building realtime Ruby web applications.}
|
@@ -19,12 +19,11 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
# specify any dependencies here; for example:
|
22
|
-
s.add_runtime_dependency "eventmachine", ">= 1.0.0.
|
22
|
+
s.add_runtime_dependency "eventmachine", ">= 1.0.0.rc"
|
23
23
|
s.add_runtime_dependency "em-hiredis"
|
24
|
-
s.add_runtime_dependency "thin"
|
25
24
|
s.add_runtime_dependency "thor"
|
26
25
|
s.add_runtime_dependency "faraday"
|
27
|
-
s.add_runtime_dependency "websocket
|
26
|
+
s.add_runtime_dependency "faye-websocket"
|
28
27
|
s.add_runtime_dependency "em-http-request", "~> 1.0.0"
|
29
28
|
|
30
29
|
s.add_development_dependency "rspec"
|
@@ -32,5 +31,9 @@ Gem::Specification.new do |s|
|
|
32
31
|
s.add_development_dependency "guard-rspec"
|
33
32
|
s.add_development_dependency "guard-bundler"
|
34
33
|
s.add_development_dependency "guard-coffeescript"
|
35
|
-
s.add_development_dependency "
|
34
|
+
s.add_development_dependency "rainbows"
|
35
|
+
s.add_development_dependency "thin"
|
36
|
+
s.add_development_dependency "rack-test"
|
37
|
+
s.add_development_dependency "async_rack_test"
|
38
|
+
s.add_development_dependency "foreman"
|
36
39
|
end
|
Binary file
|
@@ -1,5 +1,8 @@
|
|
1
1
|
#= require firehose/base
|
2
2
|
#= require firehose/transport
|
3
|
+
#= require firehose/lib/jquery.cors.headers
|
3
4
|
#= require firehose/long_poll
|
4
5
|
#= require firehose/web_socket
|
5
|
-
#= require firehose/consumer
|
6
|
+
#= require firehose/consumer
|
7
|
+
#= require firehose/lib/swfobject
|
8
|
+
#= require firehose/lib/web_socket
|
@@ -2,15 +2,7 @@ class Firehose.Consumer
|
|
2
2
|
# Transports that are available to Firehose.
|
3
3
|
@transports: [Firehose.WebSocket, Firehose.LongPoll]
|
4
4
|
|
5
|
-
# Generate a random consumer id.
|
6
|
-
@nextConsumerId: ->
|
7
|
-
Math.floor((Math.random()*99999999999)+1)
|
8
|
-
|
9
5
|
constructor: (config = {}) ->
|
10
|
-
# The consumerId is used by the server to remember messages between requests. In a production environment,
|
11
|
-
# this should probably be some combination of "user_id-rand". Why the rand? Because a user may have multiple
|
12
|
-
# tabs open to the application, and each tab needs a different channel on the server.
|
13
|
-
config.consumerId ||= Firehose.Consumer.nextConsumerId()
|
14
6
|
# List of transport stragies we have to use.
|
15
7
|
config.transports ||= Firehose.Consumer.transports
|
16
8
|
# Empty handler for messages.
|
@@ -27,8 +19,8 @@ class Firehose.Consumer
|
|
27
19
|
throw "Could not connect"
|
28
20
|
# URL that we'll connect to.
|
29
21
|
config.uri ||= undefined
|
30
|
-
# Params that we'll tack on to the URL.
|
31
|
-
config.params ||= {
|
22
|
+
# Params that we'll tack on to the URL.
|
23
|
+
config.params ||= { }
|
32
24
|
# Do stuff before we send the message into config.message. The sensible
|
33
25
|
# default on the webs is to parse JSON.
|
34
26
|
config.parse ||= (body) ->
|
@@ -58,4 +50,4 @@ class Firehose.Consumer
|
|
58
50
|
@config.failed() # Call the original fail method passed into the Firehose.Consumer
|
59
51
|
new transport(config)
|
60
52
|
# Fire off the first connection attempt.
|
61
|
-
transports[0].connect()
|
53
|
+
transports[0].connect()
|
@@ -0,0 +1,16 @@
|
|
1
|
+
_super = jQuery.ajaxSettings.xhr
|
2
|
+
|
3
|
+
jQuery.ajaxSettings.xhr = ->
|
4
|
+
xhr = _super()
|
5
|
+
getAllResponseHeaders = xhr.getAllResponseHeaders
|
6
|
+
xhr.getAllResponseHeaders = ->
|
7
|
+
allHeaders = getAllResponseHeaders.call(xhr)
|
8
|
+
return allHeaders if allHeaders
|
9
|
+
|
10
|
+
allHeaders = ""
|
11
|
+
for headerName in [ "Cache-Control", "Content-Language", "Content-Type", "Expires", "Last-Modified", "Pragma" ]
|
12
|
+
do (headerName) ->
|
13
|
+
allHeaders += headerName + ": " + xhr.getResponseHeader(headerName) + "\n" if xhr.getResponseHeader(headerName)
|
14
|
+
|
15
|
+
allHeaders
|
16
|
+
xhr
|
@@ -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,389 @@
|
|
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/rfc6455
|
5
|
+
|
6
|
+
(function() {
|
7
|
+
|
8
|
+
if (window.WEB_SOCKET_FORCE_FLASH) {
|
9
|
+
// Keeps going.
|
10
|
+
} else if (window.WebSocket) {
|
11
|
+
return;
|
12
|
+
} else if (window.MozWebSocket) {
|
13
|
+
// Firefox.
|
14
|
+
window.WebSocket = MozWebSocket;
|
15
|
+
return;
|
16
|
+
}
|
17
|
+
|
18
|
+
var logger;
|
19
|
+
if (window.WEB_SOCKET_LOGGER) {
|
20
|
+
logger = WEB_SOCKET_LOGGER;
|
21
|
+
} else if (window.console && window.console.log && window.console.error) {
|
22
|
+
// In some environment, console is defined but console.log or console.error is missing.
|
23
|
+
logger = window.console;
|
24
|
+
} else {
|
25
|
+
logger = {log: function(){ }, error: function(){ }};
|
26
|
+
}
|
27
|
+
|
28
|
+
// swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
|
29
|
+
if (swfobject.getFlashPlayerVersion().major < 10) {
|
30
|
+
logger.error("Flash Player >= 10.0.0 is required.");
|
31
|
+
return;
|
32
|
+
}
|
33
|
+
if (location.protocol == "file:") {
|
34
|
+
logger.error(
|
35
|
+
"WARNING: web-socket-js doesn't work in file:///... URL " +
|
36
|
+
"unless you set Flash Security Settings properly. " +
|
37
|
+
"Open the page via Web server i.e. http://...");
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Our own implementation of WebSocket class using Flash.
|
42
|
+
* @param {string} url
|
43
|
+
* @param {array or string} protocols
|
44
|
+
* @param {string} proxyHost
|
45
|
+
* @param {int} proxyPort
|
46
|
+
* @param {string} headers
|
47
|
+
*/
|
48
|
+
window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
|
49
|
+
var self = this;
|
50
|
+
self.__id = WebSocket.__nextId++;
|
51
|
+
WebSocket.__instances[self.__id] = self;
|
52
|
+
self.readyState = WebSocket.CONNECTING;
|
53
|
+
self.bufferedAmount = 0;
|
54
|
+
self.__events = {};
|
55
|
+
if (!protocols) {
|
56
|
+
protocols = [];
|
57
|
+
} else if (typeof protocols == "string") {
|
58
|
+
protocols = [protocols];
|
59
|
+
}
|
60
|
+
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
|
61
|
+
// Otherwise, when onopen fires immediately, onopen is called before it is set.
|
62
|
+
self.__createTask = setTimeout(function() {
|
63
|
+
WebSocket.__addTask(function() {
|
64
|
+
self.__createTask = null;
|
65
|
+
WebSocket.__flash.create(
|
66
|
+
self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
|
67
|
+
});
|
68
|
+
}, 0);
|
69
|
+
};
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Send data to the web socket.
|
73
|
+
* @param {string} data The data to send to the socket.
|
74
|
+
* @return {boolean} True for success, false for failure.
|
75
|
+
*/
|
76
|
+
WebSocket.prototype.send = function(data) {
|
77
|
+
if (this.readyState == WebSocket.CONNECTING) {
|
78
|
+
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
|
79
|
+
}
|
80
|
+
// We use encodeURIComponent() here, because FABridge doesn't work if
|
81
|
+
// the argument includes some characters. We don't use escape() here
|
82
|
+
// because of this:
|
83
|
+
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
|
84
|
+
// But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
|
85
|
+
// preserve all Unicode characters either e.g. "\uffff" in Firefox.
|
86
|
+
// Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
|
87
|
+
// additional testing.
|
88
|
+
var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
|
89
|
+
if (result < 0) { // success
|
90
|
+
return true;
|
91
|
+
} else {
|
92
|
+
this.bufferedAmount += result;
|
93
|
+
return false;
|
94
|
+
}
|
95
|
+
};
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Close this web socket gracefully.
|
99
|
+
*/
|
100
|
+
WebSocket.prototype.close = function() {
|
101
|
+
if (this.__createTask) {
|
102
|
+
clearTimeout(this.__createTask);
|
103
|
+
this.__createTask = null;
|
104
|
+
this.readyState = WebSocket.CLOSED;
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
|
108
|
+
return;
|
109
|
+
}
|
110
|
+
this.readyState = WebSocket.CLOSING;
|
111
|
+
WebSocket.__flash.close(this.__id);
|
112
|
+
};
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
116
|
+
*
|
117
|
+
* @param {string} type
|
118
|
+
* @param {function} listener
|
119
|
+
* @param {boolean} useCapture
|
120
|
+
* @return void
|
121
|
+
*/
|
122
|
+
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
|
123
|
+
if (!(type in this.__events)) {
|
124
|
+
this.__events[type] = [];
|
125
|
+
}
|
126
|
+
this.__events[type].push(listener);
|
127
|
+
};
|
128
|
+
|
129
|
+
/**
|
130
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
131
|
+
*
|
132
|
+
* @param {string} type
|
133
|
+
* @param {function} listener
|
134
|
+
* @param {boolean} useCapture
|
135
|
+
* @return void
|
136
|
+
*/
|
137
|
+
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
|
138
|
+
if (!(type in this.__events)) return;
|
139
|
+
var events = this.__events[type];
|
140
|
+
for (var i = events.length - 1; i >= 0; --i) {
|
141
|
+
if (events[i] === listener) {
|
142
|
+
events.splice(i, 1);
|
143
|
+
break;
|
144
|
+
}
|
145
|
+
}
|
146
|
+
};
|
147
|
+
|
148
|
+
/**
|
149
|
+
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
|
150
|
+
*
|
151
|
+
* @param {Event} event
|
152
|
+
* @return void
|
153
|
+
*/
|
154
|
+
WebSocket.prototype.dispatchEvent = function(event) {
|
155
|
+
var events = this.__events[event.type] || [];
|
156
|
+
for (var i = 0; i < events.length; ++i) {
|
157
|
+
events[i](event);
|
158
|
+
}
|
159
|
+
var handler = this["on" + event.type];
|
160
|
+
if (handler) handler.apply(this, [event]);
|
161
|
+
};
|
162
|
+
|
163
|
+
/**
|
164
|
+
* Handles an event from Flash.
|
165
|
+
* @param {Object} flashEvent
|
166
|
+
*/
|
167
|
+
WebSocket.prototype.__handleEvent = function(flashEvent) {
|
168
|
+
|
169
|
+
if ("readyState" in flashEvent) {
|
170
|
+
this.readyState = flashEvent.readyState;
|
171
|
+
}
|
172
|
+
if ("protocol" in flashEvent) {
|
173
|
+
this.protocol = flashEvent.protocol;
|
174
|
+
}
|
175
|
+
|
176
|
+
var jsEvent;
|
177
|
+
if (flashEvent.type == "open" || flashEvent.type == "error") {
|
178
|
+
jsEvent = this.__createSimpleEvent(flashEvent.type);
|
179
|
+
} else if (flashEvent.type == "close") {
|
180
|
+
jsEvent = this.__createSimpleEvent("close");
|
181
|
+
jsEvent.wasClean = flashEvent.wasClean ? true : false;
|
182
|
+
jsEvent.code = flashEvent.code;
|
183
|
+
jsEvent.reason = flashEvent.reason;
|
184
|
+
} else if (flashEvent.type == "message") {
|
185
|
+
var data = decodeURIComponent(flashEvent.message);
|
186
|
+
jsEvent = this.__createMessageEvent("message", data);
|
187
|
+
} else {
|
188
|
+
throw "unknown event type: " + flashEvent.type;
|
189
|
+
}
|
190
|
+
|
191
|
+
this.dispatchEvent(jsEvent);
|
192
|
+
|
193
|
+
};
|
194
|
+
|
195
|
+
WebSocket.prototype.__createSimpleEvent = function(type) {
|
196
|
+
if (document.createEvent && window.Event) {
|
197
|
+
var event = document.createEvent("Event");
|
198
|
+
event.initEvent(type, false, false);
|
199
|
+
return event;
|
200
|
+
} else {
|
201
|
+
return {type: type, bubbles: false, cancelable: false};
|
202
|
+
}
|
203
|
+
};
|
204
|
+
|
205
|
+
WebSocket.prototype.__createMessageEvent = function(type, data) {
|
206
|
+
if (document.createEvent && window.MessageEvent && !window.opera) {
|
207
|
+
var event = document.createEvent("MessageEvent");
|
208
|
+
event.initMessageEvent("message", false, false, data, null, null, window, null);
|
209
|
+
return event;
|
210
|
+
} else {
|
211
|
+
// IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
|
212
|
+
return {type: type, data: data, bubbles: false, cancelable: false};
|
213
|
+
}
|
214
|
+
};
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Define the WebSocket readyState enumeration.
|
218
|
+
*/
|
219
|
+
WebSocket.CONNECTING = 0;
|
220
|
+
WebSocket.OPEN = 1;
|
221
|
+
WebSocket.CLOSING = 2;
|
222
|
+
WebSocket.CLOSED = 3;
|
223
|
+
|
224
|
+
WebSocket.__initialized = false;
|
225
|
+
WebSocket.__flash = null;
|
226
|
+
WebSocket.__instances = {};
|
227
|
+
WebSocket.__tasks = [];
|
228
|
+
WebSocket.__nextId = 0;
|
229
|
+
|
230
|
+
/**
|
231
|
+
* Load a new flash security policy file.
|
232
|
+
* @param {string} url
|
233
|
+
*/
|
234
|
+
WebSocket.loadFlashPolicyFile = function(url){
|
235
|
+
WebSocket.__addTask(function() {
|
236
|
+
WebSocket.__flash.loadManualPolicyFile(url);
|
237
|
+
});
|
238
|
+
};
|
239
|
+
|
240
|
+
/**
|
241
|
+
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
|
242
|
+
*/
|
243
|
+
WebSocket.__initialize = function() {
|
244
|
+
|
245
|
+
if (WebSocket.__initialized) return;
|
246
|
+
WebSocket.__initialized = true;
|
247
|
+
|
248
|
+
if (WebSocket.__swfLocation) {
|
249
|
+
// For backword compatibility.
|
250
|
+
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
|
251
|
+
}
|
252
|
+
if (!window.WEB_SOCKET_SWF_LOCATION) {
|
253
|
+
logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
254
|
+
return;
|
255
|
+
}
|
256
|
+
if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
|
257
|
+
!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
|
258
|
+
WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
|
259
|
+
var swfHost = RegExp.$1;
|
260
|
+
if (location.host != swfHost) {
|
261
|
+
logger.error(
|
262
|
+
"[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
|
263
|
+
"('" + location.host + "' != '" + swfHost + "'). " +
|
264
|
+
"See also 'How to host HTML file and SWF file in different domains' section " +
|
265
|
+
"in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
|
266
|
+
"by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
|
267
|
+
}
|
268
|
+
}
|
269
|
+
var container = document.createElement("div");
|
270
|
+
container.id = "webSocketContainer";
|
271
|
+
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
|
272
|
+
// Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
|
273
|
+
// But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
|
274
|
+
// Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
|
275
|
+
// the best we can do as far as we know now.
|
276
|
+
container.style.position = "absolute";
|
277
|
+
if (WebSocket.__isFlashLite()) {
|
278
|
+
container.style.left = "0px";
|
279
|
+
container.style.top = "0px";
|
280
|
+
} else {
|
281
|
+
container.style.left = "-100px";
|
282
|
+
container.style.top = "-100px";
|
283
|
+
}
|
284
|
+
var holder = document.createElement("div");
|
285
|
+
holder.id = "webSocketFlash";
|
286
|
+
container.appendChild(holder);
|
287
|
+
document.body.appendChild(container);
|
288
|
+
// See this article for hasPriority:
|
289
|
+
// http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
|
290
|
+
swfobject.embedSWF(
|
291
|
+
WEB_SOCKET_SWF_LOCATION,
|
292
|
+
"webSocketFlash",
|
293
|
+
"1" /* width */,
|
294
|
+
"1" /* height */,
|
295
|
+
"10.0.0" /* SWF version */,
|
296
|
+
null,
|
297
|
+
null,
|
298
|
+
{hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
|
299
|
+
null,
|
300
|
+
function(e) {
|
301
|
+
if (!e.success) {
|
302
|
+
logger.error("[WebSocket] swfobject.embedSWF failed");
|
303
|
+
}
|
304
|
+
}
|
305
|
+
);
|
306
|
+
|
307
|
+
};
|
308
|
+
|
309
|
+
/**
|
310
|
+
* Called by Flash to notify JS that it's fully loaded and ready
|
311
|
+
* for communication.
|
312
|
+
*/
|
313
|
+
WebSocket.__onFlashInitialized = function() {
|
314
|
+
// We need to set a timeout here to avoid round-trip calls
|
315
|
+
// to flash during the initialization process.
|
316
|
+
setTimeout(function() {
|
317
|
+
WebSocket.__flash = document.getElementById("webSocketFlash");
|
318
|
+
WebSocket.__flash.setCallerUrl(location.href);
|
319
|
+
WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
|
320
|
+
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
|
321
|
+
WebSocket.__tasks[i]();
|
322
|
+
}
|
323
|
+
WebSocket.__tasks = [];
|
324
|
+
}, 0);
|
325
|
+
};
|
326
|
+
|
327
|
+
/**
|
328
|
+
* Called by Flash to notify WebSockets events are fired.
|
329
|
+
*/
|
330
|
+
WebSocket.__onFlashEvent = function() {
|
331
|
+
setTimeout(function() {
|
332
|
+
try {
|
333
|
+
// Gets events using receiveEvents() instead of getting it from event object
|
334
|
+
// of Flash event. This is to make sure to keep message order.
|
335
|
+
// It seems sometimes Flash events don't arrive in the same order as they are sent.
|
336
|
+
var events = WebSocket.__flash.receiveEvents();
|
337
|
+
for (var i = 0; i < events.length; ++i) {
|
338
|
+
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
|
339
|
+
}
|
340
|
+
} catch (e) {
|
341
|
+
logger.error(e);
|
342
|
+
}
|
343
|
+
}, 0);
|
344
|
+
return true;
|
345
|
+
};
|
346
|
+
|
347
|
+
// Called by Flash.
|
348
|
+
WebSocket.__log = function(message) {
|
349
|
+
logger.log(decodeURIComponent(message));
|
350
|
+
};
|
351
|
+
|
352
|
+
// Called by Flash.
|
353
|
+
WebSocket.__error = function(message) {
|
354
|
+
logger.error(decodeURIComponent(message));
|
355
|
+
};
|
356
|
+
|
357
|
+
WebSocket.__addTask = function(task) {
|
358
|
+
if (WebSocket.__flash) {
|
359
|
+
task();
|
360
|
+
} else {
|
361
|
+
WebSocket.__tasks.push(task);
|
362
|
+
}
|
363
|
+
};
|
364
|
+
|
365
|
+
/**
|
366
|
+
* Test if the browser is running flash lite.
|
367
|
+
* @return {boolean} True if flash lite is running, false otherwise.
|
368
|
+
*/
|
369
|
+
WebSocket.__isFlashLite = function() {
|
370
|
+
if (!window.navigator || !window.navigator.mimeTypes) {
|
371
|
+
return false;
|
372
|
+
}
|
373
|
+
var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
|
374
|
+
if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
|
375
|
+
return false;
|
376
|
+
}
|
377
|
+
return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
|
378
|
+
};
|
379
|
+
|
380
|
+
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
|
381
|
+
// NOTE:
|
382
|
+
// This fires immediately if web_socket.js is dynamically loaded after
|
383
|
+
// the document is loaded.
|
384
|
+
swfobject.addDomLoadEvent(function() {
|
385
|
+
WebSocket.__initialize();
|
386
|
+
});
|
387
|
+
}
|
388
|
+
|
389
|
+
})();
|