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.
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