ably 0.1.5 → 0.1.6
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/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
|