pmrpc-rails 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENCE.rdoc +15 -0
- data/README.rdoc +69 -0
- data/Rakefile +57 -0
- data/lib/pmrpc-rails.rb +1 -0
- data/lib/pmrpc/rails.rb +2 -0
- data/lib/pmrpc/rails/engine.rb +6 -0
- data/lib/pmrpc/rails/version.rb +6 -0
- data/vendor/assets/javascript/pmrpc.js +695 -0
- metadata +114 -0
data/LICENCE.rdoc
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/lib/pmrpc-rails.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "pmrpc/rails"
|
data/lib/pmrpc/rails.rb
ADDED
@@ -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: []
|