deribit 1.3.2

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.
@@ -0,0 +1,206 @@
1
+ module Deribit
2
+ class API
3
+ attr_reader :request
4
+
5
+ def initialize(key, secret, test_server: nil)
6
+ @request = Request.new(key, secret, test_server: test_server)
7
+ end
8
+
9
+ def orderbook(instrument)
10
+ request.send(path: "/api/v1/public/getorderbook", params: {instrument: instrument})
11
+ end
12
+
13
+ def instruments(expired: false, only_active: true)
14
+ response = request.send(path: '/api/v1/public/getinstruments', params: {expired: expired})
15
+ if response.is_a?(Array) and only_active
16
+ response = response.select {|i| i[:isActive] == true}
17
+ end
18
+
19
+ response
20
+ end
21
+
22
+ def index
23
+ request.send(path: '/api/v1/public/index', params: {})
24
+ end
25
+
26
+ def test
27
+ request.send(path: '/api/v1/public/test', params: {})
28
+ end
29
+
30
+ def currencies
31
+ request.send(path: '/api/v1/public/getcurrencies', params: {})
32
+ end
33
+
34
+ def last_trades(instrument, count: nil, since: nil)
35
+ params = {instrument: instrument}
36
+ params[:count] = count if count
37
+ params[:since] = since if since
38
+
39
+ request.send(path: '/api/v1/public/getlasttrades', params: params)
40
+ end
41
+
42
+ def summary(instrument = 'all')
43
+ params = {}
44
+ params[:instrument] = instrument if instrument
45
+
46
+ request.send(path: '/api/v1/public/getsummary', params: params)
47
+ end
48
+
49
+ def margins(instrument, quantity: 1, price: 0.01)
50
+ params = {
51
+ instrument: instrument,
52
+ quantity: quantity,
53
+ price: price
54
+ }
55
+
56
+ request.send(path: '/api/v1/private/getmargins', params: params)
57
+ end
58
+
59
+ def account(full: false)
60
+ params = {
61
+ ext: full
62
+ }
63
+
64
+ request.send(path: '/api/v1/private/account', params: params)
65
+ end
66
+
67
+
68
+ # | Name | Type | Decription |
69
+ # |--------------|------------|-----------------------------------------------------------------------------------|
70
+ # | `instrument` | `string` | Required, instrument name |
71
+ # | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
72
+ # | `price` | `float` | Required, USD for futures, BTC for options |
73
+ # | `type` | `string` | Required, "limit", "market" or for futures only: "stop_limit" |
74
+ # | `stopPx` | `string` | Required, needed for stop_limit order, defines stop price |
75
+ # | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
76
+ # | `label` | `string` | Optional, user defined maximum 32-char label for the order |
77
+ # | `max_show` | `string` | Optional, optional parameter, if "0" then the order will be hidden |
78
+ # | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
79
+
80
+ def buy(instrument, quantity, price, type: "limit", stopPx: nil, execInst: "index_price", post_only: nil, reduce_only: nil, label: nil, max_show: nil, adv: nil)
81
+ params = {
82
+ instrument: instrument,
83
+ quantity: quantity,
84
+ price: price
85
+ }
86
+
87
+ %i(type stopPx post_only reduce_only label max_show adv execInst).each do |var|
88
+ variable = eval(var.to_s)
89
+ params[var] = variable if variable
90
+ end
91
+
92
+ request.send(path: '/api/v1/private/buy', params: params)
93
+ end
94
+
95
+
96
+ # | Name | Type | Decription |
97
+ # |--------------|------------|-----------------------------------------------------------------------------------|
98
+ # | `instrument` | `string` | Required, instrument name |
99
+ # | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
100
+ # | `price` | `float` | Required, USD for futures, BTC for options |
101
+ # | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
102
+ # | `label` | `string` | Optional, user defined maximum 32-char label for the order |
103
+ # | `max_show` | `string` | Optional, optional parameter, if "0" then the order will be hidden |
104
+ # | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
105
+ #
106
+
107
+ def sell(instrument, quantity, price, type: "limit", stopPx: nil, execInst: "index_price", post_only: nil, reduce_only: nil, label: nil, max_show: nil, adv: nil)
108
+ params = {
109
+ instrument: instrument,
110
+ quantity: quantity,
111
+ price: price
112
+ }
113
+
114
+ %i(type stopPx post_only reduce_only label max_show adv execInst).each do |var|
115
+ variable = eval(var.to_s)
116
+ params[var] = variable if variable
117
+ end
118
+
119
+ request.send(path: '/api/v1/private/sell', params: params)
120
+ end
121
+
122
+ #
123
+ # | Name | Type | Decription |
124
+ # |--------------|------------|-----------------------------------------------------------------------------------|
125
+ # | `order_id` | `integer` | Required, ID of the order returned by "sell" or "buy" request |
126
+ # | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
127
+ # | `price` | `float` | Required, USD for futures, BTC for options |
128
+ # | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
129
+ # | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
130
+
131
+ def edit(order_id, quantity, price, post_only: nil, adv: nil, stopPx: nil)
132
+ params = {
133
+ orderId: order_id,
134
+ quantity: quantity,
135
+ price: price
136
+ }
137
+
138
+ %i(post_only adv stopPx).each do |var|
139
+ variable = eval(var.to_s)
140
+ params[var] = variable if variable
141
+ end
142
+
143
+ request.send(path: '/api/v1/private/edit', params: params)
144
+ end
145
+
146
+ def cancel(order_id)
147
+ params = {
148
+ "orderId": order_id
149
+ }
150
+
151
+ request.send(path: '/api/v1/private/cancel', params: params)
152
+ end
153
+
154
+ def cancel_all(type = "all")
155
+ params = {
156
+ "type": type
157
+ }
158
+
159
+ request.send(path: '/api/v1/private/cancelall', params: params)
160
+ end
161
+
162
+ def open_orders(instrument: nil, order_id: nil, type: nil)
163
+ params = {}
164
+ params[:instrument] = instrument if instrument
165
+ params[:orderId] = order_id if order_id
166
+ params[:type] = type if type
167
+
168
+ request.send(path: '/api/v1/private/getopenorders', params: params)
169
+ end
170
+
171
+ def positions
172
+ request.send(path: '/api/v1/private/positions', params: {})
173
+ end
174
+
175
+ def order_state(order_id)
176
+ params = {
177
+ "orderId": order_id
178
+ }
179
+
180
+ request.send(path: '/api/v1/private/orderstate', params: params)
181
+ end
182
+
183
+ def order_history(instrument: nil, count: nil, offset: nil)
184
+ params = {}
185
+ params[:instrument] = instrument if instrument
186
+ params[:count] = count if count
187
+ params[:offset] = offset if offset
188
+
189
+ request.send(path: '/api/v1/private/orderhistory', params: params)
190
+ end
191
+
192
+ def trade_history(instrument: nil, count: nil, start_trade_id: nil)
193
+ params = {}
194
+
195
+ %i(count instrument).each do |var|
196
+ variable = eval(var.to_s)
197
+ params[var] = variable if variable
198
+ end
199
+
200
+ params[:startTradeId] = start_trade_id if start_trade_id
201
+
202
+ request.send(path: '/api/v1/private/tradehistory', params: params)
203
+ end
204
+
205
+ end
206
+ end
@@ -0,0 +1,24 @@
1
+ module Deribit
2
+ class Error < StandardError
3
+ attr_reader :code, :msg
4
+
5
+ def initialize(code: nil, json: {}, message: nil)
6
+ @code = code || json[:code]
7
+ @msg = message || json[:msg]
8
+ end
9
+
10
+ def inspect
11
+ message = ""
12
+ message += "(#{code}) " unless code.nil?
13
+ message += "#{msg}" unless msg.nil?
14
+ end
15
+
16
+ def message
17
+ inspect
18
+ end
19
+
20
+ def to_s
21
+ inspect
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ class Deribit::ImmutableHeaderKey
2
+ attr_reader :key
3
+
4
+ def initialize(key)
5
+ @key = key
6
+ end
7
+
8
+ def downcase
9
+ self
10
+ end
11
+
12
+ def capitalize
13
+ self
14
+ end
15
+
16
+ def split(*)
17
+ [self]
18
+ end
19
+
20
+ def hash
21
+ key.hash
22
+ end
23
+
24
+ def eql?(other)
25
+ key.eql? other.key.eql?
26
+ end
27
+
28
+ def to_s
29
+ def self.to_s
30
+ key
31
+ end
32
+ self
33
+ end
34
+ end
@@ -0,0 +1,91 @@
1
+ require 'base64'
2
+ require 'net/http'
3
+
4
+ module Deribit
5
+ class Request
6
+ attr_accessor :key, :secret, :base_uri
7
+
8
+ def initialize(key, secret, test_server: nil)
9
+ test_server = ENV['DERIBIT_TEST_SERVER'] if test_server == nil
10
+ @key = key
11
+ @secret = secret
12
+ @base_uri = test_server ? URI(TEST_URL) : URI(SERVER_URL)
13
+ end
14
+
15
+ def send(path: DEFAULT_REQUEST_PATH, params: {})
16
+ uri = base_uri + path
17
+
18
+ if path.start_with?(PRIVATE_PATH)
19
+ request = Net::HTTP::Post.new(uri.path)
20
+ request.body = URI.encode_www_form(params)
21
+ request.add_field 'x-deribit-sig', generate_signature(path, params)
22
+
23
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
24
+ http.request(request)
25
+ end
26
+ else
27
+ uri.query = URI.encode_www_form(params)
28
+ response = Net::HTTP.get_response(uri)
29
+ end
30
+
31
+ if is_error_response?(response)
32
+ raise Error.new(code: response.code)
33
+ else
34
+ process(response)
35
+ end
36
+ end
37
+
38
+ def process(response)
39
+ json = JSON.parse(response.body, symbolize_names: true)
40
+
41
+ raise Error.new(message: "Failed for #{key}: " + json[:message]) unless json[:success]
42
+
43
+ if json.include?(:result)
44
+ json[:result]
45
+ elsif json.include?(:message)
46
+ json[:message]
47
+ else
48
+ "ok"
49
+ end
50
+ end
51
+
52
+ def generate_signature(path, params = {})
53
+ timestamp = Time.now.utc.to_i + 1000
54
+
55
+ signature_data = {
56
+ _: timestamp,
57
+ _ackey: key,
58
+ _acsec: secret,
59
+ _action: path
60
+ }
61
+
62
+ signature_data.update(params)
63
+ sorted_signature_data = signature_data.sort
64
+
65
+ converter = ->(data){
66
+ key = data[0]
67
+ value = data[1]
68
+ if value.is_a?(Array)
69
+ [key.to_s, value.join].join('=')
70
+ else
71
+ [key.to_s, value.to_s].join('=')
72
+ end
73
+ }
74
+
75
+ items = sorted_signature_data.map(&converter)
76
+ signature_string = items.join('&')
77
+
78
+ sha256 = OpenSSL::Digest::SHA256.new
79
+ sha256_signature = sha256.digest(signature_string.encode('utf-8'))
80
+
81
+ base64_signature = Base64.encode64(sha256_signature).encode('utf-8')
82
+
83
+ [key, timestamp, base64_signature].join('.').strip
84
+ end
85
+
86
+ def is_error_response?(response)
87
+ code = response.code.to_i
88
+ code == 0 || code >= 400
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module Deribit
2
+ VERSION = "1.3.2"
3
+ end
@@ -0,0 +1,56 @@
1
+ module Deribit
2
+ class WS
3
+
4
+ class Handler
5
+ attr_accessor :timestamp
6
+
7
+ AVAILABLE_METHODS = [
8
+ :account,
9
+ :getcurrencies,
10
+ :subscribe,
11
+ :subscribed,
12
+ :unsubscribe,
13
+ :buy,
14
+ :sell,
15
+ :trade,
16
+ :trade_event,
17
+ :order_book_event,
18
+ :user_order_event,
19
+ :user_orders_event,
20
+ :announcements,
21
+ :index,
22
+ :heartbeat,
23
+ :order,
24
+ :pong
25
+ ]
26
+ SILENT = [:setheartbeat, :subscribed, :heartbeat, :"public API test"]
27
+
28
+ def initialize
29
+ @timestamp = Time.now.to_i
30
+ end
31
+
32
+ def method_missing(m, *json, &block)
33
+ return false if SILENT.include?(m.to_sym)
34
+
35
+ puts "Delegating #{m}"
36
+ if AVAILABLE_METHODS.include?(m.to_sym)
37
+ notice(json)
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def notice(json)
44
+ return json.each { |e| notice(e) } if json.is_a?(Array)
45
+
46
+ msg = json.is_a?(String) ? json : json[:message]
47
+ puts "Notice: #{msg}" if msg && !SILENT.include?(msg.to_sym)
48
+ end
49
+
50
+ def handle_error(json, error)
51
+ puts "Alert! #{error.class} on message: '#{json.try(:fetch, :message)}', #{json.inspect}. Message: #{error.full_message}"
52
+ end
53
+ end
54
+
55
+ end
56
+ end
data/lib/deribit/ws.rb ADDED
@@ -0,0 +1,285 @@
1
+ require 'websocket-client-simple'
2
+
3
+ module Deribit
4
+ class WS
5
+ AVAILABLE_EVENTS = [:order_book, :trade, :my_trade, :user_order, :index, :portfolio, :announcement]
6
+
7
+ attr_reader :socket, :response, :ids_stack, :handler, :subscribed_instruments
8
+
9
+ def initialize(key, secret, handler: Handler, test_server: nil)
10
+ test_server = ENV["DERIBIT_TEST_SERVER"] if test_server == nil
11
+ @request = Request.new(key, secret, test_server: test_server)
12
+ @socket = connect(test_server ? WS_TEST_URL : WS_SERVER_URL)
13
+ @handler = handler.instance_of?(Class) ? handler.new : handler
14
+ @ids_stack = []
15
+
16
+ # the structure of subscribed_instruments: {'event_name' => ['instrument1', 'instrument2']]}
17
+ @subscribed_instruments = {}
18
+
19
+ start_handle
20
+ end
21
+
22
+ def add_subscribed_instruments(instruments: , events: )
23
+ instruments = [instruments] unless instruments.is_a?(Array)
24
+
25
+ events.each do |event|
26
+ _event = event.to_sym
27
+ @subscribed_instruments[_event] = if sub_instr = @subscribed_instruments[_event]
28
+ (sub_instr + instruments).uniq
29
+ else
30
+ instruments.uniq
31
+ end
32
+ end
33
+ end
34
+
35
+ def connect(url)
36
+ puts "Connecting to #{url}"
37
+ WebSocket::Client::Simple.connect(url)
38
+ end
39
+
40
+ def reconnect!
41
+ @socket = connect
42
+ start_handle
43
+ sleep 3
44
+ resubscribe!
45
+ end
46
+
47
+ def resubscribe!
48
+ if subscribed_instruments.any?
49
+ subscribed_instruments.each do |event, instruments|
50
+ p "Reconnecting to event: #{event} at instrument: #{instruments}"
51
+ subscribe(instruments, events: event.to_s)
52
+ end
53
+ end
54
+ end
55
+
56
+ def close
57
+ @socket.close
58
+ end
59
+
60
+ def ping
61
+ message = {action: "/api/v1/public/ping"}
62
+ @socket.send(message.to_json)
63
+ end
64
+
65
+ def test
66
+ message = {action: "/api/v1/public/test"}
67
+ @socket.send(message.to_json)
68
+ end
69
+
70
+ # events to be reported, possible events:
71
+ # "order_book" -- order book change
72
+ # "trade" -- trade notification
73
+ # "announcements" -- announcements (list of new announcements titles is send)
74
+ # "user_order" -- change of user orders (openning, cancelling, filling)
75
+ # "my_trade" -- filtered trade notification, only trades of the
76
+ # subscribed user are reported with trade direction "buy"/"sell" from the
77
+ # subscribed user point of view ("I sell ...", "I buy ..."), see below.
78
+ # Note, for "index" - events are ignored and can be []
79
+ def subscribe(instruments = ['BTC-PERPETUAL'] , events: ["user_order"], arguments: {})
80
+ instruments = [instruments] unless instruments.is_a?(Array)
81
+ events = [events] unless events.is_a?(Array)
82
+
83
+ raise "Events must include only #{AVAILABLE_EVENTS.join(", ")} actions" if events.map{|e| AVAILABLE_EVENTS.include?(e.to_sym)}.index(false) or events.empty?
84
+ raise "instruments are required" if instruments.empty?
85
+
86
+ arguments = arguments.merge(instrument: instruments, event: events)
87
+ send(path: '/api/v1/private/subscribe', arguments: arguments)
88
+ end
89
+
90
+ #unsubscribe for all notifications if instruments is empty
91
+ def unsubscribe(instruments=[])
92
+ instruments = [instruments] unless instruments.is_a?(Array)
93
+ send(path: '/api/v1/private/unsubscribe')
94
+ sleep(0.2)
95
+ if instruments.any?
96
+ @subscribed_instruments.each do |event, _instruments|
97
+ @subscribed_instruments[event] = _instruments - instruments
98
+ subscribe(@subscribed_instruments[event], events: [event])
99
+ sleep(0.2)
100
+ end
101
+ end
102
+ end
103
+
104
+ # | Name | Type | Decription |
105
+ # |--------------|------------|-----------------------------------------------------------------------------------|
106
+ # | `instrument` | `string` | Required, instrument name |
107
+ # | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
108
+ # | `price` | `float` | Required, USD for futures, BTC for options |
109
+ # | `type` | `string` | Required, "limit", "market" or for futures only: "stop_limit" |
110
+ # | `stopPx` | `string` | Required, needed for stop_limit order, defines stop price |
111
+ # | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
112
+ # | `label` | `string` | Optional, user defined maximum 32-char label for the order |
113
+ # | `max_show` | `string` | Optional, optional parameter, if "0" then the order will be hidden |
114
+ # | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
115
+
116
+ def buy(instrument, quantity, price, type: "limit", stopPx: nil, execInst: "mark_price", post_only: nil, label: nil, max_show: nil, adv: nil)
117
+ params = {
118
+ instrument: instrument,
119
+ quantity: quantity
120
+ }
121
+ params[:price] = price if price
122
+
123
+ %i(type stopPx post_only label max_show adv execInst).each do |var|
124
+ variable = eval(var.to_s)
125
+ params[var] = variable if variable
126
+ end
127
+
128
+ send(path: '/api/v1/private/buy', arguments: params)
129
+ end
130
+
131
+ # | Name | Type | Decription |
132
+ # |--------------|------------|-----------------------------------------------------------------------------------|
133
+ # | `instrument` | `string` | Required, instrument name |
134
+ # | `quantity` | `integer` | Required, quantity, in contracts ($10 per contract for futures, ฿1 — for options) |
135
+ # | `price` | `float` | Required, USD for futures, BTC for options |
136
+ # | `post_only` | `boolean` | Optional, if true then the order will be POST ONLY |
137
+ # | `label` | `string` | Optional, user defined maximum 32-char label for the order |
138
+ # | `max_show` | `string` | Optional, optional parameter, if "0" then the order will be hidden |
139
+ # | `adv` | `string` | Optional, can be "implv", "usd", or absent (advanced order type) |
140
+ #
141
+
142
+ def sell(instrument, quantity, price, type: "limit", stopPx: nil, execInst: "mark_price", post_only: nil, label: nil, max_show: nil, adv: nil)
143
+ params = {
144
+ instrument: instrument,
145
+ quantity: quantity
146
+ }
147
+ params[:price] = price if price
148
+
149
+ %i(type stopPx post_only label max_show adv execInst).each do |var|
150
+ variable = eval(var.to_s)
151
+ params[var] = variable if variable
152
+ end
153
+
154
+ send(path: '/api/v1/private/sell', arguments: params)
155
+ end
156
+
157
+ def account
158
+ send(path: '/api/v1/private/account')
159
+ end
160
+
161
+ def instruments(expired: false)
162
+ send(path: '/api/v1/public/getinstruments', arguments: {expired: expired})
163
+ end
164
+
165
+ def currencies
166
+ send(path: '/api/v1/public/getcurrencies')
167
+ end
168
+
169
+ def summary(instrument = 'BTC-PERPETUAL')
170
+ send(path: '/api/v1/public/getsummary', arguments: { instrument: instrument })
171
+ end
172
+
173
+ def openorders(instrument: "BTC-PERPETUAL", order_id: nil, type: nil)
174
+ params = {}
175
+ params[:instrument] = instrument if instrument
176
+ params[:orderId] = order_id if order_id
177
+ params[:type] = type if type
178
+
179
+ send(path: '/api/v1/private/getopenorders', arguments: params)
180
+ end
181
+
182
+ def cancel(order_id)
183
+ params = {
184
+ "orderId": order_id
185
+ }
186
+
187
+ send(path: '/api/v1/private/cancel', arguments: params)
188
+ end
189
+
190
+ def cancel_all(type = "all")
191
+ params = {
192
+ "type": type
193
+ }
194
+
195
+ send(path: '/api/v1/private/cancelall', arguments: params)
196
+ end
197
+
198
+ def set_heartbeat(interval = "60")
199
+ params = {
200
+ "interval": interval
201
+ }
202
+
203
+ send(path: '/api/v1/public/setheartbeat', arguments: params)
204
+ end
205
+
206
+ def handle_notifications(notifications)
207
+ return if notifications.empty?
208
+ notification, *tail = notifications
209
+ handler.send(notification[:message], notification[:result])
210
+
211
+ handle_notifications(tail)
212
+ end
213
+
214
+ private
215
+
216
+ def start_handle
217
+ instance = self
218
+ @socket.on :message do |msg|
219
+ # puts "msg = #{msg.inspect}"
220
+ begin
221
+ if msg.type == :text
222
+ json = JSON.parse(msg.data, symbolize_names: true)
223
+ puts "Subscribed!" if json[:message] == "subscribed"
224
+
225
+ if json[:message] == "test_request"
226
+ # puts "Got test request: #{json.inspect}" # DEBUG
227
+ instance.test
228
+ elsif json[:id] and stack_id = instance.ids_stack.find{|i| i[json[:id]]}
229
+ method = stack_id[json[:id]][0]
230
+ #pass the method to handler
231
+ params = instance.ids_stack.delete(stack_id)
232
+
233
+ #save subscribed_instruments for resubscribe in unsubscribe action
234
+ if method == 'subscribe'
235
+ params = params[json[:id]][1][:arguments]
236
+ instance.add_subscribed_instruments(instruments: params[:instrument], events: params[:event])
237
+ end
238
+
239
+ instance.handler.send(method, json)
240
+ elsif json[:notifications]
241
+ instance.handle_notifications(json[:notifications])
242
+ else
243
+ instance.handler.send(:notice, json)
244
+ end
245
+
246
+ instance.handler.timestamp = Time.now.to_i
247
+ elsif msg.type == :close
248
+ puts "trying to reconnect = got close event, msg: #{msg.inspect}"
249
+ instance.reconnect!
250
+ end
251
+ rescue StandardError => e
252
+ instance.handler.handle_error(json, e)
253
+ end
254
+ end
255
+
256
+ @socket.on :error do |e|
257
+ puts e
258
+ end
259
+ end
260
+
261
+ def send(path: , arguments: {})
262
+ return unless path
263
+ params = {action: path, arguments: arguments}
264
+ sig = @request.generate_signature(path, arguments)
265
+ params[:sig] = sig
266
+ params[:id] = Time.now.to_i
267
+
268
+ action = path[/\/api.*\/([^\/]+)$/, 1]
269
+ put_id(params[:id], [action, params])
270
+
271
+ p params
272
+ @socket.send(params.to_json)
273
+ end
274
+
275
+
276
+ def put_id(id, action)
277
+ @ids_stack << {id => action}
278
+ end
279
+
280
+ def pop_id(id)
281
+ @ids_stack.delete(id)
282
+ end
283
+ end
284
+
285
+ end