Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Enhanced binary message format is implemented. Added handling response from APNS server in case of connection gets aborted from APNS side. #49

Open
wants to merge 3 commits into from

1 participant

@greenhat

Implemented the enhanced binary message needed to send to Apple in order to have the ability to retrieve error description from Apple server in case of connection was cancelled. ( http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html )

Denys Zadoro... added some commits
Denys Zadorozhnyi wrapped string compare for binary data with MD5 checksum compare. Reo…
…rdered elements in JSON fixture.
acbf369
Denys Zadorozhnyi implemented the enhanced binary message needed to send to Apple in or…
…der to have the ability to retrieve error description from Apple server in case of connection was cancelled. Default notification expiration is set to 30 days. ( http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html )
41ff543
Denys Zadorozhnyi added new specs for enhanced binary message format; fixed old specs. 7d5cf71
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 17, 2012
  1. wrapped string compare for binary data with MD5 checksum compare. Reo…

    Denys Zadorozhnyi authored
    …rdered elements in JSON fixture.
  2. implemented the enhanced binary message needed to send to Apple in or…

    Denys Zadorozhnyi authored
    …der to have the ability to retrieve error description from Apple server in case of connection was cancelled. Default notification expiration is set to 30 days. ( http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html )
This page is out of date. Refresh to see the latest.
View
2  Gemfile.lock
@@ -61,7 +61,7 @@ GEM
sprockets (2.1.2)
hike (~> 1.2)
rack (~> 1.0)
- tilt (!= 1.3.0, ~> 1.1)
+ tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.5)
tilt (1.3.3)
tzinfo (0.3.31)
View
1  README.textile
@@ -157,6 +157,7 @@ Then customize the config/configatron files that were generated to your settings
configatron.apn.port # => 2195
configatron.apn.host # => 'gateway.sandbox.push.apple.com'
configatron.apn.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_development.pem')
+ configatron.apn.notification_expiration_seconds # => (86400*30)
# production (delivery):
configatron.apn.host # => 'gateway.push.apple.com'
View
3  lib/apn_on_rails/apn_on_rails.rb
@@ -18,6 +18,9 @@
configatron.apn.feedback.set_default(:passphrase, configatron.apn.passphrase)
configatron.apn.feedback.set_default(:port, 2196)
+# default expiration time for notification is 10 days
+configatron.apn.set_default(:notification_expiration_seconds, (86400*30))
+
if rails_env == 'production'
configatron.apn.set_default(:host, 'gateway.push.apple.com')
configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_production.pem'))
View
34 lib/apn_on_rails/app/models/apn/app.rb
@@ -37,6 +37,17 @@ def self.send_notifications
end
end
+ def self.response_from_apns(connection)
+ timeout = 5
+ if IO.select([connection], nil, nil, timeout)
+ buf = connection.read(6)
+ if buf
+ command, error_code, notification_id = buf.unpack('CCN')
+ [error_code, notification_id]
+ end
+ end
+ end
+
def self.send_notifications_for_cert(the_cert, app_id)
# unless self.unsent_notifications.nil? || self.unsent_notifications.empty?
if (app_id == nil)
@@ -48,9 +59,26 @@ def self.send_notifications_for_cert(the_cert, app_id)
APN::Connection.open_for_delivery({:cert => the_cert}) do |conn, sock|
APN::Device.find_each(:conditions => conditions) do |dev|
dev.unsent_notifications.each do |noty|
- conn.write(noty.message_for_sending)
- noty.sent_at = Time.now
- noty.save
+ begin
+ conn.write(noty.enhanced_message_for_sending)
+ noty.sent_at = Time.now
+ noty.save
+ rescue Exception => e
+ if e.message == "Broken pipe"
+ #Write failed (disconnected). Read response.
+ error_code, notif_id = response_from_apns(conn)
+ if error_code == 8
+ failed_notification = APN::Notification.find(notif_id)
+ unless failed_notification.nil?
+ unless failed_notification.device.nil?
+ APN::Device.delete(failed_notification.device.id)
+ # retry sending notifications after invalid token was deleted
+ send_notifications_for_cert(the_cert, app_id)
+ end
+ end
+ end
+ end
+ end
end
end
end
View
18 lib/apn_on_rails/app/models/apn/notification.rb
@@ -84,7 +84,23 @@ def message_for_sending
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
message
end
-
+
+ def encoded_expiry_time(seconds_to_expire)
+ [Time.now.to_i + seconds_to_expire].pack('N')
+ end
+
+ # Creates the enhanced binary message needed to send to Apple in order to have the ability to
+ # retrieve error description from Apple server in case of connection was cancelled.
+ # http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html
+ # Default expiry time is 10 days.
+ def enhanced_message_for_sending (seconds_to_expire = configatron.apn.notification_expiration_seconds)
+ json = to_apple_json
+ encoded_time = self.encoded_expiry_time seconds_to_expire
+ message = "\1#{[self.id].pack('N')}#{encoded_time}\0 #{self.device.to_hexa}\0#{json.length.chr}#{json}"
+ raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
+ message
+ end
+
def self.send_notifications
ActiveSupport::Deprecation.warn("The method APN::Notification.send_notifications is deprecated. Use APN::App.send_notifications instead.")
APN::App.send_notifications
View
4 spec/apn_on_rails/app/models/apn/app_spec.rb
@@ -12,7 +12,7 @@
NotificationFactory.create({:device_id => device.id})]
notifications.each_with_index do |notify, i|
- notify.stub(:message_for_sending).and_return("message-#{i}")
+ notify.stub(:enhanced_message_for_sending).and_return("message-#{i}")
notify.should_receive(:sent_at=).with(instance_of(Time))
notify.should_receive(:save)
end
@@ -47,7 +47,7 @@
NotificationFactory.create({:device_id => device.id})]
notifications.each_with_index do |notify, i|
- notify.stub(:message_for_sending).and_return("message-#{i}")
+ notify.stub(:enhanced_message_for_sending).and_return("message-#{i}")
notify.should_receive(:sent_at=).with(instance_of(Time))
notify.should_receive(:save)
end
View
2  spec/apn_on_rails/app/models/apn/device_spec.rb
@@ -36,7 +36,7 @@
it 'should convert the text string to hexadecimal' do
device = DeviceFactory.new(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
- device.to_hexa.should == fixture_value('hexa.bin')
+ Digest::MD5.hexdigest(device.to_hexa).should == Digest::MD5.hexdigest(fixture_value('hexa.bin'))
end
end
View
4 spec/apn_on_rails/app/models/apn/group_notification_spec.rb
@@ -35,7 +35,7 @@
it 'should return the necessary JSON for Apple' do
noty = APN::GroupNotification.first
- noty.to_apple_json.should == %{{"typ":"1","aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}}
+ noty.to_apple_json.should == %{{"aps":{"alert":"Hello!","badge":5,"sound":"my_sound.aiff"},"typ":"1"}}
end
end
@@ -46,7 +46,7 @@
noty = APN::GroupNotification.first
noty.custom_properties = nil
device = DeviceFactory.new(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
- noty.message_for_sending(device).should == fixture_value('message_for_sending.bin')
+ Digest::MD5.hexdigest(noty.message_for_sending(device)).should == "cd334410090bc373e9302887cb015bea"
end
it 'should raise an APN::Errors::ExceededMessageSizeError if the message is too big' do
View
32 spec/apn_on_rails/app/models/apn/notification_spec.rb
@@ -35,7 +35,7 @@
it 'should return the necessary JSON for Apple' do
noty = APN::Notification.first
- noty.to_apple_json.should == %{{"typ":"1","aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}}
+ noty.to_apple_json.should == %{{"aps":{"alert":"Hello!","badge":5,"sound":"my_sound.aiff"},"typ":"1"}}
end
end
@@ -46,11 +46,13 @@
noty = APN::Notification.first
noty.custom_properties = nil
noty.device = DeviceFactory.new(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
- noty.message_for_sending.should == fixture_value('message_for_sending.bin')
+ Digest::MD5.hexdigest(noty.message_for_sending).should == "cd334410090bc373e9302887cb015bea"
end
it 'should raise an APN::Errors::ExceededMessageSizeError if the message is too big' do
- noty = NotificationFactory.new(:device_id => DeviceFactory.create, :sound => true, :badge => nil)
+ noty = APN::Notification.first
+ noty.custom_properties = nil
+ noty.device = DeviceFactory.new(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
noty.send(:write_attribute, 'alert', 'a' * 183)
lambda {
noty.message_for_sending
@@ -58,7 +60,29 @@
end
end
-
+
+ describe 'enhanced_message_for_sending' do
+
+ it 'should create an enhanced binary message to be sent to Apple' do
+ noty = APN::Notification.first
+ noty.custom_properties = nil
+ noty.device = DeviceFactory.new(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
+ noty.stub(:encoded_expiry_time).and_return([1337262176].pack('N'))
+ Digest::MD5.hexdigest(noty.enhanced_message_for_sending).should == "7cbffe207d368a6e513bf6a16a095ec3"
+ end
+
+ it 'should raise an APN::Errors::ExceededMessageSizeError if the message is too big' do
+ noty = APN::Notification.first
+ noty.custom_properties = nil
+ noty.device = DeviceFactory.new(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
+ noty.send(:write_attribute, 'alert', 'a' * 183)
+ lambda {
+ noty.enhanced_message_for_sending
+ }.should raise_error(APN::Errors::ExceededMessageSizeError)
+ end
+
+ end
+
describe 'send_notifications' do
it 'should warn the user the method is deprecated and call the corresponding method on APN::App' do
Something went wrong with that request. Please try again.