pmrpc-rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ Copyright 2013 Robert Haines
2
+
3
+ Pmrpc code Copyright 2011 Ivan Zuzak, Marko Ivankovic
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
@@ -0,0 +1,69 @@
1
+ = Pmrpc packaged for Rails
2
+
3
+ Author:: Robert Haines
4
+ Contact:: mailto:rhaines@manchester.ac.uk
5
+ URL:: https://github.com/hainesr/pmrpc-rails
6
+ Licence:: Apache 2.0 (See LICENCE or http://www.apache.org/licenses/LICENSE-2.0)
7
+ Copyright:: (c) 2013 Robert Haines
8
+
9
+ == Synopsis
10
+
11
+ This gem packages the Pmrpc HTML5 JavaScript library for the Rails (3.1+) asset
12
+ pipeline.
13
+
14
+ == Description
15
+
16
+ Pmrpc is an HTML5 JavaScript library for message passing, remote procedure
17
+ call and publish-subscribe cross-contex communication in the browser. The
18
+ library provides a simple API for exposing and calling procedures between
19
+ browser windows, iframes and web workers, even between different origins.
20
+ Pmrpc also provides several advanced features: callbacks similar to AJAX
21
+ calls, ACL-based access control, asynchronous procedure support and
22
+ fault-tolerance via retries. In case this wasn't clear, pmrpc is not a
23
+ library for browser-server communication, it is a library for communication
24
+ within the browser.
25
+
26
+ Pmrpc is available from https://github.com/izuzak/pmrpc
27
+
28
+ This is purely a gem to package the Pmrpc library for Ruby on Rails.
29
+
30
+ == Installation
31
+
32
+ Simply add this gem to your Gemfile:
33
+
34
+ gem "pmrpc-rails"
35
+
36
+ And add the following to your JavaScript manifest (usually application.js):
37
+
38
+ //= require pmrpc
39
+
40
+ And that is it!
41
+
42
+ == Usage
43
+
44
+ Please see the {Pmrpc documentation}[http://ivanzuzak.info/pmrpc/apidocs.html]
45
+ for how to use it.
46
+
47
+ == Bugs
48
+
49
+ For bugs in Pmrpc itself please see the
50
+ {Pmrpc issue tracker}[https://github.com/izuzak/pmrpc/issues]
51
+
52
+ For bugs in this packaging gem please use the
53
+ {Pmrpc Rails issue tracker}[https://github.com/hainesr/pmrpc-rails/issues]
54
+
55
+ == Customizing Pmrpc itself
56
+
57
+ This repository includes the Pmrpc repository as a submodule; it is
58
+ {contributor friendly}[http://www.solitr.com/blog/2012/04/contributor-friendly-gems/]!
59
+
60
+ So you can easily work on the pmrpc code:
61
+
62
+ cd pmrpc # go into the pmrpc submodule
63
+ git checkout master
64
+ < make your changes >
65
+ cd .. # go back out to the pmrpc-rails repository root
66
+ rake build # rebuild the gem with your pmrpc changes
67
+
68
+ Then if your main app is using your local checkout of the pmrpc-rails gem then
69
+ you will be using your new version of pmrpc next time you refresh your browser.
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ Bundler::GemHelper.install_tasks
9
+
10
+ require 'rake/testtask'
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.libs << 'test'
15
+ t.pattern = 'test/**/*_test.rb'
16
+ t.verbose = false
17
+ end
18
+
19
+ def version
20
+ /^\ \*\ pmrpc\ (.+)\ -/.match(File.read("pmrpc/pmrpc.js"))[1]
21
+ end
22
+
23
+ task :submodule do
24
+ sh "git submodule update --init" unless File.exist?("pmrpc/README.markdown")
25
+ end
26
+
27
+ desc "Remove the vendor directory"
28
+ task :clean do
29
+ rm_rf "vendor"
30
+ end
31
+
32
+ desc "Install the pmrpc.js library"
33
+ task :pmrpc => :submodule do
34
+ Rake.rake_output_message "Copying pmrpc.js"
35
+
36
+ source = "pmrpc/pmrpc.js"
37
+ destination = "vendor/assets/javascript"
38
+ mkdir_p destination
39
+
40
+ FileUtils.cp(source, destination)
41
+ end
42
+
43
+ desc "Update Pmrpc::Rails::PMRPC_VERSION"
44
+ task :version => :submodule do
45
+ Rake.rake_output_message "Seting Pmrpc::Rails::PMRPC_VERSION = \"#{version}\""
46
+
47
+ version_file = "lib/pmrpc/rails/version.rb"
48
+ version_source = File.read(version_file)
49
+ new_version = "PMRPC_VERSION = \"#{version}\""
50
+ version_source.sub!(/PMRPC_VERSION = "[^"]*"/, new_version)
51
+ File.write(version_file, version_source)
52
+ end
53
+
54
+ desc "Clean and then generate everything"
55
+ task :build => [:clean, :pmrpc, :version]
56
+
57
+ task :default => :test
@@ -0,0 +1 @@
1
+ require "pmrpc/rails"
@@ -0,0 +1,2 @@
1
+ require "pmrpc/rails/engine"
2
+ require "pmrpc/rails/version"
@@ -0,0 +1,6 @@
1
+ module Pmrpc
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Pmrpc
2
+ module Rails
3
+ VERSION = "1.0.0"
4
+ PMRPC_VERSION = "0.7.1"
5
+ end
6
+ end
@@ -0,0 +1,695 @@
1
+ /*
2
+ * pmrpc 0.7.1 - Inter-widget remote procedure call library based on HTML5
3
+ * postMessage API and JSON-RPC. https://github.com/izuzak/pmrpc
4
+ *
5
+ * Copyright 2012 Ivan Zuzak, Marko Ivankovic
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+ pmrpc = self.pmrpc = function() {
21
+ // check if JSON library is available
22
+ if (typeof JSON === "undefined" || typeof JSON.stringify === "undefined" ||
23
+ typeof JSON.parse === "undefined") {
24
+ throw "pmrpc requires the JSON library";
25
+ }
26
+
27
+ // TODO: make "contextType" private variable
28
+ // check if postMessage APIs are available
29
+ if (typeof this.postMessage === "undefined" && // window or worker
30
+ typeof this.onconnect === "undefined") { // shared worker
31
+ throw "pmrpc requires the HTML5 cross-document messaging and worker APIs";
32
+ }
33
+
34
+ // Generates a version 4 UUID
35
+ function generateUUID() {
36
+ var uuid = [], nineteen = "89AB", hex = "0123456789ABCDEF";
37
+ for (var i=0; i<36; i++) {
38
+ uuid[i] = hex[Math.floor(Math.random() * 16)];
39
+ }
40
+ uuid[14] = '4';
41
+ uuid[19] = nineteen[Math.floor(Math.random() * 4)];
42
+ uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
43
+ return uuid.join('');
44
+ }
45
+
46
+ // Checks whether a domain satisfies the access control list. The access
47
+ // control list has a whitelist and a blacklist. In order to satisfy the acl,
48
+ // the domain must be on the whitelist, and must not be on the blacklist.
49
+ function checkACL(accessControlList, origin) {
50
+ var aclWhitelist = accessControlList.whitelist;
51
+ var aclBlacklist = accessControlList.blacklist;
52
+
53
+ var isWhitelisted = false;
54
+ var isBlacklisted = false;
55
+
56
+ for (var i=0; i<aclWhitelist.length; ++i) {
57
+ if(origin.match(new RegExp(aclWhitelist[i]))) {
58
+ isWhitelisted = true;
59
+ break;
60
+ }
61
+ }
62
+
63
+ for (var j=0; j<aclBlacklist.length; ++j) {
64
+ if(origin.match(new RegExp(aclBlacklist[j]))) {
65
+ isBlacklisted = true;
66
+ break;
67
+ }
68
+ }
69
+
70
+ return isWhitelisted && !isBlacklisted;
71
+ }
72
+
73
+ // Calls a function with either positional or named parameters
74
+ // In either case, additionalParams will be appended to the end
75
+ function invokeProcedure(fn, self, params, additionalParams) {
76
+ if (!(params instanceof Array)) {
77
+ // get string representation of function
78
+ var fnDef = fn.toString();
79
+
80
+ // parse the string representation and retrieve order of parameters
81
+ var argNames = fnDef.substring(fnDef.indexOf("(")+1, fnDef.indexOf(")"));
82
+ argNames = (argNames === "") ? [] : argNames.split(", ");
83
+
84
+ var argIndexes = {};
85
+ for (var i=0; i<argNames.length; i++) {
86
+ argIndexes[argNames[i]] = i;
87
+ }
88
+
89
+ // construct an array of arguments from a dictionary
90
+ var callParameters = [];
91
+ for (var paramName in params) {
92
+ if (typeof argIndexes[paramName] !== "undefined") {
93
+ callParameters[argIndexes[paramName]] = params[paramName];
94
+ } else {
95
+ throw "No such param: " + paramName;
96
+ }
97
+ }
98
+
99
+ params = callParameters;
100
+ }
101
+
102
+ // append additional parameters
103
+ if (typeof additionalParams !== "undefined") {
104
+ params = params.concat(additionalParams);
105
+ }
106
+
107
+ // invoke function with specified context and arguments array
108
+ return fn.apply(self, params);
109
+ }
110
+
111
+ // JSON encode an object into pmrpc message
112
+ function encode(obj) {
113
+ return "pmrpc." + JSON.stringify(obj);
114
+ }
115
+
116
+ // JSON decode a pmrpc message
117
+ function decode(str) {
118
+ return JSON.parse(str.substring("pmrpc.".length));
119
+ }
120
+
121
+ // Creates a base JSON-RPC object, usable for both request and response.
122
+ // As of JSON-RPC 2.0 it only contains one field "jsonrpc" with value "2.0"
123
+ function createJSONRpcBaseObject() {
124
+ var call = {};
125
+ call.jsonrpc = "2.0";
126
+ return call;
127
+ }
128
+
129
+ // Creates a JSON-RPC request object for the given method and parameters
130
+ function createJSONRpcRequestObject(procedureName, parameters, id) {
131
+ var call = createJSONRpcBaseObject();
132
+ call.method = procedureName;
133
+ call.params = parameters;
134
+ if (typeof id !== "undefined") {
135
+ call.id = id;
136
+ }
137
+ return call;
138
+ }
139
+
140
+ // Creates a JSON-RPC error object complete with message and error code
141
+ function createJSONRpcErrorObject(errorcode, message, data) {
142
+ var error = {};
143
+ error.code = errorcode;
144
+ error.message = message;
145
+ error.data = data;
146
+ return error;
147
+ }
148
+
149
+ // Creates a JSON-RPC response object.
150
+ function createJSONRpcResponseObject(error, result, id) {
151
+ var response = createJSONRpcBaseObject();
152
+ response.id = id;
153
+
154
+ if (typeof error === "undefined" || error === null) {
155
+ response.result = (result === "undefined") ? null : result;
156
+ } else {
157
+ response.error = error;
158
+ }
159
+
160
+ return response;
161
+ }
162
+
163
+ // dictionary of services registered for remote calls
164
+ var registeredServices = {};
165
+ // dictionary of requests being processed on the client side
166
+ var callQueue = {};
167
+
168
+ var reservedProcedureNames = {};
169
+ // register a service available for remote calls
170
+ // if no acl is given, assume that it is available to everyone
171
+ function register(config) {
172
+ if (config.publicProcedureName in reservedProcedureNames) {
173
+ return false;
174
+ } else {
175
+ registeredServices[config.publicProcedureName] = {
176
+ "publicProcedureName" : config.publicProcedureName,
177
+ "procedure" : config.procedure,
178
+ "context" : config.procedure.context,
179
+ "isAsync" : typeof config.isAsynchronous !== "undefined" ?
180
+ config.isAsynchronous : false,
181
+ "acl" : typeof config.acl !== "undefined" ?
182
+ config.acl : {whitelist: ["(.*)"], blacklist: []}};
183
+ return true;
184
+ }
185
+ }
186
+
187
+ // unregister a previously registered procedure
188
+ function unregister(publicProcedureName) {
189
+ if (publicProcedureName in reservedProcedureNames) {
190
+ return false;
191
+ } else {
192
+ delete registeredServices[publicProcedureName];
193
+ return true;
194
+ }
195
+ }
196
+
197
+ // retreive service for a specific procedure name
198
+ function fetchRegisteredService(publicProcedureName){
199
+ return registeredServices[publicProcedureName];
200
+ }
201
+
202
+ // receive and execute a pmrpc call which may be a request or a response
203
+ function processPmrpcMessage(eventParams) {
204
+ var serviceCallEvent = eventParams.event;
205
+ var eventSource = eventParams.source;
206
+ var isWorkerComm = typeof eventSource !== "undefined" && eventSource !== null;
207
+
208
+ // if the message is not for pmrpc, ignore it.
209
+ if (serviceCallEvent.data.indexOf("pmrpc.") !== 0) {
210
+ return;
211
+ } else {
212
+ var message = decode(serviceCallEvent.data);
213
+
214
+ if (typeof message.method !== "undefined") {
215
+ // this is a request
216
+
217
+ var newServiceCallEvent = {
218
+ data : serviceCallEvent.data,
219
+ source : isWorkerComm ? eventSource : serviceCallEvent.source,
220
+ origin : isWorkerComm ? "*" : serviceCallEvent.origin,
221
+ shouldCheckACL : !isWorkerComm
222
+ };
223
+
224
+ var response = processJSONRpcRequest(message, newServiceCallEvent);
225
+
226
+ // return the response
227
+ if (response !== null) {
228
+ sendPmrpcMessage(
229
+ newServiceCallEvent.source, response, newServiceCallEvent.origin);
230
+ }
231
+ } else {
232
+ // this is a response
233
+ processJSONRpcResponse(message);
234
+ }
235
+ }
236
+ }
237
+
238
+ // Process a single JSON-RPC Request
239
+ function processJSONRpcRequest(request, serviceCallEvent, shouldCheckACL) {
240
+ if (request.jsonrpc !== "2.0") {
241
+ // Invalid JSON-RPC request
242
+ return createJSONRpcResponseObject(
243
+ createJSONRpcErrorObject(-32600, "Invalid request.",
244
+ "The recived JSON is not a valid JSON-RPC 2.0 request."),
245
+ null,
246
+ null);
247
+ }
248
+
249
+ var id = request.id;
250
+ var service = fetchRegisteredService(request.method);
251
+
252
+ if (typeof service !== "undefined") {
253
+ // check the acl rights
254
+ if (!serviceCallEvent.shouldCheckACL ||
255
+ checkACL(service.acl, serviceCallEvent.origin)) {
256
+ try {
257
+ if (service.isAsync) {
258
+ // if the service is async, create a callback which the service
259
+ // must call in order to send a response back
260
+ var cb = function (returnValue) {
261
+ sendPmrpcMessage(
262
+ serviceCallEvent.source,
263
+ createJSONRpcResponseObject(null, returnValue, id),
264
+ serviceCallEvent.origin);
265
+ };
266
+ // create a errorback which the service
267
+ // must call in order to send an error back
268
+ var eb = function (errorValue) {
269
+ sendPmrpcMessage(
270
+ serviceCallEvent.source,
271
+ createJSONRpcResponseObject(
272
+ createJSONRpcErrorObject(
273
+ -1, "Application error.",errorValue.message),
274
+ null, id),
275
+ serviceCallEvent.origin);
276
+ };
277
+ invokeProcedure(
278
+ service.procedure, service.context, request.params, [cb, eb, serviceCallEvent]);
279
+ return null;
280
+ } else {
281
+ // if the service is not async, just call it and return the value
282
+ var returnValue = invokeProcedure(
283
+ service.procedure,
284
+ service.context,
285
+ request.params, [serviceCallEvent]);
286
+ return (typeof id === "undefined") ? null :
287
+ createJSONRpcResponseObject(null, returnValue, id);
288
+ }
289
+ } catch (error) {
290
+ if (typeof id === "undefined") {
291
+ // it was a notification nobody cares if it fails
292
+ return null;
293
+ }
294
+
295
+ if (error.match("^(No such param)")) {
296
+ return createJSONRpcResponseObject(
297
+ createJSONRpcErrorObject(
298
+ -32602, "Invalid params.", error.message),
299
+ null,
300
+ id);
301
+ }
302
+
303
+ // the -1 value is "application defined"
304
+ return createJSONRpcResponseObject(
305
+ createJSONRpcErrorObject(
306
+ -1, "Application error.", error.message),
307
+ null,
308
+ id);
309
+ }
310
+ } else {
311
+ // access denied
312
+ return (typeof id === "undefined") ? null : createJSONRpcResponseObject(
313
+ createJSONRpcErrorObject(
314
+ -2, "Application error.", "Access denied on server."),
315
+ null,
316
+ id);
317
+ }
318
+ } else {
319
+ // No such method
320
+ return (typeof id === "undefined") ? null : createJSONRpcResponseObject(
321
+ createJSONRpcErrorObject(
322
+ -32601,
323
+ "Method not found.",
324
+ "The requestd remote procedure does not exist or is not available."),
325
+ null,
326
+ id);
327
+ }
328
+ }
329
+
330
+ // internal rpc service that receives responses for rpc calls
331
+ function processJSONRpcResponse(response) {
332
+ var id = response.id;
333
+ var callObj = callQueue[id];
334
+ if (typeof callObj === "undefined" || callObj === null) {
335
+ return;
336
+ } else {
337
+ delete callQueue[id];
338
+ }
339
+
340
+ // check if the call was sucessful or not
341
+ if (typeof response.error === "undefined") {
342
+ callObj.onSuccess( {
343
+ "destination" : callObj.destination,
344
+ "publicProcedureName" : callObj.publicProcedureName,
345
+ "params" : callObj.params,
346
+ "status" : "success",
347
+ "returnValue" : response.result} );
348
+ } else {
349
+ callObj.onError( {
350
+ "destination" : callObj.destination,
351
+ "publicProcedureName" : callObj.publicProcedureName,
352
+ "params" : callObj.params,
353
+ "status" : "error",
354
+ "message" : response.error.message + " " + response.error.data} );
355
+ }
356
+ }
357
+
358
+ // call remote procedure
359
+ function call(config) {
360
+ // check that number of retries is not -1, that is a special internal value
361
+ if (config.retries && config.retries < 0) {
362
+ throw new Exception("number of retries must be 0 or higher");
363
+ }
364
+
365
+ var destContexts = [];
366
+
367
+ if (typeof config.destination === "undefined" || config.destination === null || config.destination === "workerParent") {
368
+ destContexts = [{context : null, type : "workerParent"}];
369
+ } else if (config.destination === "publish") {
370
+ destContexts = findAllReachableContexts();
371
+ } else if (config.destination instanceof Array) {
372
+ for (var i=0; i<config.destination.length; i++) {
373
+ if (config.destination[i] === "workerParent") {
374
+ destContexts.push({context : null, type : "workerParent"});
375
+ } else if (typeof config.destination[i].frames !== "undefined") {
376
+ destContexts.push({context : config.destination[i], type : "window"});
377
+ } else {
378
+ destContexts.push({context : config.destination[i], type : "worker"});
379
+ }
380
+ }
381
+ } else {
382
+ if (typeof config.destination.frames !== "undefined") {
383
+ destContexts.push({context : config.destination, type : "window"});
384
+ } else {
385
+ destContexts.push({context : config.destination, type : "worker"});
386
+ }
387
+ }
388
+
389
+ for (var i=0; i<destContexts.length; i++) {
390
+ var callObj = {
391
+ destination : destContexts[i].context,
392
+ destinationDomain : typeof config.destinationDomain === "undefined" ? ["*"] : (typeof config.destinationDomain === "string" ? [config.destinationDomain] : config.destinationDomain),
393
+ publicProcedureName : config.publicProcedureName,
394
+ onSuccess : typeof config.onSuccess !== "undefined" ?
395
+ config.onSuccess : function (){},
396
+ onError : typeof config.onError !== "undefined" ?
397
+ config.onError : function (){},
398
+ retries : typeof config.retries !== "undefined" ? config.retries : 5,
399
+ timeout : typeof config.timeout !== "undefined" ? config.timeout : 500,
400
+ status : "requestNotSent"
401
+ };
402
+
403
+ isNotification = typeof config.onError === "undefined" && typeof config.onSuccess === "undefined";
404
+ params = (typeof config.params !== "undefined") ? config.params : [];
405
+ callId = generateUUID();
406
+ callQueue[callId] = callObj;
407
+
408
+ if (isNotification) {
409
+ callObj.message = createJSONRpcRequestObject(
410
+ config.publicProcedureName, params);
411
+ } else {
412
+ callObj.message = createJSONRpcRequestObject(
413
+ config.publicProcedureName, params, callId);
414
+ }
415
+
416
+ waitAndSendRequest(callId);
417
+ }
418
+ }
419
+
420
+ // Use the postMessage API to send a pmrpc message to a destination
421
+ function sendPmrpcMessage(destination, message, acl) {
422
+ if (typeof destination === "undefined" || destination === null) {
423
+ self.postMessage(encode(message));
424
+ } else if (typeof destination.frames !== "undefined") {
425
+ return destination.postMessage(encode(message), acl);
426
+ } else {
427
+ destination.postMessage(encode(message));
428
+ }
429
+ }
430
+
431
+ // Execute a remote call by first pinging the destination and afterwards
432
+ // sending the request
433
+ function waitAndSendRequest(callId) {
434
+ var callObj = callQueue[callId];
435
+ if (typeof callObj === "undefined") {
436
+ return;
437
+ } else if (callObj.retries <= -1) {
438
+ processJSONRpcResponse(
439
+ createJSONRpcResponseObject(
440
+ createJSONRpcErrorObject(
441
+ -4, "Application error.", "Destination unavailable."),
442
+ null,
443
+ callId));
444
+ } else if (callObj.status === "requestSent") {
445
+ return;
446
+ } else if (callObj.retries === 0 || callObj.status === "available") {
447
+ callObj.status = "requestSent";
448
+ callObj.retries = -1;
449
+ callQueue[callId] = callObj;
450
+ for (var i=0; i<callObj.destinationDomain.length; i++) {
451
+ sendPmrpcMessage(
452
+ callObj.destination, callObj.message, callObj.destinationDomain[i], callObj);
453
+ self.setTimeout(function() { waitAndSendRequest(callId); }, callObj.timeout);
454
+ }
455
+ } else {
456
+ // if we can ping some more - send a new ping request
457
+ callObj.status = "pinging";
458
+ var retries = callObj.retries;
459
+ callObj.retries = retries - 1;
460
+
461
+ call({
462
+ "destination" : callObj.destination,
463
+ "publicProcedureName" : "receivePingRequest",
464
+ "onSuccess" : function (callResult) {
465
+ if (callResult.returnValue === true &&
466
+ typeof callQueue[callId] !== 'undefined') {
467
+ callQueue[callId].status = "available";
468
+ waitAndSendRequest(callId);
469
+ }
470
+ },
471
+ "params" : [callObj.publicProcedureName],
472
+ "retries" : 0,
473
+ "destinationDomain" : callObj.destinationDomain});
474
+ callQueue[callId] = callObj;
475
+ self.setTimeout(function() {
476
+ if (callQueue[callId] && callQueue[callId].status === "pinging") {
477
+ waitAndSendRequest(callId);
478
+ }
479
+ }, callObj.timeout / retries);
480
+ }
481
+ }
482
+
483
+ // attach the pmrpc event listener
484
+ function addCrossBrowserEventListerner(obj, eventName, handler, bubble) {
485
+ if ("addEventListener" in obj) {
486
+ // FF
487
+ obj.addEventListener(eventName, handler, bubble);
488
+ } else {
489
+ // IE
490
+ obj.attachEvent("on" + eventName, handler);
491
+ }
492
+ }
493
+
494
+ function createHandler(method, source, destinationType) {
495
+ return function(event) {
496
+ var params = {event : event, source : source, destinationType : destinationType};
497
+ method(params);
498
+ };
499
+ }
500
+
501
+ if ('window' in this) {
502
+ // window object - window-to-window comm
503
+ var handler = createHandler(processPmrpcMessage, null, "window");
504
+ addCrossBrowserEventListerner(this, "message", handler, false);
505
+ } else if ('onmessage' in this) {
506
+ // dedicated worker - parent X to worker comm
507
+ var handler = createHandler(processPmrpcMessage, this, "worker");
508
+ addCrossBrowserEventListerner(this, "message", handler, false);
509
+ } else if ('onconnect' in this) {
510
+ // shared worker - parent X to shared-worker comm
511
+ var connectHandler = function(e) {
512
+ //this.sendPort = e.ports[0];
513
+ var handler = createHandler(processPmrpcMessage, e.ports[0], "sharedWorker");
514
+ addCrossBrowserEventListerner(e.ports[0], "message", handler, false);
515
+ e.ports[0].start();
516
+ };
517
+ addCrossBrowserEventListerner(this, "connect", connectHandler, false);
518
+ } else {
519
+ throw "Pmrpc must be loaded within a browser window or web worker.";
520
+ }
521
+
522
+ // Override Worker and SharedWorker constructors so that pmrpc may relay
523
+ // messages. For each message received from the worker, call pmrpc processing
524
+ // method. This is child worker to parent communication.
525
+
526
+ var createDedicatedWorker = this.Worker;
527
+ this.nonPmrpcWorker = createDedicatedWorker;
528
+ var createSharedWorker = this.SharedWorker;
529
+ this.nonPmrpcSharedWorker = createSharedWorker;
530
+
531
+ var allWorkers = [];
532
+
533
+ this.Worker = function(scriptUri) {
534
+ var newWorker = new createDedicatedWorker(scriptUri);
535
+ allWorkers.push({context : newWorker, type : 'worker'});
536
+ var handler = createHandler(processPmrpcMessage, newWorker, "worker");
537
+ addCrossBrowserEventListerner(newWorker, "message", handler, false);
538
+ return newWorker;
539
+ };
540
+
541
+ this.SharedWorker = function(scriptUri, workerName) {
542
+ var newWorker = new createSharedWorker(scriptUri, workerName);
543
+ allWorkers.push({context : newWorker, type : 'sharedWorker'});
544
+ var handler = createHandler(processPmrpcMessage, newWorker.port, "sharedWorker");
545
+ addCrossBrowserEventListerner(newWorker.port, "message", handler, false);
546
+ newWorker.postMessage = function (msg, portArray) {
547
+ return newWorker.port.postMessage(msg, portArray);
548
+ };
549
+ newWorker.port.start();
550
+ return newWorker;
551
+ };
552
+
553
+ // function that receives pings for methods and returns responses
554
+ function receivePingRequest(publicProcedureName) {
555
+ return typeof fetchRegisteredService(publicProcedureName) !== "undefined";
556
+ }
557
+
558
+ function subscribe(params) {
559
+ return register(params);
560
+ }
561
+
562
+ function unsubscribe(params) {
563
+ return unregister(params);
564
+ }
565
+
566
+ function findAllWindows() {
567
+ var allWindowContexts = [];
568
+
569
+ if (typeof window !== 'undefined') {
570
+ allWindowContexts.push( { context : window.top, type : 'window' } );
571
+
572
+ // walk through all iframes, starting with window.top
573
+ for (var i=0; typeof allWindowContexts[i] !== 'undefined'; i++) {
574
+ var currentWindow = allWindowContexts[i];
575
+ for (var j=0; j<currentWindow.context.frames.length; j++) {
576
+ allWindowContexts.push({
577
+ context : currentWindow.context.frames[j],
578
+ type : 'window'
579
+ });
580
+ }
581
+ }
582
+ } else {
583
+ allWindowContexts.push( {context : this, type : 'workerParent'} );
584
+ }
585
+
586
+ return allWindowContexts;
587
+ }
588
+
589
+ function findAllWorkers() {
590
+ return allWorkers;
591
+ }
592
+
593
+ function findAllReachableContexts() {
594
+ var allWindows = findAllWindows();
595
+ var allWorkers = findAllWorkers();
596
+ var allContexts = allWindows.concat(allWorkers);
597
+
598
+ return allContexts;
599
+ }
600
+
601
+ // register method for receiving and returning pings
602
+ register({
603
+ "publicProcedureName" : "receivePingRequest",
604
+ "procedure" : receivePingRequest});
605
+
606
+ function getRegisteredProcedures() {
607
+ var regSvcs = [];
608
+ var origin = typeof this.frames !== "undefined" ? (window.location.protocol + "//" + window.location.host + (window.location.port !== "" ? ":" + window.location.port : "")) : "";
609
+ for (var publicProcedureName in registeredServices) {
610
+ if (publicProcedureName in reservedProcedureNames) {
611
+ continue;
612
+ } else {
613
+ regSvcs.push( {
614
+ "publicProcedureName" : registeredServices[publicProcedureName].publicProcedureName,
615
+ "acl" : registeredServices[publicProcedureName].acl,
616
+ "origin" : origin
617
+ } );
618
+ }
619
+ }
620
+ return regSvcs;
621
+ }
622
+
623
+ // register method for returning registered procedures
624
+ register({
625
+ "publicProcedureName" : "getRegisteredProcedures",
626
+ "procedure" : getRegisteredProcedures});
627
+
628
+ function discover(params) {
629
+ var windowsForDiscovery = null;
630
+
631
+ if (typeof params.destination === "undefined") {
632
+ windowsForDiscovery = findAllReachableContexts();
633
+ for (var i=0; i<windowsForDiscovery.length; i++) {
634
+ windowsForDiscovery[i] = windowsForDiscovery[i].context;
635
+ }
636
+ } else {
637
+ windowsForDiscovery = params.destination;
638
+ }
639
+ var originRegex = typeof params.originRegex === "undefined" ?
640
+ "(.*)" : params.originRegex;
641
+ var nameRegex = typeof params.nameRegex === "undefined" ?
642
+ "(.*)" : params.nameRegex;
643
+
644
+ var counter = windowsForDiscovery.length;
645
+
646
+ var discoveredMethods = [];
647
+ function addToDiscoveredMethods(methods, destination) {
648
+ for (var i=0; i<methods.length; i++) {
649
+ if (methods[i].origin.match(new RegExp(originRegex)) &&
650
+ methods[i].publicProcedureName.match(new RegExp(nameRegex))) {
651
+ discoveredMethods.push({
652
+ publicProcedureName : methods[i].publicProcedureName,
653
+ destination : destination,
654
+ procedureACL : methods[i].acl,
655
+ destinationOrigin : methods[i].origin
656
+ });
657
+ }
658
+ }
659
+ }
660
+
661
+ pmrpc.call({
662
+ destination : windowsForDiscovery,
663
+ destinationDomain : "*",
664
+ publicProcedureName : "getRegisteredProcedures",
665
+ onSuccess : function (callResult) {
666
+ counter--;
667
+ addToDiscoveredMethods(callResult.returnValue, callResult.destination);
668
+ if (counter === 0) {
669
+ params.callback(discoveredMethods);
670
+ }
671
+ },
672
+ onError : function (callResult) {
673
+ counter--;
674
+ if (counter === 0) {
675
+ params.callback(discoveredMethods);
676
+ }
677
+ }
678
+ });
679
+ }
680
+
681
+ reservedProcedureNames = {"getRegisteredProcedures" : null, "receivePingRequest" : null};
682
+
683
+ // return public methods
684
+ return {
685
+ register : register,
686
+ unregister : unregister,
687
+ call : call,
688
+ discover : discover
689
+ };
690
+ }();
691
+
692
+ //AMD suppport
693
+ if (typeof define == 'function' && define.amd) {
694
+ define(pmrpc);
695
+ }
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pmrpc-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Robert Haines
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: railties
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.1.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rails
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 3.2.14
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 3.2.14
46
+ - !ruby/object:Gem::Dependency
47
+ name: sqlite3
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: ! 'Pmrpc (https://github.com/izuzak/pmrpc) is an HTML5 JavaScript library
63
+ for message passing, remote procedure call and publish-subscribe cross-contex communication
64
+ in the browser. The library provides a simple API for exposing and calling procedures
65
+ between browser windows, iframes and web workers, even between different origins.
66
+ Pmrpc also provides several advanced features: callbacks similar to AJAX calls,
67
+ ACL-based access control, asynchronous procedure support and fault-tolerance via
68
+ retries. In case this wasn''t clear, pmrpc is not a library for browser-server communication,
69
+ it is a library for communication within the browser.'
70
+ email:
71
+ - rhaines@manchester.ac.uk
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/pmrpc/rails/engine.rb
77
+ - lib/pmrpc/rails/version.rb
78
+ - lib/pmrpc/rails.rb
79
+ - lib/pmrpc-rails.rb
80
+ - vendor/assets/javascript/pmrpc.js
81
+ - LICENCE.rdoc
82
+ - Rakefile
83
+ - README.rdoc
84
+ homepage: https://github.com/hainesr/pmrpc-rails
85
+ licenses: []
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ segments:
97
+ - 0
98
+ hash: 1610220569811345664
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ segments:
106
+ - 0
107
+ hash: 1610220569811345664
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.21
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Package the Pmrpc HTML5 JavaScript library for the Rails asset pipeline.
114
+ test_files: []