wamp_client 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +28 -26
  3. data/lib/{wamp_client.rb → wamp/client.rb} +8 -8
  4. data/lib/{wamp_client → wamp/client}/auth.rb +13 -11
  5. data/lib/wamp/client/check.rb +86 -0
  6. data/lib/wamp/client/connection.rb +249 -0
  7. data/lib/{wamp_client → wamp/client}/defer.rb +29 -27
  8. data/lib/wamp/client/message.rb +1322 -0
  9. data/lib/{wamp_client → wamp/client}/serializer.rb +26 -24
  10. data/lib/wamp/client/session.rb +1001 -0
  11. data/lib/wamp/client/transport/base.rb +152 -0
  12. data/lib/{wamp_client → wamp/client}/transport/event_machine_base.rb +19 -17
  13. data/lib/wamp/client/transport/faye_web_socket.rb +85 -0
  14. data/lib/wamp/client/transport/web_socket_event_machine.rb +88 -0
  15. data/lib/{wamp_client → wamp/client}/version.rb +5 -3
  16. data/scripts/gen_message.rb +54 -53
  17. data/spec/spec_helper.rb +3 -3
  18. data/spec/{auth_spec.rb → wamp/client/auth_spec.rb} +2 -2
  19. data/spec/{check_spec.rb → wamp/client/check_spec.rb} +2 -2
  20. data/spec/{connection_spec.rb → wamp/client/connection_spec.rb} +7 -7
  21. data/spec/{message_spec.rb → wamp/client/message_spec.rb} +298 -298
  22. data/spec/{session_spec.rb → wamp/client/session_spec.rb} +134 -134
  23. data/spec/{transport_spec.rb → wamp/client/transport_spec.rb} +4 -4
  24. data/wamp_client.gemspec +2 -2
  25. metadata +50 -50
  26. data/lib/wamp_client/check.rb +0 -84
  27. data/lib/wamp_client/connection.rb +0 -247
  28. data/lib/wamp_client/message.rb +0 -1348
  29. data/lib/wamp_client/session.rb +0 -1000
  30. data/lib/wamp_client/transport/base.rb +0 -151
  31. data/lib/wamp_client/transport/faye_web_socket.rb +0 -83
  32. data/lib/wamp_client/transport/web_socket_event_machine.rb +0 -86
@@ -1,6 +1,6 @@
1
1
  =begin
2
2
 
3
- Copyright (c) 2016 Eric Chapman
3
+ Copyright (c) 2018 Eric Chapman
4
4
 
5
5
  MIT License
6
6
 
@@ -27,41 +27,43 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
27
 
28
28
  require 'json'
29
29
 
30
- module WampClient
31
- module Serializer
32
- class Base
30
+ module Wamp
31
+ module Client
32
+ module Serializer
33
+ class Base
33
34
 
34
- attr_accessor :type
35
+ attr_accessor :type
35
36
 
36
- # Serializes the object
37
- # @param object - The object to serialize
38
- def serialize(object)
37
+ # Serializes the object
38
+ # @param object - The object to serialize
39
+ def serialize(object)
39
40
 
40
- end
41
+ end
42
+
43
+ # Deserializes the object
44
+ # @param string [String] - The string to deserialize
45
+ # @return The deserialized object
46
+ def deserialize(string)
41
47
 
42
- # Deserializes the object
43
- # @param string [String] - The string to deserialize
44
- # @return The deserialized object
45
- def deserialize(string)
48
+ end
46
49
 
47
50
  end
48
51
 
49
- end
52
+ class JSONSerializer < Base
50
53
 
51
- class JSONSerializer < Base
54
+ def initialize
55
+ self.type = 'json'
56
+ end
52
57
 
53
- def initialize
54
- self.type = 'json'
55
- end
58
+ def serialize(object)
59
+ JSON.generate object
60
+ end
56
61
 
57
- def serialize(object)
58
- JSON.generate object
59
- end
62
+ def deserialize(string)
63
+ JSON.parse(string, {:symbolize_names => true})
64
+ end
60
65
 
61
- def deserialize(string)
62
- JSON.parse(string, {:symbolize_names => true})
63
66
  end
64
-
65
67
  end
66
68
  end
67
69
  end
@@ -0,0 +1,1001 @@
1
+ =begin
2
+
3
+ Copyright (c) 2018 Eric Chapman
4
+
5
+ MIT License
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ =end
27
+
28
+ require 'wamp/client/transport/base'
29
+ require 'wamp/client/message'
30
+ require 'wamp/client/check'
31
+ require 'wamp/client/version'
32
+
33
+ module Wamp
34
+ module Client
35
+ WAMP_FEATURES = {
36
+ caller: {
37
+ features: {
38
+ caller_identification: true,
39
+ call_timeout: true,
40
+ call_canceling: true,
41
+ progressive_call_results: true
42
+ }
43
+ },
44
+ callee: {
45
+ features: {
46
+ caller_identification: true,
47
+ ##call_trustlevels: true,
48
+ pattern_based_registration: true,
49
+ shared_registration: true,
50
+ ##call_timeout: true,
51
+ call_canceling: true,
52
+ progressive_call_results: true,
53
+ registration_revocation: true
54
+ }
55
+ },
56
+ publisher: {
57
+ features: {
58
+ publisher_identification: true,
59
+ subscriber_blackwhite_listing: true,
60
+ publisher_exclusion: true
61
+ }
62
+ },
63
+ subscriber: {
64
+ features: {
65
+ publisher_identification: true,
66
+ ##publication_trustlevels: true,
67
+ pattern_based_subscription: true,
68
+ subscription_revocation: true
69
+ ##event_history: true,
70
+ }
71
+ }
72
+ }
73
+
74
+ class CallResult
75
+ attr_accessor :args, :kwargs
76
+
77
+ def initialize(args=nil, kwargs=nil)
78
+ self.args = args || []
79
+ self.kwargs = kwargs || {}
80
+ end
81
+ end
82
+
83
+ class CallError < Exception
84
+ attr_accessor :error, :args, :kwargs
85
+
86
+ def initialize(error, args=nil, kwargs=nil)
87
+ self.error = error
88
+ self.args = args || []
89
+ self.kwargs = kwargs || {}
90
+ end
91
+ end
92
+
93
+ class Subscription
94
+ attr_accessor :topic, :handler, :options, :session, :id
95
+
96
+ def initialize(topic, handler, options, session, id)
97
+ self.topic = topic
98
+ self.handler = handler
99
+ self.options = options
100
+ self.session = session
101
+ self.id = id
102
+ end
103
+
104
+ def unsubscribe
105
+ self.session.unsubscribe(self)
106
+ end
107
+
108
+ end
109
+
110
+ class Registration
111
+ attr_accessor :procedure, :handler, :i_handler, :options, :session, :id
112
+
113
+ def initialize(procedure, handler, options, i_handler, session, id)
114
+ self.procedure = procedure
115
+ self.handler = handler
116
+ self.options = options
117
+ self.i_handler = i_handler
118
+ self.session = session
119
+ self.id = id
120
+ end
121
+
122
+ def unregister
123
+ self.session.unregister(self)
124
+ end
125
+
126
+ end
127
+
128
+ class Call
129
+ attr_accessor :session, :id
130
+
131
+ def initialize(session, id)
132
+ self.session = session
133
+ self.id = id
134
+ end
135
+
136
+ def cancel(mode='skip')
137
+ self.session.cancel(self, mode)
138
+ end
139
+
140
+ end
141
+
142
+ class Session
143
+ include Wamp::Client::Check
144
+
145
+ # on_join callback is called when the session joins the router. It has the following parameters
146
+ # @param details [Hash] Object containing information about the joined session
147
+ @on_join
148
+ def on_join(&on_join)
149
+ @on_join = on_join
150
+ end
151
+
152
+ # on_leave callback is called when the session leaves the router. It has the following attributes
153
+ # @param reason [String] The reason the session left the router
154
+ # @param details [Hash] Object containing information about the left session
155
+ @on_leave
156
+ def on_leave(&on_leave)
157
+ @on_leave = on_leave
158
+ end
159
+
160
+ # on_challenge callback is called when an authentication challenge is received from the router. It has the
161
+ # following attributes
162
+ # @param authmethod [String] The type of auth being requested
163
+ # @param extra [Hash] Hash containing additional information
164
+ # @return signature, extras
165
+ @on_challenge
166
+ def on_challenge(&on_challenge)
167
+ @on_challenge = on_challenge
168
+ end
169
+
170
+ # Simple setter for callbacks
171
+ def on(event, &callback)
172
+ case event
173
+ when :join
174
+ self.on_join(&callback)
175
+ when :challenge
176
+ self.on_challenge(&callback)
177
+ when :leave
178
+ self.on_leave(&callback)
179
+ else
180
+ raise RuntimeError, "Unknown on(event) '#{event}'"
181
+ end
182
+ end
183
+
184
+ attr_accessor :id, :realm, :transport, :verbose, :options
185
+
186
+ # Private attributes
187
+ attr_accessor :_goodbye_sent, :_requests, :_subscriptions, :_registrations, :_defers
188
+
189
+ # Constructor
190
+ # @param transport [Wamp::Client::Transport::Base] The transport that the session will use
191
+ # @param options [Hash] Hash containing different session options
192
+ # @option options [String] :authid The authentication ID
193
+ # @option options [Array] :authmethods Different auth methods that this client supports
194
+ def initialize(transport, options={})
195
+
196
+ # Parameters
197
+ self.id = nil
198
+ self.realm = nil
199
+ self.options = options || {}
200
+ self.verbose = self.options[:verbose]
201
+
202
+ # Outstanding Requests
203
+ self._requests = {
204
+ publish: {},
205
+ subscribe: {},
206
+ unsubscribe: {},
207
+ call: {},
208
+ register: {},
209
+ unregister: {}
210
+ }
211
+
212
+ # Init Subs and Regs in place
213
+ self._subscriptions = {}
214
+ self._registrations = {}
215
+ self._defers = {}
216
+
217
+ # Setup Transport
218
+ self.transport = transport
219
+ self.transport.on_message do |msg|
220
+ self._receive_message(msg)
221
+ end
222
+
223
+ # Other parameters
224
+ self._goodbye_sent = false
225
+
226
+ # Setup session callbacks
227
+ @on_join = nil
228
+ @on_leave = nil
229
+ @on_challenge = nil
230
+
231
+ end
232
+
233
+ # Returns 'true' if the session is open
234
+ def is_open?
235
+ !self.id.nil?
236
+ end
237
+
238
+ # Joins the WAMP Router
239
+ # @param realm [String] The name of the realm
240
+ def join(realm)
241
+ if is_open?
242
+ raise RuntimeError, "Session must be closed to call 'join'"
243
+ end
244
+
245
+ self.class.check_uri('realm', realm)
246
+
247
+ self.realm = realm
248
+
249
+ details = {}
250
+ details[:roles] = WAMP_FEATURES
251
+ details[:agent] = "Ruby-Wamp::Client-#{Wamp::Client::VERSION}"
252
+ details[:authid] = self.options[:authid] if self.options[:authid]
253
+ details[:authmethods] = self.options[:authmethods] if self.options[:authmethods]
254
+
255
+ # Send Hello message
256
+ hello = Wamp::Client::Message::Hello.new(realm, details)
257
+ self._send_message(hello)
258
+ end
259
+
260
+ # Leaves the WAMP Router
261
+ # @param reason [String] URI signalling the reason for leaving
262
+ def leave(reason='wamp.close.normal', message='user initiated')
263
+ unless is_open?
264
+ raise RuntimeError, "Session must be opened to call 'leave'"
265
+ end
266
+
267
+ self.class.check_uri('reason', reason, true)
268
+ self.class.check_string('message', message, true)
269
+
270
+ details = {}
271
+ details[:message] = message
272
+
273
+ # Send Goodbye message
274
+ goodbye = Wamp::Client::Message::Goodbye.new(details, reason)
275
+ self._send_message(goodbye)
276
+ self._goodbye_sent = true
277
+ end
278
+
279
+ # Generates an ID according to the specification (Section 5.1.2)
280
+ def _generate_id
281
+ rand(0..9007199254740992)
282
+ end
283
+
284
+ # Converts and error message to a hash
285
+ # @param msg [Wamp::Client::Message::Error]
286
+ def _error_to_hash(msg)
287
+ {
288
+ error: msg.error,
289
+ args: msg.arguments,
290
+ kwargs: msg.argumentskw
291
+ }
292
+ end
293
+
294
+ # Sends a message
295
+ # @param msg [Wamp::Client::Message::Base]
296
+ def _send_message(msg)
297
+ puts 'TX: ' + msg.to_s if self.verbose
298
+ self.transport.send_message(msg.payload)
299
+ end
300
+
301
+ # Processes received messages
302
+ # @param msg [Array]
303
+ def _receive_message(msg)
304
+
305
+ message = Wamp::Client::Message::Base.parse(msg)
306
+
307
+ if self.verbose
308
+ puts 'RX: ' + message.to_s if message
309
+ puts 'RX(non-wamp): ' + msg.to_s unless message
310
+ end
311
+
312
+ # WAMP Session is not open
313
+ if self.id.nil?
314
+
315
+ # Parse the welcome message
316
+ if message.is_a? Wamp::Client::Message::Welcome
317
+ self.id = message.session
318
+ @on_join.call(message.details) unless @on_join.nil?
319
+ elsif message.is_a? Wamp::Client::Message::Challenge
320
+
321
+ if @on_challenge
322
+ signature, extra = @on_challenge.call(message.authmethod, message.extra)
323
+ else
324
+ signature = nil
325
+ extra = nil
326
+ end
327
+
328
+ signature ||= ''
329
+ extra ||= {}
330
+
331
+ authenticate = Wamp::Client::Message::Authenticate.new(signature, extra)
332
+ self._send_message(authenticate)
333
+
334
+ elsif message.is_a? Wamp::Client::Message::Abort
335
+ @on_leave.call(message.reason, message.details) unless @on_leave.nil?
336
+ end
337
+
338
+ # Wamp Session is open
339
+ else
340
+
341
+ # If goodbye, close the session
342
+ if message.is_a? Wamp::Client::Message::Goodbye
343
+
344
+ # If we didn't send the goodbye, respond
345
+ unless self._goodbye_sent
346
+ goodbye = Wamp::Client::Message::Goodbye.new({}, 'wamp.error.goodbye_and_out')
347
+ self._send_message(goodbye)
348
+ end
349
+
350
+ # Close out session
351
+ self.id = nil
352
+ self.realm = nil
353
+ self._goodbye_sent = false
354
+ @on_leave.call(message.reason, message.details) unless @on_leave.nil?
355
+
356
+ else
357
+
358
+ # Process Errors
359
+ if message.is_a? Wamp::Client::Message::Error
360
+ if message.request_type == Wamp::Client::Message::Types::SUBSCRIBE
361
+ self._process_SUBSCRIBE_error(message)
362
+ elsif message.request_type == Wamp::Client::Message::Types::UNSUBSCRIBE
363
+ self._process_UNSUBSCRIBE_error(message)
364
+ elsif message.request_type == Wamp::Client::Message::Types::PUBLISH
365
+ self._process_PUBLISH_error(message)
366
+ elsif message.request_type == Wamp::Client::Message::Types::REGISTER
367
+ self._process_REGISTER_error(message)
368
+ elsif message.request_type == Wamp::Client::Message::Types::UNREGISTER
369
+ self._process_UNREGISTER_error(message)
370
+ elsif message.request_type == Wamp::Client::Message::Types::CALL
371
+ self._process_CALL_error(message)
372
+ else
373
+ # TODO: Some Error?? Not Implemented yet
374
+ end
375
+
376
+ # Process Messages
377
+ else
378
+ if message.is_a? Wamp::Client::Message::Subscribed
379
+ self._process_SUBSCRIBED(message)
380
+ elsif message.is_a? Wamp::Client::Message::Unsubscribed
381
+ self._process_UNSUBSCRIBED(message)
382
+ elsif message.is_a? Wamp::Client::Message::Published
383
+ self._process_PUBLISHED(message)
384
+ elsif message.is_a? Wamp::Client::Message::Event
385
+ self._process_EVENT(message)
386
+ elsif message.is_a? Wamp::Client::Message::Registered
387
+ self._process_REGISTERED(message)
388
+ elsif message.is_a? Wamp::Client::Message::Unregistered
389
+ self._process_UNREGISTERED(message)
390
+ elsif message.is_a? Wamp::Client::Message::Invocation
391
+ self._process_INVOCATION(message)
392
+ elsif message.is_a? Wamp::Client::Message::Interrupt
393
+ self._process_INTERRUPT(message)
394
+ elsif message.is_a? Wamp::Client::Message::Result
395
+ self._process_RESULT(message)
396
+ else
397
+ # TODO: Some Error?? Not Implemented yet
398
+ end
399
+ end
400
+
401
+ end
402
+ end
403
+
404
+ end
405
+
406
+ #region Subscribe Logic
407
+
408
+ # Subscribes to a topic
409
+ # @param topic [String] The topic to subscribe to
410
+ # @param handler [lambda] The handler(args, kwargs, details) when an event is received
411
+ # @param options [Hash] The options for the subscription
412
+ # @param callback [block] The callback(subscription, error) called to signal if the subscription was a success or not
413
+ def subscribe(topic, handler, options={}, &callback)
414
+ unless is_open?
415
+ raise RuntimeError, "Session must be open to call 'subscribe'"
416
+ end
417
+
418
+ self.class.check_uri('topic', topic)
419
+ self.class.check_dict('options', options)
420
+ self.class.check_nil('handler', handler, false)
421
+
422
+ # Create a new subscribe request
423
+ request = self._generate_id
424
+ self._requests[:subscribe][request] = {t: topic, h: handler, o: options, c: callback}
425
+
426
+ # Send the message
427
+ subscribe = Wamp::Client::Message::Subscribe.new(request, options, topic)
428
+ self._send_message(subscribe)
429
+ end
430
+
431
+ # Processes the response to a subscribe request
432
+ # @param msg [Wamp::Client::Message::Subscribed] The response from the subscribe
433
+ def _process_SUBSCRIBED(msg)
434
+
435
+ # Remove the pending subscription, add it to the registered ones, and inform the caller
436
+ s = self._requests[:subscribe].delete(msg.subscribe_request)
437
+ if s
438
+
439
+ details = {}
440
+ details[:topic] = s[:t] unless details[:topic]
441
+ details[:type] = 'subscribe'
442
+ details[:session] = self
443
+
444
+ n_s = Subscription.new(s[:t], s[:h], s[:o], self, msg.subscription)
445
+ self._subscriptions[msg.subscription] = n_s
446
+ c = s[:c]
447
+ c.call(n_s, nil, details) if c
448
+ end
449
+
450
+ end
451
+
452
+ # Processes an error from a request
453
+ # @param msg [Wamp::Client::Message::Error] The response from the subscribe
454
+ def _process_SUBSCRIBE_error(msg)
455
+
456
+ # Remove the pending subscription and inform the caller of the failure
457
+ s = self._requests[:subscribe].delete(msg.request_request)
458
+ if s
459
+
460
+ details = msg.details || {}
461
+ details[:topic] = s[:t] unless details[:topic]
462
+ details[:type] = 'subscribe'
463
+ details[:session] = self
464
+
465
+ c = s[:c]
466
+ c.call(nil, self._error_to_hash(msg), details) if c
467
+ end
468
+
469
+ end
470
+
471
+ # Processes and event from the broker
472
+ # @param msg [Wamp::Client::Message::Event] An event that was published
473
+ def _process_EVENT(msg)
474
+
475
+ args = msg.publish_arguments || []
476
+ kwargs = msg.publish_argumentskw || {}
477
+
478
+ s = self._subscriptions[msg.subscribed_subscription]
479
+ if s
480
+ details = msg.details || {}
481
+ details[:publication] = msg.published_publication
482
+ details[:session] = self
483
+
484
+ h = s.handler
485
+ h.call(args, kwargs, details) if h
486
+ end
487
+
488
+ end
489
+
490
+ #endregion
491
+
492
+ #region Unsubscribe Logic
493
+
494
+ # Unsubscribes from a subscription
495
+ # @param subscription [Subscription] The subscription object from when the subscription was created
496
+ # @param callback [block] The callback(subscription, error, details) called to signal if the subscription was a success or not
497
+ def unsubscribe(subscription, &callback)
498
+ unless is_open?
499
+ raise RuntimeError, "Session must be open to call 'unsubscribe'"
500
+ end
501
+
502
+ self.class.check_nil('subscription', subscription, false)
503
+
504
+ # Create a new unsubscribe request
505
+ request = self._generate_id
506
+ self._requests[:unsubscribe][request] = { s: subscription, c: callback }
507
+
508
+ # Send the message
509
+ unsubscribe = Wamp::Client::Message::Unsubscribe.new(request, subscription.id)
510
+ self._send_message(unsubscribe)
511
+ end
512
+
513
+ # Processes the response to a unsubscribe request
514
+ # @param msg [Wamp::Client::Message::Unsubscribed] The response from the unsubscribe
515
+ def _process_UNSUBSCRIBED(msg)
516
+
517
+ # Remove the pending unsubscription, add it to the registered ones, and inform the caller
518
+ s = self._requests[:unsubscribe].delete(msg.unsubscribe_request)
519
+ if s
520
+ n_s = s[:s]
521
+ self._subscriptions.delete(n_s.id)
522
+
523
+ details = {}
524
+ details[:topic] = s[:s].topic
525
+ details[:type] = 'unsubscribe'
526
+ details[:session] = self
527
+
528
+ c = s[:c]
529
+ c.call(n_s, nil, details) if c
530
+ end
531
+
532
+ end
533
+
534
+
535
+ # Processes an error from a request
536
+ # @param msg [Wamp::Client::Message::Error] The response from the subscribe
537
+ def _process_UNSUBSCRIBE_error(msg)
538
+
539
+ # Remove the pending subscription and inform the caller of the failure
540
+ s = self._requests[:unsubscribe].delete(msg.request_request)
541
+ if s
542
+
543
+ details = msg.details || {}
544
+ details[:topic] = s[:s].topic unless details[:topic]
545
+ details[:type] = 'unsubscribe'
546
+ details[:session] = self
547
+
548
+ c = s[:c]
549
+ c.call(nil, self._error_to_hash(msg), details) if c
550
+ end
551
+
552
+ end
553
+
554
+ #endregion
555
+
556
+ #region Publish Logic
557
+
558
+ # Publishes and event to a topic
559
+ # @param topic [String] The topic to publish the event to
560
+ # @param args [Array] The arguments
561
+ # @param kwargs [Hash] The keyword arguments
562
+ # @param options [Hash] The options for the publish
563
+ # @param callback [block] The callback(publish, error, details) called to signal if the publish was a success or not
564
+ def publish(topic, args=nil, kwargs=nil, options={}, &callback)
565
+ unless is_open?
566
+ raise RuntimeError, "Session must be open to call 'publish'"
567
+ end
568
+
569
+ self.class.check_uri('topic', topic)
570
+ self.class.check_dict('options', options)
571
+ self.class.check_list('args', args, true)
572
+ self.class.check_dict('kwargs', kwargs, true)
573
+
574
+ # Create a new publish request
575
+ request = self._generate_id
576
+ self._requests[:publish][request] = {t: topic, a: args, k: kwargs, o: options, c: callback} if options[:acknowledge]
577
+
578
+ # Send the message
579
+ publish = Wamp::Client::Message::Publish.new(request, options, topic, args, kwargs)
580
+ self._send_message(publish)
581
+ end
582
+
583
+ # Processes the response to a publish request
584
+ # @param msg [Wamp::Client::Message::Published] The response from the subscribe
585
+ def _process_PUBLISHED(msg)
586
+
587
+ # Remove the pending publish and alert the callback
588
+ p = self._requests[:publish].delete(msg.publish_request)
589
+ if p
590
+
591
+ details = {}
592
+ details[:topic] = p[:t]
593
+ details[:type] = 'publish'
594
+ details[:publication] = msg.publication
595
+ details[:session] = self
596
+
597
+ c = p[:c]
598
+ c.call(p, nil, details) if c
599
+ end
600
+
601
+ end
602
+
603
+ # Processes an error from a publish request
604
+ # @param msg [Wamp::Client::Message::Error] The response from the subscribe
605
+ def _process_PUBLISH_error(msg)
606
+
607
+ # Remove the pending publish and inform the caller of the failure
608
+ s = self._requests[:publish].delete(msg.request_request)
609
+ if s
610
+
611
+ details = msg.details || {}
612
+ details[:topic] = s[:t] unless details[:topic]
613
+ details[:type] = 'publish'
614
+ details[:session] = self
615
+
616
+ c = s[:c]
617
+ c.call(nil, self._error_to_hash(msg), details) if c
618
+ end
619
+
620
+ end
621
+
622
+ #endregion
623
+
624
+ #region Register Logic
625
+
626
+ # Register to a procedure
627
+ # @param procedure [String] The procedure to register for
628
+ # @param handler [lambda] The handler(args, kwargs, details) when an invocation is received
629
+ # @param options [Hash, nil] The options for the registration
630
+ # @param interrupt [lambda] The handler(request, mode) when an interrupt is received
631
+ # @param callback [block] The callback(registration, error, details) called to signal if the registration was a success or not
632
+ def register(procedure, handler, options=nil, interrupt=nil, &callback)
633
+ unless is_open?
634
+ raise RuntimeError, "Session must be open to call 'register'"
635
+ end
636
+
637
+ options ||= {}
638
+
639
+ self.class.check_uri('procedure', procedure)
640
+ self.class.check_nil('handler', handler, false)
641
+
642
+ # Create a new registration request
643
+ request = self._generate_id
644
+ self._requests[:register][request] = {p: procedure, h: handler, i: interrupt, o: options, c: callback}
645
+
646
+ # Send the message
647
+ register = Wamp::Client::Message::Register.new(request, options, procedure)
648
+ self._send_message(register)
649
+ end
650
+
651
+ # Processes the response to a register request
652
+ # @param msg [Wamp::Client::Message::Registered] The response from the subscribe
653
+ def _process_REGISTERED(msg)
654
+
655
+ # Remove the pending subscription, add it to the registered ones, and inform the caller
656
+ r = self._requests[:register].delete(msg.register_request)
657
+ if r
658
+ n_r = Registration.new(r[:p], r[:h], r[:o], r[:i], self, msg.registration)
659
+ self._registrations[msg.registration] = n_r
660
+
661
+ details = {}
662
+ details[:procedure] = r[:p]
663
+ details[:type] = 'register'
664
+ details[:session] = self
665
+
666
+ c = r[:c]
667
+ c.call(n_r, nil, details) if c
668
+ end
669
+
670
+ end
671
+
672
+ # Processes an error from a request
673
+ # @param msg [Wamp::Client::Message::Error] The response from the register
674
+ def _process_REGISTER_error(msg)
675
+
676
+ # Remove the pending registration and inform the caller of the failure
677
+ r = self._requests[:register].delete(msg.request_request)
678
+ if r
679
+
680
+ details = msg.details || {}
681
+ details[:procedure] = r[:p] unless details[:procedure]
682
+ details[:type] = 'register'
683
+ details[:session] = self
684
+
685
+ c = r[:c]
686
+ c.call(nil, self._error_to_hash(msg), details) if c
687
+ end
688
+
689
+ end
690
+
691
+ # Sends an error back to the caller
692
+ # @param request[Integer] - The request ID
693
+ # @param error
694
+ def _send_INVOCATION_error(request, error, check_defer=false)
695
+ # Prevent responses for defers that have already completed or had an error
696
+ if check_defer and not self._defers[request]
697
+ return
698
+ end
699
+
700
+ if error.nil?
701
+ error = CallError.new('wamp.error.runtime')
702
+ elsif not error.is_a?(CallError)
703
+ error = CallError.new('wamp.error.runtime', [error.to_s])
704
+ end
705
+
706
+ error_msg = Wamp::Client::Message::Error.new(Wamp::Client::Message::Types::INVOCATION, request, {}, error.error, error.args, error.kwargs)
707
+ self._send_message(error_msg)
708
+ end
709
+
710
+ # Sends a result for the invocation
711
+ # @param request [Integer] - The id of the request
712
+ # @param result [CallError, CallResult, anything] - If it is a CallError, the error will be returned
713
+ # @param options [Hash] - The options to be sent with the yield
714
+ def yield(request, result, options={}, check_defer=false)
715
+ # Prevent responses for defers that have already completed or had an error
716
+ if check_defer and not self._defers[request]
717
+ return
718
+ end
719
+
720
+ if result.nil?
721
+ result = CallResult.new
722
+ elsif result.is_a?(CallError)
723
+ # Do nothing
724
+ elsif not result.is_a?(CallResult)
725
+ result = CallResult.new([result])
726
+ end
727
+
728
+ if result.is_a?(CallError)
729
+ self._send_INVOCATION_error(request, result)
730
+ else
731
+ yield_msg = Wamp::Client::Message::Yield.new(request, options, result.args, result.kwargs)
732
+ self._send_message(yield_msg)
733
+ end
734
+ end
735
+
736
+
737
+ # Processes and event from the broker
738
+ # @param msg [Wamp::Client::Message::Invocation] An procedure that was called
739
+ def _process_INVOCATION(msg)
740
+
741
+ request = msg.request
742
+ args = msg.call_arguments || []
743
+ kwargs = msg.call_argumentskw || {}
744
+
745
+ details = msg.details || {}
746
+ details[:request] = request
747
+ details[:session] = self
748
+
749
+ r = self._registrations[msg.registered_registration]
750
+ if r
751
+ h = r.handler
752
+ if h
753
+ begin
754
+ value = h.call(args, kwargs, details)
755
+
756
+ # If a defer was returned, handle accordingly
757
+ if value.is_a? Wamp::Client::Defer::CallDefer
758
+ value.request = request
759
+ value.registration = msg.registered_registration
760
+
761
+ # Store the defer
762
+ self._defers[request] = value
763
+
764
+ # On complete, send the result
765
+ value.on_complete do |defer, result|
766
+ self.yield(defer.request, result, {}, true)
767
+ self._defers.delete(defer.request)
768
+ end
769
+
770
+ # On error, send the error
771
+ value.on_error do |defer, error|
772
+ self._send_INVOCATION_error(defer.request, error, true)
773
+ self._defers.delete(defer.request)
774
+ end
775
+
776
+ # For progressive, return the progress
777
+ if value.is_a? Wamp::Client::Defer::ProgressiveCallDefer
778
+ value.on_progress do |defer, result|
779
+ self.yield(defer.request, result, {progress: true}, true)
780
+ end
781
+ end
782
+
783
+ # Else it was a normal response
784
+ else
785
+ self.yield(request, value)
786
+ end
787
+
788
+ rescue Exception => error
789
+ self._send_INVOCATION_error(request, error)
790
+ end
791
+
792
+ end
793
+ end
794
+ end
795
+
796
+ # Processes the interrupt
797
+ # @param msg [Wamp::Client::Message::Interrupt] An interrupt to a procedure
798
+ def _process_INTERRUPT(msg)
799
+
800
+ request = msg.invocation_request
801
+ mode = msg.options[:mode]
802
+
803
+ defer = self._defers[request]
804
+ if defer
805
+ r = self._registrations[defer.registration]
806
+ if r
807
+ # If it exists, call the interrupt handler to inform it of the interrupt
808
+ i = r.i_handler
809
+ error = nil
810
+ if i
811
+ begin
812
+ error = i.call(request, mode)
813
+ rescue Exception => e
814
+ error = e
815
+ end
816
+ end
817
+
818
+ error ||= 'interrupt'
819
+
820
+ # Send the error back to the client
821
+ self._send_INVOCATION_error(request, error, true)
822
+ end
823
+
824
+ # Delete the defer
825
+ self._defers.delete(request)
826
+ end
827
+
828
+ end
829
+
830
+ #endregion
831
+
832
+ #region Unregister Logic
833
+
834
+ # Unregisters from a procedure
835
+ # @param registration [Registration] The registration object from when the registration was created
836
+ # @param callback [block] The callback(registration, error, details) called to signal if the unregistration was a success or not
837
+ def unregister(registration, &callback)
838
+ unless is_open?
839
+ raise RuntimeError, "Session must be open to call 'unregister'"
840
+ end
841
+
842
+ self.class.check_nil('registration', registration, false)
843
+
844
+ # Create a new unsubscribe request
845
+ request = self._generate_id
846
+ self._requests[:unregister][request] = { r: registration, c: callback }
847
+
848
+ # Send the message
849
+ unregister = Wamp::Client::Message::Unregister.new(request, registration.id)
850
+ self._send_message(unregister)
851
+ end
852
+
853
+ # Processes the response to a unregister request
854
+ # @param msg [Wamp::Client::Message::Unregistered] The response from the unsubscribe
855
+ def _process_UNREGISTERED(msg)
856
+
857
+ # Remove the pending unregistration, add it to the registered ones, and inform the caller
858
+ r = self._requests[:unregister].delete(msg.unregister_request)
859
+ if r
860
+ r_s = r[:r]
861
+ self._registrations.delete(r_s.id)
862
+
863
+ details = {}
864
+ details[:procedure] = r_s.procedure
865
+ details[:type] = 'unregister'
866
+ details[:session] = self
867
+
868
+ c = r[:c]
869
+ c.call(r_s, nil, details) if c
870
+ end
871
+
872
+ end
873
+
874
+ # Processes an error from a request
875
+ # @param msg [Wamp::Client::Message::Error] The response from the subscribe
876
+ def _process_UNREGISTER_error(msg)
877
+
878
+ # Remove the pending subscription and inform the caller of the failure
879
+ r = self._requests[:unregister].delete(msg.request_request)
880
+ if r
881
+
882
+ details = msg.details || {}
883
+ details[:procedure] = r[:r].procedure unless details[:procedure]
884
+ details[:type] = 'unregister'
885
+ details[:session] = self
886
+
887
+ c = r[:c]
888
+ c.call(nil, self._error_to_hash(msg), details) if c
889
+ end
890
+
891
+ end
892
+
893
+ #endregion
894
+
895
+ #region Call Logic
896
+
897
+ # Publishes and event to a topic
898
+ # @param procedure [String] The procedure to invoke
899
+ # @param args [Array] The arguments
900
+ # @param kwargs [Hash] The keyword arguments
901
+ # @param options [Hash] The options for the call
902
+ # @param callback [block] The callback(result, error, details) called to signal if the call was a success or not
903
+ # @return [Call] An object representing the call
904
+ def call(procedure, args=nil, kwargs=nil, options={}, &callback)
905
+ unless is_open?
906
+ raise RuntimeError, "Session must be open to call 'call'"
907
+ end
908
+
909
+ self.class.check_uri('procedure', procedure)
910
+ self.class.check_dict('options', options)
911
+ self.class.check_list('args', args, true)
912
+ self.class.check_dict('kwargs', kwargs, true)
913
+
914
+ # Create a new call request
915
+ request = self._generate_id
916
+ self._requests[:call][request] = {p: procedure, a: args, k: kwargs, o: options, c: callback}
917
+
918
+ # Send the message
919
+ msg = Wamp::Client::Message::Call.new(request, options, procedure, args, kwargs)
920
+ self._send_message(msg)
921
+
922
+ call = Call.new(self, request)
923
+
924
+ # Timeout Logic
925
+ if options[:timeout] and options[:timeout] > 0
926
+ self.transport.add_timer(options[:timeout]) do
927
+ # Once the timer expires, if the call hasn't completed, cancel it
928
+ if self._requests[:call][call.id]
929
+ call.cancel
930
+ end
931
+ end
932
+ end
933
+
934
+ call
935
+ end
936
+
937
+ # Processes the response to a publish request
938
+ # @param msg [Wamp::Client::Message::Result] The response from the call
939
+ def _process_RESULT(msg)
940
+
941
+ details = msg.details || {}
942
+
943
+ call = self._requests[:call][msg.call_request]
944
+
945
+ # Don't remove if progress is true and the options had receive_progress true
946
+ self._requests[:call].delete(msg.call_request) unless (details[:progress] and (call and call[:o][:receive_progress]))
947
+
948
+ if call
949
+ details[:procedure] = call[:p] unless details[:procedure]
950
+ details[:type] = 'call'
951
+ details[:session] = self
952
+
953
+ c = call[:c]
954
+ c.call(CallResult.new(msg.yield_arguments, msg.yield_argumentskw), nil, details) if c
955
+ end
956
+
957
+ end
958
+
959
+ # Processes an error from a call request
960
+ # @param msg [Wamp::Client::Message::Error] The response from the call
961
+ def _process_CALL_error(msg)
962
+
963
+ # Remove the pending publish and inform the caller of the failure
964
+ call = self._requests[:call].delete(msg.request_request)
965
+ if call
966
+
967
+ details = msg.details || {}
968
+ details[:procedure] = call[:p] unless details[:procedure]
969
+ details[:type] = 'call'
970
+ details[:session] = self
971
+
972
+ c = call[:c]
973
+ c.call(nil, self._error_to_hash(msg), details) if c
974
+ end
975
+
976
+ end
977
+
978
+ #endregion
979
+
980
+ #region Cancel Logic
981
+
982
+ # Cancels a call
983
+ # @param call [Call] - The call object
984
+ # @param mode [String] - The mode of the skip. Options are 'skip', 'kill', 'killnowait'
985
+ def cancel(call, mode='skip')
986
+ unless is_open?
987
+ raise RuntimeError, "Session must be open to call 'cancel'"
988
+ end
989
+
990
+ self.class.check_nil('call', call, false)
991
+
992
+ # Send the message
993
+ cancel = Wamp::Client::Message::Cancel.new(call.id, { mode: mode })
994
+ self._send_message(cancel)
995
+ end
996
+
997
+ #endregion
998
+
999
+ end
1000
+ end
1001
+ end