ably 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -1
- data/ably.gemspec +4 -3
- data/lib/ably.rb +6 -2
- data/lib/ably/auth.rb +24 -16
- data/lib/ably/exceptions.rb +16 -5
- data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
- data/lib/ably/{realtime/models → models}/message.rb +45 -38
- data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
- data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
- data/lib/ably/models/presence_message.rb +126 -0
- data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
- data/lib/ably/models/token.rb +74 -0
- data/lib/ably/modules/channels_collection.rb +49 -0
- data/lib/ably/modules/conversions.rb +2 -0
- data/lib/ably/modules/event_emitter.rb +43 -8
- data/lib/ably/modules/event_machine_helpers.rb +1 -0
- data/lib/ably/modules/http_helpers.rb +9 -2
- data/lib/ably/modules/message_pack.rb +14 -0
- data/lib/ably/modules/model_common.rb +29 -0
- data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
- data/lib/ably/realtime.rb +37 -7
- data/lib/ably/realtime/channel.rb +154 -31
- data/lib/ably/realtime/channels.rb +47 -0
- data/lib/ably/realtime/client.rb +39 -33
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
- data/lib/ably/realtime/connection.rb +148 -79
- data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
- data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
- data/lib/ably/realtime/presence.rb +270 -0
- data/lib/ably/rest.rb +14 -3
- data/lib/ably/rest/channel.rb +3 -3
- data/lib/ably/rest/channels.rb +26 -12
- data/lib/ably/rest/client.rb +42 -25
- data/lib/ably/rest/middleware/exceptions.rb +21 -23
- data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/ably/rest/middleware/parse_json.rb +9 -2
- data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
- data/lib/ably/rest/presence.rb +4 -4
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
- data/spec/acceptance/realtime/channel_spec.rb +135 -63
- data/spec/acceptance/realtime/connection_spec.rb +86 -0
- data/spec/acceptance/realtime/message_spec.rb +116 -94
- data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
- data/spec/acceptance/realtime/presence_spec.rb +277 -0
- data/spec/acceptance/rest/auth_spec.rb +351 -347
- data/spec/acceptance/rest/base_spec.rb +43 -26
- data/spec/acceptance/rest/channel_spec.rb +88 -83
- data/spec/acceptance/rest/channels_spec.rb +32 -28
- data/spec/acceptance/rest/presence_spec.rb +83 -63
- data/spec/acceptance/rest/stats_spec.rb +38 -37
- data/spec/acceptance/rest/time_spec.rb +10 -6
- data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
- data/spec/spec_helper.rb +14 -0
- data/spec/support/api_helper.rb +4 -0
- data/spec/support/model_helper.rb +28 -9
- data/spec/support/protocol_msgbus_helper.rb +8 -1
- data/spec/support/test_app.rb +24 -14
- data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
- data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
- data/spec/unit/models/message_spec.rb +229 -0
- data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
- data/spec/unit/models/presence_message_spec.rb +230 -0
- data/spec/unit/models/protocol_message_spec.rb +280 -0
- data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
- data/spec/unit/modules/conversions_spec.rb +1 -1
- data/spec/unit/modules/event_emitter_spec.rb +36 -4
- data/spec/unit/realtime/channel_spec.rb +76 -2
- data/spec/unit/realtime/channels_spec.rb +50 -0
- data/spec/unit/realtime/client_spec.rb +31 -1
- data/spec/unit/realtime/connection_spec.rb +8 -15
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
- data/spec/unit/realtime/presence_spec.rb +100 -0
- data/spec/unit/rest/channels_spec.rb +48 -0
- metadata +72 -38
- data/lib/ably/realtime/models/shared.rb +0 -17
- data/lib/ably/rest/models/message.rb +0 -64
- data/lib/ably/rest/models/presence_message.rb +0 -21
- data/lib/ably/token.rb +0 -80
- data/spec/unit/realtime/message_spec.rb +0 -117
- data/spec/unit/realtime/protocol_message_spec.rb +0 -172
- data/spec/unit/rest/message_spec.rb +0 -75
@@ -0,0 +1,74 @@
|
|
1
|
+
module Ably::Models
|
2
|
+
# Authentication token issued by Ably in response to an token request
|
3
|
+
class Token
|
4
|
+
include Ably::Modules::ModelCommon
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
capability: { "*" => ["*"] },
|
8
|
+
ttl: 60 * 60 # 1 hour
|
9
|
+
}
|
10
|
+
|
11
|
+
# Buffer in seconds given to the use of a token prior to it being considered unusable
|
12
|
+
# For example, if buffer is 10s, the token can no longer be used for new requests 9s before it expires
|
13
|
+
TOKEN_EXPIRY_BUFFER = 5
|
14
|
+
|
15
|
+
def initialize(attributes)
|
16
|
+
@hash_object = IdiomaticRubyWrapper(attributes.clone.freeze)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @!attribute [r] id
|
20
|
+
# @return [String] Unique token ID used to authenticate requests
|
21
|
+
def id
|
22
|
+
hash.fetch(:id)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!attribute [r] key_id
|
26
|
+
# @return [String] Key ID used to create this token
|
27
|
+
def key_id
|
28
|
+
hash.fetch(:key)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @!attribute [r] issued_at
|
32
|
+
# @return [Time] Time the token was issued
|
33
|
+
def issued_at
|
34
|
+
as_time_from_epoch(hash.fetch(:issued_at), granularity: :s)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!attribute [r] expires_at
|
38
|
+
# @return [Time] Time the token expires
|
39
|
+
def expires_at
|
40
|
+
as_time_from_epoch(hash.fetch(:expires), granularity: :s)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!attribute [r] capability
|
44
|
+
# @return [Hash] Capabilities assigned to this token
|
45
|
+
def capability
|
46
|
+
hash.fetch(:capability)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @!attribute [r] client_id
|
50
|
+
# @return [String] Optional client ID assigned to this token
|
51
|
+
def client_id
|
52
|
+
hash.fetch(:client_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @!attribute [r] nonce
|
56
|
+
# @return [String] unique nonce used to generate Token and ensure token generation cannot be replayed
|
57
|
+
def nonce
|
58
|
+
hash.fetch(:nonce)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns true if token is expired or about to expire
|
62
|
+
#
|
63
|
+
# @return [Boolean]
|
64
|
+
def expired?
|
65
|
+
expires_at < Time.now + TOKEN_EXPIRY_BUFFER
|
66
|
+
end
|
67
|
+
|
68
|
+
# @!attribute [r] hash
|
69
|
+
# @return [Hash] Access the token Hash object ruby'fied to use symbolized keys
|
70
|
+
def hash
|
71
|
+
@hash_object
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Ably::Modules
|
2
|
+
# ChannelsCollection module provides common functionality to the Rest and Realtime Channels objects
|
3
|
+
# such as #get, #[], #fetch, and #release
|
4
|
+
module ChannelsCollection
|
5
|
+
# Initialize a new Channels object
|
6
|
+
#
|
7
|
+
# {self} provides simple accessor methods to access a Channel object
|
8
|
+
def initialize(client, channel_klass)
|
9
|
+
@client = client
|
10
|
+
@channel_klass = channel_klass
|
11
|
+
@channels = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Return a Channel for the given name
|
15
|
+
#
|
16
|
+
# @param name [String] The name of the channel
|
17
|
+
# @param channel_options [Hash] Channel options, currently reserved for Encryption options
|
18
|
+
#
|
19
|
+
# @return Channel
|
20
|
+
def get(name, channel_options = {})
|
21
|
+
@channels[name] ||= channel_klass.new(client, name, channel_options)
|
22
|
+
end
|
23
|
+
alias_method :[], :get
|
24
|
+
|
25
|
+
# Return a Channel for the given name if it exists, else the block will be called.
|
26
|
+
# This method is intentionally similar to {http://ruby-doc.org/core-2.1.3/Hash.html#method-i-fetch Hash#fetch} providing a simple way to check if a channel exists or not without creating one
|
27
|
+
#
|
28
|
+
# @param name [String] The name of the channel
|
29
|
+
#
|
30
|
+
# @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
|
31
|
+
# @yieldparam [String] name of the missing channel
|
32
|
+
#
|
33
|
+
# @return Channel
|
34
|
+
def fetch(name, &missing_block)
|
35
|
+
@channels.fetch(name, &missing_block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Destroy the Channel and releases the associated resources.
|
39
|
+
#
|
40
|
+
# Releasing a Channel is not typically necessary as a channel consumes no resources other than the memory footprint of the
|
41
|
+
# Channel object. Explicitly release channels to free up resources if required
|
42
|
+
def release(channel)
|
43
|
+
@channels.delete(channel)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
attr_reader :client, :channel_klass
|
48
|
+
end
|
49
|
+
end
|
@@ -39,24 +39,47 @@ module Ably
|
|
39
39
|
end
|
40
40
|
|
41
41
|
# On receiving an event matching the event_name, call the provided block
|
42
|
-
|
43
|
-
|
42
|
+
#
|
43
|
+
# @param [Array<String>] event_names event name
|
44
|
+
#
|
45
|
+
# @return <void>
|
46
|
+
def on(*event_names, &block)
|
47
|
+
event_names.each do |event_name|
|
48
|
+
callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# On receiving an event maching the event_name, call the provided block only once and remove the registered callback
|
53
|
+
#
|
54
|
+
# @param [Array<String>] event_names event name
|
55
|
+
#
|
56
|
+
# @return <void>
|
57
|
+
def once(*event_names, &block)
|
58
|
+
event_names.each do |event_name|
|
59
|
+
callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, delete_once_run: true)
|
60
|
+
end
|
44
61
|
end
|
45
62
|
|
46
63
|
# Trigger an event with event_name that will in turn call all matching callbacks setup with `on`
|
47
64
|
def trigger(event_name, *args)
|
48
|
-
callbacks[callbacks_event_coerced(event_name)].
|
65
|
+
callbacks[callbacks_event_coerced(event_name)].delete_if { |proc_hash| proc_hash[:trigger_proc].call(*args) }
|
49
66
|
end
|
50
67
|
|
51
68
|
# Remove all callbacks for event_name.
|
52
69
|
#
|
53
70
|
# If a block is provided, only callbacks matching that block signature will be removed.
|
54
71
|
# If block is not provided, all callbacks matching the event_name will be removed.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
72
|
+
#
|
73
|
+
# @param [Array<String>] event_names event name
|
74
|
+
#
|
75
|
+
# @return <void>
|
76
|
+
def off(*event_names, &block)
|
77
|
+
event_names.each do |event_name|
|
78
|
+
if block_given?
|
79
|
+
callbacks[callbacks_event_coerced(event_name)].delete_if { |proc_hash| proc_hash[:block] == block }
|
80
|
+
else
|
81
|
+
callbacks[callbacks_event_coerced(event_name)].clear
|
82
|
+
end
|
60
83
|
end
|
61
84
|
end
|
62
85
|
|
@@ -65,6 +88,18 @@ module Ably
|
|
65
88
|
klass.extend ClassMethods
|
66
89
|
end
|
67
90
|
|
91
|
+
# Create a Hash with a proc that calls the provided block and returns true if option :delete_once_run is set to true.
|
92
|
+
# #trigger automatically deletes any blocks that return true thus allowing a block to be run once
|
93
|
+
def proc_for_block(block, options = {})
|
94
|
+
{
|
95
|
+
trigger_proc: Proc.new do |*args|
|
96
|
+
block.call *args
|
97
|
+
true if options[:delete_once_run]
|
98
|
+
end,
|
99
|
+
block: block
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
68
103
|
def callbacks
|
69
104
|
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
|
70
105
|
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'base64'
|
2
2
|
|
3
3
|
require 'ably/rest/middleware/external_exceptions'
|
4
|
+
require 'ably/rest/middleware/fail_if_unsupported_mime_type'
|
4
5
|
require 'ably/rest/middleware/parse_json'
|
5
6
|
require 'ably/rest/middleware/parse_message_pack'
|
6
7
|
|
7
8
|
module Ably::Modules
|
9
|
+
# HttpHelpers provides common private methods to classes to simplify HTTP interactions with Ably
|
8
10
|
module HttpHelpers
|
9
11
|
protected
|
10
12
|
def encode64(text)
|
@@ -15,11 +17,16 @@ module Ably::Modules
|
|
15
17
|
"Ably Ruby client #{Ably::VERSION} (https://ably.io)"
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
20
|
+
def setup_outgoing_middleware(builder)
|
19
21
|
# Convert request params to "www-form-urlencoded"
|
20
22
|
builder.use Faraday::Request::UrlEncoded
|
23
|
+
end
|
21
24
|
|
22
|
-
|
25
|
+
def setup_incoming_middleware(builder, options = {})
|
26
|
+
# Parse JSON / MsgPack response bodies. ParseJson must be first (default) parsing middleware
|
27
|
+
if options[:fail_if_unsupported_mime_type] == true
|
28
|
+
builder.use Ably::Rest::Middleware::FailIfUnsupportedMimeType
|
29
|
+
end
|
23
30
|
builder.use Ably::Rest::Middleware::ParseJson
|
24
31
|
builder.use Ably::Rest::Middleware::ParseMessagePack
|
25
32
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Ably::Modules
|
4
|
+
# MessagePack module adds #to_msgpack to the class on the assumption that the class
|
5
|
+
# supports the method #as_json
|
6
|
+
#
|
7
|
+
module MessagePack
|
8
|
+
# Generate a packed MsgPack version of this object based on the JSON representation.
|
9
|
+
# Keys thus use mixedCase syntax as expected by the Realtime API
|
10
|
+
def to_msgpack(*args)
|
11
|
+
as_json(*args).to_msgpack
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Ably::Modules
|
2
|
+
# Common model functionality shared across many {Ably::Models}
|
3
|
+
module ModelCommon
|
4
|
+
include Ably::Modules::Conversions
|
5
|
+
include Ably::Modules::MessagePack
|
6
|
+
|
7
|
+
# Provide a normal Hash accessor to the underlying raw message object
|
8
|
+
#
|
9
|
+
# @return [Object]
|
10
|
+
def [](key)
|
11
|
+
hash[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
other.kind_of?(self.class) &&
|
16
|
+
hash == other.hash
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
|
20
|
+
def as_json
|
21
|
+
hash.as_json.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
# Stringify the JSON representation of this object from the underlying #hash
|
25
|
+
def to_json(*args)
|
26
|
+
as_json.to_json(*args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Ably::Modules
|
2
|
-
#
|
2
|
+
# StateEmitter module adds a set of generic state related methods to a class on the assumption that
|
3
3
|
# the instance variable @state is used exclusively, the {Enum} STATE is defined prior to inclusion of this
|
4
|
-
# module, and the class is an {EventEmitter}
|
4
|
+
# module, and the class is an {EventEmitter}. It then emits state changes.
|
5
5
|
#
|
6
6
|
# @example
|
7
7
|
# class Connection
|
@@ -12,7 +12,7 @@ module Ably::Modules
|
|
12
12
|
# :connecting,
|
13
13
|
# :connected
|
14
14
|
# )
|
15
|
-
# include Ably::Modules::
|
15
|
+
# include Ably::Modules::StateEmitter
|
16
16
|
# end
|
17
17
|
#
|
18
18
|
# connection = Connection.new
|
@@ -24,7 +24,7 @@ module Ably::Modules
|
|
24
24
|
# connection.trigger :invalid # raises an Exception as only a valid state can be used for EventEmitter
|
25
25
|
# connection.change_state :connected # emits :connected event via EventEmitter, returns STATE.Connected
|
26
26
|
#
|
27
|
-
module
|
27
|
+
module StateEmitter
|
28
28
|
# Current state {Ably::Modules::Enum}
|
29
29
|
#
|
30
30
|
# @return [Symbol] state
|
@@ -42,11 +42,12 @@ module Ably::Modules
|
|
42
42
|
# Set the current state {Ably::Modules::Enum}
|
43
43
|
#
|
44
44
|
# @return [Symbol] new state
|
45
|
-
|
45
|
+
# @api private
|
46
|
+
def state=(new_state, *args)
|
46
47
|
if state != new_state
|
47
|
-
logger.debug("#{self.class}:
|
48
|
+
logger.debug("#{self.class}: StateEmitter changed from #{state} => #{new_state}") if respond_to?(:logger, true)
|
48
49
|
@state = STATE(new_state)
|
49
|
-
trigger @state
|
50
|
+
trigger @state, *args
|
50
51
|
end
|
51
52
|
end
|
52
53
|
alias_method :change_state, :state=
|
data/lib/ably/realtime.rb
CHANGED
@@ -4,22 +4,52 @@ require "websocket/driver"
|
|
4
4
|
require "ably/modules/event_emitter"
|
5
5
|
|
6
6
|
require "ably/realtime/channel"
|
7
|
+
require "ably/realtime/channels"
|
7
8
|
require "ably/realtime/client"
|
8
9
|
require "ably/realtime/connection"
|
10
|
+
require "ably/realtime/connection/connection_state_machine"
|
11
|
+
require "ably/realtime/connection/websocket_transport"
|
12
|
+
require "ably/realtime/presence"
|
9
13
|
|
10
|
-
|
11
|
-
require
|
12
|
-
|
13
|
-
require "ably/realtime/models/nil_channel"
|
14
|
-
require "ably/realtime/models/protocol_message"
|
14
|
+
Dir.glob(File.expand_path("models/*.rb", File.dirname(__FILE__))).each do |file|
|
15
|
+
require file
|
16
|
+
end
|
15
17
|
|
16
18
|
require "ably/realtime/client/incoming_message_dispatcher"
|
17
19
|
require "ably/realtime/client/outgoing_message_dispatcher"
|
18
20
|
|
19
21
|
module Ably
|
22
|
+
# Realtime provides the top-level class to be instanced for the Ably Realtime library
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# client = Ably::Realtime.new("xxxxx")
|
26
|
+
# channel = client.channel("test")
|
27
|
+
# channel.subscribe do |message|
|
28
|
+
# message[:name] #=> "greeting"
|
29
|
+
# end
|
30
|
+
# channel.publish "greeting", "data"
|
31
|
+
#
|
20
32
|
module Realtime
|
21
|
-
|
22
|
-
|
33
|
+
# Convenience method providing an alias to {Ably::Realtime::Client} constructor.
|
34
|
+
#
|
35
|
+
# @param (see Ably::Realtime::Client#initialize)
|
36
|
+
# @option options (see Ably::Realtime::Client#initialize)
|
37
|
+
#
|
38
|
+
# @yield (see Ably::Realtime::Client#initialize)
|
39
|
+
# @yieldparam (see Ably::Realtime::Client#initialize)
|
40
|
+
# @yieldreturn (see Ably::Realtime::Client#initialize)
|
41
|
+
#
|
42
|
+
# @return [Ably::Realtime::Client]
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# # create a new client authenticating with basic auth
|
46
|
+
# client = Ably::Realtime.new('key.id:secret')
|
47
|
+
#
|
48
|
+
# # create a new client authenticating with basic auth and a client_id
|
49
|
+
# client = Ably::Realtime.new(api_key: 'key.id:secret', client_id: 'john')
|
50
|
+
#
|
51
|
+
def self.new(options, &auth_block)
|
52
|
+
Ably::Realtime::Client.new(options, &auth_block)
|
23
53
|
end
|
24
54
|
end
|
25
55
|
end
|
@@ -40,16 +40,22 @@ module Ably
|
|
40
40
|
:detached,
|
41
41
|
:failed
|
42
42
|
)
|
43
|
-
include Ably::Modules::
|
43
|
+
include Ably::Modules::StateEmitter
|
44
44
|
|
45
45
|
# Max number of messages to bundle in a single ProtocolMessage
|
46
46
|
MAX_PROTOCOL_MESSAGE_BATCH_SIZE = 50
|
47
47
|
|
48
|
-
attr_reader :client, :name
|
48
|
+
attr_reader :client, :name, :options
|
49
49
|
|
50
|
-
|
50
|
+
# Initialize a new Channel object
|
51
|
+
#
|
52
|
+
# @param client [Ably::Rest::Client]
|
53
|
+
# @param name [String] The name of the channel
|
54
|
+
# @param channel_options [Hash] Channel options, currently reserved for future Encryption options
|
55
|
+
def initialize(client, name, channel_options = {})
|
51
56
|
@client = client
|
52
57
|
@name = name
|
58
|
+
@options = channel_options.clone.freeze
|
53
59
|
@subscriptions = Hash.new { |hash, key| hash[key] = [] }
|
54
60
|
@queue = []
|
55
61
|
@state = STATE.Initialized
|
@@ -59,54 +65,137 @@ module Ably
|
|
59
65
|
|
60
66
|
# Publish a message on the channel
|
61
67
|
#
|
62
|
-
# @param
|
68
|
+
# @param name [String] The event name of the message
|
63
69
|
# @param data [String,ByteArray] payload for the message
|
64
|
-
# @yield [Ably::
|
65
|
-
# @return [Ably::
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
# @yield [Ably::Models::Message] On success, will call the block with the {Ably::Models::Message}
|
71
|
+
# @return [Ably::Models::Message] Deferrable {Ably::Models::Message} that supports both success (callback) and failure (errback) callbacks
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# channel.publish('click', 'body')
|
75
|
+
#
|
76
|
+
# channel.publish('click', 'body') do |message|
|
77
|
+
# puts "#{message.name} event received with #{message.data}"
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# channel.publish('click', 'body').errback do |message, error|
|
81
|
+
# puts "#{message.name} was not received, error #{error.message}"
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
def publish(name, data, &callback)
|
85
|
+
create_message(name, data).tap do |message|
|
74
86
|
message.callback(&callback) if block_given?
|
75
87
|
queue_message message
|
76
88
|
end
|
77
89
|
end
|
78
90
|
|
79
|
-
|
80
|
-
|
91
|
+
# Subscribe to messages matching providing event name, or all messages if event name not provided
|
92
|
+
#
|
93
|
+
# @param name [String] The event name of the message to subscribe to if provided. Defaults to all events.
|
94
|
+
# @yield [Ably::Models::Message] For each message received, the block is called
|
95
|
+
#
|
96
|
+
def subscribe(name = :all, &blk)
|
81
97
|
attach unless attached? || attaching?
|
82
|
-
|
98
|
+
subscriptions[message_name_key(name)] << blk
|
83
99
|
end
|
84
100
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
101
|
+
# Unsubscribe the matching block for messages matching providing event name, or all messages if event name not provided.
|
102
|
+
# If a block is not provided, all subscriptions will be unsubscribed
|
103
|
+
#
|
104
|
+
# @param name [String] The event name of the message to subscribe to if provided. Defaults to all events.
|
105
|
+
#
|
106
|
+
def unsubscribe(name = :all, &blk)
|
107
|
+
if message_name_key(name) == :all
|
108
|
+
subscriptions.keys
|
109
|
+
else
|
110
|
+
Array(message_name_key(name))
|
111
|
+
end.each do |key|
|
112
|
+
subscriptions[key].delete_if do |block|
|
113
|
+
!block_given? || blk == block
|
114
|
+
end
|
89
115
|
end
|
90
116
|
end
|
91
117
|
|
92
|
-
|
93
|
-
|
118
|
+
# Attach to this channel, and call the block if provided when attached.
|
119
|
+
# Attaching to a channel is implicit in when a message is published or #subscribe is called, so it is uncommon
|
120
|
+
# to need to call attach explicitly.
|
121
|
+
#
|
122
|
+
# @yield [Ably::Realtime::Channel] Block is called as soon as this channel is in the Attached state
|
123
|
+
#
|
124
|
+
def attach(&block)
|
125
|
+
if attached?
|
126
|
+
block.call self if block_given?
|
127
|
+
else
|
128
|
+
once(STATE.Attached) { block.call self } if block_given?
|
129
|
+
if !attaching?
|
130
|
+
change_state STATE.Attaching
|
131
|
+
send_attach_protocol_message
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Detach this channel, and call the block if provided when in a Detached or Failed state
|
137
|
+
#
|
138
|
+
# @yield [Ably::Realtime::Channel] Block is called as soon as this channel is in the Detached or Failed state
|
139
|
+
#
|
140
|
+
def detach(&block)
|
141
|
+
if detached? || failed?
|
142
|
+
block.call self if block_given?
|
143
|
+
else
|
144
|
+
once(STATE.Detached, STATE.Failed) { block.call self } if block_given?
|
145
|
+
if !detaching?
|
146
|
+
change_state STATE.Detaching
|
147
|
+
send_detach_protocol_message
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Presence object for this Channel. This controls this client's
|
153
|
+
# presence on the channel and may also be used to obtain presence information
|
154
|
+
# and change events for other members of the channel.
|
155
|
+
#
|
156
|
+
# @return {Ably::Realtime::Presence}
|
157
|
+
#
|
158
|
+
def presence
|
159
|
+
@presence ||= Presence.new(self)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Return the message history of the channel
|
163
|
+
#
|
164
|
+
# @param (see Ably::Rest::Channel#history)
|
165
|
+
# @option options (see Ably::Rest::Channel#history)
|
166
|
+
def history(options = {})
|
167
|
+
rest_channel.history(options)
|
168
|
+
end
|
169
|
+
|
170
|
+
# @!attribute [r] __incoming_msgbus__
|
171
|
+
# @return [Ably::Util::PubSub] Client library internal channel incoming message bus
|
172
|
+
# @api private
|
173
|
+
def __incoming_msgbus__
|
174
|
+
@__incoming_msgbus__ ||= Ably::Util::PubSub.new(
|
94
175
|
coerce_into: Proc.new { |event| Models::ProtocolMessage::ACTION(event) }
|
95
176
|
)
|
96
177
|
end
|
97
178
|
|
98
179
|
private
|
99
|
-
attr_reader :queue
|
180
|
+
attr_reader :queue, :subscriptions
|
100
181
|
|
101
182
|
def setup_event_handlers
|
102
|
-
|
103
|
-
|
104
|
-
|
183
|
+
__incoming_msgbus__.subscribe(:message) do |message|
|
184
|
+
subscriptions[:all].each { |cb| cb.call(message) }
|
185
|
+
subscriptions[message.name].each { |cb| cb.call(message) }
|
105
186
|
end
|
106
187
|
|
107
|
-
on(
|
188
|
+
on(STATE.Attached) do
|
108
189
|
process_queue
|
109
190
|
end
|
191
|
+
|
192
|
+
connection.on(Connection::STATE.Closed) do
|
193
|
+
change_state STATE.Detached
|
194
|
+
end
|
195
|
+
|
196
|
+
connection.on(Connection::STATE.Failed) do
|
197
|
+
change_state STATE.Failed unless detached? || initialized?
|
198
|
+
end
|
110
199
|
end
|
111
200
|
|
112
201
|
# Queue message and process queue if channel is attached.
|
@@ -129,7 +218,7 @@ module Ably
|
|
129
218
|
def process_queue
|
130
219
|
condition = -> { attached? && messages_in_queue? }
|
131
220
|
non_blocking_loop_while(condition) do
|
132
|
-
send_messages_within_protocol_message
|
221
|
+
send_messages_within_protocol_message queue.shift(MAX_PROTOCOL_MESSAGE_BATCH_SIZE)
|
133
222
|
end
|
134
223
|
end
|
135
224
|
|
@@ -142,16 +231,50 @@ module Ably
|
|
142
231
|
end
|
143
232
|
|
144
233
|
def send_attach_protocol_message
|
234
|
+
send_state_change_protocol_message Models::ProtocolMessage::ACTION.Attach
|
235
|
+
end
|
236
|
+
|
237
|
+
def send_detach_protocol_message
|
238
|
+
send_state_change_protocol_message Models::ProtocolMessage::ACTION.Detach
|
239
|
+
end
|
240
|
+
|
241
|
+
def send_state_change_protocol_message(state)
|
145
242
|
client.connection.send_protocol_message(
|
146
|
-
action:
|
243
|
+
action: state.to_i,
|
147
244
|
channel: name
|
148
245
|
)
|
149
246
|
end
|
150
247
|
|
151
|
-
|
248
|
+
def create_message(name, data)
|
249
|
+
model = {
|
250
|
+
name: name,
|
251
|
+
data: data
|
252
|
+
}
|
253
|
+
model.merge!(clientId: client.client_id) if client.client_id
|
254
|
+
|
255
|
+
Models::Message.new(model, nil)
|
256
|
+
end
|
257
|
+
|
258
|
+
def rest_channel
|
259
|
+
client.rest_client.channel(name)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Used by {Ably::Modules::StateEmitter} to debug state changes
|
152
263
|
def logger
|
153
264
|
client.logger
|
154
265
|
end
|
266
|
+
|
267
|
+
def connection
|
268
|
+
client.connection
|
269
|
+
end
|
270
|
+
|
271
|
+
def message_name_key(name)
|
272
|
+
if name == :all
|
273
|
+
:all
|
274
|
+
else
|
275
|
+
name.to_s
|
276
|
+
end
|
277
|
+
end
|
155
278
|
end
|
156
279
|
end
|
157
280
|
end
|