diff --git a/lib/ably/auth.rb b/lib/ably/auth.rb index 1e6e03fa6..0befcb27f 100644 --- a/lib/ably/auth.rb +++ b/lib/ably/auth.rb @@ -414,13 +414,20 @@ def auth_header # # @return [Hash] headers def extra_auth_headers - if client_id && using_basic_auth? - { 'X-Ably-ClientId' => Base64.urlsafe_encode64(client_id) } + if client_id_for_request + { 'X-Ably-ClientId' => Base64.urlsafe_encode64(client_id_for_request) } else {} end end + # ClientId that needs to be included with every rest/realtime request + # spec - RSA7e + # @return string + def client_id_for_request + options[:client_id] if options[:client_id] && using_basic_auth? + end + # Auth params used in URI endpoint for Realtime connections # Will reauthorize implicitly if required and capable # @@ -482,15 +489,16 @@ def can_assume_client_id?(assumed_client_id) # # @api private def configure_client_id(new_client_id) - # If new client ID from Ably is a wildcard, but preconfigured clientId is set, then keep the existing clientId - if has_client_id? && new_client_id == '*' - @client_id_validated = true - return - end - - # If client_id is defined and not a wildcard, prevent it changing, this is not supported - if client_id && client_id != '*' && new_client_id != client_id - raise Ably::Exceptions::IncompatibleClientId.new("Client ID is immutable once configured for a client. Client ID cannot be changed to '#{new_client_id}'") + if has_client_id? + # If new client ID from Ably is a wildcard, but preconfigured clientId is set, then keep the existing clientId + if new_client_id == "*" + @client_id_validated = true + return + end + # If client_id is defined and not a wildcard, prevent it changing, this is not supported + if new_client_id != client_id + raise Ably::Exceptions::IncompatibleClientId.new("Client ID is immutable once configured for a client. Client ID cannot be changed to '#{new_client_id}'") + end end @client_id_validated = true @client_id = new_client_id diff --git a/lib/ably/realtime/auth.rb b/lib/ably/realtime/auth.rb index 4d12f87a1..c1fc59831 100644 --- a/lib/ably/realtime/auth.rb +++ b/lib/ably/realtime/auth.rb @@ -226,6 +226,10 @@ def auth_header_sync auth_sync.auth_header end + def client_id_for_request_sync + auth_sync.client_id_for_request + end + # Auth params used in URI endpoint for Realtime connections # Will reauthorize implicitly if required and capable # diff --git a/lib/ably/realtime/channel.rb b/lib/ably/realtime/channel.rb index 6543567d2..af1772c0f 100644 --- a/lib/ably/realtime/channel.rb +++ b/lib/ably/realtime/channel.rb @@ -364,7 +364,7 @@ def __incoming_msgbus__ # @return [Ably::Models::ChannelOptions] def set_options(channel_options) @options = Ably::Models::ChannelOptions(channel_options) - + # RTL4i manager.request_reattach if (need_reattach? and connection.state?(:connected)) end alias options= set_options diff --git a/lib/ably/realtime/channel/channel_manager.rb b/lib/ably/realtime/channel/channel_manager.rb index 9bceb69d1..62f6b1a98 100644 --- a/lib/ably/realtime/channel/channel_manager.rb +++ b/lib/ably/realtime/channel/channel_manager.rb @@ -18,7 +18,7 @@ def initialize(channel, connection) def attach if can_transition_to?(:attached) connect_if_connection_initialized - send_attach_protocol_message if connection.state?(:connected) + send_attach_protocol_message if connection.state?(:connected) # RTL4i end end @@ -49,8 +49,7 @@ def log_channel_error(error) end # Request channel to be reattached by sending an attach protocol message - # @param reason - # @option options [Ably::Models::ErrorInfo] :reason + # @param [Ably::Models::ErrorInfo] reason def request_reattach(reason = nil) channel.set_channel_error_reason(reason) if reason channel.transition_state_machine! :attaching, reason: reason unless channel.attaching? @@ -168,6 +167,12 @@ def start_attach_from_suspended_timer end end + # RTL13c + def notify_state_change + @pending_state_change_timer.cancel if @pending_state_change_timer + @pending_state_change_timer = nil + end + private attr_reader :pending_state_change_timer @@ -211,13 +216,13 @@ def send_attach_protocol_message state_at_time_of_request = channel.state attach_action = Ably::Models::ProtocolMessage::ACTION.Attach + # RTL4f @pending_state_change_timer = EventMachine::Timer.new(realtime_request_timeout) do if channel.state == state_at_time_of_request error = Ably::Models::ErrorInfo.new(code: Ably::Exceptions::Codes::CHANNEL_OPERATION_FAILED_NO_RESPONSE_FROM_SERVER, message: "Channel #{attach_action} operation failed (timed out)") channel.transition_state_machine :suspended, reason: error # return to suspended state if failed end end - # Shouldn't queue attach message as per RTL4i, so message is added top of the queue # to be sent immediately while processing next message connection.send_protocol_message_immediately( @@ -255,15 +260,11 @@ def send_detach_protocol_message(previous_state) channel: channel.name ) end + resend_if_disconnected_and_connected.call send_detach_message.call end - def notify_state_change - @pending_state_change_timer.cancel if @pending_state_change_timer - @pending_state_change_timer = nil - end - def logger connection.logger end diff --git a/lib/ably/realtime/channel/channel_state_machine.rb b/lib/ably/realtime/channel/channel_state_machine.rb index a6f675167..e3cedd18c 100644 --- a/lib/ably/realtime/channel/channel_state_machine.rb +++ b/lib/ably/realtime/channel/channel_state_machine.rb @@ -29,7 +29,7 @@ class ChannelStateMachine transition :from => :failed, :to => [:attaching, :initialized] after_transition do |channel, transition| - channel.manager.send(:notify_state_change) + channel.manager.notify_state_change # RTL13c channel.synchronize_state_with_statemachine end diff --git a/lib/ably/realtime/connection.rb b/lib/ably/realtime/connection.rb index ab867501e..fbbf815ef 100644 --- a/lib/ably/realtime/connection.rb +++ b/lib/ably/realtime/connection.rb @@ -431,28 +431,27 @@ def logger # @api private def send_protocol_message(protocol_message) add_message_serial_if_ack_required_to(protocol_message) do - Ably::Models::ProtocolMessage.new(protocol_message, logger: logger).tap do |message| - add_message_to_outgoing_queue message - notify_message_dispatcher_of_new_message message - logger.debug { "Connection: Prot msg queued =>: #{message.action} #{message}" } - end + message = Ably::Models::ProtocolMessage.new(protocol_message, logger: logger) + add_message_to_outgoing_queue(message) + notify_message_dispatcher_of_new_message message end end def send_protocol_message_immediately(protocol_message) - Ably::Models::ProtocolMessage.new(protocol_message, logger: logger).tap do |message| - add_message_to_outgoing_queue(message, true) - notify_message_dispatcher_of_new_message message - logger.debug { "Connection: Prot msg pushed at the top =>: #{message.action} #{message}" } - end + message = Ably::Models::ProtocolMessage.new(protocol_message, logger: logger) + add_message_to_outgoing_queue(message, true) + notify_message_dispatcher_of_new_message message end # @api private def add_message_to_outgoing_queue(protocol_message, send_immediately = false) if send_immediately + # Adding msg at the top of the queue to get processed immediately while connection is CONNECTED __outgoing_message_queue__.prepend(protocol_message) + logger.debug { "Connection: protocol msg pushed at the top =>: #{message.action} #{message}" } else __outgoing_message_queue__ << protocol_message + logger.debug { "Connection: protocol msg queued =>: #{message.action} #{message}" } end end @@ -481,8 +480,8 @@ def create_websocket_transport else 'false' end - - url_params['clientId'] = client.auth.client_id if client.auth.has_client_id? + # RSA7e1 + url_params['clientId'] = client.auth.client_id_for_request_sync if client.auth.client_id_for_request_sync url_params.merge!(client.transport_params) if !key.nil_or_empty? and connection_state_available? diff --git a/lib/ably/realtime/presence/members_map.rb b/lib/ably/realtime/presence/members_map.rb index 5d3c46c29..8183db526 100644 --- a/lib/ably/realtime/presence/members_map.rb +++ b/lib/ably/realtime/presence/members_map.rb @@ -154,13 +154,11 @@ def local_members def enter_local_members local_members.values.each do |member| - local_client_id = member.client_id || client.auth.client_id - logger.debug { "#{self.class.name}: Manually re-entering local presence member, client ID: #{local_client_id} with data: #{member.data}" } - presence.enter_client_with_id(member.id, local_client_id, member.data).tap do |deferrable| + logger.debug { "#{self.class.name}: Manually re-entering local presence member, client ID: #{member.client_id} with data: #{member.data}" } + presence.enter_client_with_id(member.id, member.client_id, member.data).tap do |deferrable| deferrable.errback do |error| - presence_message_client_id = member.client_id || client.auth.client_id re_enter_error = Ably::Models::ErrorInfo.new( - message: "unable to automatically re-enter presence channel for client_id '#{presence_message_client_id}'. Source error code #{error.code} and message '#{error.message}'", + message: "unable to automatically re-enter presence channel for client_id '#{member.client_id}'. Source error code #{error.code} and message '#{error.message}'", code: Ably::Exceptions::Codes::UNABLE_TO_AUTOMATICALLY_REENTER_PRESENCE_CHANNEL ) channel.emit :update, Ably::Models::ChannelStateChange.new( diff --git a/lib/ably/realtime/push.rb b/lib/ably/realtime/push.rb index b3ac400a0..04d7e0381 100644 --- a/lib/ably/realtime/push.rb +++ b/lib/ably/realtime/push.rb @@ -20,33 +20,6 @@ def initialize(client) def admin @admin ||= Admin.new(self) end - - # Activates the device for push notifications with FCM or APNS, obtaining a unique identifier from them. - # Subsequently registers the device with Ably and stores the deviceIdentityToken in local storage. - # - # @spec RSH2a - # - # @note This is unsupported in the Ruby library - # - def activate(*arg) - raise_unsupported - end - - # Deactivates the device from receiving push notifications with Ably and FCM or APNS. - # - # @spec RSH2b - # - # @note This is unsupported in the Ruby library - # - def deactivate(*arg) - raise_unsupported - end - - private - - def raise_unsupported - raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable' - end end end end diff --git a/lib/ably/rest/client.rb b/lib/ably/rest/client.rb index 7aa34e3cb..3dbda2dfb 100644 --- a/lib/ably/rest/client.rb +++ b/lib/ably/rest/client.rb @@ -597,7 +597,7 @@ def send_request(method, path, params, options) end unless options[:send_auth_header] == false request.headers[:authorization] = auth.auth_header - + # RSA7e2 options[:headers].to_h.merge(auth.extra_auth_headers).map do |key, val| request.headers[key] = val end diff --git a/lib/ably/rest/push.rb b/lib/ably/rest/push.rb index a03d0400c..7d33bb3a9 100644 --- a/lib/ably/rest/push.rb +++ b/lib/ably/rest/push.rb @@ -20,25 +20,6 @@ def initialize(client) def admin @admin ||= Admin.new(self) end - - # Activate this device for push notifications by registering with the push transport such as GCM/APNS - # - # @note This is unsupported in the Ruby library - def activate(*arg) - raise_unsupported - end - - # Deactivate this device for push notifications by removing the registration with the push transport such as GCM/APNS - # - # @note This is unsupported in the Ruby library - def deactivate(*arg) - raise_unsupported - end - - private - def raise_unsupported - raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable' - end end end end diff --git a/lib/ably/rest/push/admin.rb b/lib/ably/rest/push/admin.rb index f6dc7cb6f..fcc177ea7 100644 --- a/lib/ably/rest/push/admin.rb +++ b/lib/ably/rest/push/admin.rb @@ -21,7 +21,7 @@ def initialize(push) # Publish a push message directly to a single recipient # - # @param recipient [Hash] A recipient device, client_id or raw APNS/GCM target. Refer to push documentation + # @param recipient [Hash] A recipient device, client_id or raw APNS/FCM/web target. Refer to push documentation # @param data [Hash] The notification payload data and fields. Refer to push documentation # # @return [void] diff --git a/spec/acceptance/realtime/channel_history_spec.rb b/spec/acceptance/realtime/channel_history_spec.rb index 5052de186..7e8f55d36 100644 --- a/spec/acceptance/realtime/channel_history_spec.rb +++ b/spec/acceptance/realtime/channel_history_spec.rb @@ -184,26 +184,22 @@ def ensure_message_history_direction_and_paging_is_correct(direction) end context 'when channel receives update event after an attachment' do + old_attach_serial = "" + new_attach_serial = "xxxx-xxxx-1" before do channel.on(:attached) do - channel.publish(event, message_after_attach) do - subsequent_serial = channel.properties.attach_serial.dup.tap { |serial| serial[-1] = '1' } - attached_message = Ably::Models::ProtocolMessage.new(action: 11, channel: channel_name, flags: 0, channel_serial: subsequent_serial) - client.connection.__incoming_protocol_msgbus__.publish :protocol_message, attached_message - end + old_attach_serial = channel.properties.attach_serial + attached_message = Ably::Models::ProtocolMessage.new(action: 11, channel: channel_name, flags: 0, channel_serial: new_attach_serial) + client.connection.__incoming_protocol_msgbus__.publish :protocol_message, attached_message end end - xit 'updates attach_serial' do - rest_channel.publish event, message_before_attach - + it 'updates attach_serial' do channel.on(:update) do - channel.history(until_attach: true) do |messages| - expect(messages.items.count).to eql(2) - stop_reactor - end + expect(channel.properties.attach_serial).not_to eq(old_attach_serial) + expect(channel.properties.attach_serial).to eq(new_attach_serial) + stop_reactor end - channel.attach end end diff --git a/spec/acceptance/realtime/push_spec.rb b/spec/acceptance/realtime/push_spec.rb deleted file mode 100644 index f5abb4182..000000000 --- a/spec/acceptance/realtime/push_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# encoding: utf-8 -require 'spec_helper' - -describe Ably::Realtime::Push, :event_machine do - vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol} } - let(:client_options) { default_options } - let(:client) do - Ably::Realtime::Client.new(client_options) - end - subject { client.push } - - describe '#activate' do - it 'raises an unsupported exception' do - expect { subject.activate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported) - stop_reactor - end - end - - describe '#deactivate' do - it 'raises an unsupported exception' do - expect { subject.deactivate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported) - stop_reactor - end - end - end -end diff --git a/spec/acceptance/rest/push_admin_spec.rb b/spec/acceptance/rest/push_admin_spec.rb index ea0d4ddfa..a5e1433f0 100644 --- a/spec/acceptance/rest/push_admin_spec.rb +++ b/spec/acceptance/rest/push_admin_spec.rb @@ -181,7 +181,7 @@ client_id: client_id, push: { recipient: { - transport_type: 'gcm', + transport_type: 'fcm', registration_token: 'secret_token', } } @@ -250,7 +250,7 @@ client_id: client_id, push: { recipient: { - transport_type: 'gcm', + transport_type: 'fcm', registration_token: 'secret_token', } } @@ -268,7 +268,7 @@ expect(device).to be_a(Ably::Models::DeviceDetails) expect(device.platform).to eql('ios') expect(device.client_id).to eql(client_id) - expect(device.push.recipient.fetch(:transport_type)).to eql('gcm') + expect(device.push.recipient.fetch(:transport_type)).to eql('fcm') end it 'returns a DeviceDetails object if a DeviceDetails object is provided' do @@ -276,7 +276,7 @@ expect(device).to be_a(Ably::Models::DeviceDetails) expect(device.platform).to eql('ios') expect(device.client_id).to eql(client_id) - expect(device.push.recipient.fetch(:transport_type)).to eql('gcm') + expect(device.push.recipient.fetch(:transport_type)).to eql('fcm') end it 'raises a ResourceMissing exception if device ID does not exist' do @@ -350,14 +350,14 @@ expect(device_retrieved.push.recipient['foo_bar']).to eql('string') end - context 'with GCM target' do + context 'with FCM target' do let(:device_token) { random_str } it 'saves the associated DevicePushDetails' do subject.save(device_details.merge( push: { recipient: { - transport_type: 'gcm', + transport_type: 'fcm', registrationToken: device_token } } @@ -365,7 +365,7 @@ device_retrieved = subject.get(device_details.fetch(:id)) - expect(device_retrieved.push.recipient.fetch('transportType')).to eql('gcm') + expect(device_retrieved.push.recipient.fetch('transportType')).to eql('fcm') expect(device_retrieved.push.recipient[:registration_token]).to eql(device_token) end end @@ -467,7 +467,7 @@ client_id: client_id, push: { recipient: { - transport_type: 'gcm', + transport_type: 'fcm', registrationToken: 'secret_token', } } @@ -481,7 +481,7 @@ client_id: client_id, push: { recipient: { - transport_type: 'gcm', + transport_type: 'fcm', registration_token: 'secret_token', } } @@ -530,7 +530,7 @@ client_id: client_id, push: { recipient: { - transport_type: 'gcm', + transport_type: 'fcm', registration_token: 'secret_token', } } @@ -544,7 +544,7 @@ client_id: client_id, push: { recipient: { - transport_type: 'gcm', + transport_type: 'fcm', registration_token: 'secret_token', } } @@ -585,7 +585,7 @@ client_id: client_id, push: { recipient: { - transport_type: 'gcm', + transport_type: 'fcm', registration_token: 'secret_token', } } diff --git a/spec/acceptance/rest/push_spec.rb b/spec/acceptance/rest/push_spec.rb deleted file mode 100644 index cbb9c5e8f..000000000 --- a/spec/acceptance/rest/push_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# encoding: utf-8 -require 'spec_helper' - -describe Ably::Rest::Push do - vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol} } - let(:client_options) { default_options } - let(:client) do - Ably::Rest::Client.new(client_options) - end - subject { client.push } - - describe '#activate' do - it 'raises an unsupported exception' do - expect { subject.activate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported) - end - end - - describe '#deactivate' do - it 'raises an unsupported exception' do - expect { subject.deactivate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported) - end - end - end -end