pusher-fake 1.6.0 → 1.7.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.
- checksums.yaml +4 -4
- data/lib/pusher-fake.rb +4 -1
- data/lib/pusher-fake/channel.rb +9 -3
- data/lib/pusher-fake/channel/presence.rb +11 -9
- data/lib/pusher-fake/channel/private.rb +8 -5
- data/lib/pusher-fake/channel/public.rb +13 -14
- data/lib/pusher-fake/configuration.rb +2 -1
- data/lib/pusher-fake/connection.rb +41 -29
- data/lib/pusher-fake/cucumber.rb +2 -1
- data/lib/pusher-fake/server.rb +46 -45
- data/lib/pusher-fake/server/application.rb +89 -50
- data/lib/pusher-fake/support/base.rb +4 -9
- data/lib/pusher-fake/webhook.rb +3 -1
- data/spec/features/api/channels_spec.rb +7 -5
- data/spec/features/api/users_spec.rb +1 -1
- data/spec/features/client/event_spec.rb +6 -4
- data/spec/features/client/subscribe_spec.rb +8 -6
- data/spec/features/server/event_spec.rb +7 -7
- data/spec/features/server/webhooks_spec.rb +16 -4
- data/spec/lib/pusher-fake/channel/presence_spec.rb +57 -49
- data/spec/lib/pusher-fake/channel/private_spec.rb +42 -31
- data/spec/lib/pusher-fake/channel/public_spec.rb +37 -27
- data/spec/lib/pusher-fake/channel_spec.rb +51 -91
- data/spec/lib/pusher-fake/configuration_spec.rb +11 -5
- data/spec/lib/pusher-fake/connection_spec.rb +65 -39
- data/spec/lib/pusher-fake/server/application_spec.rb +219 -94
- data/spec/lib/pusher-fake/server_spec.rb +31 -41
- data/spec/lib/pusher-fake/webhook_spec.rb +29 -18
- data/spec/lib/pusher_fake_spec.rb +17 -15
- data/spec/support/application.rb +21 -19
- data/spec/support/application/public/javascripts/vendor/{pusher-3.1.0.js → pusher-3.2.1.js} +69 -48
- data/spec/support/application/views/index.erb +1 -1
- data/spec/support/capybara.rb +1 -3
- data/spec/support/helpers/connect.rb +1 -3
- data/spec/support/matchers/have_configuration_option.rb +2 -2
- data/spec/support/{pusher-fake.rb → pusher_fake.rb} +2 -1
- data/spec/support/webhooks.rb +5 -3
- metadata +38 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a2900ffcdf8c54ba90ada932afc74bd755bf5155
|
|
4
|
+
data.tar.gz: 01f7a685ef41b5c5c155220a87576f095a9e941e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b7245c91e900ff1406d89cc106df53a691558a0f47526c7df0363ada92365f0773d7cd93a2ba4081e0a87fa13fd0b61e9d2f3b8ba946304895a2a5d28377158
|
|
7
|
+
data.tar.gz: 6c0b75ac27837cf28d8267ef4cdec7553763c2647985a1a3011b1604eada70bb264204f7822c69b36d69b202cbfc987c833fa523960828f503b0a994319e2f72
|
data/lib/pusher-fake.rb
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
# rubocop:disable Style/FileName
|
|
2
|
+
|
|
1
3
|
require "em-http-request"
|
|
2
4
|
require "em-websocket"
|
|
3
5
|
require "multi_json"
|
|
4
6
|
require "openssl"
|
|
5
7
|
require "thin"
|
|
6
8
|
|
|
9
|
+
# A Pusher fake.
|
|
7
10
|
module PusherFake
|
|
8
11
|
# The current version string.
|
|
9
|
-
VERSION = "1.
|
|
12
|
+
VERSION = "1.7.0".freeze
|
|
10
13
|
|
|
11
14
|
autoload :Channel, "pusher-fake/channel"
|
|
12
15
|
autoload :Configuration, "pusher-fake/configuration"
|
data/lib/pusher-fake/channel.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module PusherFake
|
|
2
|
+
# Channel creation and management.
|
|
2
3
|
module Channel
|
|
3
4
|
autoload :Public, "pusher-fake/channel/public"
|
|
4
5
|
autoload :Private, "pusher-fake/channel/private"
|
|
@@ -6,13 +7,18 @@ module PusherFake
|
|
|
6
7
|
|
|
7
8
|
class << self
|
|
8
9
|
# Name matcher for private channels.
|
|
9
|
-
PRIVATE_CHANNEL_MATCHER = /\Aprivate
|
|
10
|
+
PRIVATE_CHANNEL_MATCHER = /\Aprivate-/
|
|
10
11
|
|
|
11
12
|
# Name matcher for presence channels.
|
|
12
|
-
PRESENCE_CHANNEL_MATCHER = /\Apresence
|
|
13
|
+
PRESENCE_CHANNEL_MATCHER = /\Apresence-/
|
|
13
14
|
|
|
14
15
|
# @return [Hash] Cache of existing channels.
|
|
15
|
-
|
|
16
|
+
attr_writer :channels
|
|
17
|
+
|
|
18
|
+
# @return [Hash] Cache of existing channels.
|
|
19
|
+
def channels
|
|
20
|
+
@channels ||= {}
|
|
21
|
+
end
|
|
16
22
|
|
|
17
23
|
# Create a channel, determining the type by the name.
|
|
18
24
|
#
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module PusherFake
|
|
2
2
|
module Channel
|
|
3
|
+
# A presence channel.
|
|
3
4
|
class Presence < Private
|
|
4
5
|
# @return [Hash] Channel members hash.
|
|
5
6
|
attr_reader :members
|
|
@@ -21,11 +22,12 @@ module PusherFake
|
|
|
21
22
|
def remove(connection)
|
|
22
23
|
super
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
trigger("member_removed", channel: name, user_id: members[connection][:user_id])
|
|
25
|
+
return unless members.key?(connection)
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
trigger("member_removed",
|
|
28
|
+
channel: name, user_id: members[connection][:user_id])
|
|
29
|
+
|
|
30
|
+
emit("pusher_internal:member_removed", members.delete(connection))
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
# Return a hash containing presence information for the channel.
|
|
@@ -33,9 +35,7 @@ module PusherFake
|
|
|
33
35
|
# @return [Hash] Hash containing presence information.
|
|
34
36
|
def subscription_data
|
|
35
37
|
hash = Hash[
|
|
36
|
-
members.map { |_, member|
|
|
37
|
-
[member[:user_id], member[:user_info]]
|
|
38
|
-
}
|
|
38
|
+
members.map { |_, member| [member[:user_id], member[:user_info]] }
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
{ presence: { hash: hash, count: members.size } }
|
|
@@ -48,10 +48,12 @@ module PusherFake
|
|
|
48
48
|
#
|
|
49
49
|
# Also trigger the member_added webhook.
|
|
50
50
|
#
|
|
51
|
-
# @param [Connection] connection
|
|
51
|
+
# @param [Connection] connection Connection a subscription succeeded for.
|
|
52
52
|
# @param [Hash] options The options for the channel.
|
|
53
53
|
def subscription_succeeded(connection, options = {})
|
|
54
|
-
member = members[connection] = MultiJson.load(
|
|
54
|
+
member = members[connection] = MultiJson.load(
|
|
55
|
+
options[:channel_data], symbolize_keys: true
|
|
56
|
+
)
|
|
55
57
|
|
|
56
58
|
emit("pusher_internal:member_added", member)
|
|
57
59
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
module PusherFake
|
|
2
2
|
module Channel
|
|
3
|
+
# A private channel.
|
|
3
4
|
class Private < Public
|
|
4
5
|
# Add the connection to the channel if they are authorized.
|
|
5
6
|
#
|
|
6
7
|
# @param [Connection] connection The connection to add.
|
|
7
8
|
# @param [Hash] options The options for the channel.
|
|
8
9
|
# @option options [String] :auth The authentication string.
|
|
9
|
-
# @option options [Hash] :channel_data
|
|
10
|
+
# @option options [Hash] :channel_data Information for subscribed client.
|
|
10
11
|
def add(connection, options = {})
|
|
11
12
|
if authorized?(connection, options)
|
|
12
13
|
subscription_succeeded(connection, options)
|
|
@@ -22,7 +23,8 @@ module PusherFake
|
|
|
22
23
|
# @option options [String] :auth The authentication string.
|
|
23
24
|
# @return [Boolean] +true+ if authorized, +false+ otherwise.
|
|
24
25
|
def authorized?(connection, options)
|
|
25
|
-
authentication_for(connection.id, options[:channel_data]) ==
|
|
26
|
+
authentication_for(connection.id, options[:channel_data]) ==
|
|
27
|
+
options[:auth]
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
# Generate an authentication string from the channel based on the
|
|
@@ -34,9 +36,10 @@ module PusherFake
|
|
|
34
36
|
# @return [String] The authentication string.
|
|
35
37
|
def authentication_for(id, data = nil)
|
|
36
38
|
configuration = PusherFake.configuration
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
data = [id, name, data].compact.map(&:to_s).join(":")
|
|
41
|
+
digest = OpenSSL::Digest::SHA256.new
|
|
42
|
+
signature = OpenSSL::HMAC.hexdigest(digest, configuration.secret, data)
|
|
40
43
|
|
|
41
44
|
"#{configuration.key}:#{signature}"
|
|
42
45
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module PusherFake
|
|
2
2
|
module Channel
|
|
3
|
+
# A public channel.
|
|
3
4
|
class Public
|
|
4
5
|
# @return [Array] Connections in this channel.
|
|
5
6
|
attr_reader :connections
|
|
@@ -38,7 +39,7 @@ module PusherFake
|
|
|
38
39
|
# Determine if the +connection+ is in the channel.
|
|
39
40
|
#
|
|
40
41
|
# @param [Connection] connection The connection.
|
|
41
|
-
# @return [Boolean]
|
|
42
|
+
# @return [Boolean] If the connection is in the channel or not.
|
|
42
43
|
def includes?(connection)
|
|
43
44
|
connections.index(connection)
|
|
44
45
|
end
|
|
@@ -51,9 +52,7 @@ module PusherFake
|
|
|
51
52
|
def remove(connection)
|
|
52
53
|
connections.delete(connection)
|
|
53
54
|
|
|
54
|
-
if connections.empty?
|
|
55
|
-
trigger("channel_vacated", channel: name)
|
|
56
|
-
end
|
|
55
|
+
trigger("channel_vacated", channel: name) if connections.empty?
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
# Return subscription data for the channel.
|
|
@@ -64,6 +63,10 @@ module PusherFake
|
|
|
64
63
|
{}
|
|
65
64
|
end
|
|
66
65
|
|
|
66
|
+
def trigger(name, data = {})
|
|
67
|
+
PusherFake::Webhook.trigger(name, data)
|
|
68
|
+
end
|
|
69
|
+
|
|
67
70
|
private
|
|
68
71
|
|
|
69
72
|
# Notify the +connection+ of the successful subscription and add the
|
|
@@ -71,19 +74,15 @@ module PusherFake
|
|
|
71
74
|
#
|
|
72
75
|
# If it is the first connection, trigger the channel_occupied webhook.
|
|
73
76
|
#
|
|
74
|
-
# @param [Connection] connection
|
|
77
|
+
# @param [Connection] connection Connection a subscription succeeded for.
|
|
75
78
|
# @param [Hash] options The options for the channel.
|
|
76
|
-
def subscription_succeeded(connection,
|
|
77
|
-
connection.emit("pusher_internal:subscription_succeeded",
|
|
78
|
-
|
|
79
|
+
def subscription_succeeded(connection, _options = {})
|
|
80
|
+
connection.emit("pusher_internal:subscription_succeeded",
|
|
81
|
+
subscription_data, name)
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
trigger("channel_occupied", channel: name)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
83
|
+
connections.push(connection)
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
PusherFake::Webhook.trigger(name, data)
|
|
85
|
+
trigger("channel_occupied", channel: name) if connections.length == 1
|
|
87
86
|
end
|
|
88
87
|
end
|
|
89
88
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module PusherFake
|
|
2
|
+
# Configuration class.
|
|
2
3
|
class Configuration
|
|
3
4
|
# @return [String] The Pusher Applicaiton ID. (Defaults to +PUSHER_APP_ID+.)
|
|
4
5
|
attr_accessor :app_id
|
|
@@ -12,7 +13,7 @@ module PusherFake
|
|
|
12
13
|
# @return [String] The Pusher API token. (Defaults to +PUSHER_API_SECRET+.)
|
|
13
14
|
attr_accessor :secret
|
|
14
15
|
|
|
15
|
-
# Options for the socket server. See +EventMachine::WebSocket.start
|
|
16
|
+
# Options for the socket server. See +EventMachine::WebSocket.start+.
|
|
16
17
|
#
|
|
17
18
|
# @return [Hash] Options for the socket server.
|
|
18
19
|
attr_accessor :socket_options
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
module PusherFake
|
|
2
|
+
# A client connection.
|
|
2
3
|
class Connection
|
|
3
4
|
# Name matcher for client events.
|
|
4
|
-
CLIENT_EVENT_MATCHER
|
|
5
|
+
CLIENT_EVENT_MATCHER = /\Aclient-(.+)\z/
|
|
5
6
|
|
|
6
|
-
# @return [EventMachine::WebSocket::Connection]
|
|
7
|
+
# @return [EventMachine::WebSocket::Connection] Socket for the connection.
|
|
7
8
|
attr_reader :socket
|
|
8
9
|
|
|
9
10
|
# Create a new {Connection} object.
|
|
10
11
|
#
|
|
11
|
-
# @param [EventMachine::WebSocket::Connection] socket
|
|
12
|
+
# @param [EventMachine::WebSocket::Connection] socket Connection object.
|
|
12
13
|
def initialize(socket)
|
|
13
14
|
@socket = socket
|
|
14
15
|
end
|
|
@@ -39,7 +40,8 @@ module PusherFake
|
|
|
39
40
|
|
|
40
41
|
# Notify the Pusher client that a connection has been established.
|
|
41
42
|
def establish
|
|
42
|
-
emit("pusher:connection_established",
|
|
43
|
+
emit("pusher:connection_established",
|
|
44
|
+
socket_id: id, activity_timeout: 120)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
# Process an event.
|
|
@@ -47,41 +49,51 @@ module PusherFake
|
|
|
47
49
|
# @param [String] data The event data as JSON.
|
|
48
50
|
def process(data)
|
|
49
51
|
message = MultiJson.load(data, symbolize_keys: true)
|
|
52
|
+
event = message[:event]
|
|
50
53
|
|
|
51
54
|
PusherFake.log("RECV #{id}: #{message}")
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
case event
|
|
59
|
-
when "pusher:subscribe"
|
|
60
|
-
channel.add(self, data)
|
|
61
|
-
when "pusher:unsubscribe"
|
|
62
|
-
channel.remove(self)
|
|
63
|
-
when "pusher:ping"
|
|
64
|
-
emit("pusher:pong")
|
|
65
|
-
when CLIENT_EVENT_MATCHER
|
|
66
|
-
if channel.is_a?(Channel::Private) && channel.includes?(self)
|
|
67
|
-
channel.emit(event, data, socket_id: id)
|
|
68
|
-
|
|
69
|
-
trigger(channel, id, event, data)
|
|
70
|
-
end
|
|
56
|
+
if event =~ CLIENT_EVENT_MATCHER
|
|
57
|
+
process_trigger(event, message)
|
|
58
|
+
else
|
|
59
|
+
process_event(event, message)
|
|
71
60
|
end
|
|
72
61
|
end
|
|
73
62
|
|
|
74
63
|
private
|
|
75
64
|
|
|
65
|
+
def channel_for(message)
|
|
66
|
+
Channel.factory(message[:channel] || message[:data][:channel])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def process_event(event, message)
|
|
70
|
+
if event == "pusher:subscribe"
|
|
71
|
+
channel_for(message).add(self, message[:data])
|
|
72
|
+
elsif event == "pusher:unsubscribe"
|
|
73
|
+
channel_for(message).remove(self)
|
|
74
|
+
elsif event == "pusher:ping"
|
|
75
|
+
emit("pusher:pong")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def process_trigger(event, message)
|
|
80
|
+
channel = channel_for(message)
|
|
81
|
+
|
|
82
|
+
return unless channel.is_a?(Channel::Private) && channel.includes?(self)
|
|
83
|
+
|
|
84
|
+
channel.emit(event, message[:data], socket_id: id)
|
|
85
|
+
|
|
86
|
+
trigger(channel, id, event, message[:data])
|
|
87
|
+
end
|
|
88
|
+
|
|
76
89
|
def trigger(channel, id, event, data)
|
|
77
90
|
Thread.new do
|
|
78
|
-
hook = {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
hook[:user_id] = channel.members[self][:user_id] if channel.is_a?(Channel::Presence)
|
|
91
|
+
hook = { event: event, channel: channel.name, socket_id: id }
|
|
92
|
+
hook[:data] = MultiJson.dump(data) if data
|
|
93
|
+
|
|
94
|
+
if channel.is_a?(Channel::Presence)
|
|
95
|
+
hook[:user_id] = channel.members[self][:user_id]
|
|
96
|
+
end
|
|
85
97
|
|
|
86
98
|
channel.trigger("client_event", hook)
|
|
87
99
|
end
|
data/lib/pusher-fake/cucumber.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
require "pusher-fake/support/cucumber"
|
|
2
2
|
|
|
3
|
-
warn %
|
|
3
|
+
warn %([DEPRECATION] "pusher-fake/cucumber" is deprecated.) +
|
|
4
|
+
%(Please use "pusher-fake/support/cucumber" instead.)
|
data/lib/pusher-fake/server.rb
CHANGED
|
@@ -1,65 +1,66 @@
|
|
|
1
1
|
module PusherFake
|
|
2
|
+
# Socket and web server manager.
|
|
2
3
|
module Server
|
|
3
4
|
autoload :Application, "pusher-fake/server/application"
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
class << self
|
|
7
|
+
# Start the servers.
|
|
8
|
+
#
|
|
9
|
+
# @see start_socket_server
|
|
10
|
+
# @see start_web_server
|
|
11
|
+
def start
|
|
12
|
+
EventMachine.run do
|
|
13
|
+
start_web_server
|
|
14
|
+
start_socket_server
|
|
15
|
+
end
|
|
13
16
|
end
|
|
14
|
-
end
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
# Start the WebSocket server.
|
|
19
|
+
def start_socket_server
|
|
20
|
+
EventMachine::WebSocket.start(configuration.socket_options) do |socket|
|
|
21
|
+
socket.onopen do
|
|
22
|
+
connection = Connection.new(socket)
|
|
23
|
+
connection.establish
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
end
|
|
26
|
-
socket.onclose do
|
|
27
|
-
Channel.remove(connection)
|
|
25
|
+
socket.onmessage { |data| connection.process(data) }
|
|
26
|
+
socket.onclose { Channel.remove(connection) }
|
|
28
27
|
end
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
|
-
end
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
# Start the web server.
|
|
32
|
+
def start_web_server
|
|
33
|
+
options = configuration.web_options.dup
|
|
34
|
+
host = options.delete(:host)
|
|
35
|
+
port = options.delete(:port)
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
Thin::Logging.silent = true
|
|
38
|
+
Thin::Server.new(host, port, Application).tap do |server|
|
|
39
|
+
options.each do |key, value|
|
|
40
|
+
server.__send__("#{key}=", value)
|
|
41
|
+
end
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
server.start!
|
|
44
|
+
end
|
|
44
45
|
end
|
|
45
|
-
end
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
private
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
# Convenience method for access the configuration object.
|
|
50
|
+
#
|
|
51
|
+
# @return [Configuration] The configuration object.
|
|
52
|
+
def configuration
|
|
53
|
+
PusherFake.configuration
|
|
54
|
+
end
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
# Return a hash of options for the socket server based on
|
|
57
|
+
# the configuration.
|
|
58
|
+
#
|
|
59
|
+
# @return [Hash] The socket server configuration options.
|
|
60
|
+
def socket_server_options
|
|
61
|
+
{ host: configuration.socket_host,
|
|
62
|
+
port: configuration.socket_port }
|
|
63
|
+
end
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
end
|
|
@@ -1,54 +1,58 @@
|
|
|
1
1
|
module PusherFake
|
|
2
2
|
module Server
|
|
3
|
+
# The fake web application.
|
|
3
4
|
class Application
|
|
4
|
-
CHANNEL_FILTER_ERROR = "user_count may only be requested for presence
|
|
5
|
-
|
|
5
|
+
CHANNEL_FILTER_ERROR = "user_count may only be requested for presence " \
|
|
6
|
+
"channels - please supply filter_by_prefix " \
|
|
7
|
+
"begining with presence-".freeze
|
|
6
8
|
|
|
7
|
-
CHANNEL_USER_COUNT_ERROR = "Cannot retrieve the user count unless the
|
|
9
|
+
CHANNEL_USER_COUNT_ERROR = "Cannot retrieve the user count unless the " \
|
|
10
|
+
"channel is a presence channel".freeze
|
|
8
11
|
|
|
9
|
-
PRESENCE_PREFIX_MATCHER = /\Apresence
|
|
12
|
+
PRESENCE_PREFIX_MATCHER = /\Apresence-/
|
|
13
|
+
|
|
14
|
+
REQUEST_PATHS = {
|
|
15
|
+
%r{\A/apps/:id/batch_events\z} => :batch_events,
|
|
16
|
+
%r{\A/apps/:id/events\z} => :events,
|
|
17
|
+
%r{\A/apps/:id/channels\z} => :channels,
|
|
18
|
+
%r{\A/apps/:id/channels/([^/]+)\z} => :channel,
|
|
19
|
+
%r{\A/apps/:id/channels/([^/]+)/users\z} => :users
|
|
20
|
+
}.freeze
|
|
10
21
|
|
|
11
22
|
# Process an API request.
|
|
12
23
|
#
|
|
13
24
|
# @param [Hash] environment The request environment.
|
|
14
25
|
# @return [Rack::Response] A successful response.
|
|
15
26
|
def self.call(environment)
|
|
16
|
-
id = PusherFake.configuration.app_id
|
|
17
27
|
request = Rack::Request.new(environment)
|
|
18
|
-
response =
|
|
19
|
-
when %r{\A/apps/#{id}/events\Z}
|
|
20
|
-
events(request)
|
|
21
|
-
when %r{\A/apps/#{id}/channels\Z}
|
|
22
|
-
channels(request)
|
|
23
|
-
when %r{\A/apps/#{id}/channels/([^/]+)\Z}
|
|
24
|
-
channel($1, request)
|
|
25
|
-
when %r{\A/apps/#{id}/channels/([^/]+)/users\Z}
|
|
26
|
-
users($1)
|
|
27
|
-
else
|
|
28
|
-
raise "Unknown path: #{request.path}"
|
|
29
|
-
end
|
|
28
|
+
response = response_for(request)
|
|
30
29
|
|
|
31
30
|
Rack::Response.new(MultiJson.dump(response)).finish
|
|
32
31
|
rescue => error
|
|
33
32
|
Rack::Response.new(error.message, 400).finish
|
|
34
33
|
end
|
|
35
34
|
|
|
35
|
+
# Emit batch events with data to the requested channel(s).
|
|
36
|
+
#
|
|
37
|
+
# @param [Rack::Request] request The HTTP request.
|
|
38
|
+
# @return [Hash] An empty hash.
|
|
39
|
+
def self.batch_events(request)
|
|
40
|
+
batch = MultiJson.load(request.body.read)["batch"]
|
|
41
|
+
batch.each do |event|
|
|
42
|
+
send_event(event)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
{}
|
|
46
|
+
end
|
|
47
|
+
|
|
36
48
|
# Emit an event with data to the requested channel(s).
|
|
37
49
|
#
|
|
38
50
|
# @param [Rack::Request] request The HTTP request.
|
|
39
51
|
# @return [Hash] An empty hash.
|
|
40
52
|
def self.events(request)
|
|
41
53
|
event = MultiJson.load(request.body.read)
|
|
42
|
-
data = begin
|
|
43
|
-
MultiJson.load(event["data"])
|
|
44
|
-
rescue MultiJson::LoadError
|
|
45
|
-
event["data"]
|
|
46
|
-
end
|
|
47
54
|
|
|
48
|
-
event
|
|
49
|
-
channel = Channel.factory(channel_name)
|
|
50
|
-
channel.emit(event["name"], data, socket_id: event["socket_id"])
|
|
51
|
-
end
|
|
55
|
+
send_event(event)
|
|
52
56
|
|
|
53
57
|
{}
|
|
54
58
|
end
|
|
@@ -62,53 +66,71 @@ module PusherFake
|
|
|
62
66
|
# @param [Rack::Request] request The HTTP request.
|
|
63
67
|
# @return [Hash] A hash of channel information.
|
|
64
68
|
def self.channel(name, request)
|
|
65
|
-
|
|
69
|
+
count = request.params["info"].to_s.split(",").include?("user_count")
|
|
66
70
|
|
|
67
|
-
if
|
|
71
|
+
if count && name !~ PRESENCE_PREFIX_MATCHER
|
|
68
72
|
raise CHANNEL_USER_COUNT_ERROR
|
|
69
73
|
end
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
channel = PusherFake::Channel.channels[name]
|
|
76
|
+
connections = channel ? channel.connections : []
|
|
73
77
|
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
78
|
+
result = { occupied: connections.any? }
|
|
79
|
+
result[:user_count] = connections.size if count
|
|
80
|
+
result
|
|
78
81
|
end
|
|
79
82
|
|
|
80
|
-
# Returns a hash of occupied channels, optionally filtering with a
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
+
# Returns a hash of occupied channels, optionally filtering with a
|
|
84
|
+
# prefix. When filtering to presence chanenls, the user count maybe also
|
|
85
|
+
# be requested.
|
|
83
86
|
#
|
|
84
87
|
# @param [Rack::Request] request The HTTP request.
|
|
85
88
|
# @return [Hash] A hash of occupied channels.
|
|
89
|
+
#
|
|
90
|
+
# rubocop:disable Metrics/AbcSize
|
|
86
91
|
def self.channels(request)
|
|
87
|
-
|
|
92
|
+
count = request.params["info"].to_s.split(",").include?("user_count")
|
|
88
93
|
prefix = request.params["filter_by_prefix"].to_s
|
|
89
94
|
|
|
90
|
-
if
|
|
91
|
-
raise CHANNEL_FILTER_ERROR
|
|
92
|
-
end
|
|
95
|
+
raise CHANNEL_FILTER_ERROR if count && prefix !~ PRESENCE_PREFIX_MATCHER
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
channels[name
|
|
100
|
-
channels[name][:user_count] = channel.connections.
|
|
97
|
+
PusherFake::Channel
|
|
98
|
+
.channels
|
|
99
|
+
.each_with_object(channels: {}) do |(name, channel), result|
|
|
100
|
+
next unless name.start_with?(prefix)
|
|
101
|
+
|
|
102
|
+
channels = result[:channels].merge!(name => {})
|
|
103
|
+
channels[name][:user_count] = channel.connections.size if count
|
|
101
104
|
end
|
|
105
|
+
end
|
|
106
|
+
# rubocop:enable Metrics/AbcSize
|
|
107
|
+
|
|
108
|
+
# Attempt to provide a response for the provided request.
|
|
109
|
+
#
|
|
110
|
+
# @param [Rack::Request] request The HTTP request.
|
|
111
|
+
# @return [Hash] A response hash.
|
|
112
|
+
def self.response_for(request)
|
|
113
|
+
id = PusherFake.configuration.app_id
|
|
102
114
|
|
|
103
|
-
|
|
115
|
+
REQUEST_PATHS.each do |path, method|
|
|
116
|
+
matcher = Regexp.new(path.to_s.sub(":id", id))
|
|
117
|
+
matches = matcher.match(request.path)
|
|
118
|
+
|
|
119
|
+
next if matches.nil?
|
|
120
|
+
|
|
121
|
+
arguments = [matches[1], request].compact
|
|
122
|
+
|
|
123
|
+
return public_send(method, *arguments)
|
|
104
124
|
end
|
|
125
|
+
|
|
126
|
+
raise "Unknown path: #{request.path}"
|
|
105
127
|
end
|
|
106
128
|
|
|
107
129
|
# Returns a hash of the IDs for the users in the channel.
|
|
108
130
|
#
|
|
109
131
|
# @param [String] name The channel name.
|
|
110
132
|
# @return [Hash] A hash of user IDs.
|
|
111
|
-
def self.users(name)
|
|
133
|
+
def self.users(name, _request = nil)
|
|
112
134
|
channels = PusherFake::Channel.channels || {}
|
|
113
135
|
channel = channels[name]
|
|
114
136
|
|
|
@@ -120,6 +142,23 @@ module PusherFake
|
|
|
120
142
|
|
|
121
143
|
{ users: users || [] }
|
|
122
144
|
end
|
|
145
|
+
|
|
146
|
+
private_class_method
|
|
147
|
+
|
|
148
|
+
# Emit an event with data to the requested channel(s).
|
|
149
|
+
#
|
|
150
|
+
# @param [Hash] event The raw event JSON.
|
|
151
|
+
#
|
|
152
|
+
# rubocop:disable Style/RescueModifier
|
|
153
|
+
def self.send_event(event)
|
|
154
|
+
data = MultiJson.load(event["data"]) rescue event["data"]
|
|
155
|
+
channels = Array(event["channels"] || event["channel"])
|
|
156
|
+
channels.each do |channel_name|
|
|
157
|
+
channel = Channel.factory(channel_name)
|
|
158
|
+
channel.emit(event["name"], data, socket_id: event["socket_id"])
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
# rubocop:enable Style/RescueModifier
|
|
123
162
|
end
|
|
124
163
|
end
|
|
125
164
|
end
|