xrbp 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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