pmrpc-rails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []