pusher-fake 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pusher-fake.rb +4 -1
  3. data/lib/pusher-fake/channel.rb +9 -3
  4. data/lib/pusher-fake/channel/presence.rb +11 -9
  5. data/lib/pusher-fake/channel/private.rb +8 -5
  6. data/lib/pusher-fake/channel/public.rb +13 -14
  7. data/lib/pusher-fake/configuration.rb +2 -1
  8. data/lib/pusher-fake/connection.rb +41 -29
  9. data/lib/pusher-fake/cucumber.rb +2 -1
  10. data/lib/pusher-fake/server.rb +46 -45
  11. data/lib/pusher-fake/server/application.rb +89 -50
  12. data/lib/pusher-fake/support/base.rb +4 -9
  13. data/lib/pusher-fake/webhook.rb +3 -1
  14. data/spec/features/api/channels_spec.rb +7 -5
  15. data/spec/features/api/users_spec.rb +1 -1
  16. data/spec/features/client/event_spec.rb +6 -4
  17. data/spec/features/client/subscribe_spec.rb +8 -6
  18. data/spec/features/server/event_spec.rb +7 -7
  19. data/spec/features/server/webhooks_spec.rb +16 -4
  20. data/spec/lib/pusher-fake/channel/presence_spec.rb +57 -49
  21. data/spec/lib/pusher-fake/channel/private_spec.rb +42 -31
  22. data/spec/lib/pusher-fake/channel/public_spec.rb +37 -27
  23. data/spec/lib/pusher-fake/channel_spec.rb +51 -91
  24. data/spec/lib/pusher-fake/configuration_spec.rb +11 -5
  25. data/spec/lib/pusher-fake/connection_spec.rb +65 -39
  26. data/spec/lib/pusher-fake/server/application_spec.rb +219 -94
  27. data/spec/lib/pusher-fake/server_spec.rb +31 -41
  28. data/spec/lib/pusher-fake/webhook_spec.rb +29 -18
  29. data/spec/lib/pusher_fake_spec.rb +17 -15
  30. data/spec/support/application.rb +21 -19
  31. data/spec/support/application/public/javascripts/vendor/{pusher-3.1.0.js → pusher-3.2.1.js} +69 -48
  32. data/spec/support/application/views/index.erb +1 -1
  33. data/spec/support/capybara.rb +1 -3
  34. data/spec/support/helpers/connect.rb +1 -3
  35. data/spec/support/matchers/have_configuration_option.rb +2 -2
  36. data/spec/support/{pusher-fake.rb → pusher_fake.rb} +2 -1
  37. data/spec/support/webhooks.rb +5 -3
  38. metadata +38 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b8c976f869c3748f43a742935c8acad5cde44975
4
- data.tar.gz: db6247945298f77491265ae3286ea5f74919b0df
3
+ metadata.gz: a2900ffcdf8c54ba90ada932afc74bd755bf5155
4
+ data.tar.gz: 01f7a685ef41b5c5c155220a87576f095a9e941e
5
5
  SHA512:
6
- metadata.gz: 6bf7fa18cb12730f9605d35e305ee17d9a47f2504ac539f69413e382a84f9057774960536b2dbf2dd813ec84082e3265efd6f4967eb5029167bdfef1c35c088f
7
- data.tar.gz: a69826309bcd9da0fe4882cc5f153303aa46a7397ba977ca1497f68cae6cc8c3ba342cc901f27b08dd0be3dfb14dfaaff0136123d98887676823365fa159545d
6
+ metadata.gz: 9b7245c91e900ff1406d89cc106df53a691558a0f47526c7df0363ada92365f0773d7cd93a2ba4081e0a87fa13fd0b61e9d2f3b8ba946304895a2a5d28377158
7
+ data.tar.gz: 6c0b75ac27837cf28d8267ef4cdec7553763c2647985a1a3011b1604eada70bb264204f7822c69b36d69b202cbfc987c833fa523960828f503b0a994319e2f72
@@ -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.6.0"
12
+ VERSION = "1.7.0".freeze
10
13
 
11
14
  autoload :Channel, "pusher-fake/channel"
12
15
  autoload :Configuration, "pusher-fake/configuration"
@@ -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-/.freeze
10
+ PRIVATE_CHANNEL_MATCHER = /\Aprivate-/
10
11
 
11
12
  # Name matcher for presence channels.
12
- PRESENCE_CHANNEL_MATCHER = /\Apresence-/.freeze
13
+ PRESENCE_CHANNEL_MATCHER = /\Apresence-/
13
14
 
14
15
  # @return [Hash] Cache of existing channels.
15
- attr_accessor :channels
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
- if members.key?(connection)
25
- trigger("member_removed", channel: name, user_id: members[connection][:user_id])
25
+ return unless members.key?(connection)
26
26
 
27
- emit("pusher_internal:member_removed", members.delete(connection))
28
- end
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 The connection a subscription succeeded for.
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(options[:channel_data], symbolize_keys: true)
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 The ID and information for the subscribed client.
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]) == options[:auth]
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
- string = [id, name, data].compact.map(&:to_s).join(":")
38
- digest = OpenSSL::Digest::SHA256.new
39
- signature = OpenSSL::HMAC.hexdigest(digest, configuration.secret, string)
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] +true+ if the connection is in the channel, +false+ otherwise.
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 The connection a subscription succeeded for.
77
+ # @param [Connection] connection Connection a subscription succeeded for.
75
78
  # @param [Hash] options The options for the channel.
76
- def subscription_succeeded(connection, options = {})
77
- connection.emit("pusher_internal:subscription_succeeded", subscription_data, name)
78
- connections.push(connection)
79
+ def subscription_succeeded(connection, _options = {})
80
+ connection.emit("pusher_internal:subscription_succeeded",
81
+ subscription_data, name)
79
82
 
80
- if connections.length == 1
81
- trigger("channel_occupied", channel: name)
82
- end
83
- end
83
+ connections.push(connection)
84
84
 
85
- def trigger(name, data = {})
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+ for options.
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 = /\Aclient-(.+)\Z/.freeze
5
+ CLIENT_EVENT_MATCHER = /\Aclient-(.+)\z/
5
6
 
6
- # @return [EventMachine::WebSocket::Connection] The socket object for this 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 The socket object for the connection.
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", socket_id: id, activity_timeout: 120)
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
- data = message[:data]
54
- event = message[:event]
55
- name = message[:channel] || data[:channel]
56
- channel = Channel.factory(name) if name
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
- event: event,
80
- channel: channel.name,
81
- socket_id: id
82
- }
83
- hook[:data] = MultiJson.dump(data) if data
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
@@ -1,3 +1,4 @@
1
1
  require "pusher-fake/support/cucumber"
2
2
 
3
- warn %{[DEPRECATION] "pusher-fake/cucumber" is deprecated. Please use "pusher-fake/support/cucumber" instead.}
3
+ warn %([DEPRECATION] "pusher-fake/cucumber" is deprecated.) +
4
+ %(Please use "pusher-fake/support/cucumber" instead.)
@@ -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
- # Start the servers.
6
- #
7
- # @see start_socket_server
8
- # @see start_web_server
9
- def self.start
10
- EventMachine.run do
11
- start_web_server
12
- start_socket_server
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
- # Start the WebSocket server.
17
- def self.start_socket_server
18
- EventMachine::WebSocket.start(configuration.socket_options) do |socket|
19
- socket.onopen do
20
- connection = Connection.new(socket)
21
- connection.establish
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
- socket.onmessage do |data|
24
- connection.process(data)
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
- # Start the web server.
34
- def self.start_web_server
35
- options = configuration.web_options.dup
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
- Thin::Logging.silent = true
38
- Thin::Server.new(options.delete(:host), options.delete(:port), Application).tap do |server|
39
- options.each do |key, value|
40
- server.__send__("#{key}=", value)
41
- end
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
- server.start!
43
+ server.start!
44
+ end
44
45
  end
45
- end
46
46
 
47
- private
47
+ private
48
48
 
49
- # Convenience method for access the configuration object.
50
- #
51
- # @return [Configuration] The configuration object.
52
- def self.configuration
53
- PusherFake.configuration
54
- end
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
- # 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 self.socket_server_options
61
- { host: configuration.socket_host,
62
- port: configuration.socket_port }
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 channels - " +
5
- "please supply filter_by_prefix begining with presence-".freeze
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 channel is a presence channel".freeze
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-/.freeze
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 = case request.path
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["channels"].each do |channel_name|
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
- info = request.params["info"].to_s.split(",")
69
+ count = request.params["info"].to_s.split(",").include?("user_count")
66
70
 
67
- if info.include?("user_count") && name !~ PRESENCE_PREFIX_MATCHER
71
+ if count && name !~ PRESENCE_PREFIX_MATCHER
68
72
  raise CHANNEL_USER_COUNT_ERROR
69
73
  end
70
74
 
71
- channels = PusherFake::Channel.channels || {}
72
- channel = channels[name]
75
+ channel = PusherFake::Channel.channels[name]
76
+ connections = channel ? channel.connections : []
73
77
 
74
- {}.tap do |result|
75
- result[:occupied] = !channel.nil? && channel.connections.length > 0
76
- result[:user_count] = channel.connections.length if channel && info.include?("user_count")
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 prefix.
81
- #
82
- # When filtering to presence chanenls, the user count maybe also be requested.
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
- info = request.params["info"].to_s.split(",")
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 info.include?("user_count") && prefix !~ PRESENCE_PREFIX_MATCHER
91
- raise CHANNEL_FILTER_ERROR
92
- end
95
+ raise CHANNEL_FILTER_ERROR if count && prefix !~ PRESENCE_PREFIX_MATCHER
93
96
 
94
- filter = Regexp.new(%r{\A#{prefix}})
95
- channels = PusherFake::Channel.channels || {}
96
- channels.inject(channels: {}) do |result, (name, channel)|
97
- unless filter && name !~ filter
98
- channels = result[:channels]
99
- channels[name] = {}
100
- channels[name][:user_count] = channel.connections.length if info.include?("user_count")
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
- result
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