xrbp 0.0.1 → 0.1.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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +103 -5
  3. data/examples/accounts.rb +13 -0
  4. data/examples/autoconnect_timeout.rb +5 -1
  5. data/examples/autorety.rb +7 -0
  6. data/examples/crawl_nodes.rb +32 -0
  7. data/examples/dsl/account.rb +16 -0
  8. data/examples/dsl/ledger.rb +7 -0
  9. data/examples/dsl/ledger_subscribe.rb +18 -0
  10. data/examples/dsl/validators.rb +8 -0
  11. data/examples/gateways.rb +8 -0
  12. data/examples/latest_account.rb +4 -0
  13. data/examples/ledger_multi_subscribe.rb +1 -1
  14. data/examples/ledger_subscribe.rb +2 -2
  15. data/examples/market.rb +13 -0
  16. data/examples/username.rb +12 -0
  17. data/examples/validator.rb +8 -0
  18. data/lib/xrbp.rb +5 -1
  19. data/lib/xrbp/common.rb +10 -0
  20. data/lib/xrbp/core_ext.rb +24 -0
  21. data/lib/xrbp/dsl.rb +25 -0
  22. data/lib/xrbp/dsl/accounts.rb +13 -0
  23. data/lib/xrbp/dsl/ledgers.rb +23 -0
  24. data/lib/xrbp/dsl/validators.rb +10 -0
  25. data/lib/xrbp/dsl/webclient.rb +8 -0
  26. data/lib/xrbp/dsl/websocket.rb +28 -0
  27. data/lib/xrbp/model.rb +4 -0
  28. data/lib/xrbp/model/account.rb +142 -1
  29. data/lib/xrbp/model/base.rb +1 -0
  30. data/lib/xrbp/model/gateway.rb +24 -0
  31. data/lib/xrbp/model/ledger.rb +30 -1
  32. data/lib/xrbp/model/market.rb +52 -0
  33. data/lib/xrbp/model/node.rb +131 -0
  34. data/lib/xrbp/model/parsers/account.rb +44 -0
  35. data/lib/xrbp/model/parsers/gateway.rb +40 -0
  36. data/lib/xrbp/model/parsers/market.rb +28 -0
  37. data/lib/xrbp/model/parsers/node.rb +19 -0
  38. data/lib/xrbp/model/parsers/quote.rb +47 -0
  39. data/lib/xrbp/model/parsers/validator.rb +25 -0
  40. data/lib/xrbp/model/validator.rb +24 -0
  41. data/lib/xrbp/plugins.rb +6 -0
  42. data/lib/xrbp/plugins/base.rb +10 -0
  43. data/lib/xrbp/plugins/has_plugin.rb +45 -0
  44. data/lib/xrbp/plugins/has_result_parsers.rb +27 -0
  45. data/lib/xrbp/plugins/plugin_registry.rb +20 -0
  46. data/lib/xrbp/plugins/result_parser.rb +19 -0
  47. data/lib/xrbp/terminatable.rb +19 -0
  48. data/lib/xrbp/thread_registry.rb +22 -0
  49. data/lib/xrbp/version.rb +1 -1
  50. data/lib/xrbp/webclient.rb +2 -0
  51. data/lib/xrbp/webclient/connection.rb +100 -0
  52. data/lib/xrbp/webclient/plugins.rb +8 -0
  53. data/lib/xrbp/webclient/plugins/autoretry.rb +54 -0
  54. data/lib/xrbp/webclient/plugins/result_parser.rb +23 -0
  55. data/lib/xrbp/websocket/client.rb +85 -24
  56. data/lib/xrbp/websocket/cmds/account_info.rb +4 -0
  57. data/lib/xrbp/websocket/cmds/account_lines.rb +5 -0
  58. data/lib/xrbp/websocket/cmds/account_objects.rb +4 -0
  59. data/lib/xrbp/websocket/cmds/account_offers.rb +5 -0
  60. data/lib/xrbp/websocket/cmds/account_tx.rb +4 -0
  61. data/lib/xrbp/websocket/cmds/book_offers.rb +4 -0
  62. data/lib/xrbp/websocket/cmds/ledger.rb +3 -0
  63. data/lib/xrbp/websocket/cmds/ledger_entry.rb +4 -0
  64. data/lib/xrbp/websocket/cmds/paginated.rb +4 -1
  65. data/lib/xrbp/websocket/cmds/server_info.rb +4 -0
  66. data/lib/xrbp/websocket/cmds/subscribe.rb +4 -0
  67. data/lib/xrbp/websocket/command.rb +11 -0
  68. data/lib/xrbp/websocket/connection.rb +75 -22
  69. data/lib/xrbp/websocket/message.rb +5 -2
  70. data/lib/xrbp/websocket/multi/fallback.rb +2 -4
  71. data/lib/xrbp/websocket/multi/multi_connection.rb +37 -2
  72. data/lib/xrbp/websocket/multi/parallel.rb +2 -4
  73. data/lib/xrbp/websocket/multi/prioritized.rb +2 -4
  74. data/lib/xrbp/websocket/multi/round_robin.rb +4 -0
  75. data/lib/xrbp/websocket/plugins.rb +1 -7
  76. data/lib/xrbp/websocket/plugins/autoconnect.rb +25 -5
  77. data/lib/xrbp/websocket/plugins/command_dispatcher.rb +5 -0
  78. data/lib/xrbp/websocket/plugins/command_paginator.rb +9 -8
  79. data/lib/xrbp/websocket/plugins/connection_timeout.rb +27 -16
  80. data/lib/xrbp/websocket/plugins/message_dispatcher.rb +60 -30
  81. data/lib/xrbp/websocket/plugins/result_parser.rb +19 -19
  82. data/lib/xrbp/websocket/socket.rb +23 -6
  83. metadata +118 -8
  84. data/lib/xrbp/network.rb +0 -6
  85. data/lib/xrbp/network/nodes.rb +0 -8
  86. data/lib/xrbp/websocket/has_plugin.rb +0 -30
@@ -0,0 +1,8 @@
1
+ module XRBP
2
+ module WebClient
3
+ include PluginRegistry
4
+ end # module WebClient
5
+ end # module XRPB
6
+
7
+ require_relative './plugins/result_parser'
8
+ require_relative './plugins/autoretry'
@@ -0,0 +1,54 @@
1
+ module XRBP
2
+ module WebClient
3
+ module Plugins
4
+ # Plugin to automatically retry WebClient Connection requests
5
+ # multiple times.
6
+ #
7
+ # If no max_tries are specified, requests will be tried indefinitely.
8
+ # Optionally configured interval to wait between retries.
9
+ #
10
+ # @example retrying request:
11
+ # connection = WebClient::Connection.new
12
+ # connection.add_plugin :autoretry
13
+ #
14
+ # connection.max_tries = 3
15
+ # connection.interval = 1
16
+ # connection.timeout = 1
17
+ #
18
+ # connection.url = "http://doesnt.exist"
19
+ # connection.perform
20
+ class AutoRetry < PluginBase
21
+ attr_accessor :interval, :max_tries
22
+
23
+ def initialize(connection)
24
+ super(connection)
25
+ @interval = 3
26
+ @max_tries = nil
27
+ @retry_num = 0
28
+ end
29
+
30
+ def added
31
+ plugin = self
32
+ connection.define_instance_method(:retry_interval=) do |i|
33
+ plugin.interval = i
34
+ end
35
+
36
+ connection.define_instance_method(:max_retries=) do |i|
37
+ plugin.max_tries = i
38
+ end
39
+ end
40
+
41
+ def handle_error
42
+ @retry_num += 1
43
+ return nil if connection.force_quit? ||
44
+ (!@max_tries.nil? && @retry_num > @max_tries)
45
+
46
+ connection.rsleep(@interval)
47
+ connection.perform
48
+ end
49
+ end # class AutoRetry
50
+
51
+ WebClient.register_plugin :autoretry, AutoRetry
52
+ end # module Plugins
53
+ end # module WebClient
54
+ end # module XRBP
@@ -0,0 +1,23 @@
1
+ module XRBP
2
+ module WebClient
3
+ module Plugins
4
+ # Plugin to automatically parse and convert webclient results,
5
+ # before returning.
6
+ #
7
+ # @example parse json
8
+ # connection = WebClient::Connection.new
9
+ # connection.add_plugin :result_parser
10
+ #
11
+ # connection.parse_results do |res|
12
+ # JSON.parse(res)
13
+ # end
14
+ #
15
+ # connection.url = "https://data.ripple.com/v2/gateways"
16
+ # connection.perform
17
+ class ResultParser < ResultParserBase
18
+ end
19
+
20
+ WebClient.register_plugin :result_parser, ResultParser
21
+ end # module Plugins
22
+ end # module WebClient
23
+ end # module XRBP
@@ -2,8 +2,12 @@ require 'websocket'
2
2
 
3
3
  module XRBP
4
4
  module WebSocket
5
+ # Managed socket connection lifecycle and read/write operations
6
+ #
7
+ # @private
5
8
  class Client
6
9
  include EventEmitter
10
+ include Terminatable
7
11
 
8
12
  attr_reader :url, :options
9
13
 
@@ -12,18 +16,28 @@ module XRBP
12
16
  @options = options
13
17
 
14
18
  @handshaked = false
15
- @closed = false
19
+ @closed = true
20
+ @completed = true
16
21
  end
17
22
 
18
23
  def connect
19
- emit :connecting
24
+ emit_signal :connecting
25
+
20
26
  @closed = false
27
+ @completed = false
21
28
  socket.connect
22
29
  handshake!
23
- @thread = poll
30
+
31
+ start_read
32
+
24
33
  self
25
34
  end
26
35
 
36
+ # Add job to internal thread pool.
37
+ def add_work(&bl)
38
+ pool.post &bl
39
+ end
40
+
27
41
  ###
28
42
 
29
43
  def open?
@@ -34,33 +48,53 @@ module XRBP
34
48
  !!@closed
35
49
  end
36
50
 
51
+ def completed?
52
+ !!@completed
53
+ end
54
+
55
+ # Allow close to be run via seperate thread so
56
+ # as not to block caller
57
+ def async_close(err=nil)
58
+ Thread.new { close(err) }
59
+ end
60
+
37
61
  def close(err=nil)
38
62
  return if closed?
39
63
 
40
64
  # XXX set closed true first incase callbacks need to check this
41
65
  @closed = true
66
+ @handshake = nil
67
+ @handshaked = false
68
+
69
+ terminate!
42
70
 
43
71
  send_data nil, :type => :close unless socket.pipe_broken
44
- emit :close, err
72
+ emit_signal :close, err
45
73
 
46
- ensure
47
- # Always set closed true
48
- @closed = true
49
74
  socket.close if socket
50
- @handshake = nil
51
- @handshaked = false
52
75
  @socket = nil
53
- #Thread.kill @thread if @thread
54
- @thread.join if @thread && Thread.current != @thread
76
+
77
+ pool.shutdown
78
+ pool.wait_for_termination
79
+ @pool = nil
80
+
81
+ @completed = true
55
82
  emit :completed
83
+ self
56
84
  end
57
85
 
86
+ private
87
+
58
88
  ###
59
89
 
60
90
  def socket
61
91
  @socket ||= Socket.new self
62
92
  end
63
93
 
94
+ def pool
95
+ @pool ||= Concurrent::CachedThreadPool.new
96
+ end
97
+
64
98
  ###
65
99
 
66
100
  def handshake
@@ -83,43 +117,70 @@ module XRBP
83
117
 
84
118
  ###
85
119
 
120
+ def data_frame(data, type)
121
+ ::WebSocket::Frame::Outgoing::Client.new(:data => data,
122
+ :type => type,
123
+ :version => handshake.version)
124
+ end
125
+
126
+ public
127
+
86
128
  def send_data(data, opt={:type => :text})
87
129
  return if !handshaked? || closed?
88
130
 
89
- frame = ::WebSocket::Frame::Outgoing::Client.new(:data => data,
90
- :type => opt[:type],
91
- :version => handshake.version)
92
-
93
131
  begin
132
+ frame = data_frame(data, opt[:type])
94
133
  socket.write_nonblock(frame.to_s)
95
134
 
96
135
  rescue Errno::EPIPE, OpenSSL::SSL::SSLError => e
97
- close(e)
136
+ async_close(e)
98
137
  end
99
138
  end
100
139
 
101
- def poll
102
- return Thread.new(socket) do |socket|
140
+ private
141
+
142
+ def start_read
143
+ add_work do
103
144
  frame = ::WebSocket::Frame::Incoming::Client.new
104
- emit :open
145
+ emit_signal :open
105
146
 
106
- until closed? do
147
+ cl = trm = eof = false
148
+ until (trm = terminate?) || (cl = closed?) do
107
149
  begin
108
150
  socket.read_next(frame)
109
151
 
110
152
  if msg = frame.next
111
- emit :message, msg
153
+ emit_signal :message, msg
112
154
  frame = ::WebSocket::Frame::Incoming::Client.new
113
155
  end
114
156
 
115
157
  rescue EOFError => e
116
- emit :error, e
117
- close(e)
158
+ emit_signal :error, e
159
+ eof = e
118
160
 
119
161
  rescue => e
120
- emit :error, e
162
+ emit_signal :error, e
121
163
  end
122
164
  end
165
+
166
+ # ... is this right?:
167
+ async_close(eof) if !!eof && !cl && !trm
168
+ end
169
+ end
170
+
171
+ def emit_signal(*args)
172
+ # TODO add args to queue, and in add_work task, pull 1 item off queue
173
+ # & emit it (to enforce signal order)
174
+ begin
175
+ add_work do
176
+ emit *args
177
+ end
178
+
179
+ # XXX: handle race condition where connection is closed
180
+ # between calling emit_signal and pool.post (handle
181
+ # error, otherwise a mutex would be needed)
182
+ rescue Concurrent::RejectedExecutionError => e
183
+ raise e unless closed?
123
184
  end
124
185
  end
125
186
  end # class Client
@@ -1,6 +1,10 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The account_info command retrieves information about an
5
+ # account, its activity, and its XRP balance.
6
+ #
7
+ # https://developers.ripple.com/account_info.html
4
8
  class AccountInfo < Command
5
9
  attr_accessor :account, :args
6
10
 
@@ -1,6 +1,11 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The account_lines method returns information about an
5
+ # account's trust lines, including balances in all non-XRP
6
+ # currencies and assets
7
+ #
8
+ # https://developers.ripple.com/account_lines.html
4
9
  class AccountLines < Command
5
10
  include Paginated
6
11
 
@@ -1,6 +1,10 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The account_objects command returns the raw ledger format for
5
+ # all objects owned by an account
6
+ #
7
+ # https://developers.ripple.com/account_objects.html
4
8
  class AccountObjects < Command
5
9
  include Paginated
6
10
 
@@ -1,6 +1,11 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The account_offers method retrieves a list of offers made
5
+ # by a given account that are outstanding as of a particular
6
+ # ledger version.
7
+ #
8
+ # https://developers.ripple.com/account_offers.html
4
9
  class AccountOffers < Command
5
10
  include Paginated
6
11
 
@@ -1,6 +1,10 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The account_tx method retrieves a list of transactions
5
+ # that involved the specified account.
6
+ #
7
+ # https://developers.ripple.com/account_tx.html
4
8
  class AccountTx < Command
5
9
  attr_accessor :account, :args
6
10
 
@@ -1,6 +1,10 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The book_offers method retrieves a list of offers, also known as
5
+ # the order book , between two currencies
6
+ #
7
+ # https://developers.ripple.com/book_offers.html
4
8
  class BookOffers < Command
5
9
  include Paginated
6
10
 
@@ -1,6 +1,9 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # Retrieve information about the public ledger
5
+ #
6
+ # https://developers.ripple.com/ledger.html
4
7
  class Ledger < Command
5
8
  attr_accessor :id, :args
6
9
 
@@ -1,6 +1,10 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The ledger_entry method returns a single ledger object
5
+ # from the XRP Ledger in its raw format
6
+ #
7
+ # https://developers.ripple.com/ledger_entry.html
4
8
  class LedgerEntry < Command
5
9
  def initialize(args={})
6
10
  @args = args
@@ -1,8 +1,11 @@
1
- require 'active_support/core_ext/hash/except'
1
+ require 'xrbp/core_ext'
2
2
 
3
3
  module XRBP
4
4
  module WebSocket
5
5
  module Cmds
6
+ # Helper mixin facilitating paginated command retrieval.
7
+ #
8
+ # @private
6
9
  module Paginated
7
10
  attr_reader :prev_cmd
8
11
 
@@ -1,6 +1,10 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The server_info command asks the server for a human-readable version
5
+ # of various information about the rippled server being queried.
6
+ #
7
+ # https://developers.ripple.com/server_info.html
4
8
  class ServerInfo < Command
5
9
  def initialize
6
10
  super({'command' => 'server_info'})
@@ -1,6 +1,10 @@
1
1
  module XRBP
2
2
  module WebSocket
3
3
  module Cmds
4
+ # The subscribe method requests periodic notifications
5
+ # from the server when certain events happen
6
+ #
7
+ # https://developers.ripple.com/subscribe.html
4
8
  class Subscribe < Command
5
9
  attr_accessor :args
6
10
 
@@ -4,6 +4,7 @@ module XRBP
4
4
  module WebSocket
5
5
  class Command < Message
6
6
  attr_accessor :id
7
+ attr_reader :json
7
8
 
8
9
  def initialize(data)
9
10
  @@id ||= 0
@@ -12,8 +13,18 @@ module XRBP
12
13
  json = Hash[data]
13
14
  json['id'] = id
14
15
 
16
+ @json = json
17
+
15
18
  super(json.to_json)
16
19
  end
20
+
21
+ def requesting
22
+ @json[:command] || @json["command"]
23
+ end
24
+
25
+ def requesting?(tgt)
26
+ requesting.to_s == tgt.to_s
27
+ end
17
28
  end # class Command
18
29
  end # module WebSocket
19
30
  end # module XRBP
@@ -1,10 +1,21 @@
1
- require_relative './has_plugin'
1
+ require_relative '../thread_registry'
2
2
 
3
3
  module XRBP
4
4
  module WebSocket
5
+ # Primary websocket interface, use Connection to perform
6
+ # websocket requests.
7
+ #
8
+ # @example retrieve data via a websocket
9
+ # connection = WebSocket::Connection.new "wss://s1.ripple.com:443"
10
+ # puts connection.send_data('{"command" : "server_info"}')
5
11
  class Connection
6
12
  include EventEmitter
7
13
  include HasPlugin
14
+ include ThreadRegistry
15
+
16
+ def plugin_namespace
17
+ WebSocket
18
+ end
8
19
 
9
20
  attr_reader :url
10
21
  attr_accessor :parent
@@ -12,35 +23,65 @@ module XRBP
12
23
  def initialize(url)
13
24
  @url = url
14
25
  @force_quit = false
26
+
27
+ yield self if block_given?
15
28
  end
16
29
 
17
30
  ###
18
31
 
32
+ # Initiate new client connection
19
33
  def connect
20
34
  client.connect
21
35
  end
22
36
 
37
+ # Return next connection of parent if applicable
38
+ #
39
+ # @private
23
40
  def next_connection(prev)
24
41
  return nil unless !!parent
25
42
  parent.next_connection(prev)
26
43
  end
27
44
 
45
+ # Add work to the internal client thread pool
46
+ #
47
+ # @private
48
+ def add_work(&bl)
49
+ client.add_work &bl
50
+ end
51
+
52
+ # Indicates the connection is initialized
28
53
  def initialized?
29
54
  !!@client
30
55
  end
31
56
 
57
+ # Indicates the connection is open
32
58
  def open?
33
59
  initialized? && client.open?
34
60
  end
35
61
 
62
+ # Indicates the connection is closed
63
+ # (may not be completed)
36
64
  def closed?
37
65
  !open?
38
66
  end
39
67
 
68
+ # Indicates if connection is completely
69
+ # closed and cleaned up
70
+ def completed?
71
+ client.completed?
72
+ end
73
+
74
+ # Close the connection, blocking until completed
40
75
  def close!
41
76
  client.close if open?
42
77
  end
43
78
 
79
+ # Close in a non-blocking way, and immediately return.
80
+ def async_close!
81
+ client.async_close if open?
82
+ end
83
+
84
+ # Send raw data via this connection
44
85
  def send_data(data)
45
86
  client.send_data(data)
46
87
  end
@@ -51,27 +92,16 @@ module XRBP
51
92
  @force_quit
52
93
  end
53
94
 
95
+ # Immediately terminate the connection and all related operations
54
96
  def force_quit!
55
97
  @force_quit = true
56
98
  wake_all
57
- end
58
-
59
- def thread_registry
60
- @thread_registry ||= Concurrent::Array.new
61
- end
62
-
63
- def rsleep(t)
64
- thread_registry << Thread.current
65
- sleep(t)
66
- thread_registry.delete(Thread.current)
67
- end
68
-
69
- def wake_all
70
- thread_registry.each { |th| th.wakeup }
99
+ # TODO immediate terminate socket connection
71
100
  end
72
101
 
73
102
  ###
74
103
 
104
+ # Block until connection is open
75
105
  def wait_for_open
76
106
  return unless initialized?
77
107
 
@@ -80,6 +110,7 @@ module XRBP
80
110
  } until force_quit? || open?
81
111
  end
82
112
 
113
+ # Block until connection is closed
83
114
  def wait_for_close
84
115
  return unless initialized?
85
116
 
@@ -88,6 +119,15 @@ module XRBP
88
119
  } while !force_quit? && open?
89
120
  end
90
121
 
122
+ # Block until connection is completed
123
+ def wait_for_completed
124
+ return unless initialized?
125
+
126
+ state_mutex.synchronize {
127
+ completed_cv.wait(state_mutex, 0.1)
128
+ } while !force_quit? && !completed?
129
+ end
130
+
91
131
  def state_mutex
92
132
  @state_mutex ||= Mutex.new
93
133
  end
@@ -100,8 +140,13 @@ module XRBP
100
140
  @close_cv ||= ConditionVariable.new
101
141
  end
102
142
 
143
+ def completed_cv
144
+ @completed_cv ||= ConditionVariable.new
145
+ end
146
+
103
147
  ###
104
148
 
149
+ # @private
105
150
  def client
106
151
  @client ||= begin
107
152
  client = Client.new(@url)
@@ -109,12 +154,12 @@ module XRBP
109
154
 
110
155
  client.on :connecting do
111
156
  conn.emit :connecting
112
- conn.parent.emit :connecting if conn.parent
157
+ conn.parent.emit :connecting, conn if conn.parent
113
158
  end
114
159
 
115
160
  client.on :open do
116
161
  conn.emit :open
117
- conn.parent.emit :open if conn.parent
162
+ conn.parent.emit :open, conn if conn.parent
118
163
 
119
164
  conn.state_mutex.synchronize {
120
165
  conn.open_cv.signal
@@ -127,7 +172,7 @@ module XRBP
127
172
 
128
173
  client.on :close do
129
174
  conn.emit :close
130
- conn.parent.emit :close if conn.parent
175
+ conn.parent.emit :close, conn if conn.parent
131
176
 
132
177
  conn.state_mutex.synchronize {
133
178
  conn.close_cv.signal
@@ -140,12 +185,20 @@ module XRBP
140
185
 
141
186
  client.on :completed do |err|
142
187
  conn.emit :completed
143
- conn.parent.emit :completed if conn.parent
188
+ conn.parent.emit :completed, conn if conn.parent
189
+
190
+ conn.state_mutex.synchronize {
191
+ conn.completed_cv.signal
192
+ }
193
+
194
+ conn.plugins.each { |plg|
195
+ plg.completed if plg.respond_to?(:completed)
196
+ }
144
197
  end
145
198
 
146
199
  client.on :error do |err|
147
200
  conn.emit :error, err
148
- conn.parent.emit :error, err if conn.parent
201
+ conn.parent.emit :error, conn, err if conn.parent
149
202
 
150
203
  conn.plugins.each { |plg|
151
204
  plg.error err if plg.respond_to?(:error)
@@ -154,7 +207,7 @@ module XRBP
154
207
 
155
208
  client.on :message do |msg|
156
209
  conn.emit :message, msg
157
- conn.parent.emit :message, msg if conn.parent
210
+ conn.parent.emit :message, conn, msg if conn.parent
158
211
 
159
212
  conn.plugins.each { |plg|
160
213
  plg.message msg if plg.respond_to?(:message)
@@ -165,5 +218,5 @@ module XRBP
165
218
  end
166
219
  end
167
220
  end # class Connection
168
- end # module WebSocket
221
+ end # module WebSocket
169
222
  end # module XRBP