pakyow-realtime 0.10.2 → 0.11.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 (21) hide show
  1. checksums.yaml +4 -4
  2. data/pakyow-realtime/CHANGELOG.md +14 -0
  3. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/config.rb +4 -6
  4. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/connection.rb +0 -0
  5. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/context.rb +8 -0
  6. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/delegate.rb +19 -2
  7. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/exceptions.rb +0 -0
  8. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/ext/request.rb +0 -0
  9. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/helpers.rb +7 -0
  10. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/hooks.rb +15 -4
  11. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handler.rb +0 -0
  12. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handlers/call_route.rb +7 -10
  13. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/message_handlers/ping.rb +0 -0
  14. data/pakyow-realtime/lib/pakyow/realtime/redis_subscription.rb +61 -0
  15. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/registries/redis_registry.rb +41 -28
  16. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/registries/simple_registry.rb +3 -3
  17. data/pakyow-realtime/lib/{pakyow-realtime → pakyow/realtime}/websocket.rb +84 -63
  18. data/pakyow-realtime/lib/pakyow/realtime.rb +19 -0
  19. data/pakyow-realtime/lib/pakyow-realtime.rb +1 -21
  20. metadata +26 -26
  21. data/pakyow-realtime/lib/pakyow-realtime/redis_subscription.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d3af56bba7d1aa3e3b121eb2b542d0dc4f85d35d
4
- data.tar.gz: 33f0af0198c31f0b2b728d07a63635a2f85ad79a
3
+ metadata.gz: 7de6d6caa97ff92eb8c07092eb9438e952ceb4c2
4
+ data.tar.gz: dc8d6705e39160414dea747275e53bf351ddfaa5
5
5
  SHA512:
6
- metadata.gz: bf7e58a486cdef598237a63744c06069c9df16bf70c39d206d9bd18e2272e8a2035007ac3cd50d7dec232488ad89deb122d1b369a1230a229b2e6f09818dc968
7
- data.tar.gz: 2ab01fe491a66614a021fefea6e674f68cf614fb970742b57ba2a3adca12c4dfae3716dc0c25c51ff3e3b147985730e0f657cd586fa784fdcd9da4e59aba7520
6
+ metadata.gz: 562149cc4659759ae9a6154dfd5e6c5d02cab93723e50b15b188b7e240e85b24604011acceb40ac972e5a233d1a5bb82c352b1821cee86fe420ecbbf00a17072
7
+ data.tar.gz: ea8d69bf0efe8e29018d36835ab14c3d0b73b7e7707fd949a0e78d34eece2e3d013726b251e80c14b83b9de24b17be3ab72defaf478a8f9aefe42a2828617a71
@@ -1,3 +1,17 @@
1
+ # 0.11.0
2
+
3
+ * Gracefully shuts down WebSocket when something bad happens
4
+ * Fixes a bug calling the root route over a WebSocket
5
+ * Fixes a bug causing join events to be fired before the connection is registered
6
+ * Fixes a bug causing session to not be serialized in redis
7
+ * Fixes a bug causing redis subscriptions to never unsubscribe
8
+ * Properly disconnects from redis as necessary to avoid eating up connections
9
+ * Moves everything into the Pakyow namespace
10
+ * Improves subscriber management in production environments
11
+ * Fixes a memory leak caused by converting channel names to symbols
12
+ * Replaces `websocket_parser` gem with `websocket`
13
+ * Fixes a bug causing websocket failures in Internet Explorer
14
+
1
15
  # 0.10.2 / 2015-11-15
2
16
 
3
17
  * Adds ability to return only a partial in call-route response
@@ -1,7 +1,7 @@
1
1
  require_relative 'registries/simple_registry'
2
2
  require_relative 'registries/redis_registry'
3
3
 
4
- Pakyow::Config.register(:realtime) { |config|
4
+ Pakyow::Config.register :realtime do |config|
5
5
  # The registry to use when keeping up with connections.
6
6
  config.opt :registry, Pakyow::Realtime::SimpleRegistry
7
7
 
@@ -13,10 +13,8 @@ Pakyow::Config.register(:realtime) { |config|
13
13
 
14
14
  # Whether or not realtime should be enabled.
15
15
  config.opt :enabled, true
16
- }.env(:development) { |opts|
16
+ end.env :development do |opts|
17
17
  opts.registry = Pakyow::Realtime::SimpleRegistry
18
- }.env(:staging) { |opts|
18
+ end.env :production do |opts|
19
19
  opts.registry = Pakyow::Realtime::RedisRegistry
20
- }.env(:production) { |opts|
21
- opts.registry = Pakyow::Realtime::RedisRegistry
22
- }
20
+ end
@@ -49,6 +49,14 @@ module Pakyow
49
49
  delegate.push(msg, channels)
50
50
  end
51
51
 
52
+ # Push a message down a channel directed at a specific client,
53
+ # identified by key.
54
+ #
55
+ # @api public
56
+ def push_message_to_socket_with_key(msg, channel, key, propagated = false)
57
+ delegate.push_message_to_socket_with_key(msg, channel, key, propagated)
58
+ end
59
+
52
60
  # Returns an instance of the connection delegate.
53
61
  #
54
62
  # @api private
@@ -29,7 +29,7 @@ module Pakyow
29
29
  @channels[channel] << connection
30
30
  end
31
31
 
32
- registry.subscribe_for_propagation(channels) if registry.propagates?
32
+ registry.subscribe_for_propagation(channels, key) if registry.propagates?
33
33
  end
34
34
 
35
35
  # Unregisters a connection by its key.
@@ -73,6 +73,23 @@ module Pakyow
73
73
  end
74
74
  end
75
75
 
76
+ # Pushes a message down a channel to a specific client (identified by key).
77
+ def push_message_to_socket_with_key(message, channel, key, propagated = false)
78
+ return if key.nil? || key.empty?
79
+
80
+ if registry.propagates? && !propagated
81
+ return registry.push_message_to_socket_with_key(message, channel, key)
82
+ else
83
+ connection = @connections.find { |_, connection|
84
+ connection and connection.key == key
85
+ }
86
+
87
+ if connection
88
+ connection[1].push(payload: message, channel: channel)
89
+ end
90
+ end
91
+ end
92
+
76
93
  private
77
94
 
78
95
  def propagate(message, channels)
@@ -80,7 +97,7 @@ module Pakyow
80
97
  end
81
98
 
82
99
  def propagated?(message)
83
- message.include?(:__propagated)
100
+ message.include?(:__propagated) || message.include?('__propagated')
84
101
  end
85
102
 
86
103
  def connections_for_channel(channel_query)
@@ -29,5 +29,12 @@ module Pakyow
29
29
  def socket_digest(socket_connection_id)
30
30
  Digest::SHA1.hexdigest("--#{socket_key}--#{socket_connection_id}--")
31
31
  end
32
+
33
+ module App
34
+ # @api private
35
+ def socket
36
+ Realtime::Context.new(self)
37
+ end
38
+ end
32
39
  end
33
40
  end
@@ -1,15 +1,24 @@
1
1
  Pakyow::App.before :route do
2
2
  # we want to hijack websocket requests
3
3
  #
4
- if req.env['HTTP_UPGRADE'] == 'websocket'
4
+ if req.env['HTTP_UPGRADE'] && req.env['HTTP_UPGRADE'].casecmp('websocket') == 0
5
5
  if Pakyow::Config.realtime.enabled
6
6
  socket_connection_id = params[:socket_connection_id]
7
7
  socket_digest = socket_digest(socket_connection_id)
8
8
 
9
- conn = Pakyow::Realtime::Websocket.new(req, socket_digest)
9
+ begin
10
+ conn = Pakyow::Realtime::Websocket.new(req, socket_digest)
11
+ rescue Pakyow::Realtime::HandshakeError => e
12
+ logger.error e.message
13
+ res.status = 400
14
+ halt
15
+ end
10
16
 
11
17
  # register the connection with a unique key
12
18
  Pakyow::Realtime::Delegate.instance.register(socket_digest, conn)
19
+
20
+ # tell the connection that it's registered
21
+ conn.registered
13
22
  end
14
23
 
15
24
  halt
@@ -20,8 +29,10 @@ Pakyow::App.after :process do
20
29
  # mixin the socket connection id into the body tag
21
30
  # this id is used by pakyow.js to idenfity itself with the server
22
31
  #
23
- if response.header['Content-Type'] == 'text/html' && Pakyow::Config.realtime.enabled
24
- body = response.body[0]
32
+ if response.header['Content-Type'].include?('text/html') && Pakyow::Config.realtime.enabled
33
+ next if !response.body.is_a?(Array)
34
+
35
+ body = response.body.first
25
36
  next if body.nil?
26
37
 
27
38
  mixin = '<body data-socket-connection-id="' + socket_connection_id + '"'
@@ -1,12 +1,7 @@
1
1
  # Calls an app route and returns a response, just like an HTTP request!
2
2
  #
3
3
  Pakyow::Realtime.handler :'call-route' do |message, session, response|
4
- path, qs = message['uri'].split('?')
5
- path_parts = path.split('/')
6
- path_parts[-1] += '.json'
7
- uri = [path_parts.join('/'), qs].join('?')
8
-
9
- env = Rack::MockRequest.env_for(uri, method: message['method'])
4
+ env = Rack::MockRequest.env_for(message['uri'], method: message['method'])
10
5
  env['pakyow.socket'] = true
11
6
  env['pakyow.data'] = message['input']
12
7
  env['rack.session'] = session
@@ -14,13 +9,15 @@ Pakyow::Realtime.handler :'call-route' do |message, session, response|
14
9
  # TODO: in production we want to push the message to a queue and
15
10
  # let the next available app instance pick it up, rather than
16
11
  # the current instance to handle all traffic on this socket
17
- app = Pakyow.app.dup
18
- res = app.process(env)
12
+
13
+ context = Pakyow::CallContext.new(env)
14
+ context.process
15
+ res = context.finish
19
16
 
20
17
  container = message['container']
21
18
  partial = message['partial']
22
19
 
23
- composer = app.presenter.composer
20
+ composer = context.presenter.composer
24
21
 
25
22
  if container
26
23
  body = composer.container(container.to_sym).includes(composer.partials).to_s
@@ -32,6 +29,6 @@ Pakyow::Realtime.handler :'call-route' do |message, session, response|
32
29
 
33
30
  response[:status] = res[0]
34
31
  response[:headers] = res[1]
35
- response[:body] = body
32
+ response[:body] = body.is_a?(StringIO) ? body.read : body
36
33
  response
37
34
  end
@@ -0,0 +1,61 @@
1
+ require 'redis'
2
+ require 'concurrent'
3
+
4
+ module Pakyow
5
+ module Realtime
6
+ # Manages channel subscriptions for this application instance's WebSockets.
7
+ #
8
+ # @api private
9
+ class RedisSubscription
10
+ SIGNAL_UNSUBSCRIBE = 'unsubscribe'
11
+
12
+ def initialize
13
+ @redis = ::Redis.new(Config.realtime.redis)
14
+ end
15
+
16
+ def subscribe(channels = [])
17
+ channels << signal_channel
18
+
19
+ Concurrent::Future.execute {
20
+ @redis.subscribe(*channels) do |on|
21
+ on.message do |channel, msg|
22
+ begin
23
+ msg = JSON.parse(msg)
24
+
25
+ if channel == signal_channel
26
+ if msg['signal'] == SIGNAL_UNSUBSCRIBE
27
+ @resubscribe_channels = msg['resubscribe_channels']
28
+ @redis.unsubscribe
29
+ end
30
+ end
31
+
32
+ if msg.is_a?(Hash)
33
+ msg[:__propagated] = true
34
+ elsif msg.is_a?(Array)
35
+ msg << :__propagated
36
+ end
37
+
38
+ context = Pakyow::Realtime::Context.new(Pakyow.app)
39
+
40
+ if msg.key?('key')
41
+ context.push_message_to_socket_with_key(msg['message'], msg['channel'], msg['key'], true)
42
+ else
43
+ context.push(msg, channel)
44
+ end
45
+ rescue StandardError => e
46
+ Pakyow.logger.error "RedisSubscription encountered a fatal error:"
47
+ Pakyow.logger.error e.message
48
+ end
49
+ end
50
+ end
51
+
52
+ subscribe(@resubscribe_channels) if @resubscribe_channels
53
+ }
54
+ end
55
+
56
+ def signal_channel
57
+ "pw:#{object_id}:signal"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -2,7 +2,8 @@ require 'json'
2
2
  require 'redis'
3
3
  require 'singleton'
4
4
 
5
- require_relative '../redis_subscription'
5
+ require 'pakyow/realtime/redis_subscription'
6
+ require 'pakyow/realtime/registries/simple_registry'
6
7
 
7
8
  module Pakyow
8
9
  module Realtime
@@ -15,37 +16,38 @@ module Pakyow
15
16
  # deployments with more than one app instance.
16
17
  #
17
18
  # @api private
18
- class RedisRegistry
19
- include Singleton
20
-
19
+ class RedisRegistry < SimpleRegistry
21
20
  attr_reader :subscriber
22
21
 
23
- def initialize
24
- @channels = []
25
- end
26
-
27
22
  def channels_for_key(key)
28
- channels(key)
23
+ value = Pakyow::Realtime.redis.hget(channel_key, key)
24
+ value ? JSON.parse(value) : []
29
25
  end
30
26
 
31
27
  def unregister_key(key)
28
+ super
29
+
32
30
  Pakyow::Realtime.redis.hdel(channel_key, key)
31
+ resubscribe
33
32
  end
34
33
 
35
34
  def subscribe_to_channels_for_key(channels, key)
36
- new_channels = channels(key).concat(Array.ensure(channels)).uniq
37
- Pakyow::Realtime.redis.hset(channel_key, key, new_channels.to_json)
35
+ channels << "pw:socket:#{key}"
36
+ super
38
37
 
39
- @channels.concat(channels).uniq!
40
38
  resubscribe
39
+
40
+ new_channels = channels_for_key(key).concat(Array.ensure(channels)).uniq
41
+ Pakyow::Realtime.redis.hset(channel_key, key, new_channels.to_json)
41
42
  end
42
43
 
43
44
  def unsubscribe_to_channels_for_key(channels, key)
44
- new_channels = channels(key) - Array.ensure(channels)
45
- Pakyow::Realtime.redis.hset(channel_key, key, new_channels.to_json)
45
+ super
46
46
 
47
- channels.each { |channel| @channels.delete(channel) }
48
47
  resubscribe
48
+
49
+ new_channels = channels_for_key(key) - Array.ensure(channels)
50
+ Pakyow::Realtime.redis.hset(channel_key, key, new_channels.to_json)
49
51
  end
50
52
 
51
53
  def propagates?
@@ -60,35 +62,46 @@ module Pakyow
60
62
  end
61
63
  end
62
64
 
63
- def subscribe_for_propagation(channels)
64
- @channels.concat(channels).uniq!
65
+ def subscribe_for_propagation(channels, key)
66
+ @channels[key] ||= []
67
+ @channels[key].concat(Array.ensure(channels)) << "pw:socket:#{key}"
68
+ @channels[key].uniq!
69
+
65
70
  resubscribe
66
71
  end
67
72
 
73
+ def push_message_to_socket_with_key(message, channel, key)
74
+ propagate({
75
+ key: key,
76
+ channel: channel,
77
+ message: message
78
+ },
79
+
80
+ ["pw:socket:#{key}"])
81
+ end
82
+
68
83
  private
69
84
 
70
- # Terminates the current subscriber and creates a new
71
- # subscriber with the current channels.
85
+ # Tells the subscription to unsubscribe from the current
86
+ # list of channels, then subscribes it to the new list.
72
87
  def resubscribe
88
+ channels = @channels.values.flatten.uniq
89
+
73
90
  if @subscriber
74
- Pakyow::Realtime.redis.publish(@subscriber.signal_channel, RedisSubscription::SIGNAL_UNSUBSCRIBE)
91
+ Pakyow::Realtime.redis.publish(@subscriber.signal_channel, {
92
+ 'signal' => RedisSubscription::SIGNAL_UNSUBSCRIBE,
93
+ 'resubscribe_channels' => channels
94
+ }.to_json)
75
95
  else
76
96
  @subscriber = RedisSubscription.new
97
+ @subscriber.subscribe(channels)
77
98
  end
78
-
79
- @subscriber.subscribe(@channels)
80
99
  end
81
100
 
82
101
  # Returns the key used to store channels.
83
102
  def channel_key
84
103
  Config.realtime.redis_key
85
104
  end
86
-
87
- # Returns the channels for a specific key, or all channels.
88
- def channels(key)
89
- value = Pakyow::Realtime.redis.hget(channel_key, key)
90
- (value ? JSON.parse(value) : []).map(&:to_sym)
91
- end
92
105
  end
93
106
  end
94
107
  end
@@ -24,14 +24,14 @@ module Pakyow
24
24
 
25
25
  def subscribe_to_channels_for_key(channels, key)
26
26
  @channels[key] ||= []
27
- @channels[key].concat(Array.ensure(channels.map(&:to_sym))).uniq!
27
+ @channels[key].concat(Array.ensure(channels)).uniq!
28
28
  end
29
29
 
30
30
  def unsubscribe_to_channels_for_key(channels, key)
31
31
  @channels[key] ||= []
32
- @channels[key] = @channels[key] - Array.ensure(channels.map(&:to_sym))
32
+ @channels[key] = @channels[key] - Array.ensure(channels)
33
33
  end
34
-
34
+
35
35
  def propagates?
36
36
  false
37
37
  end
@@ -1,5 +1,5 @@
1
1
  require 'concurrent'
2
- require 'websocket_parser'
2
+ require 'websocket'
3
3
 
4
4
  require_relative 'connection'
5
5
 
@@ -18,10 +18,13 @@ module Pakyow
18
18
  @req = req
19
19
  @key = key
20
20
 
21
- @handshake = handshake!(req)
22
- @socket = hijack!(req)
21
+ @socket = hijack(req)
22
+ @server = handshake(req)
23
+ setup
24
+ end
23
25
 
24
- handle_handshake
26
+ def registered
27
+ handle_ws_join
25
28
  end
26
29
 
27
30
  def shutdown
@@ -41,7 +44,19 @@ module Pakyow
41
44
  def push(msg)
42
45
  json = JSON.pretty_generate(msg)
43
46
  logger.debug "(ws.#{@key}) sending message: #{json}\n"
44
- WebSocket::Message.new(json).write(@socket)
47
+
48
+ frame = WebSocket::Frame::Outgoing::Server.new(
49
+ version: @server.version,
50
+ data: json,
51
+ type: :text)
52
+ @socket.write(frame.to_s)
53
+ rescue StandardError => e
54
+ logger.error "(#{@key}): WebSocket encountered a fatal error:"
55
+ logger.error e.message
56
+
57
+ # something went wrong (like a broken pipe); shutdown
58
+ # and let the socket reconnect if it's still around
59
+ shutdown
45
60
  end
46
61
 
47
62
  def self.on(event, &block)
@@ -50,11 +65,7 @@ module Pakyow
50
65
 
51
66
  private
52
67
 
53
- def handshake!(req)
54
- WebSocket::ClientHandshake.new(:get, req.url, handshake_headers(req))
55
- end
56
-
57
- def hijack!(req)
68
+ def hijack(req)
58
69
  if req.env['rack.hijack']
59
70
  req.env['rack.hijack'].call
60
71
  return req.env['rack.hijack_io']
@@ -63,75 +74,62 @@ module Pakyow
63
74
  end
64
75
  end
65
76
 
66
- def handshake_headers(req)
67
- {
68
- 'Upgrade' => req.env['HTTP_UPGRADE'],
69
- 'Sec-WebSocket-Version' => req.env['HTTP_SEC_WEBSOCKET_VERSION'],
70
- 'Sec-Websocket-Key' => req.env['HTTP_SEC_WEBSOCKET_KEY']
71
- }
72
- end
73
-
74
- def handle_handshake
75
- return if @socket.nil?
76
-
77
- if @handshake.valid?
78
- accept_handshake
79
- setup
80
- else
81
- fail_handshake
77
+ def handshake(req)
78
+ data = "#{req.env['REQUEST_METHOD']} #{req.env['REQUEST_URI']} #{req.env['SERVER_PROTOCOL']}\r\n"
79
+ req.env.each_pair do |key, val|
80
+ if key =~ /^HTTP_(.*)/
81
+ name = rack_env_key_to_http_header_name($1)
82
+ data << "#{name}: #{val}\r\n"
83
+ end
82
84
  end
83
- end
85
+ data << "\r\n"
84
86
 
85
- def accept_handshake
86
- response = @handshake.accept_response
87
- response.render(@socket)
88
- end
87
+ server = WebSocket::Handshake::Server.new
88
+ server << data
89
89
 
90
- def fail_handshake
91
- error = @handshake.errors.first
90
+ fail HandshakeError, "(ws.#{@key}) error during handshake" unless server.valid?
91
+ @socket.write(server.to_s)
92
92
 
93
- response = Rack::Response.new(400)
94
- response.render(@socket)
93
+ server
94
+ end
95
95
 
96
- fail HandshakeError, "(ws.#{@key}) error during handshake: #{error}"
96
+ def rack_env_key_to_http_header_name(key)
97
+ name = key.downcase.gsub('_', '-')
98
+ name[0] = name[0].upcase
99
+ name.gsub!(/-(.)/) do |chr|
100
+ chr.upcase
101
+ end
102
+ name
97
103
  end
98
104
 
99
105
  def setup
100
106
  logger.info "(ws.#{@key}) client established connection"
101
- handle_ws_join
102
107
 
103
- @parser = WebSocket::Parser.new
108
+ @parser = Parser.new(version: @server.version)
104
109
 
105
- @parser.on_message do |message|
110
+ @parser.on :message do |message|
106
111
  handle_ws_message(message)
107
112
  end
108
113
 
109
- @parser.on_error do |error|
114
+ @parser.on :error do |error|
110
115
  logger.error "(ws.#{@key}) encountered error #{error}"
111
116
  handle_ws_error(error)
112
117
  end
113
118
 
114
- @parser.on_close do |status, message|
119
+ @parser.on :close do |status, message|
115
120
  logger.info "(ws.#{@key}) client closed connection"
116
121
  handle_ws_close(status, message)
117
122
  end
118
123
 
119
- @parser.on_ping do |payload|
120
- handle_ws_ping(payload)
121
- end
122
-
123
- @reader = Concurrent::Future.execute {
124
+ Concurrent::Future.execute {
124
125
  begin
125
126
  loop do
126
127
  break if shutdown?
127
- @parser << @socket.read_nonblock(16_384)
128
+ @parser << @socket.read_nonblock(1024)
128
129
  end
129
130
  rescue ::IO::WaitReadable
130
131
  IO.select([@socket])
131
132
  retry
132
- rescue EOFError
133
- @parent.delegate.unregister(@key)
134
- @parent.shutdown
135
133
  end
136
134
  }
137
135
  end
@@ -158,25 +156,14 @@ module Pakyow
158
156
  end
159
157
 
160
158
  def handle_ws_close(_status, _message)
161
- @socket << WebSocket::Message.close.to_data
162
159
  shutdown
163
160
  end
164
161
 
165
- def handle_ws_ping(payload)
166
- @socket << WebSocket::Message.pong(payload).to_data
167
- end
168
-
169
162
  def self.handle_event(event, req)
170
- if Pakyow.app
171
- app = Pakyow.app.dup
172
- app.context = AppContext.new(req)
173
-
174
- ui = Pakyow.app.instance_variable_get(:@ui)
175
- app.context.ui = ui.dup if ui
176
- end
163
+ context = CallContext.new(req.env)
177
164
 
178
165
  event_handlers(event).each do |block|
179
- app.instance_exec(&block)
166
+ context.instance_exec(&block)
180
167
  end
181
168
  end
182
169
 
@@ -184,5 +171,39 @@ module Pakyow
184
171
  @event_handlers.fetch(event, [])
185
172
  end
186
173
  end
174
+
175
+ class Parser
176
+ def initialize(version: nil)
177
+ @parser = WebSocket::Frame::Incoming::Server.new(version: version)
178
+ @handlers = {}
179
+ end
180
+
181
+ def on(event, &block)
182
+ @handlers[event] = block
183
+ end
184
+
185
+ def <<(data)
186
+ @parser << data
187
+ process
188
+ end
189
+
190
+ private
191
+
192
+ def process
193
+ while (frame = @parser.next)
194
+ case frame.type
195
+ when :text
196
+ handle :message, frame
197
+ when :close
198
+ handle :close, frame
199
+ end
200
+ end
201
+ end
202
+
203
+ def handle(event, frame)
204
+ return unless @handlers.keys.include?(event)
205
+ @handlers[event].call(frame.data)
206
+ end
207
+ end
187
208
  end
188
209
  end
@@ -0,0 +1,19 @@
1
+ require 'securerandom'
2
+ require 'json'
3
+
4
+ require 'pakyow/support'
5
+ require 'pakyow/core'
6
+ require 'pakyow/realtime/helpers'
7
+ require 'pakyow/realtime/hooks'
8
+ require 'pakyow/realtime/context'
9
+ require 'pakyow/realtime/delegate'
10
+ require 'pakyow/realtime/registries/simple_registry'
11
+ require 'pakyow/realtime/registries/redis_registry'
12
+ require 'pakyow/realtime/redis_subscription'
13
+ require 'pakyow/realtime/websocket'
14
+ require 'pakyow/realtime/config'
15
+ require 'pakyow/realtime/exceptions'
16
+ require 'pakyow/realtime/message_handler'
17
+ require 'pakyow/realtime/message_handlers/call_route'
18
+ require 'pakyow/realtime/message_handlers/ping'
19
+ require 'pakyow/realtime/ext/request'
@@ -1,21 +1 @@
1
- require 'securerandom'
2
- require 'json'
3
-
4
- require 'pakyow-support'
5
- require 'pakyow-core'
6
-
7
- require_relative 'pakyow-realtime/helpers'
8
- require_relative 'pakyow-realtime/hooks'
9
- require_relative 'pakyow-realtime/context'
10
- require_relative 'pakyow-realtime/delegate'
11
- require_relative 'pakyow-realtime/registries/simple_registry'
12
- require_relative 'pakyow-realtime/registries/redis_registry'
13
- require_relative 'pakyow-realtime/redis_subscription'
14
- require_relative 'pakyow-realtime/websocket'
15
- require_relative 'pakyow-realtime/config'
16
- require_relative 'pakyow-realtime/exceptions'
17
- require_relative 'pakyow-realtime/message_handler'
18
- require_relative 'pakyow-realtime/message_handlers/call_route'
19
- require_relative 'pakyow-realtime/message_handlers/ping'
20
-
21
- require_relative 'pakyow-realtime/ext/request'
1
+ require 'pakyow/realtime'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pakyow-realtime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-16 00:00:00.000000000 Z
11
+ date: 2016-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pakyow-support
@@ -16,42 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.10.2
19
+ version: 0.11.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.10.2
26
+ version: 0.11.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pakyow-core
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 0.10.2
33
+ version: 0.11.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 0.10.2
40
+ version: 0.11.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: websocket_parser
42
+ name: websocket
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.0'
47
+ version: '1.2'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.0'
54
+ version: '1.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: redis
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -90,21 +90,22 @@ files:
90
90
  - pakyow-realtime/LICENSE
91
91
  - pakyow-realtime/README.md
92
92
  - pakyow-realtime/lib/pakyow-realtime.rb
93
- - pakyow-realtime/lib/pakyow-realtime/config.rb
94
- - pakyow-realtime/lib/pakyow-realtime/connection.rb
95
- - pakyow-realtime/lib/pakyow-realtime/context.rb
96
- - pakyow-realtime/lib/pakyow-realtime/delegate.rb
97
- - pakyow-realtime/lib/pakyow-realtime/exceptions.rb
98
- - pakyow-realtime/lib/pakyow-realtime/ext/request.rb
99
- - pakyow-realtime/lib/pakyow-realtime/helpers.rb
100
- - pakyow-realtime/lib/pakyow-realtime/hooks.rb
101
- - pakyow-realtime/lib/pakyow-realtime/message_handler.rb
102
- - pakyow-realtime/lib/pakyow-realtime/message_handlers/call_route.rb
103
- - pakyow-realtime/lib/pakyow-realtime/message_handlers/ping.rb
104
- - pakyow-realtime/lib/pakyow-realtime/redis_subscription.rb
105
- - pakyow-realtime/lib/pakyow-realtime/registries/redis_registry.rb
106
- - pakyow-realtime/lib/pakyow-realtime/registries/simple_registry.rb
107
- - pakyow-realtime/lib/pakyow-realtime/websocket.rb
93
+ - pakyow-realtime/lib/pakyow/realtime.rb
94
+ - pakyow-realtime/lib/pakyow/realtime/config.rb
95
+ - pakyow-realtime/lib/pakyow/realtime/connection.rb
96
+ - pakyow-realtime/lib/pakyow/realtime/context.rb
97
+ - pakyow-realtime/lib/pakyow/realtime/delegate.rb
98
+ - pakyow-realtime/lib/pakyow/realtime/exceptions.rb
99
+ - pakyow-realtime/lib/pakyow/realtime/ext/request.rb
100
+ - pakyow-realtime/lib/pakyow/realtime/helpers.rb
101
+ - pakyow-realtime/lib/pakyow/realtime/hooks.rb
102
+ - pakyow-realtime/lib/pakyow/realtime/message_handler.rb
103
+ - pakyow-realtime/lib/pakyow/realtime/message_handlers/call_route.rb
104
+ - pakyow-realtime/lib/pakyow/realtime/message_handlers/ping.rb
105
+ - pakyow-realtime/lib/pakyow/realtime/redis_subscription.rb
106
+ - pakyow-realtime/lib/pakyow/realtime/registries/redis_registry.rb
107
+ - pakyow-realtime/lib/pakyow/realtime/registries/simple_registry.rb
108
+ - pakyow-realtime/lib/pakyow/realtime/websocket.rb
108
109
  homepage: http://pakyow.org
109
110
  licenses:
110
111
  - MIT
@@ -125,9 +126,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
126
  version: '0'
126
127
  requirements: []
127
128
  rubyforge_project:
128
- rubygems_version: 2.4.5
129
+ rubygems_version: 2.5.1
129
130
  signing_key:
130
131
  specification_version: 4
131
132
  summary: Pakyow Realtime
132
133
  test_files: []
133
- has_rdoc:
@@ -1,49 +0,0 @@
1
- require 'redis'
2
- require 'concurrent'
3
-
4
- module Pakyow
5
- module Realtime
6
- # Manages channel subscriptions for this application instance's WebSockets.
7
- #
8
- # @api private
9
- class RedisSubscription
10
- SIGNAL_UNSUBSCRIBE = :unsubscribe
11
-
12
- def initialize
13
- @redis = ::Redis.new(Config.realtime.redis)
14
- end
15
-
16
- def subscribe(channels = [])
17
- channels << signal_channel
18
-
19
- Concurrent::Future.execute {
20
- @redis.subscribe(*channels) do |on|
21
- on.message do |channel, msg|
22
- if channel == signal_channel
23
- if msg == SIGNAL_UNSUBSCRIBE
24
- @redis.unsubscribe
25
- return
26
- end
27
- end
28
-
29
- msg = JSON.parse(msg)
30
-
31
- if msg.is_a?(Hash)
32
- msg[:__propagated] = true
33
- elsif msg.is_a?(Array)
34
- msg << :__propagated
35
- end
36
-
37
- context = Pakyow::Realtime::Context.new(Pakyow.app)
38
- context.push(msg, channel)
39
- end
40
- end
41
- }
42
- end
43
-
44
- def signal_channel
45
- "pw:#{object_id}:signal"
46
- end
47
- end
48
- end
49
- end