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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably.rb +6 -2
  5. data/lib/ably/auth.rb +24 -16
  6. data/lib/ably/exceptions.rb +16 -5
  7. data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
  8. data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
  9. data/lib/ably/{realtime/models → models}/message.rb +45 -38
  10. data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
  11. data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
  12. data/lib/ably/models/presence_message.rb +126 -0
  13. data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
  14. data/lib/ably/models/token.rb +74 -0
  15. data/lib/ably/modules/channels_collection.rb +49 -0
  16. data/lib/ably/modules/conversions.rb +2 -0
  17. data/lib/ably/modules/event_emitter.rb +43 -8
  18. data/lib/ably/modules/event_machine_helpers.rb +1 -0
  19. data/lib/ably/modules/http_helpers.rb +9 -2
  20. data/lib/ably/modules/message_pack.rb +14 -0
  21. data/lib/ably/modules/model_common.rb +29 -0
  22. data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
  23. data/lib/ably/realtime.rb +37 -7
  24. data/lib/ably/realtime/channel.rb +154 -31
  25. data/lib/ably/realtime/channels.rb +47 -0
  26. data/lib/ably/realtime/client.rb +39 -33
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
  29. data/lib/ably/realtime/connection.rb +148 -79
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
  31. data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
  32. data/lib/ably/realtime/presence.rb +270 -0
  33. data/lib/ably/rest.rb +14 -3
  34. data/lib/ably/rest/channel.rb +3 -3
  35. data/lib/ably/rest/channels.rb +26 -12
  36. data/lib/ably/rest/client.rb +42 -25
  37. data/lib/ably/rest/middleware/exceptions.rb +21 -23
  38. data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
  39. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  40. data/lib/ably/rest/middleware/parse_json.rb +9 -2
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
  42. data/lib/ably/rest/presence.rb +4 -4
  43. data/lib/ably/version.rb +1 -1
  44. data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
  45. data/spec/acceptance/realtime/channel_spec.rb +135 -63
  46. data/spec/acceptance/realtime/connection_spec.rb +86 -0
  47. data/spec/acceptance/realtime/message_spec.rb +116 -94
  48. data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +277 -0
  50. data/spec/acceptance/rest/auth_spec.rb +351 -347
  51. data/spec/acceptance/rest/base_spec.rb +43 -26
  52. data/spec/acceptance/rest/channel_spec.rb +88 -83
  53. data/spec/acceptance/rest/channels_spec.rb +32 -28
  54. data/spec/acceptance/rest/presence_spec.rb +83 -63
  55. data/spec/acceptance/rest/stats_spec.rb +38 -37
  56. data/spec/acceptance/rest/time_spec.rb +10 -6
  57. data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/api_helper.rb +4 -0
  60. data/spec/support/model_helper.rb +28 -9
  61. data/spec/support/protocol_msgbus_helper.rb +8 -1
  62. data/spec/support/test_app.rb +24 -14
  63. data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
  64. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
  65. data/spec/unit/models/message_spec.rb +229 -0
  66. data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
  67. data/spec/unit/models/presence_message_spec.rb +230 -0
  68. data/spec/unit/models/protocol_message_spec.rb +280 -0
  69. data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
  70. data/spec/unit/modules/conversions_spec.rb +1 -1
  71. data/spec/unit/modules/event_emitter_spec.rb +36 -4
  72. data/spec/unit/realtime/channel_spec.rb +76 -2
  73. data/spec/unit/realtime/channels_spec.rb +50 -0
  74. data/spec/unit/realtime/client_spec.rb +31 -1
  75. data/spec/unit/realtime/connection_spec.rb +8 -15
  76. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
  77. data/spec/unit/realtime/presence_spec.rb +100 -0
  78. data/spec/unit/rest/channels_spec.rb +48 -0
  79. metadata +72 -38
  80. data/lib/ably/realtime/models/shared.rb +0 -17
  81. data/lib/ably/rest/models/message.rb +0 -64
  82. data/lib/ably/rest/models/presence_message.rb +0 -21
  83. data/lib/ably/token.rb +0 -80
  84. data/spec/unit/realtime/message_spec.rb +0 -117
  85. data/spec/unit/realtime/protocol_message_spec.rb +0 -172
  86. data/spec/unit/rest/message_spec.rb +0 -75
@@ -1,17 +0,0 @@
1
- module Ably::Realtime::Models
2
- module Shared
3
- include Ably::Modules::Conversions
4
-
5
- # Provide a normal Hash accessor to the underlying raw message object
6
- #
7
- # @return [Object]
8
- def [](key)
9
- json[key]
10
- end
11
-
12
- def ==(other)
13
- other.kind_of?(self.class) &&
14
- json == other.json
15
- end
16
- end
17
- end
@@ -1,64 +0,0 @@
1
- module Ably::Rest::Models
2
- # A Message object encapsulates an individual message published in Ably retrieved via Rest
3
- class Message
4
- include Ably::Modules::Conversions
5
-
6
- def initialize(message)
7
- @message = IdiomaticRubyWrapper(message.clone.freeze)
8
- end
9
-
10
- # Event name
11
- #
12
- # @return [String]
13
- def name
14
- json[:name]
15
- end
16
-
17
- # Payload
18
- #
19
- # @return [Object]
20
- def data
21
- json[:data]
22
- end
23
-
24
- # Client ID of the publisher of the message
25
- #
26
- # @return [String]
27
- def client_id
28
- json[:client_id]
29
- end
30
-
31
- # Timestamp when message was sent. This property is populated by the sender.
32
- #
33
- # @return [Time]
34
- def sender_timestamp
35
- as_time_from_epoch(json[:timestamp]) if json[:timestamp]
36
- end
37
-
38
- # Unique message ID
39
- #
40
- # @return [String]
41
- def message_id
42
- json[:message_id]
43
- end
44
-
45
- # Provide a normal Hash accessor to the underlying raw message object
46
- #
47
- # @return [Object]
48
- def [](key)
49
- json[key]
50
- end
51
-
52
- # Raw message object
53
- #
54
- # @return [Hash]
55
- def json
56
- @message
57
- end
58
-
59
- def ==(other)
60
- other.kind_of?(Message) &&
61
- json == other.json
62
- end
63
- end
64
- end
@@ -1,21 +0,0 @@
1
- require 'delegate'
2
-
3
- module Ably::Rest::Models
4
- # A placeholder class representing a presence message
5
- class PresenceMessage < Delegator
6
- include Ably::Modules::Conversions
7
-
8
- def initialize(json_object)
9
- super
10
- @json_object = IdiomaticRubyWrapper(json_object.clone.freeze, stop_at: [:client_data])
11
- end
12
-
13
- def __getobj__
14
- @json_object
15
- end
16
-
17
- def __setobj__(obj)
18
- @json_object = obj
19
- end
20
- end
21
- end
data/lib/ably/token.rb DELETED
@@ -1,80 +0,0 @@
1
- module Ably
2
- # Authentication token issued by Ably in response to an token request
3
- class Token
4
- include Ably::Modules::Conversions
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
- @attributes = attributes.clone.freeze
17
- end
18
-
19
- # Unique token ID used to authenticate requests
20
- #
21
- # @return [String]
22
- def id
23
- attributes.fetch(:id)
24
- end
25
-
26
- # Key ID used to create this token
27
- #
28
- # @return [String]
29
- def key_id
30
- attributes.fetch(:key)
31
- end
32
-
33
- # Time the token was issued
34
- #
35
- # @return [Time]
36
- def issued_at
37
- as_time_from_epoch(attributes.fetch(:issued_at), granularity: :s)
38
- end
39
-
40
- # Time the token expires
41
- #
42
- # @return [Time]
43
- def expires_at
44
- as_time_from_epoch(attributes.fetch(:expires), granularity: :s)
45
- end
46
-
47
- # Capabilities assigned to this token
48
- #
49
- # @return [Hash]
50
- def capability
51
- attributes.fetch(:capability)
52
- end
53
-
54
- # Optioanl client ID assigned to this token
55
- #
56
- # @return [String]
57
- def client_id
58
- attributes.fetch(:client_id)
59
- end
60
-
61
- def nonce
62
- attributes.fetch(:nonce)
63
- end
64
-
65
- def ==(other)
66
- other.kind_of?(Token) &&
67
- attributes == other.attributes
68
- end
69
-
70
- # Returns true if token is expired or about to expire
71
- #
72
- # @return [Boolean]
73
- def expired?
74
- expires_at < Time.now + TOKEN_EXPIRY_BUFFER
75
- end
76
-
77
- protected
78
- attr_reader :attributes
79
- end
80
- end
@@ -1,117 +0,0 @@
1
- require 'spec_helper'
2
- require 'support/model_helper'
3
-
4
- describe Ably::Realtime::Models::Message do
5
- include Ably::Modules::Conversions
6
-
7
- subject { Ably::Realtime::Models::Message }
8
- let(:protocol_message) { Ably::Realtime::Models::ProtocolMessage.new(action: 1) }
9
-
10
- it_behaves_like 'a realtime model', with_simple_attributes: %w(name client_id data) do
11
- let(:model_args) { [protocol_message] }
12
- end
13
-
14
- context '#sender_timestamp' do
15
- let(:model) { subject.new({ timestamp: as_since_epoch(Time.now) }, protocol_message) }
16
- it 'retrieves attribute :sender_timestamp' do
17
- expect(model.sender_timestamp).to be_a(Time)
18
- expect(model.sender_timestamp.to_i).to be_within(1).of(Time.now.to_i)
19
- end
20
- end
21
-
22
- context 'Java naming' do
23
- let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
24
-
25
- it 'converts the attribute to ruby symbol naming convention' do
26
- expect(model.client_id).to eql('joe')
27
- end
28
- end
29
-
30
- context '#to_json' do
31
- let(:json_object) { JSON.parse(model.to_json) }
32
-
33
- context 'with valid data' do
34
- let(:model) { subject.new({ name: 'test', clientId: 'joe' }, protocol_message) }
35
-
36
- it 'converts the attribute back to Java mixedCase notation using string keys' do
37
- expect(json_object["clientId"]).to eql('joe')
38
- end
39
-
40
- it 'autofills a missing timestamp for all messages' do
41
- expect(json_object["timestamp"].to_i).to be_within(50).of(as_since_epoch(Time.now))
42
- end
43
- end
44
-
45
- context 'with invalid data' do
46
- let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
47
-
48
- it 'raises an exception' do
49
- expect { model.to_json }.to raise_error RuntimeError, /cannot generate valid JSON/
50
- end
51
- end
52
- end
53
-
54
- context 'part of ProtocolMessage' do
55
- let(:ably_time) { Time.now + 5 }
56
- let(:sender_time_0) { Time.now - 5 }
57
- let(:sender_time_1) { Time.now - 3 }
58
- let(:message_serial) { SecureRandom.random_number(1_000_000) }
59
- let(:connection_id) { SecureRandom.hex }
60
-
61
- let(:message_0_payload) do
62
- {
63
- 'string_key' => 'string_value',
64
- 1 => 2,
65
- true => false
66
- }
67
- end
68
-
69
- let(:message_0_json) do
70
- {
71
- timestamp: sender_time_0,
72
- name: 'zero',
73
- data: message_0_payload
74
- }
75
- end
76
-
77
- let(:message_1_json) do
78
- {
79
- timestamp: sender_time_1,
80
- name: 'one',
81
- data: 'simple string'
82
- }
83
- end
84
-
85
- let(:protocol_message) do
86
- Ably::Realtime::Models::ProtocolMessage.new({
87
- timestamp: ably_time.to_i,
88
- msg_serial: message_serial,
89
- connection_id: connection_id,
90
- messages: [
91
- message_0_json, message_1_json
92
- ]
93
- })
94
- end
95
-
96
- let(:message_0) { protocol_message.messages.first }
97
- let(:message_1) { protocol_message.messages.last }
98
-
99
- it 'should generate a message ID from the index, serial and connection id' do
100
- expect(message_0.message_id).to eql("#{connection_id}:#{message_serial}:0")
101
- expect(message_1.message_id).to eql("#{connection_id}:#{message_serial}:1")
102
- end
103
-
104
- it 'should not modify the data payload' do
105
- expect(message_0.data['string_key']).to eql('string_value')
106
- expect(message_0.data[1]).to eql(2)
107
- expect(message_0.data[true]).to eql(false)
108
- expect(message_0.data).to eql(message_0_payload)
109
-
110
- expect(message_1.data).to eql('simple string')
111
- end
112
-
113
- it 'should not allow changes to the payload' do
114
- expect { message_0.data["test"] = true }.to raise_error RuntimeError, /can't modify frozen Hash/
115
- end
116
- end
117
- end
@@ -1,172 +0,0 @@
1
- require 'spec_helper'
2
- require 'support/model_helper'
3
-
4
- describe Ably::Realtime::Models::ProtocolMessage do
5
- include Ably::Modules::Conversions
6
- subject { Ably::Realtime::Models::ProtocolMessage }
7
-
8
- it_behaves_like 'a realtime model',
9
- with_simple_attributes: %w(channel channel_serial connection_id connection_serial) do
10
-
11
- let(:model_args) { [] }
12
- end
13
-
14
- context 'attributes' do
15
- let(:unique_value) { SecureRandom.hex }
16
-
17
- context 'Java naming' do
18
- let(:protocol_message) { subject.new(channelSerial: unique_value) }
19
-
20
- it 'converts the attribute to ruby symbol naming convention' do
21
- expect(protocol_message.channel_serial).to eql(unique_value)
22
- end
23
- end
24
-
25
- context '#action' do
26
- let(:protocol_message) { subject.new(action: 14) }
27
-
28
- it 'returns an Enum that behaves like a symbol' do
29
- expect(protocol_message.action).to eq(:presence)
30
- end
31
-
32
- it 'returns an Enum that behaves like a Numeric' do
33
- expect(protocol_message.action).to eq(14)
34
- end
35
-
36
- it 'returns an Enum that behaves like a String' do
37
- expect(protocol_message.action).to eq('Presence')
38
- end
39
-
40
- it 'returns an Enum that matchdes the ACTION constant' do
41
- expect(protocol_message.action).to eql(Ably::Realtime::Models::ProtocolMessage::ACTION.Presence)
42
- end
43
- end
44
-
45
- context '#timestamp' do
46
- let(:protocol_message) { subject.new(timestamp: as_since_epoch(Time.now)) }
47
- it 'retrieves attribute :timestamp' do
48
- expect(protocol_message.timestamp).to be_a(Time)
49
- expect(protocol_message.timestamp.to_i).to be_within(1).of(Time.now.to_i)
50
- end
51
- end
52
-
53
- context '#message_serial' do
54
- let(:protocol_message) { subject.new(msg_serial: "55") }
55
- it 'converts :msg_serial to an Integer' do
56
- expect(protocol_message.message_serial).to be_a(Integer)
57
- expect(protocol_message.message_serial).to eql(55)
58
- end
59
- end
60
-
61
- context '#count' do
62
- context 'when missing' do
63
- let(:protocol_message) { subject.new({}) }
64
- it 'is 1' do
65
- expect(protocol_message.count).to eql(1)
66
- end
67
- end
68
-
69
- context 'when non numeric' do
70
- let(:protocol_message) { subject.new(count: 'A') }
71
- it 'is 1' do
72
- expect(protocol_message.count).to eql(1)
73
- end
74
- end
75
-
76
- context 'when greater than 1' do
77
- let(:protocol_message) { subject.new(count: '666') }
78
- it 'is the value of count' do
79
- expect(protocol_message.count).to eql(666)
80
- end
81
- end
82
- end
83
-
84
- context '#has_message_serial?' do
85
- context 'without msg_serial' do
86
- let(:protocol_message) { subject.new({}) }
87
-
88
- it 'returns false' do
89
- expect(protocol_message.has_message_serial?).to eql(false)
90
- end
91
- end
92
-
93
- context 'with msg_serial' do
94
- let(:protocol_message) { subject.new(msg_serial: "55") }
95
-
96
- it 'returns true' do
97
- expect(protocol_message.has_message_serial?).to eql(true)
98
- end
99
- end
100
- end
101
-
102
- context '#error' do
103
- context 'with no error attribute' do
104
- let(:protocol_message) { subject.new(action: 1) }
105
-
106
- it 'returns nil' do
107
- expect(protocol_message.error).to be_nil
108
- end
109
- end
110
-
111
- context 'with nil error' do
112
- let(:protocol_message) { subject.new(error: nil) }
113
-
114
- it 'returns nil' do
115
- expect(protocol_message.error).to be_nil
116
- end
117
- end
118
-
119
- context 'with error' do
120
- let(:protocol_message) { subject.new(error: { message: 'test_error' }) }
121
-
122
- it 'returns a valid ErrorInfo object' do
123
- expect(protocol_message.error).to be_a(Ably::Realtime::Models::ErrorInfo)
124
- expect(protocol_message.error.message).to eql('test_error')
125
- end
126
- end
127
- end
128
- end
129
-
130
- context '#to_json' do
131
- let(:json_object) { JSON.parse(model.to_json) }
132
- let(:message) { { 'name' => 'event', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
133
- let(:attached_action) { Ably::Realtime::Models::ProtocolMessage::ACTION.Attached }
134
- let(:message_action) { Ably::Realtime::Models::ProtocolMessage::ACTION.Message }
135
-
136
- context 'with valid data' do
137
- let(:model) { subject.new({ :action => attached_action, :channelSerial => 'unique', messages: [message] }) }
138
-
139
- it 'converts the attribute back to Java mixedCase notation using string keys' do
140
- expect(json_object["channelSerial"]).to eql('unique')
141
- end
142
-
143
- it 'populates the messages' do
144
- expect(json_object["messages"].first).to include(message)
145
- end
146
- end
147
-
148
- context 'with invalid name data' do
149
- let(:model) { subject.new({ clientId: 'joe' }) }
150
-
151
- it 'it raises an exception' do
152
- expect { model.to_json }.to raise_error KeyError, /Action '' is not supported by ProtocolMessage/
153
- end
154
- end
155
-
156
- context 'with missing msg_serial for ack message' do
157
- let(:model) { subject.new({ :action => message_action }) }
158
-
159
- it 'it raises an exception' do
160
- expect { model.to_json }.to raise_error TypeError, /msg_serial is missing/
161
- end
162
- end
163
-
164
- context 'is aliased by #to_s' do
165
- let(:model) { subject.new({ :action => attached_action, :channelSerial => 'unique', messages: [message], :timestamp => as_since_epoch(Time.now) }) }
166
-
167
- specify do
168
- expect(json_object).to eql(JSON.parse("#{model}"))
169
- end
170
- end
171
- end
172
- end