slanger 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of slanger might be problematic. Click here for more details.

data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Slanger
2
+ [![Build Status](https://secure.travis-ci.org/stevegraham/slanger.png?branch=master)](http://travis-ci.org/stevegraham/slanger)
2
3
 
3
4
  Slanger is an open source server implementation of the Pusher protocol written in Ruby. It is designed to scale horizontally across N nodes and to be agnostic as to which Slanger node a subscriber is connected to, i.e subscribers to the same channel are NOT required to be connected to the same Slanger node. Multiple Slanger nodes can sit behind a load balancer with no special configuration. In essence it was designed to be very easy to scale.
4
5
 
@@ -96,6 +97,12 @@ Slanger supports several configuration options, which can be supplied as command
96
97
 
97
98
  -w or --websocket_host This is the address that Slanger will bind the WebSocket part of the service to. This is an optional argument and defaults to 0.0.0.0:8080
98
99
 
100
+ -i or --require Require an additional file before starting Slanger to tune it to your needs. This is an optional argument
101
+
102
+ -p or --private_key_file Private key file for SSL support. This argument is optional, if given, SSL will be enabled
103
+
104
+ -c or --cert_chain_file Certificate chain file for SSL support. This argument is optional, if given, SSL will be enabled
105
+
99
106
  -v or --[no-]verbose This makes Slanger run verbosely, meaning WebSocket frames will be echoed to STDOUT. Useful for debugging
100
107
  </pre>
101
108
 
data/bin/slanger CHANGED
@@ -11,6 +11,7 @@ options = {
11
11
 
12
12
  OptionParser.new do |opts|
13
13
  opts.on '-h', '--help', 'Display this screen' do
14
+ puts opts
14
15
  exit
15
16
  end
16
17
 
@@ -34,6 +35,11 @@ OptionParser.new do |opts|
34
35
  options[:websocket_host], options[:websocket_port] = p.split(':')
35
36
  end
36
37
 
38
+ opts.on '-i', '--require FILE', "Require a file before starting Slanger" do |p|
39
+ options[:require] ||= []
40
+ options[:require] << p
41
+ end
42
+
37
43
  opts.on '-p', '--private_key_file FILE', "Private key file for SSL transport" do |p|
38
44
  options[:tls_options] ||= {}
39
45
  options[:tls_options][:private_key_file] = p
@@ -60,6 +66,9 @@ if options[:tls_options]
60
66
  end
61
67
  end
62
68
 
69
+ EM.epoll
70
+ EM.kqueue
71
+
63
72
  EM.run do
64
73
  File.tap { |f| require f.expand_path(f.join(f.dirname(__FILE__),'..', 'slanger.rb')) }
65
74
  Slanger::Config.load options
@@ -1,6 +1,7 @@
1
+ # encoding: utf-8
1
2
  require 'sinatra/base'
2
3
  require 'signature'
3
- require 'active_support/json'
4
+ require 'json'
4
5
  require 'active_support/core_ext/hash'
5
6
  require 'eventmachine'
6
7
  require 'em-hiredis'
@@ -35,7 +36,7 @@ module Slanger
35
36
 
36
37
  def payload
37
38
  payload = {
38
- event: params['name'], data: request.body.read, channel: params[:channel_id], socket_id: params[:socket_id]
39
+ event: params['name'], data: request.body.read.tap { |s| s.force_encoding('utf-8') }, channel: params[:channel_id], socket_id: params[:socket_id]
39
40
  }
40
41
  Hash[payload.reject { |k,v| v.nil? }].to_json
41
42
  end
@@ -1,7 +1,7 @@
1
1
  # Channel class.
2
2
  #
3
3
  # Uses an EventMachine channel to let clients interact with the
4
- # Pusher channel. Relay events received from Redis into the
4
+ # Pusher channel. Relay events received from Redis into the
5
5
  # EM channel.
6
6
  #
7
7
 
@@ -16,6 +16,21 @@ module Slanger
16
16
 
17
17
  def_delegators :channel, :subscribe, :unsubscribe, :push
18
18
 
19
+ class << self
20
+ def from channel_id
21
+ klass = channel_id[/^presence-/] ? PresenceChannel : Channel
22
+ klass.find_or_create_by_channel_id channel_id
23
+ end
24
+
25
+ def unsubscribe channel_id, subscription_id
26
+ from(channel_id).try :unsubscribe, subscription_id
27
+ end
28
+
29
+ def send_client_message msg
30
+ from(msg['channel']).try :send_client_message, msg
31
+ end
32
+ end
33
+
19
34
  def initialize(attrs)
20
35
  super
21
36
  Slanger::Redis.subscribe channel_id
@@ -13,7 +13,8 @@ module Slanger
13
13
  def options
14
14
  @options ||= {
15
15
  api_host: '0.0.0.0', api_port: '4567', websocket_host: '0.0.0.0',
16
- websocket_port: '8080', debug: false, redis_address: 'redis://0.0.0.0:6379/0'
16
+ websocket_port: '8080', debug: false, redis_address: 'redis://0.0.0.0:6379/0',
17
+ socket_handler: Slanger::Handler, require: []
17
18
  }
18
19
  end
19
20
 
@@ -0,0 +1,34 @@
1
+ module Slanger
2
+ class Connection
3
+ attr_accessor :socket, :socket_id
4
+
5
+ def initialize socket, socket_id=nil
6
+ @socket, @socket_id = socket, socket_id
7
+ end
8
+
9
+ def send_message m
10
+ msg = JSON.parse m
11
+ s = msg.delete 'socket_id'
12
+ socket.send msg.to_json unless s == socket_id
13
+ end
14
+
15
+ def send_payload *args
16
+ socket.send format(*args)
17
+ end
18
+
19
+ def error e
20
+ send_payload nil, 'pusher:error', e
21
+ end
22
+
23
+ def establish
24
+ @socket_id = SecureRandom.uuid
25
+ send_payload nil, 'pusher:connection_established', { socket_id: @socket_id }
26
+ end
27
+
28
+ private
29
+
30
+ def format(channel_id, event_name, payload = {})
31
+ { channel: channel_id, event: event_name, data: payload }.to_json
32
+ end
33
+ end
34
+ end
@@ -1,7 +1,6 @@
1
1
  # Handler class.
2
2
  # Handles a client connected via a websocket connection.
3
3
 
4
- require 'active_support/json'
5
4
  require 'active_support/core_ext/hash'
6
5
  require 'securerandom'
7
6
  require 'signature'
@@ -9,156 +8,81 @@ require 'fiber'
9
8
 
10
9
  module Slanger
11
10
  class Handler
11
+
12
+ attr_accessor :connection
13
+ delegate :error, :establish, :send_payload, to: :connection
14
+
12
15
  def initialize(socket)
13
16
  @socket = socket
17
+ @connection = Connection.new(@socket)
14
18
  @subscriptions = {}
15
19
  authenticate
16
20
  end
17
21
 
18
- # Dispatches message handling to method with same name as the event name
22
+ # Dispatches message handling to method with same name as
23
+ # the event name
19
24
  def onmessage(msg)
20
- msg = JSON.parse msg
21
- event = msg['event'].gsub('pusher:', 'pusher_')
22
-
23
- if event =~ /^pusher_/
24
- # Pusher event, call method if it exists.
25
- send(event, msg) if respond_to? event, true
26
- elsif event =~ /^client-/
27
- # Client event. Send it to the destination channel.
28
- msg['socket_id'] = @socket_id
29
- channel = find_channel msg['channel']
30
- channel.try :send_client_message, msg
31
- end
32
- rescue Exception => e
33
- handle_error(e)
34
- end
25
+ begin
26
+ msg = JSON.parse msg
27
+ event = msg['event'].gsub(/^pusher:/, 'pusher_')
28
+
29
+ if event =~ /^client-/
30
+ msg['socket_id'] = @socket_id
31
+ Channel.send_client_message msg
32
+ elsif respond_to? event, true
33
+ send event, msg
34
+ end
35
35
 
36
- # Unsubscribe this connection from all the channels on close.
37
- def onclose
38
- @subscriptions.each do |channel_id, subscription_id|
39
- channel = find_channel channel_id
40
- channel.try :unsubscribe, subscription_id
36
+ rescue Exception => e
37
+ case e
38
+ when JSON::ParserError
39
+ error({ code: 5001, message: "Invalid JSON" })
40
+ else
41
+ error({ code: 500, message: "#{e.message}\n #{e.backtrace}" })
42
+ end
41
43
  end
42
44
  end
43
45
 
44
- private
45
-
46
- def find_channel(channel_id)
47
- channel_class(channel_id).find_by_channel_id(channel_id)
48
- end
49
-
50
- def channel_class(channel_name)
51
- channel_name =~ /^presence-/ ? PresenceChannel : Channel
46
+ def onclose
47
+ @subscriptions.each { |c, s| Channel.unsubscribe c, s }
52
48
  end
53
49
 
54
- # Verify app key. Send connection_established message to connection if it checks out. Send error message and disconnect if invalid.
55
50
  def authenticate
56
- app_key = @socket.request['path'].split(/\W/)[2]
57
- if app_key == Slanger::Config.app_key
58
- @socket_id = SecureRandom.uuid
59
- @socket.send(payload nil, 'pusher:connection_established', { socket_id: @socket_id })
60
- else
61
- @socket.send(payload nil, 'pusher:error', { code: '4001', message: "Could not find app by key #{app_key}" })
62
- @socket.close_websocket
63
- end
64
- end
51
+ return establish if valid_app_key? app_key
65
52
 
66
- # Dispatch to handler method if channel requires authentication, otherwise subscribe.
67
- def pusher_subscribe(msg)
68
- channel_id = msg['data']['channel']
69
- subscription_id = if match = channel_id.match(/^((private)|(presence))-/)
70
- send "handle_#{match.captures[0]}_subscription", msg
71
- else
72
- subscribe_channel channel_id
73
- end
74
- @subscriptions[channel_id] = subscription_id
53
+ error({ code: '4001', message: "Could not find app by key #{app_key}" })
54
+ @socket.close_websocket
75
55
  end
76
56
 
77
57
  def pusher_ping(msg)
78
- @socket.send(payload nil, 'pusher:ping')
58
+ send_payload nil, 'pusher:ping'
79
59
  end
80
60
 
81
61
  def pusher_pong msg; end
82
62
 
83
- #TODO: think about moving all subscription stuff into channel classes
84
- # Add connection to channel subscribers
85
- def subscribe_channel(channel_id)
86
- channel = Slanger::Channel.find_or_create_by_channel_id(channel_id)
87
- @socket.send(payload channel_id, 'pusher_internal:subscription_succeeded')
88
- # Subscribe to the channel and have the events received from it
89
- # sent to the client's socket.
90
- subscription_id = channel.subscribe do |msg|
91
- msg = JSON.parse(msg)
92
- # Don't send the event if it was sent by the client
93
- socket_id = msg.delete 'socket_id'
94
- @socket.send msg.to_json unless socket_id == @socket_id
95
- end
96
- end
63
+ def pusher_subscribe(msg)
64
+ channel_id = msg['data']['channel']
65
+ klass = subscription_klass channel_id
97
66
 
98
- # Validate authentication token for private channel and add connection to channel subscribers if it checks out
99
- def handle_private_subscription(msg)
100
- channel = msg['data']['channel']
101
- if msg['data']['auth'] && token(channel, msg['data']['channel_data']) != msg['data']['auth'].split(':')[1]
102
- @socket.send(payload nil, 'pusher:error', {
103
- message: "Invalid signature: Expected HMAC SHA256 hex digest of #{@socket_id}:#{channel}, but got #{msg['data']['auth']}"
104
- })
105
- else
106
- subscribe_channel channel
107
- end
67
+ @subscriptions[channel_id] = klass.new(connection.socket, connection.socket_id, msg).subscribe
108
68
  end
109
69
 
110
- # Validate authentication token and check channel_data. Add connection to channel subscribers if it checks out
111
- def handle_presence_subscription(msg)
112
- channel_id = msg['data']['channel']
113
- if token(channel_id, msg['data']['channel_data']) != msg['data']['auth'].split(':')[1]
114
- @socket.send(payload nil, 'pusher:error', {
115
- message: "Invalid signature: Expected HMAC SHA256 hex digest of #{@socket_id}:#{msg['data']['channel']}, but got #{msg['data']['auth']}"
116
- })
117
- elsif !msg['data']['channel_data']
118
- @socket.send(payload nil, 'pusher:error', {
119
- message: "presence-channel is a presence channel and subscription must include channel_data"
120
- })
121
- else
122
- channel = Slanger::PresenceChannel.find_or_create_by_channel_id(channel_id)
123
- callback = Proc.new {
124
- @socket.send(payload channel_id, 'pusher_internal:subscription_succeeded', {
125
- presence: {
126
- count: channel.subscribers.size,
127
- ids: channel.ids,
128
- hash: channel.subscribers
129
- }
130
- })
131
- }
132
- # Subscribe to channel, call callback when done to send a
133
- # subscription_succeeded event to the client.
134
- channel.subscribe(msg, callback) do |msg|
135
- # Send channel messages to the client, unless it is the
136
- # sender of the event.
137
- msg = JSON.parse(msg)
138
- socket_id = msg.delete 'socket_id'
139
- @socket.send msg.to_json unless socket_id == @socket_id
140
- end
141
- end
142
- end
70
+ private
143
71
 
144
- # Message helper method. Converts a hash into the Pusher JSON protocol
145
- def payload(channel_id, event_name, payload = {})
146
- { channel: channel_id, event: event_name, data: payload }.to_json
72
+ def app_key
73
+ @socket.request['path'].split(/\W/)[2]
147
74
  end
148
75
 
149
- # HMAC token validation
150
- def token(channel_id, params=nil)
151
- string_to_sign = [@socket_id, channel_id, params].compact.join ':'
152
- HMAC::SHA256.hexdigest(Slanger::Config.secret, string_to_sign)
76
+ def valid_app_key? app_key
77
+ Slanger::Config.app_key == app_key
153
78
  end
154
79
 
155
- def handle_error(e)
156
- case e
157
- when JSON::ParserError
158
- @socket.send(payload nil, 'pusher:error', { code: '5001', message: "Invalid JSON" })
159
- else
160
- @socket.send(payload nil, 'pusher:error', { code: '5000', message: "Internal Server error" })
80
+ def subscription_klass channel_id
81
+ klass = channel_id.match(/^(private|presence)-/) do |match|
82
+ Slanger.const_get "#{match[1]}_subscription".classify
161
83
  end
84
+
85
+ klass || Slanger::Subscription
162
86
  end
163
87
  end
164
88
  end
@@ -1,9 +1,9 @@
1
1
  # PresenceChannel class.
2
2
  #
3
3
  # Uses an EventMachine channel to let handlers interact with the
4
- # Pusher channel. Relay events received from Redis into the
4
+ # Pusher channel. Relay events received from Redis into the
5
5
  # EM channel. Keeps data on the subscribers to send it to clients.
6
- #
6
+ #
7
7
 
8
8
  require 'glamazon'
9
9
  require 'eventmachine'
@@ -57,11 +57,11 @@ module Slanger
57
57
  end
58
58
 
59
59
  def ids
60
- subscriptions.map { |k,v| v['user_id'] }
60
+ subscriptions.map { |_,v| v['user_id'] }
61
61
  end
62
62
 
63
63
  def subscribers
64
- Hash[subscriptions.map { |k,v| [v['user_id'], v['user_info']] }]
64
+ Hash[subscriptions.map { |_,v| [v['user_id'], v['user_info']] }]
65
65
  end
66
66
 
67
67
  def unsubscribe(public_subscription_id)
@@ -105,7 +105,7 @@ module Slanger
105
105
  # This is the state of the presence channel across the system. kept in sync
106
106
  # with redis pubsub
107
107
  def subscriptions
108
- @subscriptions = @subscriptions || get_roster || Hash.new
108
+ @subscriptions ||= get_roster || {}
109
109
  end
110
110
 
111
111
  # This is used map public subscription ids to em channel subscription ids.
@@ -0,0 +1,33 @@
1
+ module Slanger
2
+ class PresenceSubscription < Subscription
3
+ def subscribe
4
+ return handle_invalid_signature if invalid_signature?
5
+
6
+ unless channel_data?
7
+ return connection.error({
8
+ message: "presence-channel is a presence channel and subscription must include channel_data"
9
+ })
10
+ end
11
+
12
+ channel.subscribe(@msg, callback) { |m| connection.send_message m }
13
+ end
14
+
15
+ private
16
+
17
+ def channel_data?
18
+ @msg['data']['channel_data']
19
+ end
20
+
21
+ def callback
22
+ Proc.new {
23
+ connection.send_payload(channel_id, 'pusher_internal:subscription_succeeded', {
24
+ presence: {
25
+ count: channel.subscribers.size,
26
+ ids: channel.ids,
27
+ hash: channel.subscribers
28
+ }
29
+ })
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Slanger
2
+ class PrivateSubscription < Subscription
3
+ def subscribe
4
+ return handle_invalid_signature if auth && invalid_signature?
5
+
6
+ Subscription.new(connection.socket, connection.socket_id, @msg).subscribe
7
+ end
8
+ end
9
+ end
data/lib/slanger/redis.rb CHANGED
@@ -11,8 +11,8 @@ module Slanger
11
11
  # Dispatch messages received from Redis to their destination channel.
12
12
  base.on(:message) do |channel, message|
13
13
  message = JSON.parse message
14
- klass = message['channel'][/^presence-/] ? PresenceChannel : Channel
15
- klass.find_or_create_by_channel_id(message['channel']).dispatch message, channel
14
+ c = Channel.from message['channel']
15
+ c.dispatch message, channel
16
16
  end
17
17
  end
18
18
 
@@ -4,6 +4,7 @@ require 'rack'
4
4
  module Slanger
5
5
  module Service
6
6
  def run
7
+ Slanger::Config[:require].each { |f| require f }
7
8
  Thin::Logging.silent = true
8
9
  Rack::Handler::Thin.run Slanger::ApiServer, Host: Slanger::Config.api_host, Port: Slanger::Config.api_port
9
10
  Slanger::WebSocketServer.run
@@ -0,0 +1,51 @@
1
+ module Slanger
2
+ class Subscription
3
+ attr_accessor :connection, :socket
4
+ delegate :send_payload, :send_message, :error, :socket_id, to: :connection
5
+
6
+ def initialize socket, socket_id, msg
7
+ @connection = Connection.new socket, socket_id
8
+ @msg = msg
9
+ end
10
+
11
+ def subscribe
12
+ send_payload channel_id, 'pusher_internal:subscription_succeeded'
13
+
14
+ channel.subscribe { |m| send_message m }
15
+ end
16
+
17
+ private
18
+
19
+ def channel
20
+ Channel.from channel_id
21
+ end
22
+
23
+ def channel_id
24
+ @msg['data']['channel']
25
+ end
26
+
27
+ def token(channel_id, params=nil)
28
+ to_sign = [socket_id, channel_id, params].compact.join ':'
29
+ HMAC::SHA256.hexdigest Slanger::Config.secret, to_sign
30
+ end
31
+
32
+ def invalid_signature?
33
+ token(channel_id, data) != auth.split(':')[1]
34
+ end
35
+
36
+ def auth
37
+ @msg['data']['auth']
38
+ end
39
+
40
+ def data
41
+ @msg['data']['channel_data']
42
+ end
43
+
44
+ def handle_invalid_signature
45
+ message = "Invalid signature: Expected HMAC SHA256 hex digest of "
46
+ message << "#{socket_id}:#{channel_id}, but got #{auth}"
47
+
48
+ error({ message: message})
49
+ end
50
+ end
51
+ end
@@ -4,6 +4,9 @@ require 'em-websocket'
4
4
  module Slanger
5
5
  module WebSocketServer
6
6
  def run
7
+ EM.epoll
8
+ EM.kqueue
9
+
7
10
  EM.run do
8
11
  options = {
9
12
  host: Slanger::Config[:websocket_host],
@@ -21,7 +24,7 @@ module Slanger
21
24
  # Keep track of handler instance in instance of EM::Connection to ensure a unique handler instance is used per connection.
22
25
  ws.class_eval { attr_accessor :connection_handler }
23
26
  # Delegate connection management to handler instance.
24
- ws.onopen { ws.connection_handler = Slanger::Handler.new ws }
27
+ ws.onopen { ws.connection_handler = Slanger::Config.socket_handler.new ws }
25
28
  ws.onmessage { |msg| ws.connection_handler.onmessage msg }
26
29
  ws.onclose { ws.connection_handler.onclose }
27
30
  end
data/slanger.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require 'bundler/setup'
2
3
 
3
4
  require 'eventmachine'
@@ -7,6 +8,9 @@ require 'active_support/core_ext/string'
7
8
 
8
9
  module Slanger; end
9
10
 
11
+ EM.epoll
12
+ EM.kqueue
13
+
10
14
  EM.run do
11
15
  File.tap do |f|
12
16
  Dir[f.expand_path(f.join(f.dirname(__FILE__),'lib', 'slanger', '*.rb'))].each do |file|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slanger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-15 00:00:00.000000000 Z
12
+ date: 2012-04-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
16
- requirement: &70260724123600 !ruby/object:Gem::Requirement
16
+ requirement: &70166097146020 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.12.10
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70260724123600
24
+ version_requirements: *70166097146020
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: em-hiredis
27
- requirement: &70260724155460 !ruby/object:Gem::Requirement
27
+ requirement: &70166097145140 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.1.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70260724155460
35
+ version_requirements: *70166097145140
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: em-websocket
38
- requirement: &70260724154320 !ruby/object:Gem::Requirement
38
+ requirement: &70166097144120 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.3.5
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70260724154320
46
+ version_requirements: *70166097144120
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rack
49
- requirement: &70260724152840 !ruby/object:Gem::Requirement
49
+ requirement: &70166097142720 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.3.3
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70260724152840
57
+ version_requirements: *70166097142720
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rack-fiber_pool
60
- requirement: &70260724149120 !ruby/object:Gem::Requirement
60
+ requirement: &70166097141600 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - =
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 0.9.1
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70260724149120
68
+ version_requirements: *70166097141600
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: signature
71
- requirement: &70260724182220 !ruby/object:Gem::Requirement
71
+ requirement: &70166097140380 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 0.1.2
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *70260724182220
79
+ version_requirements: *70166097140380
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: activesupport
82
- requirement: &70260724178720 !ruby/object:Gem::Requirement
82
+ requirement: &70166097138660 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: 3.1.0
88
88
  type: :runtime
89
89
  prerelease: false
90
- version_requirements: *70260724178720
90
+ version_requirements: *70166097138660
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: glamazon
93
- requirement: &70260724177880 !ruby/object:Gem::Requirement
93
+ requirement: &70166097137500 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ~>
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: 0.3.1
99
99
  type: :runtime
100
100
  prerelease: false
101
- version_requirements: *70260724177880
101
+ version_requirements: *70166097137500
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: sinatra
104
- requirement: &70260724206740 !ruby/object:Gem::Requirement
104
+ requirement: &70166097136360 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ~>
@@ -109,10 +109,10 @@ dependencies:
109
109
  version: 1.2.6
110
110
  type: :runtime
111
111
  prerelease: false
112
- version_requirements: *70260724206740
112
+ version_requirements: *70166097136360
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: thin
115
- requirement: &70260724205980 !ruby/object:Gem::Requirement
115
+ requirement: &70166097135480 !ruby/object:Gem::Requirement
116
116
  none: false
117
117
  requirements:
118
118
  - - ~>
@@ -120,10 +120,10 @@ dependencies:
120
120
  version: 1.2.11
121
121
  type: :runtime
122
122
  prerelease: false
123
- version_requirements: *70260724205980
123
+ version_requirements: *70166097135480
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: em-http-request
126
- requirement: &70260724205260 !ruby/object:Gem::Requirement
126
+ requirement: &70166097134400 !ruby/object:Gem::Requirement
127
127
  none: false
128
128
  requirements:
129
129
  - - ~>
@@ -131,10 +131,10 @@ dependencies:
131
131
  version: 0.3.0
132
132
  type: :runtime
133
133
  prerelease: false
134
- version_requirements: *70260724205260
134
+ version_requirements: *70166097134400
135
135
  - !ruby/object:Gem::Dependency
136
136
  name: rspec
137
- requirement: &70260724203820 !ruby/object:Gem::Requirement
137
+ requirement: &70166097133020 !ruby/object:Gem::Requirement
138
138
  none: false
139
139
  requirements:
140
140
  - - ~>
@@ -142,10 +142,10 @@ dependencies:
142
142
  version: 2.6.0
143
143
  type: :development
144
144
  prerelease: false
145
- version_requirements: *70260724203820
145
+ version_requirements: *70166097133020
146
146
  - !ruby/object:Gem::Dependency
147
147
  name: pusher
148
- requirement: &70260724202560 !ruby/object:Gem::Requirement
148
+ requirement: &70166097131960 !ruby/object:Gem::Requirement
149
149
  none: false
150
150
  requirements:
151
151
  - - ~>
@@ -153,10 +153,10 @@ dependencies:
153
153
  version: 0.8.2
154
154
  type: :development
155
155
  prerelease: false
156
- version_requirements: *70260724202560
156
+ version_requirements: *70166097131960
157
157
  - !ruby/object:Gem::Dependency
158
158
  name: haml
159
- requirement: &70260724199480 !ruby/object:Gem::Requirement
159
+ requirement: &70166097131100 !ruby/object:Gem::Requirement
160
160
  none: false
161
161
  requirements:
162
162
  - - ~>
@@ -164,7 +164,29 @@ dependencies:
164
164
  version: 3.1.2
165
165
  type: :development
166
166
  prerelease: false
167
- version_requirements: *70260724199480
167
+ version_requirements: *70166097131100
168
+ - !ruby/object:Gem::Dependency
169
+ name: rake
170
+ requirement: &70166097129660 !ruby/object:Gem::Requirement
171
+ none: false
172
+ requirements:
173
+ - - ! '>='
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ type: :development
177
+ prerelease: false
178
+ version_requirements: *70166097129660
179
+ - !ruby/object:Gem::Dependency
180
+ name: debugger
181
+ requirement: &70166097128500 !ruby/object:Gem::Requirement
182
+ none: false
183
+ requirements:
184
+ - - ! '>='
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: *70166097128500
168
190
  description: A websocket service compatible with Pusher libraries
169
191
  email: sjtgraham@mac.com
170
192
  executables:
@@ -176,11 +198,15 @@ files:
176
198
  - lib/slanger/api_server.rb
177
199
  - lib/slanger/channel.rb
178
200
  - lib/slanger/config.rb
201
+ - lib/slanger/connection.rb
179
202
  - lib/slanger/handler.rb
180
203
  - lib/slanger/logger.rb
181
204
  - lib/slanger/presence_channel.rb
205
+ - lib/slanger/presence_subscription.rb
206
+ - lib/slanger/private_subscription.rb
182
207
  - lib/slanger/redis.rb
183
208
  - lib/slanger/service.rb
209
+ - lib/slanger/subscription.rb
184
210
  - lib/slanger/web_socket_server.rb
185
211
  - slanger.rb
186
212
  - !binary |-
@@ -210,3 +236,4 @@ signing_key:
210
236
  specification_version: 3
211
237
  summary: A websocket service compatible with Pusher libraries
212
238
  test_files: []
239
+ has_rdoc: