farcall 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -3
- data/javascript/farcall_wsclient.coffee +278 -0
- data/javascript/farcall_wsclient.js +379 -0
- data/lib/farcall/boss_transport.rb +5 -10
- data/lib/farcall/em_farcall.rb +17 -6
- data/lib/farcall/endpoint.rb +34 -13
- data/lib/farcall/json_transport.rb +5 -10
- data/lib/farcall/promise.rb +113 -0
- data/lib/farcall/transport.rb +26 -0
- data/lib/farcall/version.rb +1 -1
- data/lib/farcall/wsclient_transport.rb +3 -3
- data/spec/websock_spec.rb +54 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 339d4022a832e4cb3a7f54a253ec9b7628d31cab
|
4
|
+
data.tar.gz: 0daea40eb900be7e661790743a6b756229c0870e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28b1ef4372a5fd8d01900096af1d2360aae63bc4bbe3e973cb181d55db5a3dcad91c1d7f6fe1c3c947f4b97b8e562753a2d879453aba5490843ecb562f8909e2
|
7
|
+
data.tar.gz: fe16ade1825ff61a668ff12438c5d6befce8c14a34cbe52f446bd27c2447175c324b7e862577c5b401960e9d3e38d1869d336bc543ffd3ca36b1c18ac72de900
|
data/README.md
CHANGED
@@ -2,15 +2,18 @@
|
|
2
2
|
|
3
3
|
## News
|
4
4
|
|
5
|
-
|
5
|
+
* websocket client and server out of the box. ./javascript folder there is compatible client implementation that works in most browsers with WebSocket support. Just add it to you web project and enjoy.
|
6
|
+
|
7
|
+
* buffering of incoming data in transport to not loose incoming packets beofre Endpoint is connected
|
6
8
|
|
7
9
|
## Description
|
8
10
|
|
9
11
|
The simple and elegant cross-platform RPC protocol that uses any formatter/transport capable of
|
10
12
|
transmitting dictionary-like objects, for example, JSON,
|
11
13
|
[BOSS](https://github.com/sergeych/boss_protocol), XML, BSON and many others. This gem
|
12
|
-
|
13
|
-
|
14
|
+
provides out of the box JSON and [BOSS](https://github.com/sergeych/boss_protocol) protocols and
|
15
|
+
websockets (clientm server, and javascript version for the browser or mayme some node app),
|
16
|
+
EventMachine channels, streams and sockets.
|
14
17
|
|
15
18
|
There is also optional support for eventmachine based wbesocket server and regular client websocket
|
16
19
|
connection. All you need is to include gem 'em-websocket' and/or gem 'websocket-client-simple'.
|
@@ -0,0 +1,278 @@
|
|
1
|
+
root = exports ? this
|
2
|
+
# Farcall RPC protocol client over WebSocket transport.
|
3
|
+
#
|
4
|
+
# This script has no external dependencies except that if you want to use self-test, you'll
|
5
|
+
# need underscore.js. Test silently fails if can't find it.
|
6
|
+
#
|
7
|
+
# See protocol at github: https://github.com/sergeych/farcall/
|
8
|
+
#
|
9
|
+
# provided under MIT license.
|
10
|
+
#
|
11
|
+
# @author real.sergeych@gmail.com
|
12
|
+
#
|
13
|
+
class root.WsFarcall
|
14
|
+
|
15
|
+
# Create transport connected to the given url. A shortcut of (new WsFarrcall(url))
|
16
|
+
@open: (url) ->
|
17
|
+
new WsFarcall(url)
|
18
|
+
|
19
|
+
# Construct a protocol endpoint connected to the given url (must be ws:// or wss:// websocket
|
20
|
+
# url)
|
21
|
+
constructor: (@url) ->
|
22
|
+
@in_serial = 0
|
23
|
+
@out_serial = 0
|
24
|
+
@openHandlers = []
|
25
|
+
@closeHandlers = []
|
26
|
+
@ws = new WebSocket(@url)
|
27
|
+
@connected = false
|
28
|
+
@promises = {}
|
29
|
+
@commandHandlers = {}
|
30
|
+
@ws.onopen = =>
|
31
|
+
@connected = true
|
32
|
+
cb(this) for cb in @openHandlers
|
33
|
+
|
34
|
+
@ws.onclose = =>
|
35
|
+
@connected = false
|
36
|
+
cb(this) for cb in @closeHandlers
|
37
|
+
|
38
|
+
@ws.onmessage = (message) =>
|
39
|
+
@trace and console.log ">>> #{message.data}"
|
40
|
+
@_receive(JSON.parse message.data)
|
41
|
+
|
42
|
+
# add open callback. The callback takes a single argument - the WsFarcall instance
|
43
|
+
onopen: (callback) ->
|
44
|
+
@openHandlers.push callback
|
45
|
+
|
46
|
+
# add close callback. The callback takes a single argument - the WsFarcall instance
|
47
|
+
onclose: (callback) ->
|
48
|
+
@closeHandlers.push callback
|
49
|
+
|
50
|
+
# close the protocol and socket
|
51
|
+
close: ->
|
52
|
+
@ws.close()
|
53
|
+
|
54
|
+
# Call remote function with the specified name. Arguments can be list and.or keyword arguments.
|
55
|
+
# The Farcall arguments can be a list, a dictionary (keywords hash), or both. If the last argument
|
56
|
+
# is the object, it will be treated as dictionary argument. Add extra {} to the end of list if
|
57
|
+
# need.
|
58
|
+
#
|
59
|
+
# Returns Promise instance so you can add .success() (or .done()), fail() and always() handlers to
|
60
|
+
# it. On success callback receives whatever data remote function has returned, on error it receives
|
61
|
+
# the arcall standard error object, e.g. { error: { class: string, text: another_sting} } object.
|
62
|
+
#
|
63
|
+
# always handler receives and object { result: {}, error: {}} where only one of the two is set
|
64
|
+
#
|
65
|
+
call: (name, args...) ->
|
66
|
+
[args, kwargs] = splitArgs(args)
|
67
|
+
promise = @promises[@out_serial] = new Promise()
|
68
|
+
@_send cmd: name, args: args, kwargs: kwargs
|
69
|
+
promise
|
70
|
+
|
71
|
+
# Add an local remote-callable function. The callback receives whatever arguments are send from
|
72
|
+
# thre remote caller and its returned value will be passed to the remote. On error it should throw
|
73
|
+
# an exception.
|
74
|
+
on: (name, callback) ->
|
75
|
+
@commandHandlers[name] = callback
|
76
|
+
|
77
|
+
_receive: (data) ->
|
78
|
+
if data.serial != @in_serial++
|
79
|
+
console.error "farcall framing error"
|
80
|
+
@close()
|
81
|
+
else
|
82
|
+
if data.ref != undefined
|
83
|
+
promise = @promises[data.ref]
|
84
|
+
delete @promises[data.ref]
|
85
|
+
if data.error == undefined
|
86
|
+
promise.setDone(data.result)
|
87
|
+
else
|
88
|
+
promise.setFail(data.error)
|
89
|
+
else
|
90
|
+
@_processCall data
|
91
|
+
|
92
|
+
_send: (params) ->
|
93
|
+
params.serial = @out_serial++
|
94
|
+
params = JSON.stringify(params)
|
95
|
+
@trace and console.log "<<< #{params}"
|
96
|
+
@ws.send params
|
97
|
+
|
98
|
+
_processCall: (data) ->
|
99
|
+
handler = @commandHandlers[data.cmd]
|
100
|
+
if handler
|
101
|
+
try
|
102
|
+
data.args.push data.kwargs
|
103
|
+
@_send ref: data.serial, result: handler(data.args...)
|
104
|
+
catch e
|
105
|
+
@_send ref: data.serial, error: {class: 'RuntimeError', text: e.message}
|
106
|
+
else
|
107
|
+
@_send
|
108
|
+
ref: data.serial, error: {class: 'NoMethodError', text: "method not found: #{data.cmd}"}
|
109
|
+
|
110
|
+
@selfTest: (url, callback) ->
|
111
|
+
if _?.isEqual(1, 1)
|
112
|
+
p1 = false
|
113
|
+
p2 = false
|
114
|
+
p3 = false
|
115
|
+
cb = false
|
116
|
+
cbr = false
|
117
|
+
done = false
|
118
|
+
WsFarcall.open(url + '/fartest').onopen (fcall) ->
|
119
|
+
fcall.call('ping', 1, 2, 3, {hello: 'world'})
|
120
|
+
.done (data) ->
|
121
|
+
p1 = checkEquals(data, {pong: [1, 2, 3, {hello: 'world'}]})
|
122
|
+
fcall.call('ping', 2, 2, 3, {hello: 'world'})
|
123
|
+
.done (data) ->
|
124
|
+
p2 = checkEquals(data, {pong: [2, 2, 3, {hello: 'world'}]})
|
125
|
+
fcall.call('ping', 3, 2, 3, {hello: 'world'})
|
126
|
+
.done (data) ->
|
127
|
+
p3 = checkEquals(data, {pong: [3, 2, 3, {hello: 'world'}]})
|
128
|
+
|
129
|
+
fcall.on 'test_callback', (args...) ->
|
130
|
+
cb = checkEquals(args, [5, 4, 3, {hello: 'world'}])
|
131
|
+
# The callback request should have time to be processed
|
132
|
+
setTimeout ->
|
133
|
+
done = true
|
134
|
+
ok = p1 and p2 and p3 and cb and cbr
|
135
|
+
text = switch
|
136
|
+
when !cb
|
137
|
+
'callback was not called or wrong data'
|
138
|
+
when !cbr
|
139
|
+
'callback request did not return'
|
140
|
+
else
|
141
|
+
if ok then '' else 'ping data wrong'
|
142
|
+
callback(ok, text)
|
143
|
+
, 80
|
144
|
+
|
145
|
+
fcall.call('callback', 'test_callback', 5, 4, 3, hello: 'world')
|
146
|
+
.done (data) ->
|
147
|
+
cbr = true
|
148
|
+
|
149
|
+
setTimeout ->
|
150
|
+
callback(false, 'timed out') if !done
|
151
|
+
, 5000
|
152
|
+
else
|
153
|
+
# can't pass test = we need underscore for it
|
154
|
+
callback(false, "Can't test: need underscpre.js")
|
155
|
+
|
156
|
+
# Promise object let set any number of callbacks on the typical comletion events, namely success,
|
157
|
+
# failure and operation is somehow completed (always).
|
158
|
+
#
|
159
|
+
# The promise has 3 states: initial (operation us inder way), done and failed. When the operation
|
160
|
+
# is done or failed, attempt to add new handlers of the proper type will cause it immediate
|
161
|
+
# invocation.
|
162
|
+
#
|
163
|
+
# Promise can be set to equer success or error only once. All suitable handlers are called one time
|
164
|
+
# when the state is set.
|
165
|
+
root.WsFarcall.Promise = class Promise
|
166
|
+
|
167
|
+
constructor: ->
|
168
|
+
[@done_handlers, @fail_handlers, @always_handlers] = ([] for i in [1..3])
|
169
|
+
@state = null
|
170
|
+
@data = @error = undefined
|
171
|
+
|
172
|
+
# Add callback on the success event. Can be called any number of times, all callbacks will be
|
173
|
+
# invoked. If the state is already set to done, the callback fires immediately.
|
174
|
+
#
|
175
|
+
# callback receives whatever data passed to the #setSuccess() call.
|
176
|
+
done: (callback) ->
|
177
|
+
if @state
|
178
|
+
callback(@data) if @state == 'done'
|
179
|
+
else
|
180
|
+
@done_handlers.push callback
|
181
|
+
this
|
182
|
+
|
183
|
+
# same as done()
|
184
|
+
success: (callback) ->
|
185
|
+
@done(callback)
|
186
|
+
|
187
|
+
# Add callback on the failure event. Can be called any number of times, all callbacks will be
|
188
|
+
# invoked. If the state is already set to failure, the callback fires immediately.
|
189
|
+
#
|
190
|
+
# callback receives whatever data passed to the #setFail() call.
|
191
|
+
fail: (callback) ->
|
192
|
+
if @state
|
193
|
+
callback(@error) if @state == 'fail'
|
194
|
+
else
|
195
|
+
@fail_handlers.push callback
|
196
|
+
this
|
197
|
+
|
198
|
+
# Add callback on the both sucess and failure event. Can be called any number of times, all
|
199
|
+
# callbacks will be invoked. If the state is already set to eqither done or failure, the callback
|
200
|
+
# fires immediately.
|
201
|
+
#
|
202
|
+
# callback receives { error: {class: c. text: t}, result: any } object where only one (error or
|
203
|
+
# result) exist.
|
204
|
+
#
|
205
|
+
# always callbacks are executed after all of #success() or #fail().
|
206
|
+
always: (callback) ->
|
207
|
+
if @state
|
208
|
+
callback data: @data, error: @error
|
209
|
+
else
|
210
|
+
@always_handlers.push callback
|
211
|
+
this
|
212
|
+
|
213
|
+
# set promise to success state. If the state is already set to any, does noting. Calls all
|
214
|
+
# corresponding callbacks passing them the `data` parameter.
|
215
|
+
setSuccess: (data) ->
|
216
|
+
if !@state
|
217
|
+
@state = 'done'
|
218
|
+
@data = data
|
219
|
+
cb(data) for cb in @done_handlers
|
220
|
+
@done = null
|
221
|
+
@_fireAlways()
|
222
|
+
this
|
223
|
+
|
224
|
+
# same as #setSuccess()
|
225
|
+
setDone: (data) ->
|
226
|
+
@setSuccess data
|
227
|
+
|
228
|
+
# set promise to failure state. If the state is already set to any, does noting. Calls all
|
229
|
+
# corresponding callbacks passing them the `data` parameter.
|
230
|
+
setFail: (error) ->
|
231
|
+
if !@state
|
232
|
+
@state = 'fail'
|
233
|
+
@error = error
|
234
|
+
cb(error) for cb in @fail_handlers
|
235
|
+
@fail = null
|
236
|
+
@_fireAlways()
|
237
|
+
this
|
238
|
+
|
239
|
+
isSuccess: ->
|
240
|
+
@state == 'done'
|
241
|
+
|
242
|
+
isFail: ->
|
243
|
+
@state == 'fail'
|
244
|
+
|
245
|
+
# same as #isFail
|
246
|
+
isError: ->
|
247
|
+
@state == 'fail'
|
248
|
+
|
249
|
+
# promise is either done of fail (e.g. completed)
|
250
|
+
isCompleted: ->
|
251
|
+
!!@state
|
252
|
+
|
253
|
+
_fireAlways: ->
|
254
|
+
cb({error: @error, data: @data}) for cb in @always_handlers
|
255
|
+
@always = null
|
256
|
+
|
257
|
+
|
258
|
+
# Split javascript arguments array to args and kwargs of the Farcall notaion
|
259
|
+
# e.g. if the last argument is the pbject, it will be treated as kwargs.
|
260
|
+
splitArgs = (args) ->
|
261
|
+
last = args[args.length - 1]
|
262
|
+
if typeof(last) == 'object'
|
263
|
+
[args[0..-2], last]
|
264
|
+
else
|
265
|
+
[args, {}]
|
266
|
+
|
267
|
+
|
268
|
+
# For self-test only, relies on underscore.js
|
269
|
+
checkEquals = (a, b, text) ->
|
270
|
+
if _.isEqual a, b
|
271
|
+
true
|
272
|
+
else
|
273
|
+
console.error "#{text ? 'ERROR'}: not equal:"
|
274
|
+
console.error "expected: #{JSON.stringify b}"
|
275
|
+
console.error "got: #{JSON.stringify a}"
|
276
|
+
false
|
277
|
+
|
278
|
+
|
@@ -0,0 +1,379 @@
|
|
1
|
+
// Generated by CoffeeScript 1.8.0
|
2
|
+
(function() {
|
3
|
+
var Promise, checkEquals, root, splitArgs,
|
4
|
+
__slice = [].slice;
|
5
|
+
|
6
|
+
root = typeof exports !== "undefined" && exports !== null ? exports : this;
|
7
|
+
|
8
|
+
root.WsFarcall = (function() {
|
9
|
+
WsFarcall.open = function(url) {
|
10
|
+
return new WsFarcall(url);
|
11
|
+
};
|
12
|
+
|
13
|
+
function WsFarcall(url) {
|
14
|
+
this.url = url;
|
15
|
+
this.in_serial = 0;
|
16
|
+
this.out_serial = 0;
|
17
|
+
this.openHandlers = [];
|
18
|
+
this.closeHandlers = [];
|
19
|
+
this.ws = new WebSocket(this.url);
|
20
|
+
this.connected = false;
|
21
|
+
this.promises = {};
|
22
|
+
this.commandHandlers = {};
|
23
|
+
this.ws.onopen = (function(_this) {
|
24
|
+
return function() {
|
25
|
+
var cb, _i, _len, _ref, _results;
|
26
|
+
_this.connected = true;
|
27
|
+
_ref = _this.openHandlers;
|
28
|
+
_results = [];
|
29
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
30
|
+
cb = _ref[_i];
|
31
|
+
_results.push(cb(_this));
|
32
|
+
}
|
33
|
+
return _results;
|
34
|
+
};
|
35
|
+
})(this);
|
36
|
+
this.ws.onclose = (function(_this) {
|
37
|
+
return function() {
|
38
|
+
var cb, _i, _len, _ref, _results;
|
39
|
+
_this.connected = false;
|
40
|
+
_ref = _this.closeHandlers;
|
41
|
+
_results = [];
|
42
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
43
|
+
cb = _ref[_i];
|
44
|
+
_results.push(cb(_this));
|
45
|
+
}
|
46
|
+
return _results;
|
47
|
+
};
|
48
|
+
})(this);
|
49
|
+
this.ws.onmessage = (function(_this) {
|
50
|
+
return function(message) {
|
51
|
+
_this.trace && console.log(">>> " + message.data);
|
52
|
+
return _this._receive(JSON.parse(message.data));
|
53
|
+
};
|
54
|
+
})(this);
|
55
|
+
}
|
56
|
+
|
57
|
+
WsFarcall.prototype.onopen = function(callback) {
|
58
|
+
return this.openHandlers.push(callback);
|
59
|
+
};
|
60
|
+
|
61
|
+
WsFarcall.prototype.onclose = function(callback) {
|
62
|
+
return this.closeHandlers.push(callback);
|
63
|
+
};
|
64
|
+
|
65
|
+
WsFarcall.prototype.close = function() {
|
66
|
+
return this.ws.close();
|
67
|
+
};
|
68
|
+
|
69
|
+
WsFarcall.prototype.call = function() {
|
70
|
+
var args, kwargs, name, promise, _ref;
|
71
|
+
name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
72
|
+
_ref = splitArgs(args), args = _ref[0], kwargs = _ref[1];
|
73
|
+
promise = this.promises[this.out_serial] = new Promise();
|
74
|
+
this._send({
|
75
|
+
cmd: name,
|
76
|
+
args: args,
|
77
|
+
kwargs: kwargs
|
78
|
+
});
|
79
|
+
return promise;
|
80
|
+
};
|
81
|
+
|
82
|
+
WsFarcall.prototype.on = function(name, callback) {
|
83
|
+
return this.commandHandlers[name] = callback;
|
84
|
+
};
|
85
|
+
|
86
|
+
WsFarcall.prototype._receive = function(data) {
|
87
|
+
var promise;
|
88
|
+
if (data.serial !== this.in_serial++) {
|
89
|
+
console.error("farcall framing error");
|
90
|
+
return this.close();
|
91
|
+
} else {
|
92
|
+
if (data.ref !== void 0) {
|
93
|
+
promise = this.promises[data.ref];
|
94
|
+
delete this.promises[data.ref];
|
95
|
+
if (data.error === void 0) {
|
96
|
+
return promise.setDone(data.result);
|
97
|
+
} else {
|
98
|
+
return promise.setFail(data.error);
|
99
|
+
}
|
100
|
+
} else {
|
101
|
+
return this._processCall(data);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
};
|
105
|
+
|
106
|
+
WsFarcall.prototype._send = function(params) {
|
107
|
+
params.serial = this.out_serial++;
|
108
|
+
params = JSON.stringify(params);
|
109
|
+
this.trace && console.log("<<< " + params);
|
110
|
+
return this.ws.send(params);
|
111
|
+
};
|
112
|
+
|
113
|
+
WsFarcall.prototype._processCall = function(data) {
|
114
|
+
var e, handler;
|
115
|
+
handler = this.commandHandlers[data.cmd];
|
116
|
+
if (handler) {
|
117
|
+
try {
|
118
|
+
data.args.push(data.kwargs);
|
119
|
+
return this._send({
|
120
|
+
ref: data.serial,
|
121
|
+
result: handler.apply(null, data.args)
|
122
|
+
});
|
123
|
+
} catch (_error) {
|
124
|
+
e = _error;
|
125
|
+
return this._send({
|
126
|
+
ref: data.serial,
|
127
|
+
error: {
|
128
|
+
"class": 'RuntimeError',
|
129
|
+
text: e.message
|
130
|
+
}
|
131
|
+
});
|
132
|
+
}
|
133
|
+
} else {
|
134
|
+
return this._send({
|
135
|
+
ref: data.serial,
|
136
|
+
error: {
|
137
|
+
"class": 'NoMethodError',
|
138
|
+
text: "method not found: " + data.cmd
|
139
|
+
}
|
140
|
+
});
|
141
|
+
}
|
142
|
+
};
|
143
|
+
|
144
|
+
WsFarcall.selfTest = function(url, callback) {
|
145
|
+
var cb, cbr, done, p1, p2, p3;
|
146
|
+
if (typeof _ !== "undefined" && _ !== null ? _.isEqual(1, 1) : void 0) {
|
147
|
+
p1 = false;
|
148
|
+
p2 = false;
|
149
|
+
p3 = false;
|
150
|
+
cb = false;
|
151
|
+
cbr = false;
|
152
|
+
done = false;
|
153
|
+
WsFarcall.open(url + '/fartest').onopen(function(fcall) {
|
154
|
+
fcall.call('ping', 1, 2, 3, {
|
155
|
+
hello: 'world'
|
156
|
+
}).done(function(data) {
|
157
|
+
return p1 = checkEquals(data, {
|
158
|
+
pong: [
|
159
|
+
1, 2, 3, {
|
160
|
+
hello: 'world'
|
161
|
+
}
|
162
|
+
]
|
163
|
+
});
|
164
|
+
});
|
165
|
+
fcall.call('ping', 2, 2, 3, {
|
166
|
+
hello: 'world'
|
167
|
+
}).done(function(data) {
|
168
|
+
return p2 = checkEquals(data, {
|
169
|
+
pong: [
|
170
|
+
2, 2, 3, {
|
171
|
+
hello: 'world'
|
172
|
+
}
|
173
|
+
]
|
174
|
+
});
|
175
|
+
});
|
176
|
+
fcall.call('ping', 3, 2, 3, {
|
177
|
+
hello: 'world'
|
178
|
+
}).done(function(data) {
|
179
|
+
return p3 = checkEquals(data, {
|
180
|
+
pong: [
|
181
|
+
3, 2, 3, {
|
182
|
+
hello: 'world'
|
183
|
+
}
|
184
|
+
]
|
185
|
+
});
|
186
|
+
});
|
187
|
+
fcall.on('test_callback', function() {
|
188
|
+
var args;
|
189
|
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
190
|
+
cb = checkEquals(args, [
|
191
|
+
5, 4, 3, {
|
192
|
+
hello: 'world'
|
193
|
+
}
|
194
|
+
]);
|
195
|
+
return setTimeout(function() {
|
196
|
+
var ok, text;
|
197
|
+
done = true;
|
198
|
+
ok = p1 && p2 && p3 && cb && cbr;
|
199
|
+
text = (function() {
|
200
|
+
switch (false) {
|
201
|
+
case !!cb:
|
202
|
+
return 'callback was not called or wrong data';
|
203
|
+
case !!cbr:
|
204
|
+
return 'callback request did not return';
|
205
|
+
default:
|
206
|
+
if (ok) {
|
207
|
+
return '';
|
208
|
+
} else {
|
209
|
+
return 'ping data wrong';
|
210
|
+
}
|
211
|
+
}
|
212
|
+
})();
|
213
|
+
return callback(ok, text);
|
214
|
+
}, 80);
|
215
|
+
});
|
216
|
+
return fcall.call('callback', 'test_callback', 5, 4, 3, {
|
217
|
+
hello: 'world'
|
218
|
+
}).done(function(data) {
|
219
|
+
return cbr = true;
|
220
|
+
});
|
221
|
+
});
|
222
|
+
return setTimeout(function() {
|
223
|
+
if (!done) {
|
224
|
+
return callback(false, 'timed out');
|
225
|
+
}
|
226
|
+
}, 5000);
|
227
|
+
} else {
|
228
|
+
return callback(false, "Can't test: need underscpre.js");
|
229
|
+
}
|
230
|
+
};
|
231
|
+
|
232
|
+
return WsFarcall;
|
233
|
+
|
234
|
+
})();
|
235
|
+
|
236
|
+
root.WsFarcall.Promise = Promise = (function() {
|
237
|
+
function Promise() {
|
238
|
+
var i, _ref;
|
239
|
+
_ref = (function() {
|
240
|
+
var _i, _results;
|
241
|
+
_results = [];
|
242
|
+
for (i = _i = 1; _i <= 3; i = ++_i) {
|
243
|
+
_results.push([]);
|
244
|
+
}
|
245
|
+
return _results;
|
246
|
+
})(), this.done_handlers = _ref[0], this.fail_handlers = _ref[1], this.always_handlers = _ref[2];
|
247
|
+
this.state = null;
|
248
|
+
this.data = this.error = void 0;
|
249
|
+
}
|
250
|
+
|
251
|
+
Promise.prototype.done = function(callback) {
|
252
|
+
if (this.state) {
|
253
|
+
if (this.state === 'done') {
|
254
|
+
callback(this.data);
|
255
|
+
}
|
256
|
+
} else {
|
257
|
+
this.done_handlers.push(callback);
|
258
|
+
}
|
259
|
+
return this;
|
260
|
+
};
|
261
|
+
|
262
|
+
Promise.prototype.success = function(callback) {
|
263
|
+
return this.done(callback);
|
264
|
+
};
|
265
|
+
|
266
|
+
Promise.prototype.fail = function(callback) {
|
267
|
+
if (this.state) {
|
268
|
+
if (this.state === 'fail') {
|
269
|
+
callback(this.error);
|
270
|
+
}
|
271
|
+
} else {
|
272
|
+
this.fail_handlers.push(callback);
|
273
|
+
}
|
274
|
+
return this;
|
275
|
+
};
|
276
|
+
|
277
|
+
Promise.prototype.always = function(callback) {
|
278
|
+
if (this.state) {
|
279
|
+
callback({
|
280
|
+
data: this.data,
|
281
|
+
error: this.error
|
282
|
+
});
|
283
|
+
} else {
|
284
|
+
this.always_handlers.push(callback);
|
285
|
+
}
|
286
|
+
return this;
|
287
|
+
};
|
288
|
+
|
289
|
+
Promise.prototype.setSuccess = function(data) {
|
290
|
+
var cb, _i, _len, _ref;
|
291
|
+
if (!this.state) {
|
292
|
+
this.state = 'done';
|
293
|
+
this.data = data;
|
294
|
+
_ref = this.done_handlers;
|
295
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
296
|
+
cb = _ref[_i];
|
297
|
+
cb(data);
|
298
|
+
}
|
299
|
+
this.done = null;
|
300
|
+
this._fireAlways();
|
301
|
+
}
|
302
|
+
return this;
|
303
|
+
};
|
304
|
+
|
305
|
+
Promise.prototype.setDone = function(data) {
|
306
|
+
return this.setSuccess(data);
|
307
|
+
};
|
308
|
+
|
309
|
+
Promise.prototype.setFail = function(error) {
|
310
|
+
var cb, _i, _len, _ref;
|
311
|
+
if (!this.state) {
|
312
|
+
this.state = 'fail';
|
313
|
+
this.error = error;
|
314
|
+
_ref = this.fail_handlers;
|
315
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
316
|
+
cb = _ref[_i];
|
317
|
+
cb(error);
|
318
|
+
}
|
319
|
+
this.fail = null;
|
320
|
+
this._fireAlways();
|
321
|
+
}
|
322
|
+
return this;
|
323
|
+
};
|
324
|
+
|
325
|
+
Promise.prototype.isSuccess = function() {
|
326
|
+
return this.state === 'done';
|
327
|
+
};
|
328
|
+
|
329
|
+
Promise.prototype.isFail = function() {
|
330
|
+
return this.state === 'fail';
|
331
|
+
};
|
332
|
+
|
333
|
+
Promise.prototype.isError = function() {
|
334
|
+
return this.state === 'fail';
|
335
|
+
};
|
336
|
+
|
337
|
+
Promise.prototype.isCompleted = function() {
|
338
|
+
return !!this.state;
|
339
|
+
};
|
340
|
+
|
341
|
+
Promise.prototype._fireAlways = function() {
|
342
|
+
var cb, _i, _len, _ref;
|
343
|
+
_ref = this.always_handlers;
|
344
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
345
|
+
cb = _ref[_i];
|
346
|
+
cb({
|
347
|
+
error: this.error,
|
348
|
+
data: this.data
|
349
|
+
});
|
350
|
+
}
|
351
|
+
return this.always = null;
|
352
|
+
};
|
353
|
+
|
354
|
+
return Promise;
|
355
|
+
|
356
|
+
})();
|
357
|
+
|
358
|
+
splitArgs = function(args) {
|
359
|
+
var last;
|
360
|
+
last = args[args.length - 1];
|
361
|
+
if (typeof last === 'object') {
|
362
|
+
return [args.slice(0, -1), last];
|
363
|
+
} else {
|
364
|
+
return [args, {}];
|
365
|
+
}
|
366
|
+
};
|
367
|
+
|
368
|
+
checkEquals = function(a, b, text) {
|
369
|
+
if (_.isEqual(a, b)) {
|
370
|
+
return true;
|
371
|
+
} else {
|
372
|
+
console.error("" + (text != null ? text : 'ERROR') + ": not equal:");
|
373
|
+
console.error("expected: " + (JSON.stringify(b)));
|
374
|
+
console.error("got: " + (JSON.stringify(a)));
|
375
|
+
return false;
|
376
|
+
}
|
377
|
+
};
|
378
|
+
|
379
|
+
}).call(this);
|
@@ -8,18 +8,13 @@ module Farcall
|
|
8
8
|
|
9
9
|
# Create json transport, see Farcall::Transpor#create for parameters
|
10
10
|
def initialize **params
|
11
|
+
super()
|
11
12
|
setup_streams **params
|
12
13
|
@formatter = Boss::Formatter.new(@output)
|
13
14
|
@formatter.set_stream_mode
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
super
|
18
|
-
if block && !@thread
|
19
|
-
@thread = Thread.start {
|
20
|
-
load_loop
|
21
|
-
}
|
22
|
-
end
|
15
|
+
@thread = Thread.start {
|
16
|
+
load_loop
|
17
|
+
}
|
23
18
|
end
|
24
19
|
|
25
20
|
def send_data hash
|
@@ -39,7 +34,7 @@ module Farcall
|
|
39
34
|
|
40
35
|
def load_loop
|
41
36
|
Boss::Parser.new(@input).each { |object|
|
42
|
-
|
37
|
+
push_input object
|
43
38
|
}
|
44
39
|
rescue Errno::EPIPE
|
45
40
|
close
|
data/lib/farcall/em_farcall.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
begin
|
2
2
|
require 'hashie'
|
3
3
|
require 'eventmachine'
|
4
|
+
require_relative './promise'
|
4
5
|
|
5
6
|
# As the eventmachine callback paradigm is completely different from the threaded paradigm
|
6
7
|
# of the Farcall, that runs pretty well under JRuby and in multithreaded MRI, we provide
|
@@ -60,7 +61,9 @@ begin
|
|
60
61
|
# if block is provided, it will be called when the remote will be called and possibly return
|
61
62
|
# some data.
|
62
63
|
#
|
63
|
-
# Block receives single object paramter with two fields: `result.error` and
|
64
|
+
# Block if present receives single object paramter with two fields: `result.error` and
|
65
|
+
# `result.result`. It is also possible to use returned {Farcall::Promise} instance to set
|
66
|
+
# multiple callbacks with ease. Promise callbacks are called _after_ the block.
|
64
67
|
#
|
65
68
|
# `result.error` is not nil when the remote raised error, then `error[:class]` and
|
66
69
|
# `error.text` are set accordingly.
|
@@ -77,15 +80,23 @@ begin
|
|
77
80
|
# }
|
78
81
|
#
|
79
82
|
# @param [String] name command name
|
80
|
-
# @return [
|
83
|
+
# @return [Promise] object that call be used to set multiple handlers on success
|
84
|
+
# or fail event. {Farcall::Promise#succsess} receives remote return result on
|
85
|
+
# success and {Farcall::Promise#fail} receives error object.
|
81
86
|
def call(name, *args, **kwargs, &block)
|
87
|
+
promise = Farcall::Promise.new
|
82
88
|
EM.schedule {
|
83
|
-
|
84
|
-
|
85
|
-
|
89
|
+
@callbacks[@in_serial] = -> (result) {
|
90
|
+
block.call(result) if block != nil
|
91
|
+
if result.error
|
92
|
+
promise.set_fail result.error
|
93
|
+
else
|
94
|
+
promise.set_success result.result
|
95
|
+
end
|
96
|
+
}
|
86
97
|
send_block cmd: name, args: args, kwargs: kwargs
|
87
98
|
}
|
88
|
-
|
99
|
+
promise
|
89
100
|
end
|
90
101
|
|
91
102
|
# Close the endpoint
|
data/lib/farcall/endpoint.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'hashie'
|
2
|
+
require_relative 'promise'
|
2
3
|
|
3
4
|
module Farcall
|
4
5
|
|
@@ -20,9 +21,22 @@ module Farcall
|
|
20
21
|
|
21
22
|
# Create endpoint connected to some transport
|
22
23
|
# @param [Farcall::Transport] transport
|
23
|
-
def initialize(transport)
|
24
|
+
def initialize(transport, init_proc=nil)
|
24
25
|
@transport = transport
|
25
26
|
@in_serial = @out_serial = 0
|
27
|
+
@send_lock = Mutex.new
|
28
|
+
@receive_lock = Mutex.new
|
29
|
+
@handlers = {}
|
30
|
+
@waiting = {}
|
31
|
+
|
32
|
+
init_proc.call(self) if init_proc
|
33
|
+
|
34
|
+
def push_input data
|
35
|
+
p 'me -- pusj!', data
|
36
|
+
@in_buffer << data
|
37
|
+
drain
|
38
|
+
end
|
39
|
+
|
26
40
|
@transport.on_data_received = -> (data) {
|
27
41
|
begin
|
28
42
|
_received(data)
|
@@ -30,11 +44,10 @@ module Farcall
|
|
30
44
|
abort :format_error, $!
|
31
45
|
end
|
32
46
|
}
|
47
|
+
end
|
33
48
|
|
34
|
-
|
35
|
-
|
36
|
-
@handlers = {}
|
37
|
-
@waiting = {}
|
49
|
+
def self.open(transport, &block)
|
50
|
+
Endpoint.new(transport, block)
|
38
51
|
end
|
39
52
|
|
40
53
|
# The provided block will be called if endpoint functioning will be aborted.
|
@@ -70,20 +83,28 @@ module Farcall
|
|
70
83
|
# or result itself are instances of th Hashie::Mash. Error could be nil or
|
71
84
|
# {'class' =>, 'text' => } Hashie::Mash hash. result is always nil if error is presented.
|
72
85
|
#
|
73
|
-
#
|
74
|
-
# Farcall::RemoteInterface rather than this low-level method.
|
86
|
+
# Usually, using Farcall::Endpoint#interface or
|
87
|
+
# Farcall::RemoteInterface is more effective rather than this low-level method.
|
88
|
+
#
|
89
|
+
# The returned {Farcall::Promise} instance let add any number of callbacks on commend execution,
|
90
|
+
# success or failure.
|
75
91
|
#
|
76
92
|
# @param [String] name of the remote command
|
93
|
+
# @return [Farcall::Promise] instance
|
77
94
|
def call(name, *args, **kwargs, &block)
|
95
|
+
promise = Farcall::Promise.new
|
78
96
|
@send_lock.synchronize {
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
97
|
+
@waiting[@out_serial] = -> (error, result) {
|
98
|
+
block.call(error, result) if block
|
99
|
+
if error
|
100
|
+
promise.set_fail error
|
101
|
+
else
|
102
|
+
promise.set_success result
|
103
|
+
end
|
83
104
|
}
|
84
105
|
_send(cmd: name.to_s, args: args, kwargs: kwargs)
|
85
|
-
end
|
86
106
|
}
|
107
|
+
promise
|
87
108
|
end
|
88
109
|
|
89
110
|
# Call the remote party and wait for the return.
|
@@ -210,7 +231,7 @@ module Farcall
|
|
210
231
|
when ref
|
211
232
|
|
212
233
|
ref or abort 'no reference in return'
|
213
|
-
(
|
234
|
+
(proc = @waiting.delete ref) != nil and proc.call(error, result)
|
214
235
|
|
215
236
|
else
|
216
237
|
abort 'unknown command'
|
@@ -92,18 +92,13 @@ module Farcall
|
|
92
92
|
|
93
93
|
# Create json transport, see Farcall::Transpor#create for parameters
|
94
94
|
def initialize delimiter: "\x00", **params
|
95
|
+
super()
|
95
96
|
setup_streams **params
|
96
97
|
@delimiter = delimiter
|
97
98
|
@dlength = -delimiter.length
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
super
|
102
|
-
if block && !@thread
|
103
|
-
@thread = Thread.start {
|
104
|
-
load_loop
|
105
|
-
}
|
106
|
-
end
|
99
|
+
@thread = Thread.start {
|
100
|
+
load_loop
|
101
|
+
}
|
107
102
|
end
|
108
103
|
|
109
104
|
def send_data hash
|
@@ -126,7 +121,7 @@ module Farcall
|
|
126
121
|
while !@input.eof?
|
127
122
|
buffer << @input.read(1)
|
128
123
|
if buffer[@dlength..-1] == @delimiter
|
129
|
-
|
124
|
+
push_input JSON.parse(buffer[0...@dlength])
|
130
125
|
buffer = ''
|
131
126
|
end
|
132
127
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Farcall
|
2
|
+
# Promise let set multiple callbacks on different completion events: success, fail and completion.
|
3
|
+
# Promisee guarantte that corresponing callbacks will be called and only once. Callbacks added
|
4
|
+
# after completion are being called immediately.
|
5
|
+
#
|
6
|
+
# Promise state can be changed only once by calling either #set_success( or #set_fail().
|
7
|
+
# Its subsequent invocations raise errors.
|
8
|
+
#
|
9
|
+
class Promise
|
10
|
+
|
11
|
+
# returns data passed to the call to #set_success(data), or nil
|
12
|
+
attr :data
|
13
|
+
|
14
|
+
# returns data passed to the call to #set_success(error), or nil
|
15
|
+
attr :error
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@state = nil
|
19
|
+
@success, @fail, @always = [], [], []
|
20
|
+
end
|
21
|
+
|
22
|
+
def succsess?
|
23
|
+
@state == :success
|
24
|
+
end
|
25
|
+
|
26
|
+
def fail?
|
27
|
+
@state == :fail
|
28
|
+
end
|
29
|
+
|
30
|
+
# the promise is completed after one of #set_success(data) and #set_fail(error) is called.
|
31
|
+
def completed?
|
32
|
+
@state == :completed
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add handler for the success event. If the promise is already #success? then the block
|
36
|
+
# is called immediately, otherwise when and if the promise reach this state.
|
37
|
+
#
|
38
|
+
# block receives data parameter passed to #set_success(data)
|
39
|
+
#
|
40
|
+
# @return [Proomise] self
|
41
|
+
def success &block
|
42
|
+
if !completed?
|
43
|
+
@success << block
|
44
|
+
elsif succsess?
|
45
|
+
block.call(@data)
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add handler for the fail event. If the promise is already #fail? then the block
|
51
|
+
# is called immediately, otherwise when and if the promise reach this state.
|
52
|
+
#
|
53
|
+
# Block receives error parameter passed to the #set_fail(data)
|
54
|
+
#
|
55
|
+
# @return [Proomise] self
|
56
|
+
def fail &block
|
57
|
+
if !completed?
|
58
|
+
@fail << block
|
59
|
+
elsif fail?
|
60
|
+
block.call(@error)
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add handler to the completion event that will receive promise instance as the parameter (thus
|
66
|
+
# able to check state and ready #data or #error value).
|
67
|
+
#
|
68
|
+
# Note that `always` handlers are called after `success` or `fail` ones.
|
69
|
+
#
|
70
|
+
# @return [Proomise] self
|
71
|
+
def always &block
|
72
|
+
if !completed?
|
73
|
+
@always << block
|
74
|
+
else
|
75
|
+
block.call(self)
|
76
|
+
end
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# same as #always
|
81
|
+
alias finished always
|
82
|
+
|
83
|
+
# Set the promise as #completed? with #success? state and invoke proper handlers passing `data`
|
84
|
+
# which is also available with #data property.
|
85
|
+
#
|
86
|
+
# If invoked when already #completed?, raises error.
|
87
|
+
def set_success data
|
88
|
+
raise "state is already set" if @state
|
89
|
+
@state = :success
|
90
|
+
@data = data
|
91
|
+
invoke @success, data
|
92
|
+
end
|
93
|
+
|
94
|
+
# Set the promise as #completed? with #fail? state and invoke proper handlers. passing `error`
|
95
|
+
# which is also available with #error property.
|
96
|
+
#
|
97
|
+
# If invoked when already #completed?, raises error.
|
98
|
+
def set_fail error
|
99
|
+
raise "state is already set" if @state
|
100
|
+
@state = :fail
|
101
|
+
@error = error
|
102
|
+
invoke @fail, error
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def invoke list, data
|
108
|
+
list.each { |proc| proc.call(data) }
|
109
|
+
@always.each { |proc| proc.call(self) }
|
110
|
+
@always = @success = @fail = nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/farcall/transport.rb
CHANGED
@@ -51,6 +51,10 @@ module Farcall
|
|
51
51
|
# to call super first.
|
52
52
|
attr_accessor :on_data_received, :on_abort, :on_close
|
53
53
|
|
54
|
+
def initialize
|
55
|
+
@in_buffer = []
|
56
|
+
end
|
57
|
+
|
54
58
|
# Utility function. Calls the provided block on data reception. Resets the
|
55
59
|
# block with #on_data_received
|
56
60
|
def receive_data &block
|
@@ -72,8 +76,29 @@ module Farcall
|
|
72
76
|
@closed
|
73
77
|
end
|
74
78
|
|
79
|
+
# set handler and drain all input packets that may be buffered by the time.
|
80
|
+
def on_data_received= proc
|
81
|
+
@on_data_received = proc
|
82
|
+
drain
|
83
|
+
end
|
84
|
+
|
85
|
+
# Input buffering: transport may start before configure endpoint delegates observer, so
|
86
|
+
# the transport can simply push it here and rely on default buffering.
|
87
|
+
def push_input data
|
88
|
+
@in_buffer << data
|
89
|
+
drain
|
90
|
+
end
|
91
|
+
|
75
92
|
protected
|
76
93
|
|
94
|
+
def drain
|
95
|
+
if @in_buffer.size > 0 && on_data_received
|
96
|
+
@in_buffer.each { |x| on_data_received.call(x) }
|
97
|
+
@in_buffer.clear
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
77
102
|
# Call it when your connection is closed
|
78
103
|
def connection_closed
|
79
104
|
close
|
@@ -99,6 +124,7 @@ module Farcall
|
|
99
124
|
attr_accessor :other
|
100
125
|
|
101
126
|
def initialize other_loop = nil
|
127
|
+
super()
|
102
128
|
if other_loop
|
103
129
|
other_loop.other = self
|
104
130
|
@other = other_loop
|
data/lib/farcall/version.rb
CHANGED
@@ -25,6 +25,7 @@ begin
|
|
25
25
|
# JSON encodgin over standard websocket protocol.
|
26
26
|
def initialize ws_url
|
27
27
|
# The stranges bug around in the WebSocket::Client (actually in his eventemitter)
|
28
|
+
super()
|
28
29
|
me = self
|
29
30
|
|
30
31
|
is_open = Semaphore.new
|
@@ -39,9 +40,7 @@ begin
|
|
39
40
|
}
|
40
41
|
|
41
42
|
@ws.on(:message) { |m|
|
42
|
-
|
43
|
-
me.on_data_received and me.on_data_received.call(JSON.parse m.data)
|
44
|
-
# puts "and sent"
|
43
|
+
me.push_input JSON.parse(m.data)
|
45
44
|
}
|
46
45
|
@ws.on(:close) { close }
|
47
46
|
is_open.wait_set
|
@@ -53,6 +52,7 @@ begin
|
|
53
52
|
end
|
54
53
|
|
55
54
|
end
|
55
|
+
|
56
56
|
end
|
57
57
|
rescue LoadError
|
58
58
|
$!.to_s =~ /websocket/ or raise
|
data/spec/websock_spec.rb
CHANGED
@@ -229,5 +229,59 @@ describe 'em_farcall' do
|
|
229
229
|
standard_check_wsclient(cnt1, r1, r2, r3)
|
230
230
|
end
|
231
231
|
|
232
|
+
it 'calls from server to client' do
|
233
|
+
data1 = nil
|
234
|
+
data2 = nil
|
235
|
+
done = nil
|
236
|
+
|
237
|
+
order = 0
|
238
|
+
block_order = 0
|
239
|
+
promise_order = 0
|
240
|
+
|
241
|
+
EM.run {
|
242
|
+
params = {
|
243
|
+
:host => 'localhost',
|
244
|
+
:port => 8088
|
245
|
+
}
|
246
|
+
EM::WebSocket.run(params) do |ws|
|
247
|
+
ws.onopen { |handshake|
|
248
|
+
server = EmFarcall::WsServerEndpoint.new ws
|
249
|
+
|
250
|
+
# EM channels are synchronous so if we call too early call will be simply lost. This,
|
251
|
+
# though, should not happen to the socket - so why?
|
252
|
+
server.call(:test_method, 'hello', foo: :bar) { block_order = order+=1 }.success { |result|
|
253
|
+
data2 = result
|
254
|
+
promise_order = order += 1
|
255
|
+
}.fail { |e|
|
256
|
+
puts "Error #{e}"
|
257
|
+
}.always { |item|
|
258
|
+
done = item
|
259
|
+
EM.stop
|
260
|
+
}
|
261
|
+
}
|
262
|
+
end
|
263
|
+
|
264
|
+
EM.defer {
|
265
|
+
t1 = Farcall::WebsocketJsonClientTransport.new 'ws://localhost:8088/test'
|
266
|
+
Farcall::Endpoint.open(t1) { |client|
|
267
|
+
client.on(:test_method) { |args, kwargs|
|
268
|
+
data1 = { 'nice' => [args, kwargs] }
|
269
|
+
{ done: :success }
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
EM.add_timer(1) {
|
275
|
+
EM.stop
|
276
|
+
}
|
277
|
+
}
|
278
|
+
data1.should == { 'nice' => [['hello'], { 'foo' => 'bar' }] }
|
279
|
+
data2.should == { 'done' => 'success' }
|
280
|
+
done.should be_instance_of(Farcall::Promise)
|
281
|
+
block_order.should == 1
|
282
|
+
promise_order.should == 2
|
283
|
+
done.data.should == data2
|
284
|
+
end
|
285
|
+
|
232
286
|
|
233
287
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: farcall
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sergeych
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-06-
|
11
|
+
date: 2016-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
@@ -125,6 +125,8 @@ files:
|
|
125
125
|
- README.md
|
126
126
|
- Rakefile
|
127
127
|
- farcall.gemspec
|
128
|
+
- javascript/farcall_wsclient.coffee
|
129
|
+
- javascript/farcall_wsclient.js
|
128
130
|
- lib/farcall.rb
|
129
131
|
- lib/farcall/boss_transport.rb
|
130
132
|
- lib/farcall/em_farcall.rb
|
@@ -132,6 +134,7 @@ files:
|
|
132
134
|
- lib/farcall/endpoint.rb
|
133
135
|
- lib/farcall/json_transport.rb
|
134
136
|
- lib/farcall/monitor_lock.rb
|
137
|
+
- lib/farcall/promise.rb
|
135
138
|
- lib/farcall/transport.rb
|
136
139
|
- lib/farcall/version.rb
|
137
140
|
- lib/farcall/wsclient_transport.rb
|