farcall 0.3.4 → 0.4.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.
- 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
|