push 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .DS_Store
4
+ Gemfile.lock
5
+ pkg/*
@@ -0,0 +1 @@
1
+ 1.9.2-p290
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in push.gemspec
4
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
@@ -0,0 +1,61 @@
1
+ /
2
+ ___ ___ (___
3
+ | )| )|___ | )
4
+ |__/ |__/ __/ | /
5
+ |
6
+
7
+ A Realtime Ruby web application toolkit
8
+
9
+ # What is Push?
10
+
11
+ Push is both a Rack application and Javascript that makes building scalable real-time web applications possible.
12
+
13
+ # How is it different from socket.io?
14
+
15
+ socket.io attempts to store connection state per node instance. Push makes no attempt to store connection state.
16
+
17
+ Also, socket.io attempts to abstract a low-latency full duplex port. Push assumes that its impposible to simulate this in older web browsers that don't support WebSockets. As such, Push focuses on low-latency server-to-client connections and encourages the use of HTTP transports for client-to-server communications.
18
+
19
+ Finally, push attempts to solve data consistency issues and authentication by encourage the use of proxying to the web application.
20
+
21
+ # Getting Started
22
+
23
+ First, you'll need to install RabbitMQ. In the future, Push may support multiple backends, but for now we use RabbitMQ. Fire up that daemon and we'll setup the server.
24
+
25
+ ## The Consumer
26
+
27
+ The consumer is the web server that your client connects to for real-time updates. Create a config.ru file with the following:
28
+
29
+ ```ruby
30
+ require 'rubygems'
31
+ require 'push/consumer'
32
+
33
+ run Push::Consumer # Defaults to an AMQP server running on localhost
34
+ ```
35
+
36
+ Now run the config.ru file in a server that supports async Rack callbacks (like thin or rainbows)
37
+
38
+ ```ruby
39
+ thin -R config.ru -p 4000 start
40
+ ```
41
+
42
+ ## The Producer
43
+
44
+ Lets test the producer! Open two terminal windows. In one window, curl the consumer server:
45
+
46
+ ```sh
47
+ curl "http://localhost:4000/"
48
+ ```
49
+
50
+ Then run the following script in another terminal:
51
+
52
+ ```ruby
53
+ require 'rubygems'
54
+ require 'push/producer'
55
+
56
+ Push::Producer.publish('hi there!').to('/greetings')
57
+ ```
58
+
59
+ Viola! The curl script will return 'hi there!'
60
+
61
+ Now you're on your way to building realtime web applications.
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,31 @@
1
+ require "push/version"
2
+
3
+ module Push
4
+ autoload :Configuration, 'push/configuration'
5
+ autoload :Logging, 'push/logging'
6
+ autoload :Backend, 'push/backend'
7
+ autoload :Producer, 'push/producer'
8
+ autoload :Transport, 'push/transport'
9
+
10
+ # Register some default backends
11
+ Backend.register_adapter(:test, Backend::Test)
12
+ Backend.register_adapter(:amqp, Backend::AMQP)
13
+
14
+ def self.config(&blk)
15
+ @config ||= Configuration.new
16
+ yield @config if block_given?
17
+ @config
18
+ end
19
+
20
+ def self.config=(config)
21
+ @config = config
22
+ end
23
+
24
+ def self.logger
25
+ config.logger
26
+ end
27
+
28
+ def self.exception_reporter(e)
29
+ Push.config.exception_reporter.call(e)
30
+ end
31
+ end
@@ -0,0 +1,63 @@
1
+ module Push
2
+ # Backends for test and development environments
3
+ module Backend
4
+ class AMQP
5
+ include Push::Logging
6
+
7
+ def publish(message, exchange)
8
+ EM.reactor_running? ? async_publish(message, exchange) : sync_publish(message, exchange)
9
+ logger.debug "Published '#{message}' to exchange #{exchange}"
10
+ end
11
+
12
+ # Deal with running AMQP inside of a sync environment. This is useful
13
+ # for script/console testing and our pe_result_processor.
14
+ def sync_publish(message, exchange)
15
+ Bunny.run(Push.config.amqp.to_hash) { |channel|
16
+ channel.exchange(exchange, :type => :fanout).publish(message)
17
+ }
18
+ end
19
+
20
+ # Run this puppy inside of Em.
21
+ def async_publish(message, exchange)
22
+ channel = ::AMQP::Channel.new(connection)
23
+ channel.fanout(exchange).publish(message)
24
+ EM.next_tick {
25
+ channel.close
26
+ logger.debug "Channel closed"
27
+ }
28
+ end
29
+
30
+ # Make sure we setup and use one connection per Push instance. AMQP
31
+ # wants to minimize connects/reconnects, so we connect once here and
32
+ # let publish use this connection.
33
+ def connection
34
+ @connection ||= ::AMQP.connect(Push.config.amqp.to_hash)
35
+ end
36
+ end
37
+
38
+ class Test
39
+ include Push::Logging
40
+
41
+ def publish(message, exchange_name)
42
+ logger.debug "Published '#{message}' to exchange #{exchange}"
43
+ exchange[exchange_name] << message
44
+ end
45
+
46
+ def exchange
47
+ @exchange ||= Hash.new {|h,k| h[k] = []} # Default hash empty hash values with an array (instead of nil)
48
+ end
49
+ end
50
+
51
+ def self.register_adapter(name, adapter)
52
+ adapters[name.to_sym] = adapter
53
+ end
54
+
55
+ def self.adapter(name=Push.config.backend)
56
+ adapters[name.to_sym].new
57
+ end
58
+
59
+ def self.adapters
60
+ @adapters ||= {}
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,56 @@
1
+ module Push
2
+ class Configuration
3
+ attr_accessor :amqp, :web_socket, :logger, :backend, :long_poll, :exception_handler
4
+
5
+ class AMQP < Struct.new(:host, :port, :username, :password, :vhost, :queue_ttl)
6
+ def to_hash
7
+ {
8
+ :host => Push.config.amqp.host,
9
+ :port => Push.config.amqp.port,
10
+ :user => Push.config.amqp.username,
11
+ :pass => Push.config.amqp.password,
12
+ :vhost => Push.config.amqp.vhost
13
+ }
14
+ end
15
+ end
16
+
17
+ WebSocket = Struct.new(:url)
18
+ LongPoll = Struct.new(:url, :timeout)
19
+
20
+ def backend=(name)
21
+ @backend = name.to_sym
22
+ end
23
+
24
+ def initialize
25
+ @amqp = AMQP.new('127.0.0.1', 5672, 'guest', 'guest', '/', 5)
26
+ @web_socket = WebSocket.new('ws://localhost:3000/_push')
27
+ @long_poll = LongPoll.new('http://localhost:3000/_push', 30)
28
+ @backend = :amqp
29
+ @logger = Logger.new($stdout)
30
+ @exception_reporter = Proc.new{|e| logger.error(e) }
31
+ end
32
+
33
+ def from_hash(hash)
34
+ # Pick a backend doofus
35
+ self.backend = hash['backend'] if hash['backend']
36
+
37
+ # WS setup
38
+ if web_socket = hash['web_socket']
39
+ self.web_socket.url = web_socket['url'] if web_socket['url']
40
+ end
41
+
42
+ # HTTP longpoll setup
43
+ if long_poll = hash['long_poll']
44
+ self.long_poll.url = long_poll['url'] if long_poll['url']
45
+ self.long_poll.timeout = long_poll['timeout'] if long_poll['timeout']
46
+ end
47
+
48
+ # Setup AMQP
49
+ if amqp = hash['amqp']
50
+ %w[host port username password vhost queue_ttl].each do |key|
51
+ self.amqp.send("#{key}=", amqp[key]) if amqp[key]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,13 @@
1
+ require 'logger'
2
+
3
+ # Make logging easier in various classes
4
+ module Push::Logging
5
+ private
6
+ def logger
7
+ Push.logger
8
+ end
9
+
10
+ def log(message,level=:info)
11
+ Push.logger.send(level, "#{self.class.name}: #{message}")
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module Push
2
+ # Give us a really easy way to publish messages to an MQ exchange. This is
3
+ # MQ agnostic so that it works in a test environment.
4
+ class Producer
5
+ attr_reader :backend
6
+
7
+ # This gives us a nice Push.publish(message).to(exchange) DSL.
8
+ class DSL
9
+ def initialize(backend, message)
10
+ @backend, @message = backend, message
11
+ end
12
+
13
+ def to(exchange)
14
+ @backend.publish(@message, exchange)
15
+ end
16
+ end
17
+
18
+ def initialize
19
+ @backend = Backend.adapter
20
+ end
21
+
22
+ def publish(message)
23
+ DSL.new(backend, message)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module Push
2
+ module Transport
3
+ module Controller
4
+ autoload :HttpLongPoll, 'push/transport/controller/http_long_poll'
5
+ autoload :WebSocket, 'push/transport/controller/web_socket'
6
+ end
7
+
8
+ # Figure out which transport controller we're going to use to service the request.
9
+ class Dispatcher
10
+ def call(env)
11
+ env['Upgrade'] == 'WebSocket' ? Controller::WebSocket.new.call(env) : Controller::HttpLongPoll.new.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,115 @@
1
+ # TODO
2
+ #
3
+ # 1. Deal with clients that time-out, come back around for a
4
+ # new subscription, and haven't unsubscribed from the last connection
5
+ #
6
+ # 2. Figure out why all of the queue connections get killed/cancelled _in development env_
7
+ #
8
+
9
+ require 'sinatra/base'
10
+ require 'sinatra/async'
11
+
12
+ module Push::Transport::Controller
13
+ class HttpLongPoll < Sinatra::Base
14
+ set :root, File.expand_path('../http_long_poll', __FILE__)
15
+
16
+ include Push::Logging
17
+
18
+ register Sinatra::Async
19
+
20
+ SidHttpKey = 'HTTP_X_SID'.freeze
21
+ SidParamKey = 'sid'.freeze
22
+ JsonpCallbackParamKey = 'callback'.freeze
23
+ Connection = ::AMQP.connect(Push.config.amqp.to_hash)
24
+
25
+ # This is our iframe cross-domain magic proxy that makes long polling work in all of our browsers.
26
+ get '/_xdr_proxy' do
27
+ erb :proxy
28
+ end
29
+
30
+ aget %r{.+} do # Match EVERYTHING!
31
+ content_type 'application/json'
32
+ headers 'X-Http-Next-Request' => '0'
33
+
34
+ # This is suppose to unsubscribe the queue when the client times-out the connection. Without
35
+ # this, you'd keep queue subscriptions open and things would get messy.
36
+ on_close {
37
+ log "Closing connection"
38
+ kill_queue
39
+ }
40
+
41
+ # We use this for a client-side heart beat. The client should hear this request and reconnect
42
+ # after a time-out.
43
+ log "TIMEOUT: #{ Push.config.long_poll.timeout }"
44
+ EventMachine::add_timer(Push.config.long_poll.timeout){
45
+ log "Timeout"
46
+ kill_queue
47
+ ahalt 204 # HTTP Code 204 - No Content. Don't send a 400 level error as FF XHR doesn't pass it properly thru
48
+ }
49
+
50
+ # What for a message to come through, pop it, then close down the connection to do it all over again.
51
+ # The rabbit-mq server maintains state between requests with the Push.amqp.queue_ttl attribute.
52
+ log "Subscribed to `#{exchange}`"
53
+ queue.bind(channel.fanout(exchange)).subscribe(:ack => true) {|header, message|
54
+ log "Received message on `#{exchange}`"
55
+ @unsubscribed = true
56
+ queue.unsubscribe {
57
+ log "Unsubscribing from `#{exchange}`"
58
+ header.ack
59
+ log "Acked `#{exchange}`"
60
+ channel.close {
61
+ log "Closed channel `#{exchange}`"
62
+ body {
63
+ log "Flushing out body `#{exchange}`"
64
+ if callback = params[JsonpCallbackParamKey]
65
+ log "Wrapped in JSONP callback `#{callback}`"
66
+ "#{callback}(#{message});"
67
+ else
68
+ message
69
+ end
70
+ }
71
+ }
72
+ }
73
+ }
74
+ end
75
+
76
+ private
77
+ def javascript_include_tag(*scripts)
78
+ scripts.map{|script| %{<script type="text/javascript" src="#{request.script_name}/assets/javascripts/#{script.split(/\.js$/).first}.js"></script>} }.join("\n")
79
+ end
80
+
81
+ def log(message)
82
+ logger.debug "Push #{sid}: #{message}"
83
+ end
84
+
85
+ def queue
86
+ @queue ||= channel.queue("#{sid}@#{exchange}", :arguments => {'x-expires' => Push.config.amqp.queue_ttl * 1000}) # RabbitMQ wants milliseconds
87
+ end
88
+
89
+ def exchange
90
+ @exchange ||= env['PATH_INFO']
91
+ end
92
+
93
+ def channel
94
+ # The prefetch tells AMQP that we only want to grab one message at most when we connect to the queue. This prevents
95
+ # messages from being dropped or not ack'ed when the client comes back around to reconnect.
96
+ @channel ||= ::AMQP::Channel.new(Connection).prefetch(1)
97
+ end
98
+
99
+ def kill_queue
100
+ unless @unsubscribed
101
+ @unsubscribed = true
102
+ log "Unsubscribing from `#{exchange}`"
103
+ queue.unsubscribe {
104
+ channel.close {
105
+ log "Channel closed on close"
106
+ }
107
+ }
108
+ end
109
+ end
110
+
111
+ def sid
112
+ @sid ||= env[SidHttpKey] || params[SidParamKey] || UUID.new.generate
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,2779 @@
1
+ /**
2
+ * easyXDM
3
+ * http://easyxdm.net/
4
+ * Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ */
24
+ (function (window, document, location, setTimeout, decodeURIComponent, encodeURIComponent) {
25
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
26
+ /*global JSON, XMLHttpRequest, window, escape, unescape, ActiveXObject */
27
+ //
28
+ // easyXDM
29
+ // http://easyxdm.net/
30
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
31
+ //
32
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
33
+ // of this software and associated documentation files (the "Software"), to deal
34
+ // in the Software without restriction, including without limitation the rights
35
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36
+ // copies of the Software, and to permit persons to whom the Software is
37
+ // furnished to do so, subject to the following conditions:
38
+ //
39
+ // The above copyright notice and this permission notice shall be included in
40
+ // all copies or substantial portions of the Software.
41
+ //
42
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
48
+ // THE SOFTWARE.
49
+ //
50
+
51
+ var global = this;
52
+ var channelId = Math.floor(Math.random() * 10000); // randomize the initial id in case of multiple closures loaded
53
+ var emptyFn = Function.prototype;
54
+ var reURI = /^((http.?:)\/\/([^:\/\s]+)(:\d+)*)/; // returns groups for protocol (2), domain (3) and port (4)
55
+ var reParent = /[\-\w]+\/\.\.\//; // matches a foo/../ expression
56
+ var reDoubleSlash = /([^:])\/\//g; // matches // anywhere but in the protocol
57
+ var namespace = ""; // stores namespace under which easyXDM object is stored on the page (empty if object is global)
58
+ var easyXDM = {};
59
+ var _easyXDM = window.easyXDM; // map over global easyXDM in case of overwrite
60
+ var IFRAME_PREFIX = "easyXDM_";
61
+ var HAS_NAME_PROPERTY_BUG;
62
+ var useHash = false; // whether to use the hash over the query
63
+ var _trace = emptyFn;
64
+
65
+
66
+ // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
67
+ function isHostMethod(object, property){
68
+ var t = typeof object[property];
69
+ return t == 'function' ||
70
+ (!!(t == 'object' && object[property])) ||
71
+ t == 'unknown';
72
+ }
73
+
74
+ function isHostObject(object, property){
75
+ return !!(typeof(object[property]) == 'object' && object[property]);
76
+ }
77
+
78
+ // end
79
+
80
+ // http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
81
+ function isArray(o){
82
+ return Object.prototype.toString.call(o) === '[object Array]';
83
+ }
84
+
85
+ // end
86
+
87
+ function hasActiveX(name){
88
+ try {
89
+ var activeX = new ActiveXObject(name);
90
+ activeX = null;
91
+ return true;
92
+ }
93
+ catch (notSupportedException) {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ /*
99
+ * Cross Browser implementation for adding and removing event listeners.
100
+ */
101
+ var on, un;
102
+ if (isHostMethod(window, "addEventListener")) {
103
+ on = function(target, type, listener){
104
+ _trace("adding listener " + type);
105
+ target.addEventListener(type, listener, false);
106
+ };
107
+ un = function(target, type, listener){
108
+ _trace("removing listener " + type);
109
+ target.removeEventListener(type, listener, false);
110
+ };
111
+ }
112
+ else if (isHostMethod(window, "attachEvent")) {
113
+ on = function(object, sEvent, fpNotify){
114
+ _trace("adding listener " + sEvent);
115
+ object.attachEvent("on" + sEvent, fpNotify);
116
+ };
117
+ un = function(object, sEvent, fpNotify){
118
+ _trace("removing listener " + sEvent);
119
+ object.detachEvent("on" + sEvent, fpNotify);
120
+ };
121
+ }
122
+ else {
123
+ throw new Error("Browser not supported");
124
+ }
125
+
126
+ /*
127
+ * Cross Browser implementation of DOMContentLoaded.
128
+ */
129
+ var domIsReady = false, domReadyQueue = [], readyState;
130
+ if ("readyState" in document) {
131
+ // If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and
132
+ // 'interactive' (HTML5 specs, recent WebKit builds) states.
133
+ // https://bugs.webkit.org/show_bug.cgi?id=45119
134
+ readyState = document.readyState;
135
+ domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive"));
136
+ }
137
+ else {
138
+ // If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately
139
+ // when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not.
140
+ // We only need a body to add elements to, so the existence of document.body is enough for us.
141
+ domIsReady = !!document.body;
142
+ }
143
+
144
+ function dom_onReady(){
145
+ if (domIsReady) {
146
+ return;
147
+ }
148
+ domIsReady = true;
149
+ _trace("firing dom_onReady");
150
+ for (var i = 0; i < domReadyQueue.length; i++) {
151
+ domReadyQueue[i]();
152
+ }
153
+ domReadyQueue.length = 0;
154
+ }
155
+
156
+
157
+ if (!domIsReady) {
158
+ if (isHostMethod(window, "addEventListener")) {
159
+ on(document, "DOMContentLoaded", dom_onReady);
160
+ }
161
+ else {
162
+ on(document, "readystatechange", function(){
163
+ if (document.readyState == "complete") {
164
+ dom_onReady();
165
+ }
166
+ });
167
+ if (document.documentElement.doScroll && window === top) {
168
+ (function doScrollCheck(){
169
+ if (domIsReady) {
170
+ return;
171
+ }
172
+ // http://javascript.nwbox.com/IEContentLoaded/
173
+ try {
174
+ document.documentElement.doScroll("left");
175
+ }
176
+ catch (e) {
177
+ setTimeout(doScrollCheck, 1);
178
+ return;
179
+ }
180
+ dom_onReady();
181
+ }());
182
+ }
183
+ }
184
+
185
+ // A fallback to window.onload, that will always work
186
+ on(window, "load", dom_onReady);
187
+ }
188
+ /**
189
+ * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
190
+ * If functions are added after this event then they will be executed immediately.
191
+ * @param {function} fn The function to add
192
+ * @param {Object} scope An optional scope for the function to be called with.
193
+ */
194
+ function whenReady(fn, scope){
195
+ if (domIsReady) {
196
+ fn.call(scope);
197
+ return;
198
+ }
199
+ domReadyQueue.push(function(){
200
+ fn.call(scope);
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Returns an instance of easyXDM from the parent window with
206
+ * respect to the namespace.
207
+ *
208
+ * @return An instance of easyXDM (in the parent window)
209
+ */
210
+ function getParentObject(){
211
+ var obj = parent;
212
+ if (namespace !== "") {
213
+ for (var i = 0, ii = namespace.split("."); i < ii.length; i++) {
214
+ if (!obj) {
215
+ throw new Error(ii.slice(0, i + 1).join('.') + ' is not an object');
216
+ }
217
+ obj = obj[ii[i]];
218
+ }
219
+ }
220
+ if (!obj || !obj.easyXDM) {
221
+ throw new Error('Could not find easyXDM in parent.' + namespace);
222
+ }
223
+ return obj.easyXDM;
224
+ }
225
+
226
+ /**
227
+ * Removes easyXDM variable from the global scope. It also returns control
228
+ * of the easyXDM variable to whatever code used it before.
229
+ *
230
+ * @param {String} ns A string representation of an object that will hold
231
+ * an instance of easyXDM.
232
+ * @return An instance of easyXDM
233
+ */
234
+ function noConflict(ns){
235
+ if (typeof ns != "string" || !ns) {
236
+ throw new Error('namespace must be a non-empty string');
237
+ }
238
+ _trace("Settings namespace to '" + ns + "'");
239
+
240
+ window.easyXDM = _easyXDM;
241
+ namespace = ns;
242
+ if (namespace) {
243
+ IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_";
244
+ }
245
+ return easyXDM;
246
+ }
247
+
248
+ /*
249
+ * Methods for working with URLs
250
+ */
251
+ /**
252
+ * Get the domain name from a url.
253
+ * @param {String} url The url to extract the domain from.
254
+ * @return The domain part of the url.
255
+ * @type {String}
256
+ */
257
+ function getDomainName(url){
258
+ if (!url) {
259
+ throw new Error("url is undefined or empty");
260
+ }
261
+ return url.match(reURI)[3];
262
+ }
263
+
264
+ /**
265
+ * Get the port for a given URL, or "" if none
266
+ * @param {String} url The url to extract the port from.
267
+ * @return The port part of the url.
268
+ * @type {String}
269
+ */
270
+ function getPort(url){
271
+ if (!url) {
272
+ throw new Error("url is undefined or empty");
273
+ }
274
+ return url.match(reURI)[4] || "";
275
+ }
276
+
277
+ /**
278
+ * Returns a string containing the schema, domain and if present the port
279
+ * @param {String} url The url to extract the location from
280
+ * @return {String} The location part of the url
281
+ */
282
+ function getLocation(url){
283
+ if (!url) {
284
+ throw new Error("url is undefined or empty");
285
+ }
286
+ if (/^file/.test(url)) {
287
+ throw new Error("The file:// protocol is not supported");
288
+ }
289
+ var m = url.match(reURI);
290
+ var proto = m[2], domain = m[3], port = m[4] || "";
291
+ if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) {
292
+ port = "";
293
+ }
294
+ return proto + "//" + domain + port;
295
+ }
296
+
297
+ /**
298
+ * Resolves a relative url into an absolute one.
299
+ * @param {String} url The path to resolve.
300
+ * @return {String} The resolved url.
301
+ */
302
+ function resolveUrl(url){
303
+ if (!url) {
304
+ throw new Error("url is undefined or empty");
305
+ }
306
+
307
+ // replace all // except the one in proto with /
308
+ url = url.replace(reDoubleSlash, "$1/");
309
+
310
+ // If the url is a valid url we do nothing
311
+ if (!url.match(/^(http||https):\/\//)) {
312
+ // If this is a relative path
313
+ var path = (url.substring(0, 1) === "/") ? "" : location.pathname;
314
+ if (path.substring(path.length - 1) !== "/") {
315
+ path = path.substring(0, path.lastIndexOf("/") + 1);
316
+ }
317
+
318
+ url = location.protocol + "//" + location.host + path + url;
319
+ }
320
+
321
+ // reduce all 'xyz/../' to just ''
322
+ while (reParent.test(url)) {
323
+ url = url.replace(reParent, "");
324
+ }
325
+
326
+ _trace("resolved url '" + url + "'");
327
+ return url;
328
+ }
329
+
330
+ /**
331
+ * Appends the parameters to the given url.<br/>
332
+ * The base url can contain existing query parameters.
333
+ * @param {String} url The base url.
334
+ * @param {Object} parameters The parameters to add.
335
+ * @return {String} A new valid url with the parameters appended.
336
+ */
337
+ function appendQueryParameters(url, parameters){
338
+ if (!parameters) {
339
+ throw new Error("parameters is undefined or null");
340
+ }
341
+
342
+ var hash = "", indexOf = url.indexOf("#");
343
+ if (indexOf !== -1) {
344
+ hash = url.substring(indexOf);
345
+ url = url.substring(0, indexOf);
346
+ }
347
+ var q = [];
348
+ for (var key in parameters) {
349
+ if (parameters.hasOwnProperty(key)) {
350
+ q.push(key + "=" + encodeURIComponent(parameters[key]));
351
+ }
352
+ }
353
+ return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash;
354
+ }
355
+
356
+
357
+ // build the query object either from location.query, if it contains the xdm_e argument, or from location.hash
358
+ var query = (function(input){
359
+ input = input.substring(1).split("&");
360
+ var data = {}, pair, i = input.length;
361
+ while (i--) {
362
+ pair = input[i].split("=");
363
+ data[pair[0]] = decodeURIComponent(pair[1]);
364
+ }
365
+ return data;
366
+ }(/xdm_e=/.test(location.search) ? location.search : location.hash));
367
+
368
+ /*
369
+ * Helper methods
370
+ */
371
+ /**
372
+ * Helper for checking if a variable/property is undefined
373
+ * @param {Object} v The variable to test
374
+ * @return {Boolean} True if the passed variable is undefined
375
+ */
376
+ function undef(v){
377
+ return typeof v === "undefined";
378
+ }
379
+
380
+ /**
381
+ * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
382
+ * @return {JSON} A valid JSON conforming object, or null if not found.
383
+ */
384
+ var getJSON = function(){
385
+ var cached = {};
386
+ var obj = {
387
+ a: [1, 2, 3]
388
+ }, json = "{\"a\":[1,2,3]}";
389
+
390
+ if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) {
391
+ // this is a working JSON instance
392
+ return JSON;
393
+ }
394
+ if (Object.toJSON) {
395
+ if (Object.toJSON(obj).replace((/\s/g), "") === json) {
396
+ // this is a working stringify method
397
+ cached.stringify = Object.toJSON;
398
+ }
399
+ }
400
+
401
+ if (typeof String.prototype.evalJSON === "function") {
402
+ obj = json.evalJSON();
403
+ if (obj.a && obj.a.length === 3 && obj.a[2] === 3) {
404
+ // this is a working parse method
405
+ cached.parse = function(str){
406
+ return str.evalJSON();
407
+ };
408
+ }
409
+ }
410
+
411
+ if (cached.stringify && cached.parse) {
412
+ // Only memoize the result if we have valid instance
413
+ getJSON = function(){
414
+ return cached;
415
+ };
416
+ return cached;
417
+ }
418
+ return null;
419
+ };
420
+
421
+ /**
422
+ * Applies properties from the source object to the target object.<br/>
423
+ * @param {Object} target The target of the properties.
424
+ * @param {Object} source The source of the properties.
425
+ * @param {Boolean} noOverwrite Set to True to only set non-existing properties.
426
+ */
427
+ function apply(destination, source, noOverwrite){
428
+ var member;
429
+ for (var prop in source) {
430
+ if (source.hasOwnProperty(prop)) {
431
+ if (prop in destination) {
432
+ member = source[prop];
433
+ if (typeof member === "object") {
434
+ apply(destination[prop], member, noOverwrite);
435
+ }
436
+ else if (!noOverwrite) {
437
+ destination[prop] = source[prop];
438
+ }
439
+ }
440
+ else {
441
+ destination[prop] = source[prop];
442
+ }
443
+ }
444
+ }
445
+ return destination;
446
+ }
447
+
448
+ // This tests for the bug in IE where setting the [name] property using javascript causes the value to be redirected into [submitName].
449
+ function testForNamePropertyBug(){
450
+ var el = document.createElement("iframe");
451
+ el.name = IFRAME_PREFIX + "TEST";
452
+ apply(el.style, {
453
+ position: "absolute",
454
+ left: "-2000px",
455
+ top: "0px"
456
+ });
457
+ document.body.appendChild(el);
458
+ HAS_NAME_PROPERTY_BUG = el.contentWindow !== window.frames[el.name];
459
+ document.body.removeChild(el);
460
+ _trace("HAS_NAME_PROPERTY_BUG: " + HAS_NAME_PROPERTY_BUG);
461
+ }
462
+
463
+ /**
464
+ * Creates a frame and appends it to the DOM.
465
+ * @param config {object} This object can have the following properties
466
+ * <ul>
467
+ * <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li>
468
+ * <li> {object} attr The attributes that should be set on the frame.</li>
469
+ * <li> {DOMElement} container Its parent element (Optional).</li>
470
+ * <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li>
471
+ * </ul>
472
+ * @return The frames DOMElement
473
+ * @type DOMElement
474
+ */
475
+ function createFrame(config){
476
+ _trace("creating frame: " + config.props.src);
477
+ if (undef(HAS_NAME_PROPERTY_BUG)) {
478
+ testForNamePropertyBug();
479
+ }
480
+ var frame;
481
+ // This is to work around the problems in IE6/7 with setting the name property.
482
+ // Internally this is set as 'submitName' instead when using 'iframe.name = ...'
483
+ // This is not required by easyXDM itself, but is to facilitate other use cases
484
+ if (HAS_NAME_PROPERTY_BUG) {
485
+ frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>");
486
+ }
487
+ else {
488
+ frame = document.createElement("IFRAME");
489
+ frame.name = config.props.name;
490
+ }
491
+
492
+ frame.id = frame.name = config.props.name;
493
+ delete config.props.name;
494
+
495
+ if (config.onLoad) {
496
+ on(frame, "load", config.onLoad);
497
+ }
498
+
499
+ if (typeof config.container == "string") {
500
+ config.container = document.getElementById(config.container);
501
+ }
502
+
503
+ if (!config.container) {
504
+ // This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers.
505
+ frame.style.position = "absolute";
506
+ frame.style.top = "-2000px";
507
+ config.container = document.body;
508
+ }
509
+
510
+ // HACK for some reason, IE needs the source set
511
+ // after the frame has been appended into the DOM
512
+ // so remove the src, and set it afterwards
513
+ var src = config.props.src;
514
+ delete config.props.src;
515
+
516
+ // transfer properties to the frame
517
+ apply(frame, config.props);
518
+
519
+ frame.border = frame.frameBorder = 0;
520
+ config.container.appendChild(frame);
521
+
522
+ // HACK see above
523
+ frame.src = src;
524
+ config.props.src = src;
525
+
526
+ return frame;
527
+ }
528
+
529
+ /**
530
+ * Check whether a domain is allowed using an Access Control List.
531
+ * The ACL can contain * and ? as wildcards, or can be regular expressions.
532
+ * If regular expressions they need to begin with ^ and end with $.
533
+ * @param {Array/String} acl The list of allowed domains
534
+ * @param {String} domain The domain to test.
535
+ * @return {Boolean} True if the domain is allowed, false if not.
536
+ */
537
+ function checkAcl(acl, domain){
538
+ // normalize into an array
539
+ if (typeof acl == "string") {
540
+ acl = [acl];
541
+ }
542
+ var re, i = acl.length;
543
+ while (i--) {
544
+ re = acl[i];
545
+ re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "$"));
546
+ if (re.test(domain)) {
547
+ return true;
548
+ }
549
+ }
550
+ return false;
551
+ }
552
+
553
+ /*
554
+ * Functions related to stacks
555
+ */
556
+ /**
557
+ * Prepares an array of stack-elements suitable for the current configuration
558
+ * @param {Object} config The Transports configuration. See easyXDM.Socket for more.
559
+ * @return {Array} An array of stack-elements with the TransportElement at index 0.
560
+ */
561
+ function prepareTransportStack(config){
562
+ var protocol = config.protocol, stackEls;
563
+ config.isHost = config.isHost || undef(query.xdm_p);
564
+ useHash = config.hash || false;
565
+ _trace("preparing transport stack");
566
+
567
+ if (!config.props) {
568
+ config.props = {};
569
+ }
570
+ if (!config.isHost) {
571
+ _trace("using parameters from query");
572
+ config.channel = query.xdm_c;
573
+ config.secret = query.xdm_s;
574
+ config.remote = query.xdm_e;
575
+ protocol = query.xdm_p;
576
+ if (config.acl && !checkAcl(config.acl, config.remote)) {
577
+ throw new Error("Access denied for " + config.remote);
578
+ }
579
+ }
580
+ else {
581
+ config.remote = resolveUrl(config.remote);
582
+ config.channel = config.channel || "default" + channelId++;
583
+ config.secret = Math.random().toString(16).substring(2);
584
+ if (undef(protocol)) {
585
+ if (getLocation(location.href) == getLocation(config.remote)) {
586
+ /*
587
+ * Both documents has the same origin, lets use direct access.
588
+ */
589
+ protocol = "4";
590
+ }
591
+ else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) {
592
+ /*
593
+ * This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+
594
+ */
595
+ protocol = "1";
596
+ }
597
+ else if (isHostMethod(window, "ActiveXObject") && hasActiveX("ShockwaveFlash.ShockwaveFlash")) {
598
+ /*
599
+ * The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS
600
+ */
601
+ protocol = "6";
602
+ }
603
+ else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) {
604
+ /*
605
+ * This is supported in Gecko (Firefox 1+)
606
+ */
607
+ protocol = "5";
608
+ }
609
+ else if (config.remoteHelper) {
610
+ /*
611
+ * This is supported in all browsers that retains the value of window.name when
612
+ * navigating from one domain to another, and where parent.frames[foo] can be used
613
+ * to get access to a frame from the same domain
614
+ */
615
+ config.remoteHelper = resolveUrl(config.remoteHelper);
616
+ protocol = "2";
617
+ }
618
+ else {
619
+ /*
620
+ * This is supported in all browsers where [window].location is writable for all
621
+ * The resize event will be used if resize is supported and the iframe is not put
622
+ * into a container, else polling will be used.
623
+ */
624
+ protocol = "0";
625
+ }
626
+ _trace("selecting protocol: " + protocol);
627
+ }
628
+ else {
629
+ _trace("using protocol: " + protocol);
630
+ }
631
+ }
632
+
633
+ switch (protocol) {
634
+ case "0":// 0 = HashTransport
635
+ apply(config, {
636
+ interval: 100,
637
+ delay: 2000,
638
+ useResize: true,
639
+ useParent: false,
640
+ usePolling: false
641
+ }, true);
642
+ if (config.isHost) {
643
+ if (!config.local) {
644
+ _trace("looking for image to use as local");
645
+ // If no local is set then we need to find an image hosted on the current domain
646
+ var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image;
647
+ var i = images.length;
648
+ while (i--) {
649
+ image = images[i];
650
+ if (image.src.substring(0, domain.length) === domain) {
651
+ config.local = image.src;
652
+ break;
653
+ }
654
+ }
655
+ if (!config.local) {
656
+ _trace("no image found, defaulting to using the window");
657
+ // If no local was set, and we are unable to find a suitable file, then we resort to using the current window
658
+ config.local = window;
659
+ }
660
+ }
661
+
662
+ var parameters = {
663
+ xdm_c: config.channel,
664
+ xdm_p: 0
665
+ };
666
+
667
+ if (config.local === window) {
668
+ // We are using the current window to listen to
669
+ config.usePolling = true;
670
+ config.useParent = true;
671
+ config.local = location.protocol + "//" + location.host + location.pathname + location.search;
672
+ parameters.xdm_e = config.local;
673
+ parameters.xdm_pa = 1; // use parent
674
+ }
675
+ else {
676
+ parameters.xdm_e = resolveUrl(config.local);
677
+ }
678
+
679
+ if (config.container) {
680
+ config.useResize = false;
681
+ parameters.xdm_po = 1; // use polling
682
+ }
683
+ config.remote = appendQueryParameters(config.remote, parameters);
684
+ }
685
+ else {
686
+ apply(config, {
687
+ channel: query.xdm_c,
688
+ remote: query.xdm_e,
689
+ useParent: !undef(query.xdm_pa),
690
+ usePolling: !undef(query.xdm_po),
691
+ useResize: config.useParent ? false : config.useResize
692
+ });
693
+ }
694
+ stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({
695
+ encode: true,
696
+ maxLength: 4000 - config.remote.length
697
+ }), new easyXDM.stack.VerifyBehavior({
698
+ initiate: config.isHost
699
+ })];
700
+ break;
701
+ case "1":
702
+ stackEls = [new easyXDM.stack.PostMessageTransport(config)];
703
+ break;
704
+ case "2":
705
+ stackEls = [new easyXDM.stack.NameTransport(config), new easyXDM.stack.QueueBehavior(), new easyXDM.stack.VerifyBehavior({
706
+ initiate: config.isHost
707
+ })];
708
+ break;
709
+ case "3":
710
+ stackEls = [new easyXDM.stack.NixTransport(config)];
711
+ break;
712
+ case "4":
713
+ stackEls = [new easyXDM.stack.SameOriginTransport(config)];
714
+ break;
715
+ case "5":
716
+ stackEls = [new easyXDM.stack.FrameElementTransport(config)];
717
+ break;
718
+ case "6":
719
+ if (!config.swf) {
720
+ config.swf = "../../tools/easyxdm.swf";
721
+ }
722
+ stackEls = [new easyXDM.stack.FlashTransport(config)];
723
+ break;
724
+ }
725
+ // this behavior is responsible for buffering outgoing messages, and for performing lazy initialization
726
+ stackEls.push(new easyXDM.stack.QueueBehavior({
727
+ lazy: config.lazy,
728
+ remove: true
729
+ }));
730
+ return stackEls;
731
+ }
732
+
733
+ /**
734
+ * Chains all the separate stack elements into a single usable stack.<br/>
735
+ * If an element is missing a necessary method then it will have a pass-through method applied.
736
+ * @param {Array} stackElements An array of stack elements to be linked.
737
+ * @return {easyXDM.stack.StackElement} The last element in the chain.
738
+ */
739
+ function chainStack(stackElements){
740
+ var stackEl, defaults = {
741
+ incoming: function(message, origin){
742
+ this.up.incoming(message, origin);
743
+ },
744
+ outgoing: function(message, recipient){
745
+ this.down.outgoing(message, recipient);
746
+ },
747
+ callback: function(success){
748
+ this.up.callback(success);
749
+ },
750
+ init: function(){
751
+ this.down.init();
752
+ },
753
+ destroy: function(){
754
+ this.down.destroy();
755
+ }
756
+ };
757
+ for (var i = 0, len = stackElements.length; i < len; i++) {
758
+ stackEl = stackElements[i];
759
+ apply(stackEl, defaults, true);
760
+ if (i !== 0) {
761
+ stackEl.down = stackElements[i - 1];
762
+ }
763
+ if (i !== len - 1) {
764
+ stackEl.up = stackElements[i + 1];
765
+ }
766
+ }
767
+ return stackEl;
768
+ }
769
+
770
+ /**
771
+ * This will remove a stackelement from its stack while leaving the stack functional.
772
+ * @param {Object} element The elment to remove from the stack.
773
+ */
774
+ function removeFromStack(element){
775
+ element.up.down = element.down;
776
+ element.down.up = element.up;
777
+ element.up = element.down = null;
778
+ }
779
+
780
+ /*
781
+ * Export the main object and any other methods applicable
782
+ */
783
+ /**
784
+ * @class easyXDM
785
+ * A javascript library providing cross-browser, cross-domain messaging/RPC.
786
+ * @version 2.4.13.0
787
+ * @singleton
788
+ */
789
+ apply(easyXDM, {
790
+ /**
791
+ * The version of the library
792
+ * @type {string}
793
+ */
794
+ version: "2.4.13.0",
795
+ /**
796
+ * This is a map containing all the query parameters passed to the document.
797
+ * All the values has been decoded using decodeURIComponent.
798
+ * @type {object}
799
+ */
800
+ query: query,
801
+ /**
802
+ * @private
803
+ */
804
+ stack: {},
805
+ /**
806
+ * Applies properties from the source object to the target object.<br/>
807
+ * @param {object} target The target of the properties.
808
+ * @param {object} source The source of the properties.
809
+ * @param {boolean} noOverwrite Set to True to only set non-existing properties.
810
+ */
811
+ apply: apply,
812
+
813
+ /**
814
+ * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
815
+ * @return {JSON} A valid JSON conforming object, or null if not found.
816
+ */
817
+ getJSONObject: getJSON,
818
+ /**
819
+ * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
820
+ * If functions are added after this event then they will be executed immediately.
821
+ * @param {function} fn The function to add
822
+ * @param {object} scope An optional scope for the function to be called with.
823
+ */
824
+ whenReady: whenReady,
825
+ /**
826
+ * Removes easyXDM variable from the global scope. It also returns control
827
+ * of the easyXDM variable to whatever code used it before.
828
+ *
829
+ * @param {String} ns A string representation of an object that will hold
830
+ * an instance of easyXDM.
831
+ * @return An instance of easyXDM
832
+ */
833
+ noConflict: noConflict
834
+ });
835
+
836
+ // Expose helper functions so we can test them
837
+ apply(easyXDM, {
838
+ checkAcl: checkAcl,
839
+ getDomainName: getDomainName,
840
+ getLocation: getLocation,
841
+ appendQueryParameters: appendQueryParameters
842
+ });
843
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
844
+ /*global console, _FirebugCommandLine, easyXDM, window, escape, unescape, isHostObject, undef, _trace, domIsReady, emptyFn, namespace */
845
+ //
846
+ // easyXDM
847
+ // http://easyxdm.net/
848
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
849
+ //
850
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
851
+ // of this software and associated documentation files (the "Software"), to deal
852
+ // in the Software without restriction, including without limitation the rights
853
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
854
+ // copies of the Software, and to permit persons to whom the Software is
855
+ // furnished to do so, subject to the following conditions:
856
+ //
857
+ // The above copyright notice and this permission notice shall be included in
858
+ // all copies or substantial portions of the Software.
859
+ //
860
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
861
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
862
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
863
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
864
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
865
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
866
+ // THE SOFTWARE.
867
+ //
868
+
869
+ var debug = {
870
+ _deferred: [],
871
+ flush: function(){
872
+ this.trace("... deferred messages ...");
873
+ for (var i = 0, len = this._deferred.length; i < len; i++) {
874
+ this.trace(this._deferred[i]);
875
+ }
876
+ this._deferred.length = 0;
877
+ this.trace("... end of deferred messages ...");
878
+ },
879
+ getTime: function(){
880
+ var d = new Date(), h = d.getHours() + "", m = d.getMinutes() + "", s = d.getSeconds() + "", ms = d.getMilliseconds() + "", zeros = "000";
881
+ if (h.length == 1) {
882
+ h = "0" + h;
883
+ }
884
+ if (m.length == 1) {
885
+ m = "0" + m;
886
+ }
887
+ if (s.length == 1) {
888
+ s = "0" + s;
889
+ }
890
+ ms = zeros.substring(ms.length) + ms;
891
+ return h + ":" + m + ":" + s + "." + ms;
892
+ },
893
+ /**
894
+ * Logs the message to console.log if available
895
+ * @param {String} msg The message to log
896
+ */
897
+ log: function(msg){
898
+ // Uses memoizing to cache the implementation
899
+ if (!isHostObject(window, "console") || undef(console.log)) {
900
+ /**
901
+ * Sets log to be an empty function since we have no output available
902
+ * @ignore
903
+ */
904
+ this.log = emptyFn;
905
+ }
906
+ else {
907
+ /**
908
+ * Sets log to be a wrapper around console.log
909
+ * @ignore
910
+ * @param {String} msg
911
+ */
912
+ this.log = function(msg){
913
+ console.log(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ": " + msg);
914
+ };
915
+ }
916
+ this.log(msg);
917
+ },
918
+ /**
919
+ * Will try to trace the given message either to a DOMElement with the id "log",
920
+ * or by using console.info.
921
+ * @param {String} msg The message to trace
922
+ */
923
+ trace: function(msg){
924
+ // Uses memoizing to cache the implementation
925
+ if (!domIsReady) {
926
+ if (this._deferred.length === 0) {
927
+ easyXDM.whenReady(debug.flush, debug);
928
+ }
929
+ this._deferred.push(msg);
930
+ this.log(msg);
931
+ }
932
+ else {
933
+ var el = document.getElementById("log");
934
+ // is there a log element present?
935
+ if (el) {
936
+ /**
937
+ * Sets trace to be a function that outputs the messages to the DOMElement with id "log"
938
+ * @ignore
939
+ * @param {String} msg
940
+ */
941
+ this.trace = function(msg){
942
+ try {
943
+ el.appendChild(document.createElement("div")).appendChild(document.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg));
944
+ el.scrollTop = el.scrollHeight;
945
+ }
946
+ catch (e) {
947
+ //In case we are unloading
948
+ }
949
+ };
950
+ }
951
+ else if (isHostObject(window, "console") && !undef(console.info)) {
952
+ /**
953
+ * Sets trace to be a wrapper around console.info
954
+ * @ignore
955
+ * @param {String} msg
956
+ */
957
+ this.trace = function(msg){
958
+ console.info(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg);
959
+ };
960
+ }
961
+ else {
962
+ /**
963
+ * Create log window
964
+ * @ignore
965
+ */
966
+ var domain = location.host, windowname = domain.replace(/\.|:/g, "") + "easyxdm_log", logWin;
967
+ try {
968
+ logWin = window.open("", windowname, "width=800,height=200,status=0,navigation=0,scrollbars=1");
969
+ }
970
+ catch (e) {
971
+ }
972
+ if (logWin) {
973
+ var doc = logWin.document;
974
+ el = doc.getElementById("log");
975
+ if (!el) {
976
+ doc.write("<html><head><title>easyXDM log " + domain + "</title></head>");
977
+ doc.write("<body><div id=\"log\"></div></body></html>");
978
+ doc.close();
979
+ el = doc.getElementById("log");
980
+ }
981
+ this.trace = function(msg){
982
+ try {
983
+ el.appendChild(doc.createElement("div")).appendChild(doc.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg));
984
+ el.scrollTop = el.scrollHeight;
985
+ }
986
+ catch (e) {
987
+ //In case we are unloading
988
+ }
989
+ };
990
+ this.trace("---- new logger at " + location.href);
991
+ }
992
+
993
+ if (!el) {
994
+ // We are unable to use any logging
995
+ this.trace = emptyFn;
996
+ }
997
+ }
998
+ this.trace(msg);
999
+ }
1000
+ },
1001
+ /**
1002
+ * Creates a method usable for tracing.
1003
+ * @param {String} name The name the messages should be marked with
1004
+ * @return {Function} A function that accepts a single string as argument.
1005
+ */
1006
+ getTracer: function(name){
1007
+ return function(msg){
1008
+ debug.trace(name + ": " + msg);
1009
+ };
1010
+ }
1011
+ };
1012
+ debug.log("easyXDM present on '" + location.href);
1013
+ easyXDM.Debug = debug;
1014
+ _trace = debug.getTracer("{Private}");
1015
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1016
+ /*global easyXDM, window, escape, unescape, isHostObject, isHostMethod, un, on, createFrame, debug */
1017
+ //
1018
+ // easyXDM
1019
+ // http://easyxdm.net/
1020
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1021
+ //
1022
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1023
+ // of this software and associated documentation files (the "Software"), to deal
1024
+ // in the Software without restriction, including without limitation the rights
1025
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1026
+ // copies of the Software, and to permit persons to whom the Software is
1027
+ // furnished to do so, subject to the following conditions:
1028
+ //
1029
+ // The above copyright notice and this permission notice shall be included in
1030
+ // all copies or substantial portions of the Software.
1031
+ //
1032
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1033
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1034
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1035
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1036
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1037
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1038
+ // THE SOFTWARE.
1039
+ //
1040
+
1041
+ /**
1042
+ * @class easyXDM.DomHelper
1043
+ * Contains methods for dealing with the DOM
1044
+ * @singleton
1045
+ */
1046
+ easyXDM.DomHelper = {
1047
+ /**
1048
+ * Provides a consistent interface for adding eventhandlers
1049
+ * @param {Object} target The target to add the event to
1050
+ * @param {String} type The name of the event
1051
+ * @param {Function} listener The listener
1052
+ */
1053
+ on: on,
1054
+ /**
1055
+ * Provides a consistent interface for removing eventhandlers
1056
+ * @param {Object} target The target to remove the event from
1057
+ * @param {String} type The name of the event
1058
+ * @param {Function} listener The listener
1059
+ */
1060
+ un: un,
1061
+ /**
1062
+ * Checks for the presence of the JSON object.
1063
+ * If it is not present it will use the supplied path to load the JSON2 library.
1064
+ * This should be called in the documents head right after the easyXDM script tag.
1065
+ * http://json.org/json2.js
1066
+ * @param {String} path A valid path to json2.js
1067
+ */
1068
+ requiresJSON: function(path){
1069
+ if (!isHostObject(window, "JSON")) {
1070
+ debug.log("loading external JSON");
1071
+ // we need to encode the < in order to avoid an illegal token error
1072
+ // when the script is inlined in a document.
1073
+ document.write('<' + 'script type="text/javascript" src="' + path + '"><' + '/script>');
1074
+ }
1075
+ else {
1076
+ debug.log("native JSON found");
1077
+ }
1078
+ }
1079
+ };
1080
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1081
+ /*global easyXDM, window, escape, unescape, debug */
1082
+ //
1083
+ // easyXDM
1084
+ // http://easyxdm.net/
1085
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1086
+ //
1087
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1088
+ // of this software and associated documentation files (the "Software"), to deal
1089
+ // in the Software without restriction, including without limitation the rights
1090
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1091
+ // copies of the Software, and to permit persons to whom the Software is
1092
+ // furnished to do so, subject to the following conditions:
1093
+ //
1094
+ // The above copyright notice and this permission notice shall be included in
1095
+ // all copies or substantial portions of the Software.
1096
+ //
1097
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1098
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1099
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1100
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1101
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1102
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1103
+ // THE SOFTWARE.
1104
+ //
1105
+
1106
+ (function(){
1107
+ // The map containing the stored functions
1108
+ var _map = {};
1109
+
1110
+ /**
1111
+ * @class easyXDM.Fn
1112
+ * This contains methods related to function handling, such as storing callbacks.
1113
+ * @singleton
1114
+ * @namespace easyXDM
1115
+ */
1116
+ easyXDM.Fn = {
1117
+ /**
1118
+ * Stores a function using the given name for reference
1119
+ * @param {String} name The name that the function should be referred by
1120
+ * @param {Function} fn The function to store
1121
+ * @namespace easyXDM.fn
1122
+ */
1123
+ set: function(name, fn){
1124
+ this._trace("storing function " + name);
1125
+ _map[name] = fn;
1126
+ },
1127
+ /**
1128
+ * Retrieves the function referred to by the given name
1129
+ * @param {String} name The name of the function to retrieve
1130
+ * @param {Boolean} del If the function should be deleted after retrieval
1131
+ * @return {Function} The stored function
1132
+ * @namespace easyXDM.fn
1133
+ */
1134
+ get: function(name, del){
1135
+ this._trace("retrieving function " + name);
1136
+ var fn = _map[name];
1137
+ if (!fn) {
1138
+ this._trace(name + " not found");
1139
+ }
1140
+
1141
+ if (del) {
1142
+ delete _map[name];
1143
+ }
1144
+ return fn;
1145
+ }
1146
+ };
1147
+
1148
+ easyXDM.Fn._trace = debug.getTracer("easyXDM.Fn");
1149
+ }());
1150
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1151
+ /*global easyXDM, window, escape, unescape, chainStack, prepareTransportStack, getLocation, debug */
1152
+ //
1153
+ // easyXDM
1154
+ // http://easyxdm.net/
1155
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1156
+ //
1157
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1158
+ // of this software and associated documentation files (the "Software"), to deal
1159
+ // in the Software without restriction, including without limitation the rights
1160
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1161
+ // copies of the Software, and to permit persons to whom the Software is
1162
+ // furnished to do so, subject to the following conditions:
1163
+ //
1164
+ // The above copyright notice and this permission notice shall be included in
1165
+ // all copies or substantial portions of the Software.
1166
+ //
1167
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1168
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1169
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1170
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1171
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1172
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1173
+ // THE SOFTWARE.
1174
+ //
1175
+
1176
+ /**
1177
+ * @class easyXDM.Socket
1178
+ * This class creates a transport channel between two domains that is usable for sending and receiving string-based messages.<br/>
1179
+ * The channel is reliable, supports queueing, and ensures that the message originates from the expected domain.<br/>
1180
+ * Internally different stacks will be used depending on the browsers features and the available parameters.
1181
+ * <h2>How to set up</h2>
1182
+ * Setting up the provider:
1183
+ * <pre><code>
1184
+ * var socket = new easyXDM.Socket({
1185
+ * &nbsp; local: "name.html",
1186
+ * &nbsp; onReady: function(){
1187
+ * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
1188
+ * &nbsp; &nbsp; socket.postMessage("foo-message");
1189
+ * &nbsp; },
1190
+ * &nbsp; onMessage: function(message, origin) {
1191
+ * &nbsp;&nbsp; alert("received " + message + " from " + origin);
1192
+ * &nbsp; }
1193
+ * });
1194
+ * </code></pre>
1195
+ * Setting up the consumer:
1196
+ * <pre><code>
1197
+ * var socket = new easyXDM.Socket({
1198
+ * &nbsp; remote: "http:&#47;&#47;remotedomain/page.html",
1199
+ * &nbsp; remoteHelper: "http:&#47;&#47;remotedomain/name.html",
1200
+ * &nbsp; onReady: function(){
1201
+ * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
1202
+ * &nbsp; &nbsp; socket.postMessage("foo-message");
1203
+ * &nbsp; },
1204
+ * &nbsp; onMessage: function(message, origin) {
1205
+ * &nbsp;&nbsp; alert("received " + message + " from " + origin);
1206
+ * &nbsp; }
1207
+ * });
1208
+ * </code></pre>
1209
+ * If you are unable to upload the <code>name.html</code> file to the consumers domain then remove the <code>remoteHelper</code> property
1210
+ * and easyXDM will fall back to using the HashTransport instead of the NameTransport when not able to use any of the primary transports.
1211
+ * @namespace easyXDM
1212
+ * @constructor
1213
+ * @cfg {String/Window} local The url to the local name.html document, a local static file, or a reference to the local window.
1214
+ * @cfg {Boolean} lazy (Consumer only) Set this to true if you want easyXDM to defer creating the transport until really needed.
1215
+ * @cfg {String} remote (Consumer only) The url to the providers document.
1216
+ * @cfg {String} remoteHelper (Consumer only) The url to the remote name.html file. This is to support NameTransport as a fallback. Optional.
1217
+ * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. Optional, defaults to 2000.
1218
+ * @cfg {Number} interval The interval used when polling for messages. Optional, defaults to 300.
1219
+ * @cfg {String} channel (Consumer only) The name of the channel to use. Can be used to set consistent iframe names. Must be unique. Optional.
1220
+ * @cfg {Function} onMessage The method that should handle incoming messages.<br/> This method should accept two arguments, the message as a string, and the origin as a string. Optional.
1221
+ * @cfg {Function} onReady A method that should be called when the transport is ready. Optional.
1222
+ * @cfg {DOMElement|String} container (Consumer only) The element, or the id of the element that the primary iframe should be inserted into. If not set then the iframe will be positioned off-screen. Optional.
1223
+ * @cfg {Array/String} acl (Provider only) Here you can specify which '[protocol]://[domain]' patterns that should be allowed to act as the consumer towards this provider.<br/>
1224
+ * This can contain the wildcards ? and *. Examples are 'http://example.com', '*.foo.com' and '*dom?.com'. If you want to use reqular expressions then you pattern needs to start with ^ and end with $.
1225
+ * If none of the patterns match an Error will be thrown.
1226
+ * @cfg {Object} props (Consumer only) Additional properties that should be applied to the iframe. This can also contain nested objects e.g: <code>{style:{width:"100px", height:"100px"}}</code>.
1227
+ * Properties such as 'name' and 'src' will be overrided. Optional.
1228
+ */
1229
+ easyXDM.Socket = function(config){
1230
+ var trace = debug.getTracer("easyXDM.Socket");
1231
+ trace("constructor");
1232
+
1233
+ // create the stack
1234
+ var stack = chainStack(prepareTransportStack(config).concat([{
1235
+ incoming: function(message, origin){
1236
+ config.onMessage(message, origin);
1237
+ },
1238
+ callback: function(success){
1239
+ if (config.onReady) {
1240
+ config.onReady(success);
1241
+ }
1242
+ }
1243
+ }])), recipient = getLocation(config.remote);
1244
+
1245
+ // set the origin
1246
+ this.origin = getLocation(config.remote);
1247
+
1248
+ /**
1249
+ * Initiates the destruction of the stack.
1250
+ */
1251
+ this.destroy = function(){
1252
+ stack.destroy();
1253
+ };
1254
+
1255
+ /**
1256
+ * Posts a message to the remote end of the channel
1257
+ * @param {String} message The message to send
1258
+ */
1259
+ this.postMessage = function(message){
1260
+ stack.outgoing(message, recipient);
1261
+ };
1262
+
1263
+ stack.init();
1264
+ };
1265
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1266
+ /*global easyXDM, window, escape, unescape, undef,, chainStack, prepareTransportStack, debug, getLocation */
1267
+ //
1268
+ // easyXDM
1269
+ // http://easyxdm.net/
1270
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1271
+ //
1272
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1273
+ // of this software and associated documentation files (the "Software"), to deal
1274
+ // in the Software without restriction, including without limitation the rights
1275
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1276
+ // copies of the Software, and to permit persons to whom the Software is
1277
+ // furnished to do so, subject to the following conditions:
1278
+ //
1279
+ // The above copyright notice and this permission notice shall be included in
1280
+ // all copies or substantial portions of the Software.
1281
+ //
1282
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1283
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1284
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1285
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1286
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1287
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1288
+ // THE SOFTWARE.
1289
+ //
1290
+
1291
+ /**
1292
+ * @class easyXDM.Rpc
1293
+ * Creates a proxy object that can be used to call methods implemented on the remote end of the channel, and also to provide the implementation
1294
+ * of methods to be called from the remote end.<br/>
1295
+ * The instantiated object will have methods matching those specified in <code>config.remote</code>.<br/>
1296
+ * This requires the JSON object present in the document, either natively, using json.org's json2 or as a wrapper around library spesific methods.
1297
+ * <h2>How to set up</h2>
1298
+ * <pre><code>
1299
+ * var rpc = new easyXDM.Rpc({
1300
+ * &nbsp; &#47;&#47; this configuration is equal to that used by the Socket.
1301
+ * &nbsp; remote: "http:&#47;&#47;remotedomain/...",
1302
+ * &nbsp; onReady: function(){
1303
+ * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the proxy
1304
+ * &nbsp; &nbsp; rpc.foo(...
1305
+ * &nbsp; }
1306
+ * },{
1307
+ * &nbsp; local: {..},
1308
+ * &nbsp; remote: {..}
1309
+ * });
1310
+ * </code></pre>
1311
+ *
1312
+ * <h2>Exposing functions (procedures)</h2>
1313
+ * <pre><code>
1314
+ * var rpc = new easyXDM.Rpc({
1315
+ * &nbsp; ...
1316
+ * },{
1317
+ * &nbsp; local: {
1318
+ * &nbsp; &nbsp; nameOfMethod: {
1319
+ * &nbsp; &nbsp; &nbsp; method: function(arg1, arg2, success, error){
1320
+ * &nbsp; &nbsp; &nbsp; &nbsp; ...
1321
+ * &nbsp; &nbsp; &nbsp; }
1322
+ * &nbsp; &nbsp; },
1323
+ * &nbsp; &nbsp; &#47;&#47; with shorthand notation
1324
+ * &nbsp; &nbsp; nameOfAnotherMethod: function(arg1, arg2, success, error){
1325
+ * &nbsp; &nbsp; }
1326
+ * &nbsp; },
1327
+ * &nbsp; remote: {...}
1328
+ * });
1329
+ * </code></pre>
1330
+
1331
+ * The function referenced by [method] will receive the passed arguments followed by the callback functions <code>success</code> and <code>error</code>.<br/>
1332
+ * To send a successfull result back you can use
1333
+ * <pre><code>
1334
+ * return foo;
1335
+ * </pre></code>
1336
+ * or
1337
+ * <pre><code>
1338
+ * success(foo);
1339
+ * </pre></code>
1340
+ * To return an error you can use
1341
+ * <pre><code>
1342
+ * throw new Error("foo error");
1343
+ * </code></pre>
1344
+ * or
1345
+ * <pre><code>
1346
+ * error("foo error");
1347
+ * </code></pre>
1348
+ *
1349
+ * <h2>Defining remotely exposed methods (procedures/notifications)</h2>
1350
+ * The definition of the remote end is quite similar:
1351
+ * <pre><code>
1352
+ * var rpc = new easyXDM.Rpc({
1353
+ * &nbsp; ...
1354
+ * },{
1355
+ * &nbsp; local: {...},
1356
+ * &nbsp; remote: {
1357
+ * &nbsp; &nbsp; nameOfMethod: {}
1358
+ * &nbsp; }
1359
+ * });
1360
+ * </code></pre>
1361
+ * To call a remote method use
1362
+ * <pre><code>
1363
+ * rpc.nameOfMethod("arg1", "arg2", function(value) {
1364
+ * &nbsp; alert("success: " + value);
1365
+ * }, function(message) {
1366
+ * &nbsp; alert("error: " + message + );
1367
+ * });
1368
+ * </code></pre>
1369
+ * Both the <code>success</code> and <code>errror</code> callbacks are optional.<br/>
1370
+ * When called with no callback a JSON-RPC 2.0 notification will be executed.
1371
+ * Be aware that you will not be notified of any errors with this method.
1372
+ * <br/>
1373
+ * <h2>Specifying a custom serializer</h2>
1374
+ * If you do not want to use the JSON2 library for non-native JSON support, but instead capabilities provided by some other library
1375
+ * then you can specify a custom serializer using <code>serializer: foo</code>
1376
+ * <pre><code>
1377
+ * var rpc = new easyXDM.Rpc({
1378
+ * &nbsp; ...
1379
+ * },{
1380
+ * &nbsp; local: {...},
1381
+ * &nbsp; remote: {...},
1382
+ * &nbsp; serializer : {
1383
+ * &nbsp; &nbsp; parse: function(string){ ... },
1384
+ * &nbsp; &nbsp; stringify: function(object) {...}
1385
+ * &nbsp; }
1386
+ * });
1387
+ * </code></pre>
1388
+ * If <code>serializer</code> is set then the class will not attempt to use the native implementation.
1389
+ * @namespace easyXDM
1390
+ * @constructor
1391
+ * @param {Object} config The underlying transports configuration. See easyXDM.Socket for available parameters.
1392
+ * @param {Object} jsonRpcConfig The description of the interface to implement.
1393
+ */
1394
+ easyXDM.Rpc = function(config, jsonRpcConfig){
1395
+ var trace = debug.getTracer("easyXDM.Rpc");
1396
+ trace("constructor");
1397
+
1398
+ // expand shorthand notation
1399
+ if (jsonRpcConfig.local) {
1400
+ for (var method in jsonRpcConfig.local) {
1401
+ if (jsonRpcConfig.local.hasOwnProperty(method)) {
1402
+ var member = jsonRpcConfig.local[method];
1403
+ if (typeof member === "function") {
1404
+ jsonRpcConfig.local[method] = {
1405
+ method: member
1406
+ };
1407
+ }
1408
+ }
1409
+ }
1410
+ }
1411
+
1412
+ // create the stack
1413
+ var stack = chainStack(prepareTransportStack(config).concat([new easyXDM.stack.RpcBehavior(this, jsonRpcConfig), {
1414
+ callback: function(success){
1415
+ if (config.onReady) {
1416
+ config.onReady(success);
1417
+ }
1418
+ }
1419
+ }]));
1420
+
1421
+ // set the origin
1422
+ this.origin = getLocation(config.remote);
1423
+
1424
+
1425
+ /**
1426
+ * Initiates the destruction of the stack.
1427
+ */
1428
+ this.destroy = function(){
1429
+ stack.destroy();
1430
+ };
1431
+
1432
+ stack.init();
1433
+ };
1434
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1435
+ /*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, un, on, apply, whenReady, getParentObject, IFRAME_PREFIX*/
1436
+ //
1437
+ // easyXDM
1438
+ // http://easyxdm.net/
1439
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1440
+ //
1441
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1442
+ // of this software and associated documentation files (the "Software"), to deal
1443
+ // in the Software without restriction, including without limitation the rights
1444
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1445
+ // copies of the Software, and to permit persons to whom the Software is
1446
+ // furnished to do so, subject to the following conditions:
1447
+ //
1448
+ // The above copyright notice and this permission notice shall be included in
1449
+ // all copies or substantial portions of the Software.
1450
+ //
1451
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1452
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1453
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1454
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1455
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1456
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1457
+ // THE SOFTWARE.
1458
+ //
1459
+
1460
+ /**
1461
+ * @class easyXDM.stack.SameOriginTransport
1462
+ * SameOriginTransport is a transport class that can be used when both domains have the same origin.<br/>
1463
+ * This can be useful for testing and for when the main application supports both internal and external sources.
1464
+ * @namespace easyXDM.stack
1465
+ * @constructor
1466
+ * @param {Object} config The transports configuration.
1467
+ * @cfg {String} remote The remote document to communicate with.
1468
+ */
1469
+ easyXDM.stack.SameOriginTransport = function(config){
1470
+ var trace = debug.getTracer("easyXDM.stack.SameOriginTransport");
1471
+ trace("constructor");
1472
+ var pub, frame, send, targetOrigin;
1473
+
1474
+ return (pub = {
1475
+ outgoing: function(message, domain, fn){
1476
+ send(message);
1477
+ if (fn) {
1478
+ fn();
1479
+ }
1480
+ },
1481
+ destroy: function(){
1482
+ trace("destroy");
1483
+ if (frame) {
1484
+ frame.parentNode.removeChild(frame);
1485
+ frame = null;
1486
+ }
1487
+ },
1488
+ onDOMReady: function(){
1489
+ trace("init");
1490
+ targetOrigin = getLocation(config.remote);
1491
+
1492
+ if (config.isHost) {
1493
+ // set up the iframe
1494
+ apply(config.props, {
1495
+ src: appendQueryParameters(config.remote, {
1496
+ xdm_e: location.protocol + "//" + location.host + location.pathname,
1497
+ xdm_c: config.channel,
1498
+ xdm_p: 4 // 4 = SameOriginTransport
1499
+ }),
1500
+ name: IFRAME_PREFIX + config.channel + "_provider"
1501
+ });
1502
+ frame = createFrame(config);
1503
+ easyXDM.Fn.set(config.channel, function(sendFn){
1504
+ send = sendFn;
1505
+ setTimeout(function(){
1506
+ pub.up.callback(true);
1507
+ }, 0);
1508
+ return function(msg){
1509
+ pub.up.incoming(msg, targetOrigin);
1510
+ };
1511
+ });
1512
+ }
1513
+ else {
1514
+ send = getParentObject().Fn.get(config.channel, true)(function(msg){
1515
+ pub.up.incoming(msg, targetOrigin);
1516
+ });
1517
+ setTimeout(function(){
1518
+ pub.up.callback(true);
1519
+ }, 0);
1520
+ }
1521
+ },
1522
+ init: function(){
1523
+ whenReady(pub.onDOMReady, pub);
1524
+ }
1525
+ });
1526
+ };
1527
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1528
+ /*global global, easyXDM, window, getLocation, appendQueryParameters, createFrame, debug, apply, whenReady, IFRAME_PREFIX, namespace, getDomainName,, getPort, query*/
1529
+ //
1530
+ // easyXDM
1531
+ // http://easyxdm.net/
1532
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1533
+ //
1534
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1535
+ // of this software and associated documentation files (the "Software"), to deal
1536
+ // in the Software without restriction, including without limitation the rights
1537
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1538
+ // copies of the Software, and to permit persons to whom the Software is
1539
+ // furnished to do so, subject to the following conditions:
1540
+ //
1541
+ // The above copyright notice and this permission notice shall be included in
1542
+ // all copies or substantial portions of the Software.
1543
+ //
1544
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1545
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1546
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1547
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1548
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1549
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1550
+ // THE SOFTWARE.
1551
+ //
1552
+
1553
+ /**
1554
+ * @class easyXDM.stack.FlashTransport
1555
+ * FlashTransport is a transport class that uses an SWF with LocalConnection to pass messages back and forth.
1556
+ * @namespace easyXDM.stack
1557
+ * @constructor
1558
+ * @param {Object} config The transports configuration.
1559
+ * @cfg {String} remote The remote domain to communicate with.
1560
+ * @cfg {String} secret the pre-shared secret used to secure the communication.
1561
+ * @cfg {String} swf The path to the swf file
1562
+ */
1563
+ easyXDM.stack.FlashTransport = function(config){
1564
+ var trace = debug.getTracer("easyXDM.stack.FlashTransport");
1565
+ trace("constructor");
1566
+ if (!config.swf) {
1567
+ throw new Error("Path to easyxdm.swf is missing");
1568
+ }
1569
+ var pub, // the public interface
1570
+ frame, send, targetOrigin, swf, swfContainer;
1571
+
1572
+ function onMessage(message, origin){
1573
+ setTimeout(function(){
1574
+ trace("received message");
1575
+ pub.up.incoming(message, targetOrigin);
1576
+ }, 0);
1577
+ }
1578
+
1579
+ /**
1580
+ * This method adds the SWF to the DOM and prepares the initialization of the channel
1581
+ */
1582
+ function addSwf(callback){
1583
+ // the differentiating query argument is needed in Flash9 to avoid a caching issue where LocalConnection would throw an error.
1584
+ var url = config.swf + "?host=" + config.isHost;
1585
+ var id = "easyXDM_swf_" + Math.floor(Math.random() * 10000);
1586
+
1587
+ // prepare the init function that will fire once the swf is ready
1588
+ easyXDM.Fn.set("flash_loaded", function(){
1589
+ easyXDM.stack.FlashTransport.__swf = swf = swfContainer.firstChild;
1590
+ callback();
1591
+ });
1592
+
1593
+ // create the container that will hold the swf
1594
+ swfContainer = document.createElement('div');
1595
+ apply(swfContainer.style, {
1596
+ height: "1px",
1597
+ width: "1px",
1598
+ position: "absolute",
1599
+ left: 0,
1600
+ top: 0
1601
+ });
1602
+ document.body.appendChild(swfContainer);
1603
+
1604
+ // create the object/embed
1605
+ var flashVars = "proto=" + global.location.protocol + "&domain=" + getDomainName(global.location.href) + "&port=" + getPort(global.location.href) + "&ns=" + namespace;
1606
+ flashVars += "&log=true";
1607
+ swfContainer.innerHTML = "<object height='1' width='1' type='application/x-shockwave-flash' id='" + id + "' data='" + url + "'>" +
1608
+ "<param name='allowScriptAccess' value='always'></param>" +
1609
+ "<param name='wmode' value='transparent'>" +
1610
+ "<param name='movie' value='" +
1611
+ url +
1612
+ "'></param>" +
1613
+ "<param name='flashvars' value='" +
1614
+ flashVars +
1615
+ "'></param>" +
1616
+ "<embed type='application/x-shockwave-flash' FlashVars='" +
1617
+ flashVars +
1618
+ "' allowScriptAccess='always' wmode='transparent' src='" +
1619
+ url +
1620
+ "' height='1' width='1'></embed>" +
1621
+ "</object>";
1622
+ }
1623
+
1624
+ return (pub = {
1625
+ outgoing: function(message, domain, fn){
1626
+ swf.postMessage(config.channel, message.toString());
1627
+ if (fn) {
1628
+ fn();
1629
+ }
1630
+ },
1631
+ destroy: function(){
1632
+ trace("destroy");
1633
+ try {
1634
+ swf.destroyChannel(config.channel);
1635
+ }
1636
+ catch (e) {
1637
+ }
1638
+ swf = null;
1639
+ if (frame) {
1640
+ frame.parentNode.removeChild(frame);
1641
+ frame = null;
1642
+ }
1643
+ },
1644
+ onDOMReady: function(){
1645
+ trace("init");
1646
+
1647
+ targetOrigin = config.remote;
1648
+ swf = easyXDM.stack.FlashTransport.__swf;
1649
+
1650
+ /**
1651
+ * Prepare the code that will be run after the swf has been intialized
1652
+ */
1653
+ easyXDM.Fn.set("flash_" + config.channel + "_init", function(){
1654
+ setTimeout(function(){
1655
+ trace("firing onReady");
1656
+ pub.up.callback(true);
1657
+ });
1658
+ });
1659
+
1660
+ // set up the omMessage handler
1661
+ easyXDM.Fn.set("flash_" + config.channel + "_onMessage", onMessage);
1662
+
1663
+ var fn = function(){
1664
+ // create the channel
1665
+ swf.createChannel(config.channel, config.secret, getLocation(config.remote), config.isHost);
1666
+
1667
+ if (config.isHost) {
1668
+ // set up the iframe
1669
+ apply(config.props, {
1670
+ src: appendQueryParameters(config.remote, {
1671
+ xdm_e: getLocation(location.href),
1672
+ xdm_c: config.channel,
1673
+ xdm_p: 6, // 6 = FlashTransport
1674
+ xdm_s: config.secret
1675
+ }),
1676
+ name: IFRAME_PREFIX + config.channel + "_provider"
1677
+ });
1678
+ frame = createFrame(config);
1679
+ }
1680
+ };
1681
+
1682
+ if (swf) {
1683
+ // if the swf is in place and we are the consumer
1684
+ fn();
1685
+ }
1686
+ else {
1687
+ // if the swf does not yet exist
1688
+ addSwf(fn);
1689
+ }
1690
+ },
1691
+ init: function(){
1692
+ whenReady(pub.onDOMReady, pub);
1693
+ }
1694
+ });
1695
+ };
1696
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1697
+ /*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, un, on, apply, whenReady, IFRAME_PREFIX*/
1698
+ //
1699
+ // easyXDM
1700
+ // http://easyxdm.net/
1701
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1702
+ //
1703
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1704
+ // of this software and associated documentation files (the "Software"), to deal
1705
+ // in the Software without restriction, including without limitation the rights
1706
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1707
+ // copies of the Software, and to permit persons to whom the Software is
1708
+ // furnished to do so, subject to the following conditions:
1709
+ //
1710
+ // The above copyright notice and this permission notice shall be included in
1711
+ // all copies or substantial portions of the Software.
1712
+ //
1713
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1714
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1715
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1716
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1717
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1718
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1719
+ // THE SOFTWARE.
1720
+ //
1721
+
1722
+ /**
1723
+ * @class easyXDM.stack.PostMessageTransport
1724
+ * PostMessageTransport is a transport class that uses HTML5 postMessage for communication.<br/>
1725
+ * <a href="http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx</a><br/>
1726
+ * <a href="https://developer.mozilla.org/en/DOM/window.postMessage">https://developer.mozilla.org/en/DOM/window.postMessage</a>
1727
+ * @namespace easyXDM.stack
1728
+ * @constructor
1729
+ * @param {Object} config The transports configuration.
1730
+ * @cfg {String} remote The remote domain to communicate with.
1731
+ */
1732
+ easyXDM.stack.PostMessageTransport = function(config){
1733
+ var trace = debug.getTracer("easyXDM.stack.PostMessageTransport");
1734
+ trace("constructor");
1735
+ var pub, // the public interface
1736
+ frame, // the remote frame, if any
1737
+ callerWindow, // the window that we will call with
1738
+ targetOrigin; // the domain to communicate with
1739
+ /**
1740
+ * Resolves the origin from the event object
1741
+ * @private
1742
+ * @param {Object} event The messageevent
1743
+ * @return {String} The scheme, host and port of the origin
1744
+ */
1745
+ function _getOrigin(event){
1746
+ if (event.origin) {
1747
+ // This is the HTML5 property
1748
+ return getLocation(event.origin);
1749
+ }
1750
+ if (event.uri) {
1751
+ // From earlier implementations
1752
+ return getLocation(event.uri);
1753
+ }
1754
+ if (event.domain) {
1755
+ // This is the last option and will fail if the
1756
+ // origin is not using the same schema as we are
1757
+ return location.protocol + "//" + event.domain;
1758
+ }
1759
+ throw "Unable to retrieve the origin of the event";
1760
+ }
1761
+
1762
+ /**
1763
+ * This is the main implementation for the onMessage event.<br/>
1764
+ * It checks the validity of the origin and passes the message on if appropriate.
1765
+ * @private
1766
+ * @param {Object} event The messageevent
1767
+ */
1768
+ function _window_onMessage(event){
1769
+ var origin = _getOrigin(event);
1770
+ trace("received message '" + event.data + "' from " + origin);
1771
+ if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ") {
1772
+ pub.up.incoming(event.data.substring(config.channel.length + 1), origin);
1773
+ }
1774
+ }
1775
+
1776
+ return (pub = {
1777
+ outgoing: function(message, domain, fn){
1778
+ callerWindow.postMessage(config.channel + " " + message, domain || targetOrigin);
1779
+ if (fn) {
1780
+ fn();
1781
+ }
1782
+ },
1783
+ destroy: function(){
1784
+ trace("destroy");
1785
+ un(window, "message", _window_onMessage);
1786
+ if (frame) {
1787
+ callerWindow = null;
1788
+ frame.parentNode.removeChild(frame);
1789
+ frame = null;
1790
+ }
1791
+ },
1792
+ onDOMReady: function(){
1793
+ trace("init");
1794
+ targetOrigin = getLocation(config.remote);
1795
+ if (config.isHost) {
1796
+ // add the event handler for listening
1797
+ var waitForReady = function(event){
1798
+ if (event.data == config.channel + "-ready") {
1799
+ trace("firing onReady");
1800
+ // replace the eventlistener
1801
+ callerWindow = ("postMessage" in frame.contentWindow) ? frame.contentWindow : frame.contentWindow.document;
1802
+ un(window, "message", waitForReady);
1803
+ on(window, "message", _window_onMessage);
1804
+ setTimeout(function(){
1805
+ pub.up.callback(true);
1806
+ }, 0);
1807
+ }
1808
+ };
1809
+ on(window, "message", waitForReady);
1810
+
1811
+ // set up the iframe
1812
+ apply(config.props, {
1813
+ src: appendQueryParameters(config.remote, {
1814
+ xdm_e: getLocation(location.href),
1815
+ xdm_c: config.channel,
1816
+ xdm_p: 1 // 1 = PostMessage
1817
+ }),
1818
+ name: IFRAME_PREFIX + config.channel + "_provider"
1819
+ });
1820
+ frame = createFrame(config);
1821
+ }
1822
+ else {
1823
+ // add the event handler for listening
1824
+ on(window, "message", _window_onMessage);
1825
+ callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document;
1826
+ callerWindow.postMessage(config.channel + "-ready", targetOrigin);
1827
+
1828
+ setTimeout(function(){
1829
+ pub.up.callback(true);
1830
+ }, 0);
1831
+ }
1832
+ },
1833
+ init: function(){
1834
+ whenReady(pub.onDOMReady, pub);
1835
+ }
1836
+ });
1837
+ };
1838
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1839
+ /*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, apply, query, whenReady, IFRAME_PREFIX*/
1840
+ //
1841
+ // easyXDM
1842
+ // http://easyxdm.net/
1843
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1844
+ //
1845
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1846
+ // of this software and associated documentation files (the "Software"), to deal
1847
+ // in the Software without restriction, including without limitation the rights
1848
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1849
+ // copies of the Software, and to permit persons to whom the Software is
1850
+ // furnished to do so, subject to the following conditions:
1851
+ //
1852
+ // The above copyright notice and this permission notice shall be included in
1853
+ // all copies or substantial portions of the Software.
1854
+ //
1855
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1856
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1857
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1858
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1859
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1860
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1861
+ // THE SOFTWARE.
1862
+ //
1863
+
1864
+ /**
1865
+ * @class easyXDM.stack.FrameElementTransport
1866
+ * FrameElementTransport is a transport class that can be used with Gecko-browser as these allow passing variables using the frameElement property.<br/>
1867
+ * Security is maintained as Gecho uses Lexical Authorization to determine under which scope a function is running.
1868
+ * @namespace easyXDM.stack
1869
+ * @constructor
1870
+ * @param {Object} config The transports configuration.
1871
+ * @cfg {String} remote The remote document to communicate with.
1872
+ */
1873
+ easyXDM.stack.FrameElementTransport = function(config){
1874
+ var trace = debug.getTracer("easyXDM.stack.FrameElementTransport");
1875
+ trace("constructor");
1876
+ var pub, frame, send, targetOrigin;
1877
+
1878
+ return (pub = {
1879
+ outgoing: function(message, domain, fn){
1880
+ send.call(this, message);
1881
+ if (fn) {
1882
+ fn();
1883
+ }
1884
+ },
1885
+ destroy: function(){
1886
+ trace("destroy");
1887
+ if (frame) {
1888
+ frame.parentNode.removeChild(frame);
1889
+ frame = null;
1890
+ }
1891
+ },
1892
+ onDOMReady: function(){
1893
+ trace("init");
1894
+ targetOrigin = getLocation(config.remote);
1895
+
1896
+ if (config.isHost) {
1897
+ // set up the iframe
1898
+ apply(config.props, {
1899
+ src: appendQueryParameters(config.remote, {
1900
+ xdm_e: getLocation(location.href),
1901
+ xdm_c: config.channel,
1902
+ xdm_p: 5 // 5 = FrameElementTransport
1903
+ }),
1904
+ name: IFRAME_PREFIX + config.channel + "_provider"
1905
+ });
1906
+ frame = createFrame(config);
1907
+ frame.fn = function(sendFn){
1908
+ delete frame.fn;
1909
+ send = sendFn;
1910
+ setTimeout(function(){
1911
+ pub.up.callback(true);
1912
+ }, 0);
1913
+ // remove the function so that it cannot be used to overwrite the send function later on
1914
+ return function(msg){
1915
+ pub.up.incoming(msg, targetOrigin);
1916
+ };
1917
+ };
1918
+ }
1919
+ else {
1920
+ // This is to mitigate origin-spoofing
1921
+ if (document.referrer && getLocation(document.referrer) != query.xdm_e) {
1922
+ window.top.location = query.xdm_e;
1923
+ }
1924
+ send = window.frameElement.fn(function(msg){
1925
+ pub.up.incoming(msg, targetOrigin);
1926
+ });
1927
+ pub.up.callback(true);
1928
+ }
1929
+ },
1930
+ init: function(){
1931
+ whenReady(pub.onDOMReady, pub);
1932
+ }
1933
+ });
1934
+ };
1935
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
1936
+ /*global easyXDM, window, escape, unescape, undef, getLocation, appendQueryParameters, resolveUrl, createFrame, debug, un, apply, whenReady, IFRAME_PREFIX*/
1937
+ //
1938
+ // easyXDM
1939
+ // http://easyxdm.net/
1940
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
1941
+ //
1942
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1943
+ // of this software and associated documentation files (the "Software"), to deal
1944
+ // in the Software without restriction, including without limitation the rights
1945
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1946
+ // copies of the Software, and to permit persons to whom the Software is
1947
+ // furnished to do so, subject to the following conditions:
1948
+ //
1949
+ // The above copyright notice and this permission notice shall be included in
1950
+ // all copies or substantial portions of the Software.
1951
+ //
1952
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1953
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1954
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1955
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1956
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1957
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1958
+ // THE SOFTWARE.
1959
+ //
1960
+
1961
+ /**
1962
+ * @class easyXDM.stack.NameTransport
1963
+ * NameTransport uses the window.name property to relay data.
1964
+ * The <code>local</code> parameter needs to be set on both the consumer and provider,<br/>
1965
+ * and the <code>remoteHelper</code> parameter needs to be set on the consumer.
1966
+ * @constructor
1967
+ * @param {Object} config The transports configuration.
1968
+ * @cfg {String} remoteHelper The url to the remote instance of hash.html - this is only needed for the host.
1969
+ * @namespace easyXDM.stack
1970
+ */
1971
+ easyXDM.stack.NameTransport = function(config){
1972
+ var trace = debug.getTracer("easyXDM.stack.NameTransport");
1973
+ trace("constructor");
1974
+ if (config.isHost && undef(config.remoteHelper)) {
1975
+ trace("missing remoteHelper");
1976
+ throw new Error("missing remoteHelper");
1977
+ }
1978
+
1979
+ var pub; // the public interface
1980
+ var isHost, callerWindow, remoteWindow, readyCount, callback, remoteOrigin, remoteUrl;
1981
+
1982
+ function _sendMessage(message){
1983
+ var url = config.remoteHelper + (isHost ? "#_3" : "#_2") + config.channel;
1984
+ trace("sending message " + message);
1985
+ trace("navigating to '" + url + "'");
1986
+ callerWindow.contentWindow.sendMessage(message, url);
1987
+ }
1988
+
1989
+ function _onReady(){
1990
+ if (isHost) {
1991
+ if (++readyCount === 2 || !isHost) {
1992
+ pub.up.callback(true);
1993
+ }
1994
+ }
1995
+ else {
1996
+ _sendMessage("ready");
1997
+ trace("calling onReady");
1998
+ pub.up.callback(true);
1999
+ }
2000
+ }
2001
+
2002
+ function _onMessage(message){
2003
+ trace("received message " + message);
2004
+ pub.up.incoming(message, remoteOrigin);
2005
+ }
2006
+
2007
+ function _onLoad(){
2008
+ if (callback) {
2009
+ setTimeout(function(){
2010
+ callback(true);
2011
+ }, 0);
2012
+ }
2013
+ }
2014
+
2015
+ return (pub = {
2016
+ outgoing: function(message, domain, fn){
2017
+ callback = fn;
2018
+ _sendMessage(message);
2019
+ },
2020
+ destroy: function(){
2021
+ trace("destroy");
2022
+ callerWindow.parentNode.removeChild(callerWindow);
2023
+ callerWindow = null;
2024
+ if (isHost) {
2025
+ remoteWindow.parentNode.removeChild(remoteWindow);
2026
+ remoteWindow = null;
2027
+ }
2028
+ },
2029
+ onDOMReady: function(){
2030
+ trace("init");
2031
+ isHost = config.isHost;
2032
+ readyCount = 0;
2033
+ remoteOrigin = getLocation(config.remote);
2034
+ config.local = resolveUrl(config.local);
2035
+
2036
+ if (isHost) {
2037
+ // Register the callback
2038
+ easyXDM.Fn.set(config.channel, function(message){
2039
+ trace("received initial message " + message);
2040
+ if (isHost && message === "ready") {
2041
+ // Replace the handler
2042
+ easyXDM.Fn.set(config.channel, _onMessage);
2043
+ _onReady();
2044
+ }
2045
+ });
2046
+
2047
+ // Set up the frame that points to the remote instance
2048
+ remoteUrl = appendQueryParameters(config.remote, {
2049
+ xdm_e: config.local,
2050
+ xdm_c: config.channel,
2051
+ xdm_p: 2
2052
+ });
2053
+ apply(config.props, {
2054
+ src: remoteUrl + '#' + config.channel,
2055
+ name: IFRAME_PREFIX + config.channel + "_provider"
2056
+ });
2057
+ remoteWindow = createFrame(config);
2058
+ }
2059
+ else {
2060
+ config.remoteHelper = config.remote;
2061
+ easyXDM.Fn.set(config.channel, _onMessage);
2062
+ }
2063
+ // Set up the iframe that will be used for the transport
2064
+
2065
+ callerWindow = createFrame({
2066
+ props: {
2067
+ src: config.local + "#_4" + config.channel
2068
+ },
2069
+ onLoad: function onLoad(){
2070
+ // Remove the handler
2071
+ var w = callerWindow || this;
2072
+ un(w, "load", onLoad);
2073
+ easyXDM.Fn.set(config.channel + "_load", _onLoad);
2074
+ (function test(){
2075
+ if (typeof w.contentWindow.sendMessage == "function") {
2076
+ _onReady();
2077
+ }
2078
+ else {
2079
+ setTimeout(test, 50);
2080
+ }
2081
+ }());
2082
+ }
2083
+ });
2084
+ },
2085
+ init: function(){
2086
+ whenReady(pub.onDOMReady, pub);
2087
+ }
2088
+ });
2089
+ };
2090
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
2091
+ /*global easyXDM, window, escape, unescape, getLocation, createFrame, debug, un, on, apply, whenReady, IFRAME_PREFIX*/
2092
+ //
2093
+ // easyXDM
2094
+ // http://easyxdm.net/
2095
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
2096
+ //
2097
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
2098
+ // of this software and associated documentation files (the "Software"), to deal
2099
+ // in the Software without restriction, including without limitation the rights
2100
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2101
+ // copies of the Software, and to permit persons to whom the Software is
2102
+ // furnished to do so, subject to the following conditions:
2103
+ //
2104
+ // The above copyright notice and this permission notice shall be included in
2105
+ // all copies or substantial portions of the Software.
2106
+ //
2107
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2108
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2109
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2110
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2111
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2112
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2113
+ // THE SOFTWARE.
2114
+ //
2115
+
2116
+ /**
2117
+ * @class easyXDM.stack.HashTransport
2118
+ * HashTransport is a transport class that uses the IFrame URL Technique for communication.<br/>
2119
+ * <a href="http://msdn.microsoft.com/en-us/library/bb735305.aspx">http://msdn.microsoft.com/en-us/library/bb735305.aspx</a><br/>
2120
+ * @namespace easyXDM.stack
2121
+ * @constructor
2122
+ * @param {Object} config The transports configuration.
2123
+ * @cfg {String/Window} local The url to the local file used for proxying messages, or the local window.
2124
+ * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window.
2125
+ * @cfg {Number} interval The interval used when polling for messages.
2126
+ */
2127
+ easyXDM.stack.HashTransport = function(config){
2128
+ var trace = debug.getTracer("easyXDM.stack.HashTransport");
2129
+ trace("constructor");
2130
+ var pub;
2131
+ var me = this, isHost, _timer, pollInterval, _lastMsg, _msgNr, _listenerWindow, _callerWindow;
2132
+ var useParent, _remoteOrigin;
2133
+
2134
+ function _sendMessage(message){
2135
+ trace("sending message '" + (_msgNr + 1) + " " + message + "' to " + _remoteOrigin);
2136
+ if (!_callerWindow) {
2137
+ trace("no caller window");
2138
+ return;
2139
+ }
2140
+ var url = config.remote + "#" + (_msgNr++) + "_" + message;
2141
+ ((isHost || !useParent) ? _callerWindow.contentWindow : _callerWindow).location = url;
2142
+ }
2143
+
2144
+ function _handleHash(hash){
2145
+ _lastMsg = hash;
2146
+ trace("received message '" + _lastMsg + "' from " + _remoteOrigin);
2147
+ pub.up.incoming(_lastMsg.substring(_lastMsg.indexOf("_") + 1), _remoteOrigin);
2148
+ }
2149
+
2150
+ /**
2151
+ * Checks location.hash for a new message and relays this to the receiver.
2152
+ * @private
2153
+ */
2154
+ function _pollHash(){
2155
+ if (!_listenerWindow) {
2156
+ return;
2157
+ }
2158
+ var href = _listenerWindow.location.href, hash = "", indexOf = href.indexOf("#");
2159
+ if (indexOf != -1) {
2160
+ hash = href.substring(indexOf);
2161
+ }
2162
+ if (hash && hash != _lastMsg) {
2163
+ trace("poll: new message");
2164
+ _handleHash(hash);
2165
+ }
2166
+ }
2167
+
2168
+ function _attachListeners(){
2169
+ trace("starting polling");
2170
+ _timer = setInterval(_pollHash, pollInterval);
2171
+ }
2172
+
2173
+ return (pub = {
2174
+ outgoing: function(message, domain){
2175
+ _sendMessage(message);
2176
+ },
2177
+ destroy: function(){
2178
+ window.clearInterval(_timer);
2179
+ if (isHost || !useParent) {
2180
+ _callerWindow.parentNode.removeChild(_callerWindow);
2181
+ }
2182
+ _callerWindow = null;
2183
+ },
2184
+ onDOMReady: function(){
2185
+ isHost = config.isHost;
2186
+ pollInterval = config.interval;
2187
+ _lastMsg = "#" + config.channel;
2188
+ _msgNr = 0;
2189
+ useParent = config.useParent;
2190
+ _remoteOrigin = getLocation(config.remote);
2191
+ if (isHost) {
2192
+ config.props = {
2193
+ src: config.remote,
2194
+ name: IFRAME_PREFIX + config.channel + "_provider"
2195
+ };
2196
+ if (useParent) {
2197
+ config.onLoad = function(){
2198
+ _listenerWindow = window;
2199
+ _attachListeners();
2200
+ pub.up.callback(true);
2201
+ };
2202
+ }
2203
+ else {
2204
+ var tries = 0, max = config.delay / 50;
2205
+ (function getRef(){
2206
+ if (++tries > max) {
2207
+ trace("unable to get reference to _listenerWindow, giving up");
2208
+ throw new Error("Unable to reference listenerwindow");
2209
+ }
2210
+ try {
2211
+ _listenerWindow = _callerWindow.contentWindow.frames[IFRAME_PREFIX + config.channel + "_consumer"];
2212
+ }
2213
+ catch (ex) {
2214
+ }
2215
+ if (_listenerWindow) {
2216
+ _attachListeners();
2217
+ trace("got a reference to _listenerWindow");
2218
+ pub.up.callback(true);
2219
+ }
2220
+ else {
2221
+ setTimeout(getRef, 50);
2222
+ }
2223
+ }());
2224
+ }
2225
+ _callerWindow = createFrame(config);
2226
+ }
2227
+ else {
2228
+ _listenerWindow = window;
2229
+ _attachListeners();
2230
+ if (useParent) {
2231
+ _callerWindow = parent;
2232
+ pub.up.callback(true);
2233
+ }
2234
+ else {
2235
+ apply(config, {
2236
+ props: {
2237
+ src: config.remote + "#" + config.channel + new Date(),
2238
+ name: IFRAME_PREFIX + config.channel + "_consumer"
2239
+ },
2240
+ onLoad: function(){
2241
+ pub.up.callback(true);
2242
+ }
2243
+ });
2244
+ _callerWindow = createFrame(config);
2245
+ }
2246
+ }
2247
+ },
2248
+ init: function(){
2249
+ whenReady(pub.onDOMReady, pub);
2250
+ }
2251
+ });
2252
+ };
2253
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
2254
+ /*global easyXDM, window, escape, unescape, debug */
2255
+ //
2256
+ // easyXDM
2257
+ // http://easyxdm.net/
2258
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
2259
+ //
2260
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
2261
+ // of this software and associated documentation files (the "Software"), to deal
2262
+ // in the Software without restriction, including without limitation the rights
2263
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2264
+ // copies of the Software, and to permit persons to whom the Software is
2265
+ // furnished to do so, subject to the following conditions:
2266
+ //
2267
+ // The above copyright notice and this permission notice shall be included in
2268
+ // all copies or substantial portions of the Software.
2269
+ //
2270
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2271
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2272
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2273
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2274
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2275
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2276
+ // THE SOFTWARE.
2277
+ //
2278
+
2279
+ /**
2280
+ * @class easyXDM.stack.ReliableBehavior
2281
+ * This is a behavior that tries to make the underlying transport reliable by using acknowledgements.
2282
+ * @namespace easyXDM.stack
2283
+ * @constructor
2284
+ * @param {Object} config The behaviors configuration.
2285
+ */
2286
+ easyXDM.stack.ReliableBehavior = function(config){
2287
+ var trace = debug.getTracer("easyXDM.stack.ReliableBehavior");
2288
+ trace("constructor");
2289
+ var pub, // the public interface
2290
+ callback; // the callback to execute when we have a confirmed success/failure
2291
+ var idOut = 0, idIn = 0, currentMessage = "";
2292
+
2293
+ return (pub = {
2294
+ incoming: function(message, origin){
2295
+ trace("incoming: " + message);
2296
+ var indexOf = message.indexOf("_"), ack = message.substring(0, indexOf).split(",");
2297
+ message = message.substring(indexOf + 1);
2298
+
2299
+ if (ack[0] == idOut) {
2300
+ trace("message delivered");
2301
+ currentMessage = "";
2302
+ if (callback) {
2303
+ callback(true);
2304
+ }
2305
+ }
2306
+ if (message.length > 0) {
2307
+ trace("sending ack, and passing on " + message);
2308
+ pub.down.outgoing(ack[1] + "," + idOut + "_" + currentMessage, origin);
2309
+ if (idIn != ack[1]) {
2310
+ idIn = ack[1];
2311
+ pub.up.incoming(message, origin);
2312
+ }
2313
+ }
2314
+
2315
+ },
2316
+ outgoing: function(message, origin, fn){
2317
+ currentMessage = message;
2318
+ callback = fn;
2319
+ pub.down.outgoing(idIn + "," + (++idOut) + "_" + message, origin);
2320
+ }
2321
+ });
2322
+ };
2323
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
2324
+ /*global easyXDM, window, escape, unescape, debug, undef, removeFromStack*/
2325
+ //
2326
+ // easyXDM
2327
+ // http://easyxdm.net/
2328
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
2329
+ //
2330
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
2331
+ // of this software and associated documentation files (the "Software"), to deal
2332
+ // in the Software without restriction, including without limitation the rights
2333
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2334
+ // copies of the Software, and to permit persons to whom the Software is
2335
+ // furnished to do so, subject to the following conditions:
2336
+ //
2337
+ // The above copyright notice and this permission notice shall be included in
2338
+ // all copies or substantial portions of the Software.
2339
+ //
2340
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2341
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2342
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2343
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2344
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2345
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2346
+ // THE SOFTWARE.
2347
+ //
2348
+
2349
+ /**
2350
+ * @class easyXDM.stack.QueueBehavior
2351
+ * This is a behavior that enables queueing of messages. <br/>
2352
+ * It will buffer incoming messages and dispach these as fast as the underlying transport allows.
2353
+ * This will also fragment/defragment messages so that the outgoing message is never bigger than the
2354
+ * set length.
2355
+ * @namespace easyXDM.stack
2356
+ * @constructor
2357
+ * @param {Object} config The behaviors configuration. Optional.
2358
+ * @cfg {Number} maxLength The maximum length of each outgoing message. Set this to enable fragmentation.
2359
+ */
2360
+ easyXDM.stack.QueueBehavior = function(config){
2361
+ var trace = debug.getTracer("easyXDM.stack.QueueBehavior");
2362
+ trace("constructor");
2363
+ var pub, queue = [], waiting = true, incoming = "", destroying, maxLength = 0, lazy = false, doFragment = false;
2364
+
2365
+ function dispatch(){
2366
+ if (config.remove && queue.length === 0) {
2367
+ trace("removing myself from the stack");
2368
+ removeFromStack(pub);
2369
+ return;
2370
+ }
2371
+ if (waiting || queue.length === 0 || destroying) {
2372
+ return;
2373
+ }
2374
+ trace("dispatching from queue");
2375
+ waiting = true;
2376
+ var message = queue.shift();
2377
+
2378
+ pub.down.outgoing(message.data, message.origin, function(success){
2379
+ waiting = false;
2380
+ if (message.callback) {
2381
+ setTimeout(function(){
2382
+ message.callback(success);
2383
+ }, 0);
2384
+ }
2385
+ dispatch();
2386
+ });
2387
+ }
2388
+ return (pub = {
2389
+ init: function(){
2390
+ if (undef(config)) {
2391
+ config = {};
2392
+ }
2393
+ if (config.maxLength) {
2394
+ maxLength = config.maxLength;
2395
+ doFragment = true;
2396
+ }
2397
+ if (config.lazy) {
2398
+ lazy = true;
2399
+ }
2400
+ else {
2401
+ pub.down.init();
2402
+ }
2403
+ },
2404
+ callback: function(success){
2405
+ waiting = false;
2406
+ var up = pub.up; // in case dispatch calls removeFromStack
2407
+ dispatch();
2408
+ up.callback(success);
2409
+ },
2410
+ incoming: function(message, origin){
2411
+ if (doFragment) {
2412
+ var indexOf = message.indexOf("_"), seq = parseInt(message.substring(0, indexOf), 10);
2413
+ incoming += message.substring(indexOf + 1);
2414
+ if (seq === 0) {
2415
+ trace("received the last fragment");
2416
+ if (config.encode) {
2417
+ incoming = decodeURIComponent(incoming);
2418
+ }
2419
+ pub.up.incoming(incoming, origin);
2420
+ incoming = "";
2421
+ }
2422
+ else {
2423
+ trace("waiting for more fragments, seq=" + message);
2424
+ }
2425
+ }
2426
+ else {
2427
+ pub.up.incoming(message, origin);
2428
+ }
2429
+ },
2430
+ outgoing: function(message, origin, fn){
2431
+ if (config.encode) {
2432
+ message = encodeURIComponent(message);
2433
+ }
2434
+ var fragments = [], fragment;
2435
+ if (doFragment) {
2436
+ // fragment into chunks
2437
+ while (message.length !== 0) {
2438
+ fragment = message.substring(0, maxLength);
2439
+ message = message.substring(fragment.length);
2440
+ fragments.push(fragment);
2441
+ }
2442
+ // enqueue the chunks
2443
+ while ((fragment = fragments.shift())) {
2444
+ trace("enqueuing");
2445
+ queue.push({
2446
+ data: fragments.length + "_" + fragment,
2447
+ origin: origin,
2448
+ callback: fragments.length === 0 ? fn : null
2449
+ });
2450
+ }
2451
+ }
2452
+ else {
2453
+ queue.push({
2454
+ data: message,
2455
+ origin: origin,
2456
+ callback: fn
2457
+ });
2458
+ }
2459
+ if (lazy) {
2460
+ pub.down.init();
2461
+ }
2462
+ else {
2463
+ dispatch();
2464
+ }
2465
+ },
2466
+ destroy: function(){
2467
+ trace("destroy");
2468
+ destroying = true;
2469
+ pub.down.destroy();
2470
+ }
2471
+ });
2472
+ };
2473
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
2474
+ /*global easyXDM, window, escape, unescape, undef, debug */
2475
+ //
2476
+ // easyXDM
2477
+ // http://easyxdm.net/
2478
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
2479
+ //
2480
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
2481
+ // of this software and associated documentation files (the "Software"), to deal
2482
+ // in the Software without restriction, including without limitation the rights
2483
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2484
+ // copies of the Software, and to permit persons to whom the Software is
2485
+ // furnished to do so, subject to the following conditions:
2486
+ //
2487
+ // The above copyright notice and this permission notice shall be included in
2488
+ // all copies or substantial portions of the Software.
2489
+ //
2490
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2491
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2492
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2493
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2494
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2495
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2496
+ // THE SOFTWARE.
2497
+ //
2498
+
2499
+ /**
2500
+ * @class easyXDM.stack.VerifyBehavior
2501
+ * This behavior will verify that communication with the remote end is possible, and will also sign all outgoing,
2502
+ * and verify all incoming messages. This removes the risk of someone hijacking the iframe to send malicious messages.
2503
+ * @namespace easyXDM.stack
2504
+ * @constructor
2505
+ * @param {Object} config The behaviors configuration.
2506
+ * @cfg {Boolean} initiate If the verification should be initiated from this end.
2507
+ */
2508
+ easyXDM.stack.VerifyBehavior = function(config){
2509
+ var trace = debug.getTracer("easyXDM.stack.VerifyBehavior");
2510
+ trace("constructor");
2511
+ if (undef(config.initiate)) {
2512
+ throw new Error("settings.initiate is not set");
2513
+ }
2514
+ var pub, mySecret, theirSecret, verified = false;
2515
+
2516
+ function startVerification(){
2517
+ trace("requesting verification");
2518
+ mySecret = Math.random().toString(16).substring(2);
2519
+ pub.down.outgoing(mySecret);
2520
+ }
2521
+
2522
+ return (pub = {
2523
+ incoming: function(message, origin){
2524
+ var indexOf = message.indexOf("_");
2525
+ if (indexOf === -1) {
2526
+ if (message === mySecret) {
2527
+ trace("verified, calling callback");
2528
+ pub.up.callback(true);
2529
+ }
2530
+ else if (!theirSecret) {
2531
+ trace("returning secret");
2532
+ theirSecret = message;
2533
+ if (!config.initiate) {
2534
+ startVerification();
2535
+ }
2536
+ pub.down.outgoing(message);
2537
+ }
2538
+ }
2539
+ else {
2540
+ if (message.substring(0, indexOf) === theirSecret) {
2541
+ pub.up.incoming(message.substring(indexOf + 1), origin);
2542
+ }
2543
+ }
2544
+ },
2545
+ outgoing: function(message, origin, fn){
2546
+ pub.down.outgoing(mySecret + "_" + message, origin, fn);
2547
+ },
2548
+ callback: function(success){
2549
+ if (config.initiate) {
2550
+ startVerification();
2551
+ }
2552
+ }
2553
+ });
2554
+ };
2555
+ /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/
2556
+ /*global easyXDM, window, escape, unescape, undef, getJSON, debug, emptyFn, isArray */
2557
+ //
2558
+ // easyXDM
2559
+ // http://easyxdm.net/
2560
+ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
2561
+ //
2562
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
2563
+ // of this software and associated documentation files (the "Software"), to deal
2564
+ // in the Software without restriction, including without limitation the rights
2565
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2566
+ // copies of the Software, and to permit persons to whom the Software is
2567
+ // furnished to do so, subject to the following conditions:
2568
+ //
2569
+ // The above copyright notice and this permission notice shall be included in
2570
+ // all copies or substantial portions of the Software.
2571
+ //
2572
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2573
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2574
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2575
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2576
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2577
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2578
+ // THE SOFTWARE.
2579
+ //
2580
+
2581
+ /**
2582
+ * @class easyXDM.stack.RpcBehavior
2583
+ * This uses JSON-RPC 2.0 to expose local methods and to invoke remote methods and have responses returned over the the string based transport stack.<br/>
2584
+ * Exposed methods can return values synchronous, asyncronous, or bet set up to not return anything.
2585
+ * @namespace easyXDM.stack
2586
+ * @constructor
2587
+ * @param {Object} proxy The object to apply the methods to.
2588
+ * @param {Object} config The definition of the local and remote interface to implement.
2589
+ * @cfg {Object} local The local interface to expose.
2590
+ * @cfg {Object} remote The remote methods to expose through the proxy.
2591
+ * @cfg {Object} serializer The serializer to use for serializing and deserializing the JSON. Should be compatible with the HTML5 JSON object. Optional, will default to JSON.
2592
+ */
2593
+ easyXDM.stack.RpcBehavior = function(proxy, config){
2594
+ var trace = debug.getTracer("easyXDM.stack.RpcBehavior");
2595
+ var pub, serializer = config.serializer || getJSON();
2596
+ var _callbackCounter = 0, _callbacks = {};
2597
+
2598
+ /**
2599
+ * Serializes and sends the message
2600
+ * @private
2601
+ * @param {Object} data The JSON-RPC message to be sent. The jsonrpc property will be added.
2602
+ */
2603
+ function _send(data){
2604
+ data.jsonrpc = "2.0";
2605
+ pub.down.outgoing(serializer.stringify(data));
2606
+ }
2607
+
2608
+ /**
2609
+ * Creates a method that implements the given definition
2610
+ * @private
2611
+ * @param {Object} The method configuration
2612
+ * @param {String} method The name of the method
2613
+ * @return {Function} A stub capable of proxying the requested method call
2614
+ */
2615
+ function _createMethod(definition, method){
2616
+ var slice = Array.prototype.slice;
2617
+
2618
+ trace("creating method " + method);
2619
+ return function(){
2620
+ trace("executing method " + method);
2621
+ var l = arguments.length, callback, message = {
2622
+ method: method
2623
+ };
2624
+
2625
+ if (l > 0 && typeof arguments[l - 1] === "function") {
2626
+ //with callback, procedure
2627
+ if (l > 1 && typeof arguments[l - 2] === "function") {
2628
+ // two callbacks, success and error
2629
+ callback = {
2630
+ success: arguments[l - 2],
2631
+ error: arguments[l - 1]
2632
+ };
2633
+ message.params = slice.call(arguments, 0, l - 2);
2634
+ }
2635
+ else {
2636
+ // single callback, success
2637
+ callback = {
2638
+ success: arguments[l - 1]
2639
+ };
2640
+ message.params = slice.call(arguments, 0, l - 1);
2641
+ }
2642
+ _callbacks["" + (++_callbackCounter)] = callback;
2643
+ message.id = _callbackCounter;
2644
+ }
2645
+ else {
2646
+ // no callbacks, a notification
2647
+ message.params = slice.call(arguments, 0);
2648
+ }
2649
+ if (definition.namedParams && message.params.length === 1) {
2650
+ message.params = message.params[0];
2651
+ }
2652
+ // Send the method request
2653
+ _send(message);
2654
+ };
2655
+ }
2656
+
2657
+ /**
2658
+ * Executes the exposed method
2659
+ * @private
2660
+ * @param {String} method The name of the method
2661
+ * @param {Number} id The callback id to use
2662
+ * @param {Function} method The exposed implementation
2663
+ * @param {Array} params The parameters supplied by the remote end
2664
+ */
2665
+ function _executeMethod(method, id, fn, params){
2666
+ if (!fn) {
2667
+ trace("requested to execute non-existent procedure " + method);
2668
+ if (id) {
2669
+ _send({
2670
+ id: id,
2671
+ error: {
2672
+ code: -32601,
2673
+ message: "Procedure not found."
2674
+ }
2675
+ });
2676
+ }
2677
+ return;
2678
+ }
2679
+
2680
+ trace("requested to execute procedure " + method);
2681
+ var success, error;
2682
+ if (id) {
2683
+ success = function(result){
2684
+ success = emptyFn;
2685
+ _send({
2686
+ id: id,
2687
+ result: result
2688
+ });
2689
+ };
2690
+ error = function(message, data){
2691
+ error = emptyFn;
2692
+ var msg = {
2693
+ id: id,
2694
+ error: {
2695
+ code: -32099,
2696
+ message: message
2697
+ }
2698
+ };
2699
+ if (data) {
2700
+ msg.error.data = data;
2701
+ }
2702
+ _send(msg);
2703
+ };
2704
+ }
2705
+ else {
2706
+ success = error = emptyFn;
2707
+ }
2708
+ // Call local method
2709
+ if (!isArray(params)) {
2710
+ params = [params];
2711
+ }
2712
+ try {
2713
+ var result = fn.method.apply(fn.scope, params.concat([success, error]));
2714
+ if (!undef(result)) {
2715
+ success(result);
2716
+ }
2717
+ }
2718
+ catch (ex1) {
2719
+ error(ex1.message);
2720
+ }
2721
+ }
2722
+
2723
+ return (pub = {
2724
+ incoming: function(message, origin){
2725
+ var data = serializer.parse(message);
2726
+ if (data.method) {
2727
+ trace("received request to execute method " + data.method + (data.id ? (" using callback id " + data.id) : ""));
2728
+ // A method call from the remote end
2729
+ if (config.handle) {
2730
+ config.handle(data, _send);
2731
+ }
2732
+ else {
2733
+ _executeMethod(data.method, data.id, config.local[data.method], data.params);
2734
+ }
2735
+ }
2736
+ else {
2737
+ trace("received return value destined to callback with id " + data.id);
2738
+ // A method response from the other end
2739
+ var callback = _callbacks[data.id];
2740
+ if (data.error) {
2741
+ if (callback.error) {
2742
+ callback.error(data.error);
2743
+ }
2744
+ else {
2745
+ trace("unhandled error returned.");
2746
+ }
2747
+ }
2748
+ else if (callback.success) {
2749
+ callback.success(data.result);
2750
+ }
2751
+ delete _callbacks[data.id];
2752
+ }
2753
+ },
2754
+ init: function(){
2755
+ trace("init");
2756
+ if (config.remote) {
2757
+ trace("creating stubs");
2758
+ // Implement the remote sides exposed methods
2759
+ for (var method in config.remote) {
2760
+ if (config.remote.hasOwnProperty(method)) {
2761
+ proxy[method] = _createMethod(config.remote[method], method);
2762
+ }
2763
+ }
2764
+ }
2765
+ pub.down.init();
2766
+ },
2767
+ destroy: function(){
2768
+ trace("destroy");
2769
+ for (var method in config.remote) {
2770
+ if (config.remote.hasOwnProperty(method) && proxy.hasOwnProperty(method)) {
2771
+ delete proxy[method];
2772
+ }
2773
+ }
2774
+ pub.down.destroy();
2775
+ }
2776
+ });
2777
+ };
2778
+ global.easyXDM = easyXDM;
2779
+ })(window, document, location, window.setTimeout, decodeURIComponent, encodeURIComponent);