kalebr-pusher 0.6.1
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.
- 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
|