require_relative 'apn_connection' require 'houston' module NotifyUser class Apns < Push NO_ERROR = -42 INVALID_TOKEN_ERROR = 8 attr_accessor :push_options, :apn_connection def initialize(notifications, devices, options) super(notifications, devices, options) @push_options = setup_options @devices = devices end def push APNConnection::POOL.with do |apn_conn| @apn_connection = apn_conn send_notifications end end private attr_accessor :devices def connection apn_connection.connection end def reset_connection apn_connection.reset end def setup_options space_allowance = PAYLOAD_LIMIT - used_space mobile_message = '' if @notification.parent_id parent = @notification.class.find(@notification.parent_id) mobile_message = parent.mobile_message(space_allowance) else mobile_message = @notification.mobile_message(space_allowance) end mobile_message.gsub!('\n', "\n") push_options = { alert: mobile_message, badge: @notification.count_for_target, category: @notification.params[:category] || @notification.type, custom_data: @notification.params, sound: @options[:sound] || 'default' } if @options[:silent] push_options.merge!({ alert: '', sound: '', content_available: true }).delete(:badge) end push_options end def valid?(payload) payload.to_json.bytesize <= PAYLOAD_LIMIT end def send_notifications connection.open if connection.closed? Rails.logger.info "PAYLOAD" Rails.logger.info "----" Rails.logger.info "#{@push_options}" unless valid?(@push_options) Rails.logger.info "Error: Payload exceeds size limit." end devices.each_with_index do |device, index| notification = ::Houston::Notification.new(@push_options.dup.merge({ token: device.token, id: index })) connection.write(notification.message) end error_index = io_errors if error_index == NO_ERROR return true else # Resend all notifications after the once that produced the error: send_notifications end rescue OpenSSL::SSL::SSLError, Errno::EPIPE, Errno::ETIMEDOUT => e Rails.logger.error "[##{connection.object_id}] Exception occurred: #{e.inspect}." reset_connection Rails.logger.debug "[##{connection.object_id}] Socket reestablished." retry end def io_errors error_index = NO_ERROR ssl = connection.ssl Rails.logger.info "READING ERRORS" Rails.logger.info "----" read_socket, write_socket = IO.select([ssl], [], [ssl], 1) Rails.logger.info "#{ssl}" if (read_socket && read_socket[0]) error = connection.read(6) Rails.logger.info "#{error}" if error command, status, error_index = error.unpack("ccN") Rails.logger.info "Error: #{status} with id: #{error_index}." # Remove all the devices prior to the error (we assume they were successful), and close the current connection: if error_index != NO_ERROR device = devices.at(error_index) # If we encounter the Invalid Token error from APNS, just remove the device: if status == INVALID_TOKEN_ERROR Rails.logger.info "Invalid token encountered, removing device. Token: #{device.token}." device.destroy end devices.slice!(0..error_index) reset_connection end end end return error_index end end end