cool.io-websocket 0.1.4

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/ChangeLog ADDED
@@ -0,0 +1,23 @@
1
+
2
+ 2011-1-15 version 0.1.4:
3
+
4
+ ** change all references to Rev to Cool.io
5
+
6
+ 2010-07-22 version 0.1.3:
7
+
8
+ * rescue all StandardErrors on_readable
9
+ * adds 'RPC push' example
10
+
11
+ 2010-07-17 version 0.1.2:
12
+
13
+ * sends <cross-domain-policy> on <policy-file-request> to accept Flash-based clients
14
+ * adds examples
15
+
16
+ 2010-07-13 version 0.1.1:
17
+
18
+ * fixes compatibility with Chrome 6 (draft-76)
19
+
20
+ 2010-07-13 version 0.1.0:
21
+
22
+ * first release
23
+
data/README.rdoc ADDED
@@ -0,0 +1,144 @@
1
+ = Cool.io-WebSocket
2
+
3
+ Cool.io-WebSocket is a WebSocket server implementation based on Cool.io,
4
+ a high performance event-driven I/O library for Ruby.
5
+
6
+ This library conforms to WebSocket draft-75 and draft-76.
7
+
8
+ - http://github.com/maxjustus/cool.io-websocket
9
+
10
+
11
+ == Installation
12
+
13
+ gem install cool.io-websocket
14
+
15
+
16
+ == Examples
17
+
18
+ - examples/echo.rb - receives a message from clients and just echos.
19
+ - examples/shoutchat.rb - simple chat application.
20
+ - examples/rpc.rb - pushes messages to browsers from programs separated from the web socket server.
21
+
22
+ See examples/README.md[http://github.com/maxjustus/cool.io-websocket/tree/master/examples/] for details.
23
+
24
+ === Simple echo server
25
+
26
+ require 'rubygems'
27
+ require 'cool.io-websocket'
28
+
29
+ class MyConnection < Cool.io::WebSocket
30
+ def on_open
31
+ puts "WebSocket opened"
32
+ send_message("Hello, world!")
33
+ end
34
+
35
+ def on_message(data)
36
+ puts "WebSocket data received: '#{data}'"
37
+ send_message("echo: #{data}")
38
+ end
39
+
40
+ def on_close
41
+ puts "WebSocket closed"
42
+ end
43
+ end
44
+
45
+ host = '0.0.0.0'
46
+ port = '8080'
47
+
48
+ server = Cool.io::WebSocketServer.new(host, port, MyConnection)
49
+ server.attach(Cool.io::Loop.default)
50
+
51
+ Cool.io::Loop.default.run
52
+
53
+
54
+ === Publisher/Subscriber-style message routing
55
+ require 'rubygems'
56
+ require 'cool.io-websocket'
57
+
58
+ class PubSub
59
+ def initialize
60
+ @subscriber = {}
61
+ @seqid = 0
62
+ end
63
+
64
+ def subscribe(&block)
65
+ sid = @seqid += 1
66
+ @subscriber[sid] = block
67
+ return sid
68
+ end
69
+
70
+ def unsubscribe(key)
71
+ @subscriber.delete(key)
72
+ end
73
+
74
+ def publish(data)
75
+ @subscriber.each_value {|block|
76
+ block.call(data)
77
+ }
78
+ end
79
+ end
80
+
81
+ $pubsub = PubSub.new
82
+
83
+ class MyConnection < Cool.io::WebSocket
84
+ def on_open
85
+ puts "WebSocket opened"
86
+ @sid = $pubsub.subscribe {|data|
87
+ send_message data
88
+ }
89
+ end
90
+
91
+ def on_message(data)
92
+ puts "WebSocket data received, broadcasting: '#{data}'"
93
+ $pubsub.publish(data)
94
+ end
95
+
96
+ def on_close
97
+ puts "WebSocket closed"
98
+ $pubsub.unsubscribe(@sid)
99
+ end
100
+ end
101
+
102
+ host = '0.0.0.0'
103
+ port = ARGV[0] || 8080
104
+
105
+ server = Cool.io::WebSocketServer.new(host, port, MyConnection)
106
+ server.attach(Cool.io::Loop.default)
107
+
108
+ puts "start on #{host}:#{port}"
109
+
110
+ Cool.io::Loop.default.run
111
+
112
+
113
+ == Learn more
114
+
115
+ - [Cool.io API reference] http://rev.rubyforge.org/rdoc/
116
+ - [JSON for object serialization] http://github.com/brianmario/yajl-ruby
117
+ - [MessagePack for object serialization] http://msgpack.org/
118
+ - [The WebSocket protocol draft-76] http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
119
+ - [EventMachine based WebSocket server] http://github.com/igrigorik/em-websocket
120
+
121
+
122
+ == License
123
+
124
+ Copyright (c) 2010 FURUHASHI Sadayuki
125
+
126
+ Permission is hereby granted, free of charge, to any person obtaining
127
+ a copy of this software and associated documentation files (the
128
+ 'Software'), to deal in the Software without restriction, including
129
+ without limitation the rights to use, copy, modify, merge, publish,
130
+ distribute, sublicense, and/or sell copies of the Software, and to
131
+ permit persons to whom the Software is furnished to do so, subject to
132
+ the following conditions:
133
+
134
+ The above copyright notice and this permission notice shall be
135
+ included in all copies or substantial portions of the Software.
136
+
137
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
138
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
139
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
140
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
141
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
142
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
143
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
144
+
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "cool.io-websocket"
7
+ gemspec.summary = "WebSocket server based on Coolio"
8
+ gemspec.description = "Cool.io-WebSocket is a WebSocket server implementation based on Cool.io, a high performance event-driven I/O library for Ruby. This library conforms to WebSocket draft-75 and draft-76."
9
+ gemspec.email = "frsyuki@users.sourceforge.jp"
10
+ gemspec.homepage = "http://github.com/maxjustus/cool.io-websocket"
11
+ gemspec.authors = ["FURUHASHI Sadayuki", "Max Justus Spransy"]
12
+ gemspec.files.include "lib/**/*"
13
+ gemspec.files.include "examples/**/*"
14
+ gemspec.files.exclude ".gitignore"
15
+ gemspec.add_dependency("cool.io", ">= 0.3.2")
16
+ gemspec.add_dependency("thin", '>= 1.2.7')
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ task :default => :build
24
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.4
@@ -0,0 +1,41 @@
1
+ Rev-WebSocket Examlpes
2
+ ======================
3
+
4
+ Documents for *rev-websocket* is available at [frsyuki's rev-websocket repository](http://github.com/frsyuki/rev-websocket).
5
+
6
+
7
+ ## Echo server
8
+
9
+ $ gem install rev-websocket
10
+ $ ruby ./echo
11
+
12
+ A HTTP server runs on localhost:8080 and WebSocket server runs on localhost:8081.
13
+
14
+ Then access to [htt://localhost:8080/echo.html](http://localhost:8080/echo.html).
15
+
16
+
17
+ ## RPC push
18
+
19
+ With RPC (Remote Procedure Call), you can push messages to browsers from programs separated from the WebSocket server.
20
+
21
+ In this example, a Sinatra based web appliction pushes messages using MessagePack-RPC, a simple cross-language RPC library.
22
+
23
+ $ gem install msgpack-rpc
24
+ $ gem install rev-websocket
25
+ $ gem install sinatra
26
+ $ gem install json
27
+ $ ruby ./rpc
28
+
29
+ Then access to [htt://localhost:8080/](http://localhost:8080/).
30
+
31
+
32
+ ## ShoutChat
33
+
34
+ ShoutChat is a simple browser-based chat application.
35
+
36
+ $ gem install rev-websocket
37
+ $ gem install json
38
+ $ ruby ./shoutchat
39
+
40
+ Then access to [htt://localhost:8080/shoutchat.html](http://localhost:8080/shoutchat.html).
41
+
data/examples/echo ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ fork {
4
+ load 'echo.rb'
5
+ exit 0
6
+ }
7
+
8
+ require 'webrick'
9
+
10
+ server = WEBrick::HTTPServer.new({
11
+ :Port => 8080,
12
+ :BindAddress => '127.0.0.1',
13
+ :DocumentRoot => File.dirname(__FILE__)+'/public'})
14
+
15
+ trap('INT') { server.shutdown }
16
+
17
+ server.start
18
+
data/examples/echo.rb ADDED
@@ -0,0 +1,31 @@
1
+ # WebSocket echo server
2
+
3
+ require 'rubygems'
4
+ require 'cool.io-websocket'
5
+
6
+ class EchoConnection < Cool.io::WebSocket
7
+ def on_open
8
+ puts "WebSocket opened from '#{peeraddr[2]}': request=#{request.inspect}"
9
+ send_message("server: Hello, world!")
10
+ end
11
+
12
+ def on_message(data)
13
+ puts "WebSocket data received: '#{data}'"
14
+ send_message(data)
15
+ end
16
+
17
+ def on_close
18
+ puts "WebSocket closed"
19
+ end
20
+ end
21
+
22
+ host = '0.0.0.0'
23
+ port = ARGV[0] || 8081
24
+
25
+ server = Cool.io::WebSocketServer.new(host, port, EchoConnection)
26
+ server.attach(Cool.io::Loop.default)
27
+
28
+ puts "start on #{host}:#{port}"
29
+
30
+ Cool.io::Loop.default.run
31
+
@@ -0,0 +1,11 @@
1
+
2
+ .time {
3
+ color: #007010;
4
+ margin-right: 0.2em;
5
+ padding-right: 0.2em;
6
+ }
7
+
8
+ .time:after {
9
+ content: ":";
10
+ }
11
+
@@ -0,0 +1,62 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta content="text/css" http-equiv="content-style-type" />
6
+ <meta content="text/javascript" http-equiv="content-script-type" />
7
+
8
+ <script type="text/javascript" src='js/jquery.min.js'></script>
9
+ <script type="text/javascript" src='js/swfobject.js'></script>
10
+ <script type="text/javascript" src='js/FABridge.js'></script>
11
+ <script type="text/javascript" src='js/web_socket.js'></script>
12
+ <script type="text/javascript" src='js/json2.js'></script>
13
+ <link rel="stylesheet" type="text/css" href="echo.css" />
14
+
15
+ <title>Cool.io-WebSocket Demo: Echo server</title>
16
+
17
+ <script>
18
+ WS_URL = "ws://localhost:8081";
19
+ WEB_SOCKET_SWF_LOCATION = "js/WebSocketMain.swf";
20
+
21
+ var global = this;
22
+
23
+ $(document).ready(function(){
24
+
25
+ function debug(message) {
26
+ html = "<p><span class='time'>"+new Date()+"</span>"+message+"</p>"
27
+ $("#debug").append(html);
28
+ }
29
+
30
+ debug("connecting to "+WS_URL+"...");
31
+ ws = new WebSocket(WS_URL);
32
+
33
+ ws.onopen = function() {
34
+ debug("connected.");
35
+
36
+ text = "client: hello";
37
+ ws.send(text);
38
+
39
+ debug("message sent: "+text);
40
+ }
41
+
42
+ ws.onclose = function() {
43
+ debug("disconnected...");
44
+ }
45
+
46
+ ws.onerror = function(msg) {
47
+ debug("failed to connect:"+msg);
48
+ }
49
+
50
+ ws.onmessage = function(event) {
51
+ debug("message received: "+event.data);
52
+ $("#message").append("<p>"+event.data+"</p>");
53
+ }
54
+
55
+ setTimeout(function(){ws.send('YOYOYOYOYO')}, 5000);
56
+ });
57
+ </script>
58
+ </head>
59
+ <body>
60
+ <div id="message"></div>
61
+ <div id="debug"></div>
62
+ </body>
@@ -0,0 +1,604 @@
1
+ /*
2
+ /*
3
+ Copyright 2006 Adobe Systems Incorporated
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
6
+ to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
15
+ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+
17
+ */
18
+
19
+
20
+ /*
21
+ * The Bridge class, responsible for navigating AS instances
22
+ */
23
+ function FABridge(target,bridgeName)
24
+ {
25
+ this.target = target;
26
+ this.remoteTypeCache = {};
27
+ this.remoteInstanceCache = {};
28
+ this.remoteFunctionCache = {};
29
+ this.localFunctionCache = {};
30
+ this.bridgeID = FABridge.nextBridgeID++;
31
+ this.name = bridgeName;
32
+ this.nextLocalFuncID = 0;
33
+ FABridge.instances[this.name] = this;
34
+ FABridge.idMap[this.bridgeID] = this;
35
+
36
+ return this;
37
+ }
38
+
39
+ // type codes for packed values
40
+ FABridge.TYPE_ASINSTANCE = 1;
41
+ FABridge.TYPE_ASFUNCTION = 2;
42
+
43
+ FABridge.TYPE_JSFUNCTION = 3;
44
+ FABridge.TYPE_ANONYMOUS = 4;
45
+
46
+ FABridge.initCallbacks = {};
47
+ FABridge.userTypes = {};
48
+
49
+ FABridge.addToUserTypes = function()
50
+ {
51
+ for (var i = 0; i < arguments.length; i++)
52
+ {
53
+ FABridge.userTypes[arguments[i]] = {
54
+ 'typeName': arguments[i],
55
+ 'enriched': false
56
+ };
57
+ }
58
+ }
59
+
60
+ FABridge.argsToArray = function(args)
61
+ {
62
+ var result = [];
63
+ for (var i = 0; i < args.length; i++)
64
+ {
65
+ result[i] = args[i];
66
+ }
67
+ return result;
68
+ }
69
+
70
+ function instanceFactory(objID)
71
+ {
72
+ this.fb_instance_id = objID;
73
+ return this;
74
+ }
75
+
76
+ function FABridge__invokeJSFunction(args)
77
+ {
78
+ var funcID = args[0];
79
+ var throughArgs = args.concat();//FABridge.argsToArray(arguments);
80
+ throughArgs.shift();
81
+
82
+ var bridge = FABridge.extractBridgeFromID(funcID);
83
+ return bridge.invokeLocalFunction(funcID, throughArgs);
84
+ }
85
+
86
+ FABridge.addInitializationCallback = function(bridgeName, callback)
87
+ {
88
+ var inst = FABridge.instances[bridgeName];
89
+ if (inst != undefined)
90
+ {
91
+ callback.call(inst);
92
+ return;
93
+ }
94
+
95
+ var callbackList = FABridge.initCallbacks[bridgeName];
96
+ if(callbackList == null)
97
+ {
98
+ FABridge.initCallbacks[bridgeName] = callbackList = [];
99
+ }
100
+
101
+ callbackList.push(callback);
102
+ }
103
+
104
+ // updated for changes to SWFObject2
105
+ function FABridge__bridgeInitialized(bridgeName) {
106
+ var objects = document.getElementsByTagName("object");
107
+ var ol = objects.length;
108
+ var activeObjects = [];
109
+ if (ol > 0) {
110
+ for (var i = 0; i < ol; i++) {
111
+ if (typeof objects[i].SetVariable != "undefined") {
112
+ activeObjects[activeObjects.length] = objects[i];
113
+ }
114
+ }
115
+ }
116
+ var embeds = document.getElementsByTagName("embed");
117
+ var el = embeds.length;
118
+ var activeEmbeds = [];
119
+ if (el > 0) {
120
+ for (var j = 0; j < el; j++) {
121
+ if (typeof embeds[j].SetVariable != "undefined") {
122
+ activeEmbeds[activeEmbeds.length] = embeds[j];
123
+ }
124
+ }
125
+ }
126
+ var aol = activeObjects.length;
127
+ var ael = activeEmbeds.length;
128
+ var searchStr = "bridgeName="+ bridgeName;
129
+ if ((aol == 1 && !ael) || (aol == 1 && ael == 1)) {
130
+ FABridge.attachBridge(activeObjects[0], bridgeName);
131
+ }
132
+ else if (ael == 1 && !aol) {
133
+ FABridge.attachBridge(activeEmbeds[0], bridgeName);
134
+ }
135
+ else {
136
+ var flash_found = false;
137
+ if (aol > 1) {
138
+ for (var k = 0; k < aol; k++) {
139
+ var params = activeObjects[k].childNodes;
140
+ for (var l = 0; l < params.length; l++) {
141
+ var param = params[l];
142
+ if (param.nodeType == 1 && param.tagName.toLowerCase() == "param" && param["name"].toLowerCase() == "flashvars" && param["value"].indexOf(searchStr) >= 0) {
143
+ FABridge.attachBridge(activeObjects[k], bridgeName);
144
+ flash_found = true;
145
+ break;
146
+ }
147
+ }
148
+ if (flash_found) {
149
+ break;
150
+ }
151
+ }
152
+ }
153
+ if (!flash_found && ael > 1) {
154
+ for (var m = 0; m < ael; m++) {
155
+ var flashVars = activeEmbeds[m].attributes.getNamedItem("flashVars").nodeValue;
156
+ if (flashVars.indexOf(searchStr) >= 0) {
157
+ FABridge.attachBridge(activeEmbeds[m], bridgeName);
158
+ break;
159
+ }
160
+ }
161
+ }
162
+ }
163
+ return true;
164
+ }
165
+
166
+ // used to track multiple bridge instances, since callbacks from AS are global across the page.
167
+
168
+ FABridge.nextBridgeID = 0;
169
+ FABridge.instances = {};
170
+ FABridge.idMap = {};
171
+ FABridge.refCount = 0;
172
+
173
+ FABridge.extractBridgeFromID = function(id)
174
+ {
175
+ var bridgeID = (id >> 16);
176
+ return FABridge.idMap[bridgeID];
177
+ }
178
+
179
+ FABridge.attachBridge = function(instance, bridgeName)
180
+ {
181
+ var newBridgeInstance = new FABridge(instance, bridgeName);
182
+
183
+ FABridge[bridgeName] = newBridgeInstance;
184
+
185
+ /* FABridge[bridgeName] = function() {
186
+ return newBridgeInstance.root();
187
+ }
188
+ */
189
+ var callbacks = FABridge.initCallbacks[bridgeName];
190
+ if (callbacks == null)
191
+ {
192
+ return;
193
+ }
194
+ for (var i = 0; i < callbacks.length; i++)
195
+ {
196
+ callbacks[i].call(newBridgeInstance);
197
+ }
198
+ delete FABridge.initCallbacks[bridgeName]
199
+ }
200
+
201
+ // some methods can't be proxied. You can use the explicit get,set, and call methods if necessary.
202
+
203
+ FABridge.blockedMethods =
204
+ {
205
+ toString: true,
206
+ get: true,
207
+ set: true,
208
+ call: true
209
+ };
210
+
211
+ FABridge.prototype =
212
+ {
213
+
214
+
215
+ // bootstrapping
216
+
217
+ root: function()
218
+ {
219
+ return this.deserialize(this.target.getRoot());
220
+ },
221
+ //clears all of the AS objects in the cache maps
222
+ releaseASObjects: function()
223
+ {
224
+ return this.target.releaseASObjects();
225
+ },
226
+ //clears a specific object in AS from the type maps
227
+ releaseNamedASObject: function(value)
228
+ {
229
+ if(typeof(value) != "object")
230
+ {
231
+ return false;
232
+ }
233
+ else
234
+ {
235
+ var ret = this.target.releaseNamedASObject(value.fb_instance_id);
236
+ return ret;
237
+ }
238
+ },
239
+ //create a new AS Object
240
+ create: function(className)
241
+ {
242
+ return this.deserialize(this.target.create(className));
243
+ },
244
+
245
+
246
+ // utilities
247
+
248
+ makeID: function(token)
249
+ {
250
+ return (this.bridgeID << 16) + token;
251
+ },
252
+
253
+
254
+ // low level access to the flash object
255
+
256
+ //get a named property from an AS object
257
+ getPropertyFromAS: function(objRef, propName)
258
+ {
259
+ if (FABridge.refCount > 0)
260
+ {
261
+ throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
262
+ }
263
+ else
264
+ {
265
+ FABridge.refCount++;
266
+ retVal = this.target.getPropFromAS(objRef, propName);
267
+ retVal = this.handleError(retVal);
268
+ FABridge.refCount--;
269
+ return retVal;
270
+ }
271
+ },
272
+ //set a named property on an AS object
273
+ setPropertyInAS: function(objRef,propName, value)
274
+ {
275
+ if (FABridge.refCount > 0)
276
+ {
277
+ throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
278
+ }
279
+ else
280
+ {
281
+ FABridge.refCount++;
282
+ retVal = this.target.setPropInAS(objRef,propName, this.serialize(value));
283
+ retVal = this.handleError(retVal);
284
+ FABridge.refCount--;
285
+ return retVal;
286
+ }
287
+ },
288
+
289
+ //call an AS function
290
+ callASFunction: function(funcID, args)
291
+ {
292
+ if (FABridge.refCount > 0)
293
+ {
294
+ throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
295
+ }
296
+ else
297
+ {
298
+ FABridge.refCount++;
299
+ retVal = this.target.invokeASFunction(funcID, this.serialize(args));
300
+ retVal = this.handleError(retVal);
301
+ FABridge.refCount--;
302
+ return retVal;
303
+ }
304
+ },
305
+ //call a method on an AS object
306
+ callASMethod: function(objID, funcName, args)
307
+ {
308
+ if (FABridge.refCount > 0)
309
+ {
310
+ throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
311
+ }
312
+ else
313
+ {
314
+ FABridge.refCount++;
315
+ args = this.serialize(args);
316
+ retVal = this.target.invokeASMethod(objID, funcName, args);
317
+ retVal = this.handleError(retVal);
318
+ FABridge.refCount--;
319
+ return retVal;
320
+ }
321
+ },
322
+
323
+ // responders to remote calls from flash
324
+
325
+ //callback from flash that executes a local JS function
326
+ //used mostly when setting js functions as callbacks on events
327
+ invokeLocalFunction: function(funcID, args)
328
+ {
329
+ var result;
330
+ var func = this.localFunctionCache[funcID];
331
+
332
+ if(func != undefined)
333
+ {
334
+ result = this.serialize(func.apply(null, this.deserialize(args)));
335
+ }
336
+
337
+ return result;
338
+ },
339
+
340
+ // Object Types and Proxies
341
+
342
+ // accepts an object reference, returns a type object matching the obj reference.
343
+ getTypeFromName: function(objTypeName)
344
+ {
345
+ return this.remoteTypeCache[objTypeName];
346
+ },
347
+ //create an AS proxy for the given object ID and type
348
+ createProxy: function(objID, typeName)
349
+ {
350
+ var objType = this.getTypeFromName(typeName);
351
+ instanceFactory.prototype = objType;
352
+ var instance = new instanceFactory(objID);
353
+ this.remoteInstanceCache[objID] = instance;
354
+ return instance;
355
+ },
356
+ //return the proxy associated with the given object ID
357
+ getProxy: function(objID)
358
+ {
359
+ return this.remoteInstanceCache[objID];
360
+ },
361
+
362
+ // accepts a type structure, returns a constructed type
363
+ addTypeDataToCache: function(typeData)
364
+ {
365
+ newType = new ASProxy(this, typeData.name);
366
+ var accessors = typeData.accessors;
367
+ for (var i = 0; i < accessors.length; i++)
368
+ {
369
+ this.addPropertyToType(newType, accessors[i]);
370
+ }
371
+
372
+ var methods = typeData.methods;
373
+ for (var i = 0; i < methods.length; i++)
374
+ {
375
+ if (FABridge.blockedMethods[methods[i]] == undefined)
376
+ {
377
+ this.addMethodToType(newType, methods[i]);
378
+ }
379
+ }
380
+
381
+
382
+ this.remoteTypeCache[newType.typeName] = newType;
383
+ return newType;
384
+ },
385
+
386
+ //add a property to a typename; used to define the properties that can be called on an AS proxied object
387
+ addPropertyToType: function(ty, propName)
388
+ {
389
+ var c = propName.charAt(0);
390
+ var setterName;
391
+ var getterName;
392
+ if(c >= "a" && c <= "z")
393
+ {
394
+ getterName = "get" + c.toUpperCase() + propName.substr(1);
395
+ setterName = "set" + c.toUpperCase() + propName.substr(1);
396
+ }
397
+ else
398
+ {
399
+ getterName = "get" + propName;
400
+ setterName = "set" + propName;
401
+ }
402
+ ty[setterName] = function(val)
403
+ {
404
+ this.bridge.setPropertyInAS(this.fb_instance_id, propName, val);
405
+ }
406
+ ty[getterName] = function()
407
+ {
408
+ return this.bridge.deserialize(this.bridge.getPropertyFromAS(this.fb_instance_id, propName));
409
+ }
410
+ },
411
+
412
+ //add a method to a typename; used to define the methods that can be callefd on an AS proxied object
413
+ addMethodToType: function(ty, methodName)
414
+ {
415
+ ty[methodName] = function()
416
+ {
417
+ return this.bridge.deserialize(this.bridge.callASMethod(this.fb_instance_id, methodName, FABridge.argsToArray(arguments)));
418
+ }
419
+ },
420
+
421
+ // Function Proxies
422
+
423
+ //returns the AS proxy for the specified function ID
424
+ getFunctionProxy: function(funcID)
425
+ {
426
+ var bridge = this;
427
+ if (this.remoteFunctionCache[funcID] == null)
428
+ {
429
+ this.remoteFunctionCache[funcID] = function()
430
+ {
431
+ bridge.callASFunction(funcID, FABridge.argsToArray(arguments));
432
+ }
433
+ }
434
+ return this.remoteFunctionCache[funcID];
435
+ },
436
+
437
+ //reutrns the ID of the given function; if it doesnt exist it is created and added to the local cache
438
+ getFunctionID: function(func)
439
+ {
440
+ if (func.__bridge_id__ == undefined)
441
+ {
442
+ func.__bridge_id__ = this.makeID(this.nextLocalFuncID++);
443
+ this.localFunctionCache[func.__bridge_id__] = func;
444
+ }
445
+ return func.__bridge_id__;
446
+ },
447
+
448
+ // serialization / deserialization
449
+
450
+ serialize: function(value)
451
+ {
452
+ var result = {};
453
+
454
+ var t = typeof(value);
455
+ //primitives are kept as such
456
+ if (t == "number" || t == "string" || t == "boolean" || t == null || t == undefined)
457
+ {
458
+ result = value;
459
+ }
460
+ else if (value instanceof Array)
461
+ {
462
+ //arrays are serializesd recursively
463
+ result = [];
464
+ for (var i = 0; i < value.length; i++)
465
+ {
466
+ result[i] = this.serialize(value[i]);
467
+ }
468
+ }
469
+ else if (t == "function")
470
+ {
471
+ //js functions are assigned an ID and stored in the local cache
472
+ result.type = FABridge.TYPE_JSFUNCTION;
473
+ result.value = this.getFunctionID(value);
474
+ }
475
+ else if (value instanceof ASProxy)
476
+ {
477
+ result.type = FABridge.TYPE_ASINSTANCE;
478
+ result.value = value.fb_instance_id;
479
+ }
480
+ else
481
+ {
482
+ result.type = FABridge.TYPE_ANONYMOUS;
483
+ result.value = value;
484
+ }
485
+
486
+ return result;
487
+ },
488
+
489
+ //on deserialization we always check the return for the specific error code that is used to marshall NPE's into JS errors
490
+ // the unpacking is done by returning the value on each pachet for objects/arrays
491
+ deserialize: function(packedValue)
492
+ {
493
+
494
+ var result;
495
+
496
+ var t = typeof(packedValue);
497
+ if (t == "number" || t == "string" || t == "boolean" || packedValue == null || packedValue == undefined)
498
+ {
499
+ result = this.handleError(packedValue);
500
+ }
501
+ else if (packedValue instanceof Array)
502
+ {
503
+ result = [];
504
+ for (var i = 0; i < packedValue.length; i++)
505
+ {
506
+ result[i] = this.deserialize(packedValue[i]);
507
+ }
508
+ }
509
+ else if (t == "object")
510
+ {
511
+ for(var i = 0; i < packedValue.newTypes.length; i++)
512
+ {
513
+ this.addTypeDataToCache(packedValue.newTypes[i]);
514
+ }
515
+ for (var aRefID in packedValue.newRefs)
516
+ {
517
+ this.createProxy(aRefID, packedValue.newRefs[aRefID]);
518
+ }
519
+ if (packedValue.type == FABridge.TYPE_PRIMITIVE)
520
+ {
521
+ result = packedValue.value;
522
+ }
523
+ else if (packedValue.type == FABridge.TYPE_ASFUNCTION)
524
+ {
525
+ result = this.getFunctionProxy(packedValue.value);
526
+ }
527
+ else if (packedValue.type == FABridge.TYPE_ASINSTANCE)
528
+ {
529
+ result = this.getProxy(packedValue.value);
530
+ }
531
+ else if (packedValue.type == FABridge.TYPE_ANONYMOUS)
532
+ {
533
+ result = packedValue.value;
534
+ }
535
+ }
536
+ return result;
537
+ },
538
+ //increases the reference count for the given object
539
+ addRef: function(obj)
540
+ {
541
+ this.target.incRef(obj.fb_instance_id);
542
+ },
543
+ //decrease the reference count for the given object and release it if needed
544
+ release:function(obj)
545
+ {
546
+ this.target.releaseRef(obj.fb_instance_id);
547
+ },
548
+
549
+ // check the given value for the components of the hard-coded error code : __FLASHERROR
550
+ // used to marshall NPE's into flash
551
+
552
+ handleError: function(value)
553
+ {
554
+ if (typeof(value)=="string" && value.indexOf("__FLASHERROR")==0)
555
+ {
556
+ var myErrorMessage = value.split("||");
557
+ if(FABridge.refCount > 0 )
558
+ {
559
+ FABridge.refCount--;
560
+ }
561
+ throw new Error(myErrorMessage[1]);
562
+ return value;
563
+ }
564
+ else
565
+ {
566
+ return value;
567
+ }
568
+ }
569
+ };
570
+
571
+ // The root ASProxy class that facades a flash object
572
+
573
+ ASProxy = function(bridge, typeName)
574
+ {
575
+ this.bridge = bridge;
576
+ this.typeName = typeName;
577
+ return this;
578
+ };
579
+ //methods available on each ASProxy object
580
+ ASProxy.prototype =
581
+ {
582
+ get: function(propName)
583
+ {
584
+ return this.bridge.deserialize(this.bridge.getPropertyFromAS(this.fb_instance_id, propName));
585
+ },
586
+
587
+ set: function(propName, value)
588
+ {
589
+ this.bridge.setPropertyInAS(this.fb_instance_id, propName, value);
590
+ },
591
+
592
+ call: function(funcName, args)
593
+ {
594
+ this.bridge.callASMethod(this.fb_instance_id, funcName, args);
595
+ },
596
+
597
+ addRef: function() {
598
+ this.bridge.addRef(this);
599
+ },
600
+
601
+ release: function() {
602
+ this.bridge.release(this);
603
+ }
604
+ };