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.
- 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: []
|