Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for apple feedback service. #12

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/em-apn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
require "yajl"
require "logger"
require "em-apn/client"
require "em-apn/feedback_connection"
require 'em-apn/failed_delivery_attempt'
require "em-apn/connection"
require "em-apn/notification"
require "em-apn/log_message"
Expand Down
20 changes: 20 additions & 0 deletions lib/em-apn/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ class Client
SANDBOX_GATEWAY = "gateway.sandbox.push.apple.com"
PRODUCTION_GATEWAY = "gateway.push.apple.com"
PORT = 2195
SANDBOX_FEEDBACK_GATEWAY = "feedback.sandbox.push.apple.com"
PRODUCTION_FEEDBACK_GATEWAY = "feedback.push.apple.com"
FEEDBACK_PORT = 2196


attr_reader :gateway, :port, :key, :cert, :connection, :error_callback, :close_callback, :open_callback
attr_reader :feedback_connection, :feedback_gateway, :feedback_port, :feedback_callback

# A convenience method for creating and connecting.
def self.connect(options = {})
new(options).tap do |client|
client.connect
client.connect_feedback
end
end

Expand All @@ -24,13 +30,23 @@ def initialize(options = {})
@gateway = options[:gateway] || ENV["APN_GATEWAY"]
@gateway ||= (ENV["APN_ENV"] == "production") ? PRODUCTION_GATEWAY : SANDBOX_GATEWAY


@feedback_gateway = options[:feedback_gateway] || ENV["APN_FEEDBACK_GATEWAY"]
@feedback_gateway ||= (ENV["APN_ENV"] == "production") ? PRODUCTION_FEEDBACK_GATEWAY : SANDBOX_FEEDBACK_GATEWAY
@feedback_port = options[:feedback_port] || FEEDBACK_PORT

@connection = nil
@feedback_connection = nil
end

def connect
@connection = EM.connect(gateway, port, Connection, self)
end

def connect_feedback
@feedback_connection = EM.connect(feedback_gateway, feedback_port, FeedbackConnection, self)
end

def deliver(notification)
notification.validate!
connect if connection.nil? || connection.disconnected?
Expand All @@ -50,6 +66,10 @@ def on_open(&block)
@open_callback = block
end

def on_feedback(&block)
@feedback_callback = block
end

def log(notification)
EM::APN.logger.info("TOKEN=#{notification.token} PAYLOAD=#{notification.payload.inspect}")
end
Expand Down
18 changes: 18 additions & 0 deletions lib/em-apn/failed_delivery_attempt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module EventMachine
module APN
class FailedDeliveryAttempt
LENGTH = 38

attr_accessor :timestamp, :device_token

def initialize(binary_tuple)
# N => 4 byte timestamp
# n => 2 byte token_length
# H64 => 32 byte device_token
seconds, _, @device_token = binary_tuple.unpack('NnH64')
raise ArgumentError('invalid format') unless seconds && @device_token
@timestamp = Time.at(seconds)
end
end
end
end
43 changes: 43 additions & 0 deletions lib/em-apn/feedback_connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module EventMachine
module APN
class FeedbackConnection < EM::Connection
attr_reader :client

def initialize(*args)
super
@client = args.last
@disconnected = false
end

def disconnected?
@disconnected
end

def post_init
start_tls(
:private_key_file => client.key,
:cert_chain_file => client.cert,
:verify_peer => false
)
end

def connection_completed
EM::APN.logger.info("Feedback connection completed")
end

def receive_data(data)
attempt = FailedDeliveryAttempt.new(data)
EM::APN.logger.warn(attempt.to_s)

if client.feedback_callback
client.feedback_callback.call(attempt)
end
end

def unbind
@disconnected = true
EM::APN.logger.info("Feedback connection closed")
end
end
end
end
41 changes: 41 additions & 0 deletions spec/em-apn/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def new_client(*args)
it "creates a new client without a connection" do
client = EM::APN::Client.new
client.connection.should be_nil
client.feedback_connection.should be_nil
end

context "configuring the gateway" do
Expand Down Expand Up @@ -191,4 +192,44 @@ def new_client(*args)
called.should be_true
end
end

describe "#connect_feedback" do
it "creates a connection to the feedback service" do
client = EM::APN::Client.new
client.feedback_connection.should be_nil

EM.run_block { client.connect_feedback }
client.feedback_connection.should be_an_instance_of(EM::APN::FeedbackConnection)
end

it "passes the client to the new connection" do
client = EM::APN::Client.new
connection = double(EM::APN::FeedbackConnection).as_null_object

EM::APN::FeedbackConnection.should_receive(:new).with(instance_of(Fixnum), client).and_return(connection)
EM.run_block { client.connect_feedback }
end
end

describe "#on_feedback" do
it "sets a callback that is invoked when we receive data from Apple" do
failed_attempt = nil


timestamp = Time.utc(1995, 12, 21)
device_token = 'fe15a27d5df3c34778defb1f4f3980265cc52c0c047682223be59fb68500a9a2'
tuple = [timestamp.to_i, 32, device_token].pack('NnH64')

EM.run_block do
client = EM::APN::Client.new
client.connect_feedback
client.on_feedback { |data | failed_attempt = data }
client.feedback_connection.receive_data(tuple)
end

failed_attempt.should be_an_instance_of(EM::APN::FailedDeliveryAttempt)
failed_attempt.device_token.should == device_token
failed_attempt.timestamp.should == timestamp
end
end
end