kalebr-pusher 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +66 -0
- data/bin/kalebr-pusher +152 -0
- data/lib/slanger/api.rb +5 -0
- data/lib/slanger/api/event.rb +17 -0
- data/lib/slanger/api/event_publisher.rb +24 -0
- data/lib/slanger/api/request_validation.rb +105 -0
- data/lib/slanger/api/server.rb +57 -0
- data/lib/slanger/channel.rb +106 -0
- data/lib/slanger/config.rb +27 -0
- data/lib/slanger/connection.rb +46 -0
- data/lib/slanger/handler.rb +121 -0
- data/lib/slanger/logger.rb +7 -0
- data/lib/slanger/presence_channel.rb +140 -0
- data/lib/slanger/presence_subscription.rb +33 -0
- data/lib/slanger/private_subscription.rb +9 -0
- data/lib/slanger/redis.rb +41 -0
- data/lib/slanger/service.rb +20 -0
- data/lib/slanger/subscription.rb +53 -0
- data/lib/slanger/version.rb +3 -0
- data/lib/slanger/version.rb~ +3 -0
- data/lib/slanger/web_socket_server.rb +37 -0
- data/lib/slanger/webhook.rb +31 -0
- data/pusher.rb +23 -0
- metadata +427 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0f501a6e7ae0150b3488b530dff35f2414706c28
|
4
|
+
data.tar.gz: b5d894b0e0c269434b2edad9eb0c6de3b60909e3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2fb04dd4ef2724efa44103e5964a70ad265a3f9688d488c16410eabb972d1cf14d6f8fa1a2fb409d8c73e8b76ec272e0319741cfbd6bf4314105a285d3b610e7
|
7
|
+
data.tar.gz: dba884b83725ddb7827a4847a018f19b4e9aaa7ba501eb240c830a9a76fc2529e96b11ab36bea90d604fdf15c27fc9a4a1108adc1685e63dff4076db7f9163e6
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# How to use it
|
2
|
+
|
3
|
+
## Requirements
|
4
|
+
|
5
|
+
- Ruby 2.1.2 or greater
|
6
|
+
- Redis
|
7
|
+
|
8
|
+
## Server setup
|
9
|
+
|
10
|
+
Most linux distributions have by defualt a very low open files limit. In order to sustain more than 1024 ( default ) connections, you need to apply the following changes to your system:
|
11
|
+
Add to `/etc/sysctl.conf`:
|
12
|
+
```
|
13
|
+
fs.file-max = 50000
|
14
|
+
```
|
15
|
+
Add to `/etc/security/limits.conf`:
|
16
|
+
```
|
17
|
+
* hard nofile 50000
|
18
|
+
* soft nofile 50000
|
19
|
+
* hard nproc 50000
|
20
|
+
* soft nproc 50000
|
21
|
+
```
|
22
|
+
|
23
|
+
```bash
|
24
|
+
$ kalerbr-pusher --app_key 765ec374ae0a69f4ce44 --secret your-pusher-secret
|
25
|
+
```
|
26
|
+
|
27
|
+
If all went to plan you should see the following output to STDOUT
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
kalebr-pusher API server listening on port 4567
|
32
|
+
kalebr-pusher WebSocket server listening on port 8080
|
33
|
+
|
34
|
+
|
35
|
+
## Modifying your application code to use the Kalebr service
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
...
|
41
|
+
|
42
|
+
Pusher.host = 'kalebr.example.com'
|
43
|
+
Pusher.port = 4567
|
44
|
+
```
|
45
|
+
|
46
|
+
You will also need to do the same to the Pusher JavaScript client in your client side JavaScript, e.g
|
47
|
+
|
48
|
+
```html
|
49
|
+
<script type="text/javascript">
|
50
|
+
var pusher = new Pusher('#{Pusher.key}', {
|
51
|
+
wsHost: "0.0.0.0",
|
52
|
+
wsPort: "8080",
|
53
|
+
wssPort: "8080",
|
54
|
+
enabledTransports: ['ws', 'flash']
|
55
|
+
});
|
56
|
+
</script>
|
57
|
+
```
|
58
|
+
|
59
|
+
Of course you could proxy all requests to `ws.example.com` to port 8080 of your kalerbr-pusher node and `api.example.com` to port 4567 of your kalerbr-pusher node for example, that way you would only need to set the host property of the Pusher client.
|
60
|
+
|
61
|
+
# Author
|
62
|
+
|
63
|
+
- Nilanga Saluwadana
|
64
|
+
|
65
|
+
|
66
|
+
© 2016
|
data/bin/kalebr-pusher
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'eventmachine'
|
6
|
+
require 'yaml'
|
7
|
+
require 'active_support/core_ext/hash'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
|
12
|
+
OptionParser.new do |opts|
|
13
|
+
opts.on '-h', '--help', 'Display this screen' do
|
14
|
+
puts opts
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on '-k', '--app_key APP_KEY', "Pusher application key. This parameter is required on command line or in optional config file." do |k|
|
19
|
+
options[:app_key] = k
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on '-s', '--secret SECRET', "Pusher application secret. This parameter is required on command line or in optional config file." do |k|
|
23
|
+
options[:secret] = k
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on '-C', '--config_file FILE', "Path to Yaml file that can contain all configuration options, including required ones." do |k|
|
27
|
+
options[:config_file] = k
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on '-r', '--redis_address URL', "Address to bind to (Default: redis://127.0.0.1:6379/0)" do |h|
|
31
|
+
options[:redis_address] = h
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on '-a', '--api_host HOST', "API service address (Default: 0.0.0.0:4567)" do |p|
|
35
|
+
options[:api_host], options[:api_port] = p.split(':')
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on '-w', '--websocket_host HOST', "WebSocket service address (Default: 0.0.0.0:8080)" do |p|
|
39
|
+
options[:websocket_host], options[:websocket_port] = p.split(':')
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on '-i', '--require FILE', "Require a file before starting pusher" do |p|
|
43
|
+
options[:require] ||= []
|
44
|
+
options[:require] << p
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on '-p', '--private_key_file FILE', "Private key file for SSL transport" do |p|
|
48
|
+
options[:tls_options] ||= {}
|
49
|
+
options[:tls_options][:private_key_file] = p
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on '-b', '--webhook_url URL', "Callback URL for webhooks" do |p|
|
53
|
+
options[:webhook_url] = p
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on '-c', '--cert_file FILE', "Certificate file for SSL transport" do |p|
|
57
|
+
options[:tls_options] ||= {}
|
58
|
+
options[:tls_options][:cert_chain_file] = p
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on "-v", "--[no-]verbose", "Run verbosely" do |v|
|
62
|
+
options[:debug] = v
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on "-t" "--activity_timeout", "Activity (ping-pong) Timeout" do |t|
|
66
|
+
options[:activity_timeout] = t
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on '--pid_file PIDFILE', "The Kalebr-pusher process ID file name." do |k|
|
70
|
+
options[:pid_file] = k
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.parse!
|
74
|
+
|
75
|
+
if options[:config_file] and File.exists? options[:config_file]
|
76
|
+
config_file_contents = YAML::load(File.open(options[:config_file]))
|
77
|
+
options.reverse_merge! config_file_contents.deep_symbolize_keys!
|
78
|
+
end
|
79
|
+
|
80
|
+
%w<app_key secret>.each do |parameter|
|
81
|
+
unless options[parameter.to_sym]
|
82
|
+
puts "--#{parameter} STRING is a required argument. Use your Pusher #{parameter}.\n"
|
83
|
+
puts opts
|
84
|
+
exit
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
if options[:tls_options]
|
93
|
+
[:cert_chain_file, :private_key_file].each do |param|
|
94
|
+
raise RuntimeError.new "Both --cert_file and --private_key_file need to be specified" unless options[:tls_options][param]
|
95
|
+
raise RuntimeError.new "--#{param} does not exist at `#{options[:tls_options][param]}`" unless File.exists? options[:tls_options][param]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
STDOUT.sync = true
|
100
|
+
|
101
|
+
case
|
102
|
+
when EM.epoll? then EM.epoll
|
103
|
+
when EM.kqueue? then EM.kqueue
|
104
|
+
end
|
105
|
+
|
106
|
+
EM.run do
|
107
|
+
File.tap { |f| require f.expand_path(f.join(f.dirname(__FILE__),'..', 'pusher.rb')) }
|
108
|
+
Slanger::Config.load options
|
109
|
+
|
110
|
+
# Write PID to file
|
111
|
+
unless options[:pid_file].nil?
|
112
|
+
File.open(options[:pid_file], 'w') { |f| f.puts Process.pid }
|
113
|
+
end
|
114
|
+
|
115
|
+
Slanger::Service.run
|
116
|
+
|
117
|
+
puts "\n"
|
118
|
+
puts "\n"
|
119
|
+
puts "\n"
|
120
|
+
|
121
|
+
puts " PPPPPPPPPPPPPPPPP hhhhhhh\n"
|
122
|
+
puts " P::::::::::::::::P h:::::h\n"
|
123
|
+
puts " P::::::PPPPPP:::::P h:::::h\n"
|
124
|
+
puts " PP:::::P P:::::P h:::::h\n"
|
125
|
+
puts " P::::P P:::::uuuuuu uuuuuu ssssssssss h::::h hhhhh eeeeeeeeeeee rrrrr rrrrrrrrr\n"
|
126
|
+
puts " P::::P P:::::u::::u u::::u ss::::::::::s h::::hh:::::hhh ee::::::::::::ee r::::rrr:::::::::r\n"
|
127
|
+
puts " P::::PPPPPP:::::Pu::::u u::::u ss:::::::::::::sh::::::::::::::hh e::::::eeeee:::::er:::::::::::::::::r\n"
|
128
|
+
puts " P:::::::::::::PP u::::u u::::u s::::::ssss:::::h:::::::hhh::::::he::::::e e:::::rr::::::rrrrr::::::r\n"
|
129
|
+
puts " P::::PPPPPPPPP u::::u u::::u s:::::s ssssssh::::::h h::::::e:::::::eeeee::::::er:::::r r:::::r\n"
|
130
|
+
puts " P::::P u::::u u::::u s::::::s h:::::h h:::::e:::::::::::::::::e r:::::r rrrrrrr\n"
|
131
|
+
puts " P::::P u::::u u::::u s::::::s h:::::h h:::::e::::::eeeeeeeeeee r:::::r\n"
|
132
|
+
puts " P::::P u:::::uuuu:::::u ssssss s:::::sh:::::h h:::::e:::::::e r:::::r\n"
|
133
|
+
puts " PP::::::PP u:::::::::::::::us:::::ssss::::::h:::::h h:::::e::::::::e r:::::r\n"
|
134
|
+
puts " P::::::::P u:::::::::::::::s::::::::::::::sh:::::h h:::::he::::::::eeeeeeee r:::::r \n"
|
135
|
+
puts " P::::::::P uu::::::::uu:::us:::::::::::ss h:::::h h:::::h ee:::::::::::::e r:::::r\n"
|
136
|
+
puts " PPPPPPPPPP uuuuuuuu uuuu sssssssssss hhhhhhh hhhhhhh eeeeeeeeeeeeee rrrrrrr\n"
|
137
|
+
puts "\n"
|
138
|
+
puts "\n"
|
139
|
+
|
140
|
+
puts "Running Kalebr-pusher v.#{Slanger::VERSION}"
|
141
|
+
puts "\n"
|
142
|
+
|
143
|
+
puts "Kalebr-pusher API server listening on port #{Slanger::Config.api_port}"
|
144
|
+
puts "Kalebr-pusher WebSocket server listening on port #{Slanger::Config.websocket_port}"
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
|
data/lib/slanger/api.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
3
|
+
module Slanger
|
4
|
+
module Api
|
5
|
+
class Event < Struct.new :name, :data, :socket_id
|
6
|
+
def payload(channel_id)
|
7
|
+
Oj.dump({
|
8
|
+
event: name,
|
9
|
+
data: data,
|
10
|
+
channel: channel_id,
|
11
|
+
socket_id: socket_id
|
12
|
+
}.select { |_,v| v }, mode: :compat)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Slanger
|
2
|
+
module Api
|
3
|
+
class EventPublisher < Struct.new(:channels, :event)
|
4
|
+
def self.publish(channels, event)
|
5
|
+
new(channels, event).publish
|
6
|
+
end
|
7
|
+
|
8
|
+
def publish
|
9
|
+
Array(channels).each do |c|
|
10
|
+
publish_event(c)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def publish_event(channel_id)
|
17
|
+
Slanger::Redis.publish(channel_id, event.payload(channel_id))
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
3
|
+
module Slanger
|
4
|
+
module Api
|
5
|
+
class RequestValidation < Struct.new :raw_body, :raw_params, :path_info
|
6
|
+
def initialize(*args)
|
7
|
+
super(*args)
|
8
|
+
|
9
|
+
validate!
|
10
|
+
authenticate!
|
11
|
+
parse_body!
|
12
|
+
end
|
13
|
+
|
14
|
+
def data
|
15
|
+
@data ||= Oj.load(body["data"] || params["data"])
|
16
|
+
end
|
17
|
+
|
18
|
+
def body
|
19
|
+
@body ||= validate_body!
|
20
|
+
end
|
21
|
+
|
22
|
+
def auth_params
|
23
|
+
params.except('channel_id', 'app_id')
|
24
|
+
end
|
25
|
+
|
26
|
+
def socket_id
|
27
|
+
@socket_id ||= determine_valid_socket_id
|
28
|
+
end
|
29
|
+
|
30
|
+
def params
|
31
|
+
@params ||= validate_raw_params!
|
32
|
+
end
|
33
|
+
|
34
|
+
def channels
|
35
|
+
@channels ||= Array(body["channels"] || params["channels"])
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def validate_body!
|
41
|
+
@body ||= assert_valid_json!(raw_body.tap{ |s| s.force_encoding('utf-8')})
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate!
|
45
|
+
raise InvalidRequest.new "no body" unless raw_body.present?
|
46
|
+
raise InvalidRequest.new "invalid params" unless raw_params.is_a? Hash
|
47
|
+
raise InvalidRequest.new "invalid path" unless path_info.is_a? String
|
48
|
+
|
49
|
+
determine_valid_socket_id
|
50
|
+
channels.each{|id| validate_channel_id!(id)}
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_socket_id!(socket_id)
|
54
|
+
validate_with_regex!(/\A\d+\.\d+\z/, socket_id, "socket_id")
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_channel_id!(channel_id)
|
58
|
+
validate_with_regex!(/\A[\w@\-;_.=,]{1,164}\z/, channel_id, "channel_id")
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_with_regex!(regex, value, name)
|
62
|
+
raise InvalidRequest, "Invalid #{name} #{value.inspect}" unless value =~ regex
|
63
|
+
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_raw_params!
|
68
|
+
restricted = user_params.slice "body_md5", "auth_version", "auth_key", "auth_timestamp", "auth_signature", "app_id"
|
69
|
+
|
70
|
+
invalid_keys = restricted.keys - user_params.keys
|
71
|
+
|
72
|
+
if invalid_keys.any?
|
73
|
+
raise Slanger::InvalidRequest.new "Invalid params: #{invalid_keys}"
|
74
|
+
end
|
75
|
+
|
76
|
+
restricted
|
77
|
+
end
|
78
|
+
|
79
|
+
def authenticate!
|
80
|
+
# Raises Signature::AuthenticationError if request does not authenticate.
|
81
|
+
Signature::Request.new('POST', path_info, auth_params).
|
82
|
+
authenticate { |key| Signature::Token.new key, Slanger::Config.secret }
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_body!
|
86
|
+
assert_valid_json!(raw_body)
|
87
|
+
end
|
88
|
+
|
89
|
+
def assert_valid_json!(string)
|
90
|
+
Oj.load(string)
|
91
|
+
rescue Oj::ParserError
|
92
|
+
raise Slanger::InvalidRequest.new("Invalid request body: #{raw_body}")
|
93
|
+
end
|
94
|
+
|
95
|
+
def determine_valid_socket_id
|
96
|
+
return validate_socket_id!(body["socket_id"]) if body["socket_id"]
|
97
|
+
return validate_socket_id!(params["socket_id"]) if params["socket_id"]
|
98
|
+
end
|
99
|
+
|
100
|
+
def user_params
|
101
|
+
raw_params.reject{|k,_| %w(splat captures).include?(k)}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'signature'
|
4
|
+
require 'json'
|
5
|
+
require 'active_support/core_ext/hash'
|
6
|
+
require 'eventmachine'
|
7
|
+
require 'em-hiredis'
|
8
|
+
require 'rack'
|
9
|
+
require 'fiber'
|
10
|
+
require 'rack/fiber_pool'
|
11
|
+
require 'oj'
|
12
|
+
|
13
|
+
module Slanger
|
14
|
+
module Api
|
15
|
+
class Server < Sinatra::Base
|
16
|
+
use Rack::FiberPool
|
17
|
+
set :raise_errors, lambda { false }
|
18
|
+
set :show_exceptions, false
|
19
|
+
|
20
|
+
error(Signature::AuthenticationError) { |e| halt 401, "401 UNAUTHORIZED" }
|
21
|
+
error(Slanger::Api::InvalidRequest) { |c| halt 400, "400 Bad Request" }
|
22
|
+
|
23
|
+
before do
|
24
|
+
valid_request
|
25
|
+
end
|
26
|
+
|
27
|
+
post '/apps/:app_id/events' do
|
28
|
+
socket_id = valid_request.socket_id
|
29
|
+
body = valid_request.body
|
30
|
+
|
31
|
+
event = Slanger::Api::Event.new(body["name"], body["data"], socket_id)
|
32
|
+
EventPublisher.publish(valid_request.channels, event)
|
33
|
+
|
34
|
+
status 202
|
35
|
+
return Oj.dump({}, mode: :compat)
|
36
|
+
end
|
37
|
+
|
38
|
+
post '/apps/:app_id/channels/:channel_id/events' do
|
39
|
+
params = valid_request.params
|
40
|
+
|
41
|
+
event = Event.new(params["name"], valid_request.body, valid_request.socket_id)
|
42
|
+
EventPublisher.publish(valid_request.channels, event)
|
43
|
+
|
44
|
+
status 202
|
45
|
+
return Oj.dump({}, mode: :compat)
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid_request
|
49
|
+
@valid_request ||=
|
50
|
+
begin
|
51
|
+
request_body ||= request.body.read.tap{|s| s.force_encoding("utf-8")}
|
52
|
+
RequestValidation.new(request_body, params, env["PATH_INFO"])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Channel class.
|
2
|
+
#
|
3
|
+
# Uses an EventMachine channel to let clients interact with the
|
4
|
+
# Pusher channel. Relay events received from Redis into the
|
5
|
+
# EM channel.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'eventmachine'
|
9
|
+
require 'forwardable'
|
10
|
+
require 'oj'
|
11
|
+
|
12
|
+
module Slanger
|
13
|
+
class Channel
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def_delegators :channel, :push
|
17
|
+
attr_reader :channel_id
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def from channel_id
|
21
|
+
klass = channel_id[/\Apresence-/] ? PresenceChannel : Channel
|
22
|
+
|
23
|
+
klass.lookup(channel_id) || klass.create(channel_id: channel_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def lookup(channel_id)
|
27
|
+
all.detect { |o| o.channel_id == channel_id }
|
28
|
+
end
|
29
|
+
|
30
|
+
def create(params = {})
|
31
|
+
new(params).tap { |r| all << r }
|
32
|
+
end
|
33
|
+
|
34
|
+
def all
|
35
|
+
@all ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
def unsubscribe channel_id, subscription_id
|
39
|
+
from(channel_id).try :unsubscribe, subscription_id
|
40
|
+
end
|
41
|
+
|
42
|
+
def send_client_message msg
|
43
|
+
from(msg['channel']).try :send_client_message, msg
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(attrs)
|
48
|
+
@channel_id = attrs.with_indifferent_access[:channel_id]
|
49
|
+
Slanger::Redis.subscribe channel_id
|
50
|
+
end
|
51
|
+
|
52
|
+
def channel
|
53
|
+
@channel ||= EM::Channel.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def subscribe *a, &blk
|
57
|
+
Slanger::Redis.hincrby('channel_subscriber_count', channel_id, 1).
|
58
|
+
callback do |value|
|
59
|
+
Slanger::Webhook.post name: 'channel_occupied', channel: channel_id if value == 1
|
60
|
+
end
|
61
|
+
|
62
|
+
channel.subscribe *a, &blk
|
63
|
+
end
|
64
|
+
|
65
|
+
def unsubscribe *a, &blk
|
66
|
+
Slanger::Redis.hincrby('channel_subscriber_count', channel_id, -1).
|
67
|
+
callback do |value|
|
68
|
+
Slanger::Webhook.post name: 'channel_vacated', channel: channel_id if value == 0
|
69
|
+
end
|
70
|
+
|
71
|
+
channel.unsubscribe *a, &blk
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Send a client event to the EventMachine channel.
|
76
|
+
# Only events to channels requiring authentication (private or presence)
|
77
|
+
# are accepted. Public channels only get events from the API.
|
78
|
+
def send_client_message(message)
|
79
|
+
Slanger::Redis.publish(message['channel'], Oj.dump(message, mode: :compat)) if authenticated?
|
80
|
+
end
|
81
|
+
|
82
|
+
# Send an event received from Redis to the EventMachine channel
|
83
|
+
# which will send it to subscribed clients.
|
84
|
+
def dispatch(message, channel)
|
85
|
+
push(Oj.dump(message, mode: :compat)) unless channel =~ /\Aslanger:/
|
86
|
+
|
87
|
+
perform_client_webhook!(message)
|
88
|
+
end
|
89
|
+
|
90
|
+
def authenticated?
|
91
|
+
channel_id =~ /\Aprivate-/ || channel_id =~ /\Apresence-/
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def perform_client_webhook!(message)
|
97
|
+
if (message['event'].start_with?('client-')) then
|
98
|
+
|
99
|
+
event = message.merge({'name' => 'client_event'})
|
100
|
+
event['data'] = Oj.dump(event['data'])
|
101
|
+
|
102
|
+
Slanger::Webhook.post(event)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|