ably-rest 1.0.5 → 1.1.3

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 (118) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -3
  3. data/CHANGELOG.md +1 -1
  4. data/LICENSE +1 -1
  5. data/README.md +26 -7
  6. data/SPEC.md +2003 -1605
  7. data/ably-rest.gemspec +4 -2
  8. data/lib/submodules/ably-ruby/.editorconfig +14 -0
  9. data/lib/submodules/ably-ruby/.travis.yml +10 -8
  10. data/lib/submodules/ably-ruby/CHANGELOG.md +97 -1
  11. data/lib/submodules/ably-ruby/LICENSE +1 -3
  12. data/lib/submodules/ably-ruby/README.md +12 -7
  13. data/lib/submodules/ably-ruby/Rakefile +32 -0
  14. data/lib/submodules/ably-ruby/SPEC.md +1277 -835
  15. data/lib/submodules/ably-ruby/ably.gemspec +17 -11
  16. data/lib/submodules/ably-ruby/lib/ably/auth.rb +34 -8
  17. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
  18. data/lib/submodules/ably-ruby/lib/ably/logger.rb +8 -2
  19. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
  20. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
  21. data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
  24. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +12 -12
  25. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +6 -4
  26. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +6 -4
  27. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
  28. data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
  29. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +2 -2
  30. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +2 -2
  31. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +2 -2
  32. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +2 -2
  33. data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
  34. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
  35. data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +1 -1
  36. data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +1 -1
  37. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +5 -5
  38. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
  39. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +2 -2
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +27 -105
  42. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +4 -8
  43. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  44. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
  45. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
  46. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
  47. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +9 -4
  48. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  49. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +45 -26
  50. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
  51. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +2 -2
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +7 -7
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +9 -9
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
  58. data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
  59. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +54 -18
  60. data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
  61. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +171 -41
  62. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  63. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
  64. data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
  68. data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
  69. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +253 -49
  70. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +33 -21
  71. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +180 -62
  72. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +155 -2
  73. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +293 -13
  74. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +142 -39
  75. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +38 -36
  76. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +12 -3
  77. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +207 -173
  78. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  79. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +62 -51
  81. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
  82. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
  83. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +318 -74
  85. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
  86. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
  89. data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
  90. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +1 -9
  91. data/lib/submodules/ably-ruby/spec/spec_helper.rb +3 -1
  92. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
  93. data/lib/submodules/ably-ruby/spec/support/event_emitter_helper.rb +31 -0
  94. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
  95. data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
  96. data/lib/submodules/ably-ruby/spec/support/test_logger_helper.rb +42 -0
  97. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +11 -12
  98. data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
  99. data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
  100. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
  101. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
  102. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
  103. data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  104. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -2
  105. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
  106. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -3
  107. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +10 -10
  108. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +1 -1
  109. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
  110. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +2 -2
  111. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +1 -1
  112. data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
  113. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +30 -1
  114. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
  115. data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
  116. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +3 -3
  117. data/spec/spec_helper.rb +1 -0
  118. metadata +51 -10
@@ -43,25 +43,38 @@ module Ably::Realtime
43
43
  end
44
44
 
45
45
  unless client.auth.authentication_security_requirements_met?
46
- connection.transition_state_machine :failed, reason: Ably::Exceptions::InsecureRequest.new('Cannot use Basic Auth over non-TLS connections', 401, 40103)
46
+ connection.transition_state_machine :failed, reason: Ably::Exceptions::InsecureRequest.new('Cannot use Basic Auth over non-TLS connections', 401, Ably::Exceptions::Codes::INVALID_USE_OF_BASIC_AUTH_OVER_NONTLS_TRANSPORT)
47
47
  return
48
48
  end
49
49
 
50
50
  logger.debug { 'ConnectionManager: Opening a websocket transport connection' }
51
51
 
52
+ # The socket attempt can fail at the same time as a timer firing so ensure
53
+ # only one outcome is processed from this setup attempt
54
+ setup_attempt_status = {}
55
+ setup_failed = lambda do
56
+ return true if setup_attempt_status[:failed]
57
+ setup_attempt_status[:failed] = true
58
+ false
59
+ end
60
+
52
61
  connection.create_websocket_transport.tap do |socket_deferrable|
53
62
  socket_deferrable.callback do |websocket_transport|
54
63
  subscribe_to_transport_events websocket_transport
55
64
  yield websocket_transport if block_given?
56
65
  end
57
66
  socket_deferrable.errback do |error|
67
+ next if setup_failed.call
58
68
  connection_opening_failed error
59
69
  end
60
70
  end
61
71
 
72
+ # The connection request timeout must be marginally higher than the REST request timeout to ensure
73
+ # any HTTP auth request failure due to timeout triggers before the connection timer kicks in
62
74
  logger.debug { "ConnectionManager: Setting up automatic connection timeout timer for #{realtime_request_timeout}s" }
63
75
  create_timeout_timer_whilst_in_state(:connecting, realtime_request_timeout) do
64
- connection_opening_failed Ably::Exceptions::ConnectionTimeout.new("Connection to Ably timed out after #{realtime_request_timeout}s", nil, 80014)
76
+ next if setup_failed.call
77
+ connection_opening_failed Ably::Exceptions::ConnectionTimeout.new("Connection to Ably timed out after #{realtime_request_timeout}s", nil, Ably::Exceptions::Codes::CONNECTION_TIMED_OUT)
65
78
  end
66
79
  end
67
80
 
@@ -72,7 +85,7 @@ module Ably::Realtime
72
85
  if error.kind_of?(Ably::Exceptions::BaseAblyException)
73
86
  # Authentication errors that indicate the authentication failure is terminal should move to the failed state
74
87
  if ([401, 403].include?(error.status) && !RESOLVABLE_ERROR_CODES.fetch(:token_expired).include?(error.code)) ||
75
- (error.code == Ably::Exceptions::INVALID_CLIENT_ID)
88
+ (error.code == Ably::Exceptions::Codes::INVALID_CLIENT_ID)
76
89
  connection.transition_state_machine :failed, reason: error
77
90
  return
78
91
  end
@@ -80,7 +93,12 @@ module Ably::Realtime
80
93
 
81
94
  logger.warn { "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}" }
82
95
  next_state = get_next_retry_state_info
83
- connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: Ably::Exceptions::ConnectionError.new("Connection failed: #{error.message}", nil, 80000, error)
96
+
97
+ if connection.state == next_state.fetch(:state)
98
+ logger.error { "ConnectionManager: Skipping next retry state after connection opening failed as already in state #{next_state}\n#{caller[0..20].join("\n")}" }
99
+ else
100
+ connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: Ably::Exceptions::ConnectionError.new("Connection failed: #{error.message}", nil, Ably::Exceptions::Codes::CONNECTION_FAILED, error)
101
+ end
84
102
  end
85
103
 
86
104
  # Called whenever a new connection is made
@@ -100,13 +118,11 @@ module Ably::Realtime
100
118
  resend_pending_message_ack_queue
101
119
  else
102
120
  logger.debug { "ConnectionManager: Connection was not resumed, old connection ID #{connection.id} has been updated with new connection ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
103
- connection.reset_client_serial
104
121
  nack_messages_on_all_channels protocol_message.error
105
122
  force_reattach_on_channels protocol_message.error
106
123
  end
107
124
  else
108
125
  logger.debug { "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
109
- connection.reset_client_serial
110
126
  end
111
127
 
112
128
  reattach_suspended_channels protocol_message.error
@@ -319,7 +335,7 @@ module Ably::Realtime
319
335
  @liveness_timer = EventMachine::Timer.new(connection.heartbeat_interval + 0.1) do
320
336
  if connection.connected? && (connection.time_since_connection_confirmed_alive? >= connection.heartbeat_interval)
321
337
  msg = "No activity seen from realtime in #{connection.heartbeat_interval}; assuming connection has dropped";
322
- error = Ably::Exceptions::ConnectionTimeout.new(msg, 80003, 408)
338
+ error = Ably::Exceptions::ConnectionTimeout.new(msg, Ably::Exceptions::Codes::DISCONNECTED, 408)
323
339
  connection.transition_state_machine! :disconnected, reason: error
324
340
  end
325
341
  end
@@ -492,9 +508,9 @@ module Ably::Realtime
492
508
  connection.transition_state_machine :closed
493
509
  elsif !connection.closed? && !connection.disconnected? && !connection.failed? && !connection.suspended?
494
510
  exception = if reason
495
- Ably::Exceptions::TransportClosed.new(reason, nil, 80003)
511
+ Ably::Exceptions::TransportClosed.new(reason, nil, Ably::Exceptions::Codes::DISCONNECTED)
496
512
  else
497
- Ably::Exceptions::TransportClosed.new('Transport disconnected unexpectedly', nil, 80003)
513
+ Ably::Exceptions::TransportClosed.new('Transport disconnected unexpectedly', nil, Ably::Exceptions::Codes::DISCONNECTED)
498
514
  end
499
515
  next_state = get_next_retry_state_info
500
516
  connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: exception
@@ -157,7 +157,7 @@ module Ably::Realtime
157
157
  logger.debug { "WebsocketTransport: Prot msg recv <=: #{action_name} - #{event_data}" }
158
158
 
159
159
  if protocol_message.invalid?
160
- error = Ably::Exceptions::ProtocolError.new("Invalid Protocol Message received: #{event_data}\nConnection moving to the failed state as the protocol is invalid and unsupported", 400, 80013)
160
+ error = Ably::Exceptions::ProtocolError.new("Invalid Protocol Message received: #{event_data}\nConnection moving to the failed state as the protocol is invalid and unsupported", 400, Ably::Exceptions::Codes::PROTOCOL_ERROR)
161
161
  logger.fatal { "WebsocketTransport: #{error.message}" }
162
162
  failed_protocol_message = Ably::Models::ProtocolMessage.new(
163
163
  action: Ably::Models::ProtocolMessage::ACTION.Error,
@@ -208,7 +208,7 @@ module Ably::Realtime
208
208
 
209
209
  def create_pub_sub_message_bus
210
210
  Ably::Util::PubSub.new(
211
- coerce_into: Proc.new do |event|
211
+ coerce_into: lambda do |event|
212
212
  raise KeyError, "Expected :protocol_message, :#{event} is disallowed" unless event == :protocol_message
213
213
  :protocol_message
214
214
  end
@@ -71,7 +71,7 @@ module Ably::Realtime
71
71
 
72
72
  ensure_channel_attached(deferrable) do
73
73
  if entering?
74
- once_or_if(STATE.Entered, else: proc { |args| deferrable_fail deferrable, *args }) do
74
+ once_or_if(STATE.Entered, else: lambda { |args| deferrable_fail deferrable, *args }) do
75
75
  deferrable_succeed deferrable, &success_block
76
76
  end
77
77
  else
@@ -132,7 +132,7 @@ module Ably::Realtime
132
132
 
133
133
  ensure_channel_attached(deferrable) do
134
134
  if leaving?
135
- once_or_if(STATE.Left, else: proc { |error|deferrable_fail deferrable, *args }) do
135
+ once_or_if(STATE.Left, else: lambda { |error|deferrable_fail deferrable, *args }) do
136
136
  deferrable_succeed deferrable, &success_block
137
137
  end
138
138
  else
@@ -233,7 +233,7 @@ module Ably::Realtime
233
233
  deferrable.fail Ably::Exceptions::InvalidState.new(
234
234
  'Presence state is out of sync as channel is SUSPENDED. Presence#get on a SUSPENDED channel is only supported with option wait_for_sync: false',
235
235
  nil,
236
- 91005
236
+ Ably::Exceptions::Codes::PRESENCE_STATE_IS_OUT_OF_SYNC
237
237
  )
238
238
  end
239
239
  return deferrable
@@ -310,7 +310,7 @@ module Ably::Realtime
310
310
  # @api private
311
311
  def __incoming_msgbus__
312
312
  @__incoming_msgbus__ ||= Ably::Util::PubSub.new(
313
- coerce_into: Proc.new { |event| Ably::Models::ProtocolMessage::ACTION(event) }
313
+ coerce_into: lambda { |event| Ably::Models::ProtocolMessage::ACTION(event) }
314
314
  )
315
315
  end
316
316
 
@@ -330,7 +330,7 @@ module Ably::Realtime
330
330
  def send_presence_protocol_message(presence_action, client_id, data)
331
331
  presence_message = create_presence_message(presence_action, client_id, data)
332
332
  unless presence_message.client_id
333
- raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, 91000)
333
+ raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_NO_CLIENTID)
334
334
  end
335
335
 
336
336
  protocol_message = {
@@ -437,13 +437,13 @@ module Ably::Realtime
437
437
 
438
438
  def attach_channel_then(deferrable)
439
439
  if channel.detached? || channel.failed?
440
- deferrable.fail Ably::Exceptions::InvalidState.new("Operation is not allowed when channel is in #{channel.state}", 400, 91001)
440
+ deferrable.fail Ably::Exceptions::InvalidState.new("Operation is not allowed when channel is in #{channel.state}", 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_INVALID_CHANNEL_STATE)
441
441
  else
442
442
  channel.unsafe_once(:attached, :detached, :failed) do |channel_state_change|
443
443
  if channel_state_change.current == :attached
444
444
  yield
445
445
  else
446
- deferrable.fail Ably::Exceptions::InvalidState.new("Operation failed as channel transitioned to #{channel_state_change.current}", 400, 91001)
446
+ deferrable.fail Ably::Exceptions::InvalidState.new("Operation failed as channel transitioned to #{channel_state_change.current}", 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_INVALID_CHANNEL_STATE)
447
447
  end
448
448
  end
449
449
  channel.attach
@@ -94,7 +94,7 @@ module Ably::Realtime
94
94
  wait_for_sync = options.fetch(:wait_for_sync, true)
95
95
  deferrable = Ably::Util::SafeDeferrable.new(logger)
96
96
 
97
- result_block = proc do
97
+ result_block = lambda do
98
98
  present_members.tap do |members|
99
99
  members.keep_if { |member| member.connection_id == options[:connection_id] } if options[:connection_id]
100
100
  members.keep_if { |member| member.client_id == options[:client_id] } if options[:client_id]
@@ -110,17 +110,17 @@ module Ably::Realtime
110
110
  # Must be defined before subsequent procs reference this callback
111
111
  reset_callbacks = nil
112
112
 
113
- in_sync_callback = proc do
114
- reset_callbacks
113
+ in_sync_callback = lambda do
114
+ reset_callbacks.call if reset_callbacks
115
115
  result_block.call
116
116
  end
117
117
 
118
- failed_callback = proc do |error|
119
- reset_callbacks
118
+ failed_callback = lambda do |error|
119
+ reset_callbacks.call if reset_callbacks
120
120
  deferrable.fail error
121
121
  end
122
122
 
123
- reset_callbacks = proc do
123
+ reset_callbacks = lambda do
124
124
  off(&in_sync_callback)
125
125
  off(&failed_callback)
126
126
  channel.off(&failed_callback)
@@ -275,7 +275,7 @@ module Ably::Realtime
275
275
  presence_message_client_id = presence_message.client_id || client.auth.client_id
276
276
  re_enter_error = Ably::Models::ErrorInfo.new(
277
277
  message: "unable to automatically re-enter presence channel for client_id '#{presence_message_client_id}'. Source error code #{error.code} and message '#{error.message}'",
278
- code: 91004
278
+ code: Ably::Exceptions::Codes::UNABLE_TO_AUTOMATICALLY_REENTER_PRESENCE_CHANNEL
279
279
  )
280
280
  channel.emit :update, Ably::Models::ChannelStateChange.new(
281
281
  current: channel.state,
@@ -312,14 +312,14 @@ module Ably::Realtime
312
312
  when Ably::Models::PresenceMessage::ACTION.Leave
313
313
  remove_presence_member presence_message
314
314
  else
315
- Ably::Exceptions::ProtocolError.new("Protocol error, unknown presence action #{presence_message.action}", 400, 80013)
315
+ Ably::Exceptions::ProtocolError.new("Protocol error, unknown presence action #{presence_message.action}", 400, Ably::Exceptions::Codes::PROTOCOL_ERROR)
316
316
  end
317
317
  end
318
318
 
319
319
  def ensure_presence_message_is_valid(presence_message)
320
320
  return true if presence_message.connection_id
321
321
 
322
- error = Ably::Exceptions::ProtocolError.new("Protocol error, presence message is missing connectionId", 400, 80013)
322
+ error = Ably::Exceptions::ProtocolError.new("Protocol error, presence message is missing connectionId", 400, Ably::Exceptions::Codes::PROTOCOL_ERROR)
323
323
  logger.error { "PresenceMap: On channel '#{channel.name}' error: #{error}" }
324
324
  end
325
325
 
@@ -0,0 +1,40 @@
1
+ require 'ably/realtime/push/admin'
2
+
3
+ module Ably
4
+ module Realtime
5
+ # Class providing push notification functionality
6
+ class Push
7
+ # @private
8
+ attr_reader :client
9
+
10
+ def initialize(client)
11
+ @client = client
12
+ end
13
+
14
+ # Admin features for push notifications like managing devices and channel subscriptions
15
+ # @return [Ably::Realtime::Push::Admin]
16
+ def admin
17
+ @admin ||= Admin.new(self)
18
+ end
19
+
20
+ # Activate this device for push notifications by registering with the push transport such as GCM/APNS
21
+ #
22
+ # @note This is unsupported in the Ruby library
23
+ def activate(*arg)
24
+ raise_unsupported
25
+ end
26
+
27
+ # Deactivate this device for push notifications by removing the registration with the push transport such as GCM/APNS
28
+ #
29
+ # @note This is unsupported in the Ruby library
30
+ def deactivate(*arg)
31
+ raise_unsupported
32
+ end
33
+
34
+ private
35
+ def raise_unsupported
36
+ raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable'
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ require 'ably/realtime/push/device_registrations'
2
+ require 'ably/realtime/push/channel_subscriptions'
3
+
4
+ module Ably::Realtime
5
+ class Push
6
+ # Class providing push notification administrative functionality
7
+ # for registering devices and attaching to channels etc.
8
+ class Admin
9
+ include Ably::Modules::AsyncWrapper
10
+ include Ably::Modules::Conversions
11
+
12
+ # @api private
13
+ attr_reader :client
14
+
15
+ # @api private
16
+ attr_reader :push
17
+
18
+ def initialize(push)
19
+ @push = push
20
+ @client = push.client
21
+ end
22
+
23
+ # (see Ably::Rest::Push#publish)
24
+ #
25
+ # @yield Block is invoked upon successful publish of the message
26
+ # @return [Ably::Util::SafeDeferrable]
27
+ #
28
+ def publish(recipient, data, &callback)
29
+ raise ArgumentError, "Expecting a Hash object for recipient, got #{recipient.class}" unless recipient.kind_of?(Hash)
30
+ raise ArgumentError, "Recipient data is empty. You must provide recipient details" if recipient.empty?
31
+ raise ArgumentError, "Expecting a Hash object for data, got #{data.class}" unless data.kind_of?(Hash)
32
+ raise ArgumentError, "Push data field is empty. You must provide attributes for the push notification" if data.empty?
33
+
34
+ async_wrap(callback) do
35
+ rest_push_admin.publish(recipient, data)
36
+ end
37
+ end
38
+
39
+ # Manage device registrations
40
+ # @return [Ably::Realtime::Push::DeviceRegistrations]
41
+ def device_registrations
42
+ @device_registrations ||= DeviceRegistrations.new(self)
43
+ end
44
+
45
+ # Manage channel subscriptions for devices or clients
46
+ # @return [Ably::Realtime::Push::ChannelSubscriptions]
47
+ def channel_subscriptions
48
+ @channel_subscriptions ||= ChannelSubscriptions.new(self)
49
+ end
50
+
51
+ private
52
+ def rest_push_admin
53
+ client.rest_client.push.admin
54
+ end
55
+
56
+ def logger
57
+ client.logger
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,108 @@
1
+ module Ably::Realtime
2
+ class Push
3
+ # Manage push notification channel subscriptions for devices or clients
4
+ class ChannelSubscriptions
5
+ include Ably::Modules::Conversions
6
+ include Ably::Modules::AsyncWrapper
7
+
8
+ # @api private
9
+ attr_reader :client
10
+
11
+ # @api private
12
+ attr_reader :admin
13
+
14
+ def initialize(admin)
15
+ @admin = admin
16
+ @client = admin.client
17
+ end
18
+
19
+ # (see Ably::Rest::Push::ChannelSubscriptions#list)
20
+ #
21
+ # @yield Block is invoked when request succeeds
22
+ # @return [Ably::Util::SafeDeferrable]
23
+ #
24
+ def list(params, &callback)
25
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
26
+
27
+ if (IdiomaticRubyWrapper(params).keys & [:channel, :client_id, :device_id]).length == 0
28
+ raise ArgumentError, "at least one channel, client_id or device_id filter param must be provided"
29
+ end
30
+
31
+ async_wrap(callback) do
32
+ rest_channel_subscriptions.list(params.merge(async_blocking_operations: true))
33
+ end
34
+ end
35
+
36
+ # (see Ably::Rest::Push::ChannelSubscriptions#list_channels)
37
+ #
38
+ # @yield Block is invoked when request succeeds
39
+ # @return [Ably::Util::SafeDeferrable]
40
+ #
41
+ def list_channels(params = {}, &callback)
42
+ params = {} if params.nil?
43
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
44
+
45
+ async_wrap(callback) do
46
+ rest_channel_subscriptions.list_channels(params.merge(async_blocking_operations: true))
47
+ end
48
+ end
49
+
50
+ # (see Ably::Rest::Push::ChannelSubscriptions#save)
51
+ #
52
+ # @yield Block is invoked when request succeeds
53
+ # @return [Ably::Util::SafeDeferrable]
54
+ #
55
+ def save(push_channel_subscription, &callback)
56
+ push_channel_subscription_object = PushChannelSubscription(push_channel_subscription)
57
+ raise ArgumentError, "Channel is required yet is empty" if push_channel_subscription_object.channel.to_s.empty?
58
+
59
+ async_wrap(callback) do
60
+ rest_channel_subscriptions.save(push_channel_subscription)
61
+ end
62
+ end
63
+
64
+ # (see Ably::Rest::Push::ChannelSubscriptions#remove)
65
+ #
66
+ # @yield Block is invoked when request succeeds
67
+ # @return [Ably::Util::SafeDeferrable]
68
+ #
69
+ def remove(push_channel_subscription, &callback)
70
+ push_channel_subscription_object = PushChannelSubscription(push_channel_subscription)
71
+ raise ArgumentError, "Channel is required yet is empty" if push_channel_subscription_object.channel.to_s.empty?
72
+ if push_channel_subscription_object.client_id.to_s.empty? && push_channel_subscription_object.device_id.to_s.empty?
73
+ raise ArgumentError, "Either client_id or device_id must be present"
74
+ end
75
+
76
+ async_wrap(callback) do
77
+ rest_channel_subscriptions.remove(push_channel_subscription)
78
+ end
79
+ end
80
+
81
+ # (see Ably::Rest::Push::ChannelSubscriptions#remove_where)
82
+ #
83
+ # @yield Block is invoked when request succeeds
84
+ # @return [Ably::Util::SafeDeferrable]
85
+ #
86
+ def remove_where(params, &callback)
87
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
88
+
89
+ if (IdiomaticRubyWrapper(params).keys & [:channel, :client_id, :device_id]).length == 0
90
+ raise ArgumentError, "at least one channel, client_id or device_id filter param must be provided"
91
+ end
92
+
93
+ async_wrap(callback) do
94
+ rest_channel_subscriptions.remove_where(params)
95
+ end
96
+ end
97
+
98
+ private
99
+ def rest_channel_subscriptions
100
+ client.rest_client.push.admin.channel_subscriptions
101
+ end
102
+
103
+ def logger
104
+ client.logger
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,105 @@
1
+ module Ably::Realtime
2
+ class Push
3
+ # Manage device registrations for push notifications
4
+ class DeviceRegistrations
5
+ include Ably::Modules::Conversions
6
+ include Ably::Modules::AsyncWrapper
7
+
8
+ # @api private
9
+ attr_reader :client
10
+
11
+ # @api private
12
+ attr_reader :admin
13
+
14
+ def initialize(admin)
15
+ @admin = admin
16
+ @client = admin.client
17
+ end
18
+
19
+ # (see Ably::Rest::Push::DeviceRegistrations#get)
20
+ #
21
+ # @yield Block is invoked when request succeeds
22
+ # @return [Ably::Util::SafeDeferrable]
23
+ #
24
+ def get(device_id, &callback)
25
+ device_id = device_id.id if device_id.kind_of?(Ably::Models::DeviceDetails)
26
+ raise ArgumentError, "device_id must be a string or DeviceDetails object" unless device_id.kind_of?(String)
27
+
28
+ async_wrap(callback) do
29
+ rest_device_registrations.get(device_id)
30
+ end
31
+ end
32
+
33
+ # (see Ably::Rest::Push::DeviceRegistrations#list)
34
+ #
35
+ # @yield Block is invoked when request succeeds
36
+ # @return [Ably::Util::SafeDeferrable]
37
+ #
38
+ def list(params = {}, &callback)
39
+ params = {} if params.nil?
40
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
41
+ raise ArgumentError, "device_id filter cannot be specified alongside a client_id filter. Use one or the other" if params[:client_id] && params[:device_id]
42
+
43
+ async_wrap(callback) do
44
+ rest_device_registrations.list(params.merge(async_blocking_operations: true))
45
+ end
46
+ end
47
+
48
+ # (see Ably::Rest::Push::DeviceRegistrations#save)
49
+ #
50
+ # @yield Block is invoked when request succeeds
51
+ # @return [Ably::Util::SafeDeferrable]
52
+ #
53
+ def save(device, &callback)
54
+ device_details = DeviceDetails(device)
55
+ raise ArgumentError, "Device ID is required yet is empty" if device_details.id.nil? || device_details == ''
56
+
57
+ async_wrap(callback) do
58
+ rest_device_registrations.save(device_details)
59
+ end
60
+ end
61
+
62
+ # (see Ably::Rest::Push::DeviceRegistrations#remove)
63
+ #
64
+ # @yield Block is invoked when request succeeds
65
+ # @return [Ably::Util::SafeDeferrable]
66
+ #
67
+ def remove(device_id, &callback)
68
+ device_id = device_id.id if device_id.kind_of?(Ably::Models::DeviceDetails)
69
+ raise ArgumentError, "device_id must be a string or DeviceDetails object" unless device_id.kind_of?(String)
70
+
71
+ async_wrap(callback) do
72
+ rest_device_registrations.remove(device_id)
73
+ end
74
+ end
75
+
76
+ # (see Ably::Rest::Push::DeviceRegistrations#remove_where)
77
+ #
78
+ # @yield Block is invoked when request succeeds
79
+ # @return [Ably::Util::SafeDeferrable]
80
+ #
81
+ def remove_where(params = {}, &callback)
82
+ filter = if params.kind_of?(Ably::Models::DeviceDetails)
83
+ { 'deviceId' => params.id }
84
+ else
85
+ raise ArgumentError, "params must be a Hash" unless params.kind_of?(Hash)
86
+ raise ArgumentError, "device_id filter cannot be specified alongside a client_id filter. Use one or the other" if params[:client_id] && params[:device_id]
87
+ IdiomaticRubyWrapper(params).as_json
88
+ end
89
+
90
+ async_wrap(callback) do
91
+ rest_device_registrations.remove_where(filter)
92
+ end
93
+ end
94
+
95
+ private
96
+ def rest_device_registrations
97
+ client.rest_client.push.admin.device_registrations
98
+ end
99
+
100
+ def logger
101
+ client.logger
102
+ end
103
+ end
104
+ end
105
+ end