ably 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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