├── flash
├── jsSocket.swf
├── jsSocket2.swf
├── Makefile
├── jsSocket.as
└── JsSocket.hx
├── README
├── examples
├── echo.rb
└── stocks.rb
└── js
├── jsonStringify.js
└── jsSocket.js
/flash/jsSocket.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defunkt/jssocket/master/flash/jsSocket.swf
--------------------------------------------------------------------------------
/flash/jsSocket2.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/defunkt/jssocket/master/flash/jsSocket2.swf
--------------------------------------------------------------------------------
/flash/Makefile:
--------------------------------------------------------------------------------
1 | default: jsSocket.swf jsSocket2.swf
2 |
3 | jsSocket.swf: jsSocket.as
4 | mtasc jsSocket.as -version 8 -main -mx -header 800:600:31 -swf jsSocket.swf
5 |
6 | jsSocket2.swf: JsSocket.hx
7 | haxe -swf jsSocket2.swf -swf-version 9 -main JsSocket -swf-header 800:600:31
8 |
--------------------------------------------------------------------------------
/flash/jsSocket.as:
--------------------------------------------------------------------------------
1 | import flash.external.ExternalInterface;
2 |
3 | class jsSocket {
4 | private var sock:XMLSocket;
5 | private var id:String;
6 |
7 | private function calljs(type, data) {
8 | ExternalInterface.call('jsSocket.callback', this.id, type, data);
9 | }
10 |
11 | public function jsSocket(id) {
12 | this.id = id;
13 |
14 | ExternalInterface.addCallback('open', this, open);
15 | ExternalInterface.addCallback('send', this, send);
16 | ExternalInterface.addCallback('close', this, close);
17 |
18 | this.calljs('onLoaded', true);
19 | }
20 |
21 | public function open(host, port) {
22 | System.security.loadPolicyFile('xmlsocket://' + host + ':' + port);
23 | sock = new XMLSocket();
24 |
25 | var self = this;
26 | sock.onConnect = function(s) { self.calljs('onOpen', s); }
27 | sock.onData = function(d) { self.calljs('onData', d); }
28 | sock.onClose = function( ) { self.calljs('onClose'); }
29 |
30 | return sock.connect(host, port);
31 | }
32 |
33 | public function send(data) {
34 | if (data != 'null') // calling send() from js with no arguments sets data == 'null'
35 | return sock.send(data);
36 | }
37 |
38 | public function close() {
39 | sock.close();
40 | sock.onClose();
41 | }
42 |
43 | static function main(mc) {
44 | _root.jsSocket = new jsSocket(_root.id);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/flash/JsSocket.hx:
--------------------------------------------------------------------------------
1 | class JsSocket {
2 | static var socket:flash.net.Socket;
3 | static var id:String;
4 | static var buffer:String = "";
5 | static var sizedReads:Bool = false;
6 | static var packetSize:Int = -1;
7 | static var slash:EReg = ~/\\/g;
8 |
9 | private static function calljs(type, data:Dynamic) {
10 | flash.external.ExternalInterface.call('jsSocket.callback', id, type, data);
11 | }
12 |
13 | private static function debug(data:Dynamic) {
14 | flash.external.ExternalInterface.call('console.log', data);
15 | }
16 |
17 | static function main() {
18 | id = flash.Lib.current.loaderInfo.parameters.id;
19 | sizedReads = flash.Lib.current.loaderInfo.parameters.sizedReads;
20 |
21 | flash.external.ExternalInterface.addCallback("open", open);
22 | flash.external.ExternalInterface.addCallback("send", send);
23 | flash.external.ExternalInterface.addCallback("close", close);
24 |
25 | calljs('onLoaded', true);
26 | }
27 |
28 | static function open(host, port) {
29 | flash.system.Security.loadPolicyFile('xmlsocket://' + host + ':' + port);
30 | socket = new flash.net.Socket();
31 |
32 | socket.addEventListener(flash.events.Event.CONNECT, function(s){
33 | calljs('onOpen', true);
34 | });
35 |
36 | socket.addEventListener(flash.events.Event.CLOSE, function(e){
37 | calljs('onClose', null);
38 | });
39 |
40 | socket.addEventListener(flash.events.IOErrorEvent.IO_ERROR, function(e){
41 | calljs('onClose', e.text);
42 | });
43 |
44 | socket.addEventListener(flash.events.SecurityErrorEvent.SECURITY_ERROR, function(e){
45 | calljs('onClose', e.text);
46 | });
47 |
48 | socket.addEventListener(flash.events.ProgressEvent.SOCKET_DATA, function(d){
49 | if (sizedReads) {
50 | while (socket.bytesAvailable > 0) {
51 | // figure out the packet size
52 | if (packetSize > -1) {
53 | // we have it already
54 | } else if (socket.bytesAvailable >= 2) {
55 | // read the packet size
56 | packetSize = socket.readShort();
57 | } else {
58 | // lets wait
59 | break;
60 | }
61 |
62 | // read the packet, if possible
63 | if (socket.bytesAvailable >= packetSize) {
64 | calljs('onData', slash.replace(socket.readUTFBytes(packetSize), '\\\\'));
65 | packetSize = -1;
66 | } else {
67 | break;
68 | }
69 | }
70 | } else {
71 | var size = socket.bytesAvailable;
72 | var data = new flash.utils.ByteArray();
73 | socket.readBytes(data);
74 |
75 | buffer += data.toString();
76 |
77 | if (buffer.indexOf("\x00") > -1) {
78 | var packets = buffer.split("\x00");
79 | while (packets.length > 1) {
80 | calljs('onData', packets.shift());
81 | }
82 | buffer = packets.shift();
83 | }
84 | }
85 | });
86 |
87 | return socket.connect(host, Std.parseInt(port));
88 | }
89 |
90 | static function send(data:String) {
91 | if (socket.connected && data.length > 0) {
92 | var t = new flash.utils.Timer(0, 1);
93 | t.addEventListener(flash.events.TimerEvent.TIMER, function(d){
94 | socket.writeUTFBytes(data);
95 | socket.writeByte(0);
96 | socket.flush();
97 | });
98 | t.start();
99 |
100 | return true;
101 | } else
102 | return false;
103 | }
104 |
105 | static function close() {
106 | if (socket.connected)
107 | socket.close();
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | jsSocket: generic javascript socket API
2 | (c) 2008 Aman Gupta (tmm1)
3 |
4 | http://github.com/tmm1/jsSocket
5 |
6 |
7 | WHAT IS IT
8 |
9 | jsSocket uses Flash's XMLSocket to expose a simple socket API to javascript
10 |
11 |
12 | REQUIREMENTS
13 |
14 | jsonStringify (or json2) and jsSocket.swf
15 | mtasc (http://mtasc.org) or haxe (http://haxe.org) to re-compile the swfs from source
16 |
17 |
18 | FEATURES
19 |
20 | * multiple sockets on the same page
21 | * keepalive to keep connections open
22 | * automatic reconnect
23 | * status callback for custom connecting/disconnected/reconnecting UI
24 | * customizable logger for debugging
25 |
26 |
27 | WHY FLASH
28 |
29 | * high market penetration
30 | (according to a meebo study, more people had flash installed than javascript enabled)
31 | * single TCP connection (instead of one ajax connection per outgoing packet)
32 | * lower bandwidth usage and latency than long-polling
33 |
34 |
35 | CAVEATS
36 |
37 | * extra initial TCP connection required for security policy
38 | * packets must be delimited by null bytes
39 | * html and binary content must be base64 encoded
40 |
41 |
42 | USAGE
43 |
44 | - Specify the path to jsSocket.swf
45 |
46 | jsSocket.swf = '/flash/jsSocket.swf'
47 |
48 |
49 | - Connect to site.com on port 1234
50 |
51 | var socket = jsSocket()
52 | socket.connect('site.com', 1234)
53 | socket.send('hello world')
54 |
55 |
56 | - Connect to document.location.hostname on port 443
57 |
58 | var socket = jsSocket({ port: 443 })
59 |
60 |
61 | - Install socket with default port and connect manually after setting data callback
62 |
63 | var socket = jsSocket({ port: 443, autoconnect: false })
64 | socket.onData = function(data){ alert(data) }
65 | socket.connect()
66 |
67 |
68 | - Hook into connected/disconnected/data events
69 |
70 | var socket = jsSocket({
71 | onOpen: function() { alert('connected') },
72 | onData: function(data){ alert('got data: ' + data) },
73 | onClose: function() { alert('disconnected') }
74 | })
75 |
76 |
77 | - Disable keepalive pings and auto-reconnect
78 |
79 | var socket = jsSocket({ keepalive: false, autoreconnect: false })
80 | socket.connect('site.com', 1234)
81 |
82 |
83 | - Send custom keepalive packets every minute
84 |
85 | var socket = jsSocket()
86 | socket.keepalive = function(){ socket.send('ping') }
87 |
88 |
89 | - Track the status of a socket connection
90 |
91 | var socket = jsSocket({ port: 443 })
92 | socket.onStatus = function(type, val){
93 | switch(type){
94 | case 'connecting': // connecting to the server
95 | break
96 |
97 | case 'connected': // connected
98 | break
99 |
100 | case 'disconnected': // disconnected
101 | break
102 |
103 | case 'waiting': // waiting to reconnect in val seconds
104 | break
105 |
106 | case 'failed': // attempted max reconnects
107 | break
108 | }
109 | }
110 |
111 |
112 | - Debug jsSocket
113 |
114 | var socket = jsSocket({ debug: true })
115 | socket.logger = console.log // log to firebug
116 | socket.logger = function(arg){ // log to a div
117 | if(!$('#logger'))
118 | $('
').attr('id', 'logger').appendTo('body')
119 |
120 | $('#logger').append(
121 | $('').text(arg.toString())
122 | )
123 | }
124 |
125 |
126 | OTHER RESOURCES
127 |
128 | Alex MacCaw's Juggernaut: http://juggernaut.rubyforge.org
129 | jssockets (uses Flex/Flash9's RemotingSocket): http://code.google.com/p/jssockets
130 | XMLSocket bridge: http://www.devpro.it/xmlsocket/
131 | FlashSocket: http://ionelmc.wordpress.com/2008/11/29/flash-socket-bridge-with-haxe/
132 | socketBridge: http://matthaynes.net/blog/2008/07/17/socketbridge-flash-javascript-socket-bridge/
133 |
134 |
135 | LICENSE
136 |
137 | Licensed under the Ruby License (http://www.ruby-lang.org/en/LICENSE.txt).
138 |
--------------------------------------------------------------------------------
/examples/echo.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'eventmachine'
3 | require 'thin'
4 | require 'haml'
5 |
6 | __DIR__ = File.dirname File.expand_path(__FILE__)
7 |
8 | EM.run{
9 |
10 | class FlashServer < EM::Connection
11 | def self.start host, port
12 | puts ">> FlashServer started on #{host}:#{port}"
13 | EM.start_server host, port, self
14 | end
15 |
16 | def post_init
17 | @buf = BufferedTokenizer.new("\0")
18 | @ip = Socket.unpack_sockaddr_in(get_peername).last rescue '0.0.0.0'
19 | puts ">> FlashServer got connection from #{@ip}"
20 | end
21 |
22 | def unbind
23 | @timer.cancel if @timer
24 | puts ">> FlashServer got disconnect from #{@ip}"
25 | end
26 |
27 | def receive_data data
28 | if data.strip == ""
29 | send %[
30 |
31 |
32 |
33 |
34 |
35 | ]
36 | close_connection_after_writing
37 | return
38 | end
39 |
40 | @buf.extract(data).each do |packet|
41 | puts ">> FlashServer got packet from #{@ip}: #{packet}"
42 | send "you said: '#{packet}' from #{@ip} at #{Time.now}"
43 | end
44 | end
45 |
46 | def send data
47 | send_data "#{data}\0"
48 | end
49 | end
50 |
51 | class StaticApp
52 | def self.call env
53 | [
54 | 200,
55 | {'Content-Type' => 'text/html'},
56 | @page ||= Haml::Engine.new(%[
57 | %html
58 | %head
59 | %title jsSocket example: #{File.basename __FILE__}
60 | %style{ :type => 'text/css'}
61 | :sass
62 | body
63 | margin: 1.5em
64 | font-size: 14pt
65 | font-family: monospace sans-serif
66 | #history
67 | height: 200px
68 | width: 90%
69 | overflow-y: scroll
70 | p
71 | margin: 0
72 | padding: 0
73 | form#input
74 | input
75 | width: 90%
76 | %body
77 | %h1 jsSocket example: #{File.basename __FILE__}
78 |
79 | #history
80 | %form#input
81 | %input{ :type => 'text' }/
82 |
83 | %script{ :type => 'text/javascript', :src => 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' }= ''
84 | %script{ :type => 'text/javascript', :src => '/js/jsonStringify.js' }= ''
85 | %script{ :type => 'text/javascript', :src => '/js/jsSocket.js' }= ''
86 |
87 | :javascript
88 | $socket = jsSocket({ port: 1234,
89 | debug: true,
90 | logger: console.log,
91 | onData: function(data){
92 | $('#history').append(
93 | $('').text(data)
94 | ).each(function(){
95 | this.scrollTop = this.scrollHeight
96 | })
97 | }
98 | })
99 | $('form#input').submit(function(){
100 | $socket.send($(this).find('input').val())
101 | $(this).find('input').val('')
102 | return false
103 | })
104 | ].gsub(/^ /,'')).render
105 | ]
106 | end
107 | end
108 |
109 | map = Rack::URLMap.new '/' => StaticApp,
110 | '/js' => Rack::File.new(__DIR__ + '/../js'),
111 | '/flash' => Rack::File.new(__DIR__ + '/../flash')
112 |
113 | http = Thin::Server.start 'localhost', 1233, map
114 | flash = FlashServer.start 'localhost', 1234
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/js/jsonStringify.js:
--------------------------------------------------------------------------------
1 | /*
2 | JSONstring v 1.01
3 | copyright 2006 Thomas Frank
4 | (small sanitizer added to the toObject-method, May 2008)
5 |
6 | This EULA grants you the following rights:
7 |
8 | Installation and Use. You may install and use an unlimited number of copies of the SOFTWARE PRODUCT.
9 |
10 | Reproduction and Distribution. You may reproduce and distribute an unlimited number of copies of the SOFTWARE PRODUCT either in whole or in part; each copy should include all copyright and trademark notices, and shall be accompanied by a copy of this EULA. Copies of the SOFTWARE PRODUCT may be distributed as a standalone product or included with your own product.
11 |
12 | Commercial Use. You may sell for profit and freely distribute scripts and/or compiled scripts that were created with the SOFTWARE PRODUCT.
13 |
14 | Based on Steve Yen's implementation:
15 | http://trimpath.com/project/wiki/JsonLibrary
16 |
17 | Sanitizer regExp:
18 | Andrea Giammarchi 2007
19 |
20 | */
21 |
22 | JSONstring={
23 | compactOutput:false,
24 | includeProtos:false,
25 | includeFunctions: false,
26 | detectCirculars:true,
27 | restoreCirculars:true,
28 | make:function(arg,restore) {
29 | this.restore=restore;
30 | this.mem=[];this.pathMem=[];
31 | return this.toJsonStringArray(arg).join('');
32 | },
33 | toObject:function(x){
34 | if(!this.cleaner){
35 | try{this.cleaner=new RegExp('^("(\\\\.|[^"\\\\\\n\\r])*?"|[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t])+?$')}
36 | catch(a){this.cleaner=/^(true|false|null|\[.*\]|\{.*\}|".*"|\d+|\d+\.\d+)$/}
37 | };
38 | if(!this.cleaner.test(x)){return {}};
39 | eval("this.myObj="+x);
40 | if(!this.restoreCirculars || !alert){return this.myObj};
41 | if(this.includeFunctions){
42 | var x=this.myObj;
43 | for(var i in x){if(typeof x[i]=="string" && !x[i].indexOf("JSONincludedFunc:")){
44 | x[i]=x[i].substring(17);
45 | eval("x[i]="+x[i])
46 | }}
47 | };
48 | this.restoreCode=[];
49 | this.make(this.myObj,true);
50 | var r=this.restoreCode.join(";")+";";
51 | eval('r=r.replace(/\\W([0-9]{1,})(\\W)/g,"[$1]$2").replace(/\\.\\;/g,";")');
52 | eval(r);
53 | return this.myObj
54 | },
55 | toJsonStringArray:function(arg, out) {
56 | if(!out){this.path=[]};
57 | out = out || [];
58 | var u; // undefined
59 | switch (typeof arg) {
60 | case 'object':
61 | this.lastObj=arg;
62 | if(this.detectCirculars){
63 | var m=this.mem; var n=this.pathMem;
64 | for(var i=0;i 0)
77 | out.push(',\n');
78 | this.toJsonStringArray(arg[i], out);
79 | this.path.pop();
80 | }
81 | out.push(']');
82 | return out;
83 | } else if (typeof arg.toString != 'undefined') {
84 | out.push('{');
85 | var first = true;
86 | for (var i in arg) {
87 | if(!this.includeProtos && arg[i]===arg.constructor.prototype[i]){continue};
88 | this.path.push(i);
89 | var curr = out.length;
90 | if (!first)
91 | out.push(this.compactOutput?',':',\n');
92 | this.toJsonStringArray(i, out);
93 | out.push(':');
94 | this.toJsonStringArray(arg[i], out);
95 | if (out[out.length - 1] == u)
96 | out.splice(curr, out.length - curr);
97 | else
98 | first = false;
99 | this.path.pop();
100 | }
101 | out.push('}');
102 | return out;
103 | }
104 | return out;
105 | }
106 | out.push('null');
107 | return out;
108 | case 'unknown':
109 | case 'undefined':
110 | case 'function':
111 | if(!this.includeFunctions){out.push(u);return out};
112 | arg="JSONincludedFunc:"+arg;
113 | out.push('"');
114 | var a=['\n','\\n','\r','\\r','"','\\"'];
115 | arg+=""; for(var i=0;i<6;i+=2){arg=arg.split(a[i]).join(a[i+1])};
116 | out.push(arg);
117 | out.push('"');
118 | return out;
119 | case 'string':
120 | if(this.restore && arg.indexOf("JSONcircRef:")==0){
121 | this.restoreCode.push('this.myObj.'+this.path.join(".")+"="+arg.split("JSONcircRef:").join("this.myObj."));
122 | };
123 | out.push('"');
124 | var a=['\n','\\n','\r','\\r','"','\\"'];
125 | arg+=""; for(var i=0;i<6;i+=2){arg=arg.split(a[i]).join(a[i+1])};
126 | out.push(arg);
127 | out.push('"');
128 | return out;
129 | default:
130 | out.push(String(arg));
131 | return out;
132 | }
133 | }
134 | };
--------------------------------------------------------------------------------
/examples/stocks.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'eventmachine'
3 | require 'thin'
4 | require 'haml'
5 | require 'json'
6 |
7 | __DIR__ = File.dirname File.expand_path(__FILE__)
8 |
9 | EM.run{
10 |
11 | class FlashServer < EM::Connection
12 | def self.start host, port
13 | puts ">> FlashServer started on #{host}:#{port}"
14 | EM.start_server host, port, self
15 | end
16 |
17 | def post_init
18 | @buf = BufferedTokenizer.new("\0")
19 | @ip = Socket.unpack_sockaddr_in(get_peername).last rescue '0.0.0.0'
20 | puts ">> FlashServer got connection from #{@ip}"
21 | end
22 |
23 | def unbind
24 | @timer.cancel if @timer
25 | puts ">> FlashServer got disconnect from #{@ip}"
26 | end
27 |
28 | def receive_data data
29 | if data.strip == ""
30 | send %[
31 |
32 |
33 |
34 |
35 |
36 | ]
37 | close_connection_after_writing
38 | return
39 | end
40 |
41 | @buf.extract(data).each do |packet|
42 | puts ">> FlashServer got packet from #{@ip}: #{packet}"
43 | packet = JSON.parse(packet) # XXX: error handling goes here
44 |
45 | if stock = packet['stock']
46 | # add stock to watch list
47 | (@stocks ||= []) << stock
48 |
49 | @timer ||= EM::PeriodicTimer.new(1) do
50 | # lookup (i.e. generate) stock prices and send to client
51 | @stocks.each do |s|
52 | send({ :time => Time.now.to_s,
53 | :stock => s,
54 | :price => rand(10_000)/100.0 }.to_json)
55 | end
56 | end
57 | end
58 |
59 | end
60 | end
61 |
62 | def send data
63 | send_data "#{data}\0"
64 | end
65 | end
66 |
67 | class StaticApp
68 | def self.call env
69 | [
70 | 200,
71 | {'Content-Type' => 'text/html'},
72 | @page ||= Haml::Engine.new(%[
73 | %html
74 | %head
75 | %title jsSocket example: #{File.basename __FILE__}
76 | %style{ :type => 'text/css'}
77 | :sass
78 | body
79 | margin: 1.5em
80 | font-size: 14pt
81 | font-family: monospace sans-serif
82 | #history
83 | height: 200px
84 | width: 90%
85 | overflow-y: scroll
86 | p
87 | margin: 0
88 | padding: 0
89 | form#input
90 | input
91 | width: 60px
92 | %body
93 | %h1 jsSocket example: #{File.basename __FILE__}
94 |
95 | #history
96 | %form#input
97 | watch this stock:
98 | %input{ :type => 'text' }/
99 |
100 | %script{ :type => 'text/javascript', :src => 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' }= ''
101 | %script{ :type => 'text/javascript', :src => '/js/jsonStringify.js' }= ''
102 | %script{ :type => 'text/javascript', :src => '/js/jsSocket.js' }= ''
103 |
104 | :javascript
105 | $socket = jsSocket({ port: 1234,
106 | debug: true,
107 | logger: console.log,
108 | onData: function(data){
109 | $('#history').append(
110 | $('').text(data)
111 | ).each(function(){
112 | this.scrollTop = this.scrollHeight
113 | })
114 | }
115 | })
116 | $('form#input').submit(function(){
117 | $socket.send({ stock: $(this).find('input').val() })
118 | $(this).find('input').val('')
119 | return false
120 | })
121 | ].gsub(/^ /,'')).render
122 | ]
123 | end
124 | end
125 |
126 | map = Rack::URLMap.new '/' => StaticApp,
127 | '/js' => Rack::File.new(__DIR__ + '/../js'),
128 | '/flash' => Rack::File.new(__DIR__ + '/../flash')
129 |
130 | http = Thin::Server.start 'localhost', 1233, map
131 | flash = FlashServer.start 'localhost', 1234
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/js/jsSocket.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jsSocket: generic javascript socket API
3 | * (c) 2008 Aman Gupta (tmm1)
4 | *
5 | * http://github.com/tmm1/jsSocket
6 | */
7 |
8 | function jsSocket(args){
9 | if ( this instanceof arguments.callee ) {
10 | if ( typeof this.init == "function" )
11 | this.init.apply( this, (args && args.callee) ? args : arguments );
12 | } else
13 | return new arguments.callee( arguments );
14 | }
15 |
16 | // location of jsSocket.swf
17 | jsSocket.swf = "/flash/jsSocket.swf"
18 |
19 | // list of sockets
20 | jsSocket.sockets = {}
21 |
22 | // flash entry point for callbacks
23 | jsSocket.callback = function(id, type, data){
24 | var sock = jsSocket.sockets[id]
25 | sock.callback.apply(sock, [type, data])
26 | }
27 |
28 | jsSocket.prototype = {
29 | num: null, // numeric id
30 | id: null, // dom id
31 |
32 | wrapper: null, // dom container for flash object
33 | sock: null, // flash object
34 |
35 | host: null, // host == null means window.location.hostname
36 | port: null, // port to connect to (443 is recommended to get through firewalls)
37 |
38 | // toggle packet reading mode between null terminated or size prefixed
39 | // null terminated is the default to be bacwards compatible with flash8/AS2s XMLSocket
40 | // flash9/AS3 supports size prefixed mode which allows sending raw data without base64
41 | sizedReads: false,
42 |
43 | init: function(opts) {
44 | var self = this
45 |
46 | // update options
47 | for (key in opts)
48 | self[key] = opts[key]
49 |
50 | // don't autoconnect unless port is defined
51 | if (!self.port)
52 | self.autoconnect = false
53 |
54 | // assign unique id
55 | if (!self.num) {
56 | if (!jsSocket.id)
57 | jsSocket.id = 1
58 |
59 | self.num = jsSocket.id++
60 | self.id = "jsSocket_" + self.num
61 | }
62 |
63 | // register with flash callback handler
64 | jsSocket.sockets[self.id] = self
65 |
66 | // install flash socket
67 | window.onload = function() {
68 | var flashvars = "id=" + self.id + "&sizedReads=" + self.sizedReads
69 | var wrapper = document.createElement('div')
70 | wrapper.id = 'jsSocketWrapper_' + self.num
71 | wrapper.style.position = 'absolute'
72 | wrapper.innerHTML = ''
73 | document.body.appendChild(wrapper)
74 | }
75 |
76 | window.onbeforeunload = function() {
77 | self.close()
78 | }
79 | },
80 |
81 |
82 | loaded: false, // socket loaded into dom?
83 | connected: false, // socket connected to remote host?
84 | debug: false, // debugging enabled?
85 |
86 | autoconnect: true, // connect when flash loads
87 | autoreconnect: true, // reconnect on disconnect
88 |
89 | // send ping every minute
90 | keepalive: function(){ this.send({ type: 'ping' }) },
91 | keepalive_timer: null,
92 |
93 | // reconnect logic (called if autoreconnect == true)
94 | reconnect: function(){
95 | this.log('reconnecting')
96 |
97 | if (this.reconnect_interval) {
98 | clearInterval(this.reconnect_interval)
99 | }
100 |
101 | this.reconnect_countdown = this.reconnect_countdown * 2
102 |
103 | if (this.reconnect_countdown > 48) {
104 | this.log('reconnect failed, giving up')
105 | this.onStatus('failed')
106 | return
107 | } else {
108 | this.log('will reconnect in ' + this.reconnect_countdown)
109 | this.onStatus('waiting', this.reconnect_countdown)
110 | }
111 |
112 | var secs = 0, self = this
113 |
114 | this.reconnect_interval = setInterval(function(){
115 | var remain = self.reconnect_countdown - ++secs
116 | if (remain == 0) {
117 | self.log('reconnecting now..')
118 | clearInterval(self.reconnect_interval)
119 |
120 | self.autoconnect = true
121 | self.remove()
122 | self.init()
123 | } else {
124 | self.log('reconnecting in '+remain)
125 | self.onStatus('waiting', remain)
126 | }
127 | }, 1000);
128 | },
129 | reconnect_interval: null,
130 | reconnect_countdown: 3,
131 |
132 | // wrappers for flash functions
133 |
134 | // open/connect the socket
135 | // happens automatically if autoconnect is true
136 | open: function(host, port) {
137 | if (host) this.host = host
138 | if (port) this.port = port
139 |
140 | this.host = this.host || window.location.hostname
141 | if (!this.port)
142 | this.log('error: no port specified')
143 |
144 | this.onStatus('connecting')
145 |
146 | return this.sock.open(this.host, this.port);
147 | },
148 | connect: function(){ this.open.apply(this, arguments) },
149 |
150 | // send/write data to the socket
151 | // if argument is an object, it will be json-ified
152 | send: function(data) {
153 | if (typeof data == "object") {
154 | if ('JSONstring' in window)
155 | data = JSONstring.make(data)
156 | else if ('JSON' in window)
157 | data = JSON.stringify(data)
158 | }
159 |
160 | return this.sock.send(data);
161 | },
162 | write: function(){ this.send.apply(this, arguments) },
163 |
164 | // close/disconnect the socket
165 | close: function() {
166 | this.autoreconnect = true
167 | if (this.loaded && this.connected)
168 | this.sock.close()
169 | },
170 | disconnect: function(){ this.close.apply(this) },
171 |
172 | // uninstall the socket
173 | remove: function() {
174 | delete jsSocket.sockets[this.id]
175 | if (this.loaded && this.connected)
176 | this.sock.close()
177 | var wrapper = document.getElementById('jsSocketWrapper_'+this.num)
178 | wrapper.parentNode.removeChild(wrapper)
179 | },
180 |
181 | // debugging
182 |
183 | log: function(){
184 | if (!this.debug) return;
185 |
186 | arguments.slice = Array.prototype.slice
187 | var args = arguments.slice(0)
188 |
189 | if (this.logger)
190 | this.logger.apply(null, [[this.id].concat(args)])
191 | },
192 |
193 | // flash callback
194 |
195 | callback: function(type, data) {
196 | var self = this
197 |
198 | setTimeout(function(){ // wrap in setTimeout(.., 0) to free up flash's ExternalInterface
199 | switch(type){
200 | case 'onLoaded':
201 | self.log('loaded')
202 | self.loaded = true
203 | self.sock = document.getElementById(self.id)
204 |
205 | if (self.autoconnect)
206 | self.connect()
207 |
208 | break
209 |
210 | case 'onOpen':
211 | if (data == true) {
212 | self.log('connected')
213 | self.connected = true
214 |
215 | if (self.keepalive)
216 | self.keepalive_timer = setInterval(function(){
217 | self.keepalive.apply(self)
218 | }, 1*60*1000)
219 |
220 | self.reconnect_countdown = 3
221 | if (self.reconnect_interval)
222 | clearInterval(self.reconnect_interval)
223 |
224 | self.onStatus('connected')
225 |
226 | } else {
227 | self.log('connect failed')
228 | if (self.autoreconnect)
229 | self.reconnect()
230 | }
231 |
232 | break
233 |
234 | case 'onClose':
235 | self.connected = false
236 | self.log('disconnected')
237 | self.onStatus('disconnected')
238 |
239 | if (self.keepalive && self.keepalive_timer) {
240 | clearInterval(self.keepalive_timer)
241 | self.keepalive_timer = null
242 | }
243 |
244 | if (self.autoreconnect)
245 | self.reconnect()
246 |
247 | break
248 |
249 | case 'onData':
250 | self.log('got data: ', data)
251 | }
252 |
253 | self[type](data)
254 | }, 0)
255 | },
256 |
257 | // callback hooks
258 |
259 | onLoaded: function(){},
260 | onOpen: function(){},
261 | onClose: function(){},
262 | onStatus: function(){},
263 | onData: function(){}
264 | }
265 |
--------------------------------------------------------------------------------