Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Fix for group notification #36

Open
wants to merge 15 commits into from
Commits on Jan 4, 2012
  1. @ramigg
  2. @ramigg

    fix feedback

    ramigg authored
Commits on Jan 9, 2012
  1. @ramigg
  2. @ramigg

    Removed rails 3 deprecations

    ramigg authored
  3. @ramigg

    small init fix

    ramigg authored
Commits on Feb 13, 2012
  1. @ramigg
  2. @ramigg

    removed rescue

    ramigg authored
Commits on Mar 21, 2012
  1. @ramigg
  2. @ramigg
Commits on Jul 9, 2012
  1. @ramigg
Commits on Aug 29, 2012
  1. @ramigg
  2. @ramigg
  3. @ramigg
Commits on Sep 3, 2012
  1. @ramigg

    Update lib/apn_on_rails/app/models/apn/group_notification.rb

    ramigg authored
    fix for rails 3 utf8 issue (combining US-ASCII string with UTF-8 string)
Commits on Sep 4, 2012
  1. @ramigg
This page is out of date. Refresh to see the latest.
View
1  .gitignore
@@ -15,3 +15,4 @@ profile
spec/active_record/test.db
.bundle
**/.svn
+.idea
View
8 README
@@ -123,21 +123,21 @@ APN on Rails has the following default configurations that you change as you see
configatron.apn.passphrase # => ''
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.cert #=> File.join(::Rails.root, 'config', 'apple_push_notification_development.pem')
# production (delivery):
configatron.apn.host # => 'gateway.push.apple.com'
- configatron.apn.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_production.pem')
+ configatron.apn.cert #=> File.join(::Rails.root, 'config', 'apple_push_notification_production.pem')
# development (feedback):
configatron.apn.feedback.passphrase # => ''
configatron.apn.feedback.port # => 2196
configatron.apn.feedback.host # => 'feedback.sandbox.push.apple.com'
- configatron.apn.feedback.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_development.pem')
+ configatron.apn.feedback.cert #=> File.join(::Rails.root, 'config', 'apple_push_notification_development.pem')
# production (feedback):
configatron.apn.feedback.host # => 'feedback.push.apple.com'
- configatron.apn.feedback.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_production.pem')
+ configatron.apn.feedback.cert #=> File.join(::Rails.root, 'config', 'apple_push_notification_production.pem')
That's it, now you're ready to start creating notifications.
View
8 README.textile
@@ -141,21 +141,21 @@ see fit:
configatron.apn.passphrase # => ''
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.cert #=> File.join(::Rails.root, 'config', 'apple_push_notification_development.pem')
# production (delivery):
configatron.apn.host # => 'gateway.push.apple.com'
- configatron.apn.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_production.pem')
+ configatron.apn.cert #=> File.join(::Rails.root, 'config', 'apple_push_notification_production.pem')
# development (feedback):
configatron.apn.feedback.passphrase # => ''
configatron.apn.feedback.port # => 2196
configatron.apn.feedback.host # => 'feedback.sandbox.push.apple.com'
- configatron.apn.feedback.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_development.pem')
+ configatron.apn.feedback.cert #=> File.join(::Rails.root, 'config', 'apple_push_notification_development.pem')
# production (feedback):
configatron.apn.feedback.host # => 'feedback.push.apple.com'
- configatron.apn.feedback.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_production.pem')
+ configatron.apn.feedback.cert #=> File.join(::Rails.root, 'config', 'apple_push_notification_production.pem')
</pre></code>
That's it, now you're ready to start creating notifications.
View
8 lib/apn_on_rails/apn_on_rails.rb
@@ -3,13 +3,13 @@
require 'configatron'
rails_root = File.join(FileUtils.pwd, 'rails_root')
-if defined?(RAILS_ROOT)
- rails_root = RAILS_ROOT
+if defined?(::Rails.root)
+ rails_root = ::Rails.root.to_s
end
rails_env = 'development'
-if defined?(RAILS_ENV)
- rails_env = RAILS_ENV
+if defined?(::Rails.env)
+ rails_env = ::Rails.env
end
configatron.apn.set_default(:passphrase, '')
View
101 lib/apn_on_rails/app/models/apn/app.rb
@@ -1,16 +1,17 @@
+# encoding: utf-8
class APN::App < APN::Base
-
+
has_many :groups, :class_name => 'APN::Group', :dependent => :destroy
has_many :devices, :class_name => 'APN::Device', :dependent => :destroy
has_many :notifications, :through => :devices, :dependent => :destroy
has_many :unsent_notifications, :through => :devices
has_many :group_notifications, :through => :groups
has_many :unsent_group_notifications, :through => :groups
-
+
def cert
- (RAILS_ENV == 'production' ? apn_prod_cert : apn_dev_cert)
+ (::Rails.env.production? ? apn_prod_cert : apn_dev_cert)
end
-
+
# Opens a connection to the Apple APN server and attempts to batch deliver
# an Array of group notifications.
#
@@ -25,9 +26,9 @@ def send_notifications
end
APN::App.send_notifications_for_cert(self.cert, self.id)
end
-
+
def self.send_notifications
- apps = APN::App.all
+ apps = APN::App.all
apps.each do |app|
app.send_notifications
end
@@ -36,50 +37,56 @@ def self.send_notifications
send_notifications_for_cert(global_cert, nil)
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)
- conditions = "app_id is null"
- else
- conditions = ["app_id = ?", app_id]
- end
- begin
- 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
- end
- end
+ if (app_id == nil)
+ conditions = "app_id is null"
+ else
+ conditions = ["app_id = ?", app_id]
+ end
+ 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
end
- rescue Exception => e
- log_connection_exception(e)
end
- # end
+ end
+ # end
end
-
+
def send_group_notifications
- if self.cert.nil?
+ if self.cert.nil?
raise APN::Errors::MissingCertificateError.new
return
end
- unless self.unsent_group_notifications.nil? || self.unsent_group_notifications.empty?
- APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
- unsent_group_notifications.each do |gnoty|
- gnoty.devices.find_each do |device|
- conn.write(gnoty.message_for_sending(device))
+ unless self.unsent_group_notifications.nil? || self.unsent_group_notifications.empty?
+ unsent_group_notifications.each do |gnoty|
+ failed = 0
+ devices_to_send = gnoty.devices.count
+ gnoty.devices.find_in_batches(:batch_size => 100) do |devices|
+ APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
+ devices.each do |device|
+ begin
+ conn.write(gnoty.message_for_sending(device))
+ rescue Exception => e
+ puts e.message
+ failed += 1
+ end
+ end
end
- gnoty.sent_at = Time.now
- gnoty.save
end
+ puts "Sent to: #{devices_to_send - failed}/#{devices_to_send} "
+ gnoty.sent_at = Time.now
+ gnoty.save
end
end
end
-
+
def send_group_notification(gnoty)
- if self.cert.nil?
+ if self.cert.nil?
raise APN::Errors::MissingCertificateError.new
return
end
@@ -93,14 +100,14 @@ def send_group_notification(gnoty)
end
end
end
-
+
def self.send_group_notifications
apps = APN::App.all
apps.each do |app|
app.send_group_notifications
end
- end
-
+ end
+
# Retrieves a list of APN::Device instnces from Apple using
# the <tt>devices</tt> method. It then checks to see if the
# <tt>last_registered_at</tt> date of each APN::Device is
@@ -117,8 +124,10 @@ def process_devices
return
end
APN::App.process_devices_for_cert(self.cert)
- end # process_devices
-
+ end
+
+ # process_devices
+
def self.process_devices
apps = APN::App.all
apps.each do |app|
@@ -129,23 +138,23 @@ def self.process_devices
APN::App.process_devices_for_cert(global_cert)
end
end
-
+
def self.process_devices_for_cert(the_cert)
puts "in APN::App.process_devices_for_cert"
APN::Feedback.devices(the_cert).each do |device|
if device.last_registered_at < device.feedback_at
puts "device #{device.id} -> #{device.last_registered_at} < #{device.feedback_at}"
device.destroy
- else
+ else
puts "device #{device.id} -> #{device.last_registered_at} not < #{device.feedback_at}"
end
- end
+ end
end
-
-
+
+
protected
def log_connection_exception(ex)
puts ex.message
end
-
+
end
View
2  lib/apn_on_rails/app/models/apn/base.rb
@@ -1,6 +1,8 @@
module APN
class Base < ActiveRecord::Base # :nodoc:
+ self.abstract_class = true
+
def self.table_name # :nodoc:
self.to_s.gsub("::", "_").tableize
end
View
2  lib/apn_on_rails/app/models/apn/device.rb
@@ -15,7 +15,7 @@ class APN::Device < APN::Base
has_many :unsent_notifications, :class_name => 'APN::Notification', :conditions => 'sent_at is null'
validates_uniqueness_of :token, :scope => :app_id
- validates_format_of :token, :with => /^[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}$/
+ #validates_format_of :token, :with => /^[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}$/
before_create :set_last_registered_at
View
61 lib/apn_on_rails/app/models/apn/group_notification.rb
@@ -1,18 +1,19 @@
+# encoding: utf-8
class APN::GroupNotification < APN::Base
include ::ActionView::Helpers::TextHelper
extend ::ActionView::Helpers::TextHelper
serialize :custom_properties
-
+
belongs_to :group, :class_name => 'APN::Group'
- has_one :app, :class_name => 'APN::App', :through => :group
- has_many :device_groupings, :through => :group
-
+ has_one :app, :class_name => 'APN::App', :through => :group
+ has_many :device_groupings, :through => :group
+
validates_presence_of :group_id
-
+
def devices
self.group.devices
end
-
+
# Stores the text alert message you want to send to the device.
#
# If the message is over 150 characters long it will get truncated
@@ -23,7 +24,7 @@ def alert=(message)
end
write_attribute('alert', message)
end
-
+
# Creates a Hash that will be the payload of an APN.
#
# Example:
@@ -45,17 +46,25 @@ def apple_hash
result['aps']['alert'] = self.alert if self.alert
result['aps']['badge'] = self.badge.to_i if self.badge
if self.sound
- result['aps']['sound'] = self.sound if self.sound.is_a? String
+ result['aps']['sound'] = self.sound if self.sound.is_a?(String) && self.sound.strip.present?
result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
end
if self.custom_properties
- self.custom_properties.each do |key,value|
+ self.custom_properties.each do |key, value|
result["#{key}"] = "#{value}"
end
end
result
end
-
+
+ def payload
+ multi_json_dump(apple_hash)
+ end
+
+ def payload_size
+ payload.bytesize
+ end
+
# Creates the JSON string required for an APN message.
#
# Example:
@@ -67,13 +76,37 @@ def apple_hash
def to_apple_json
self.apple_hash.to_json
end
-
+
# Creates the binary message needed to send to Apple.
+ #def message_for_sending(device)
+ # json = self.to_apple_json.gsub(/\\u([0-9a-z]{4})/) { |s| [$1.to_i(16)].pack("U") } # This will create non encoded string. Otherwise the string is encoded from utf8 to ascii with unicode representation (i.e. \\u05d2)
+ # message = "\0\0 #{device.to_hexa}\0".force_encoding("UTF-8") + "#{json.length.chr}#{json}".force_encoding("UTF-8")
+ # raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
+ # message
+ #end
+
+ # This method conforms to the enhanced binary format.
+ # http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4
def message_for_sending(device)
- json = self.to_apple_json
- message = "\0\0 #{device.to_hexa}\0#{json.length.chr}#{json}"
+ message = [1, device.id, 1.day.to_i, 0, 32, device.token, payload_size, payload].pack("cNNccH*na*")
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
message
end
-
+
+
+ private
+
+ def multi_json_load(string, options = {})
+ # Calling load on multi_json less than v1.3.0 attempts to load a file from disk. Check the version explicitly.
+ if Gem.loaded_specs['multi_json'].version >= Gem::Version.create('1.3.0')
+ MultiJson.load(string, options)
+ else
+ MultiJson.decode(string, options)
+ end
+ end
+
+ def multi_json_dump(string, options = {})
+ MultiJson.respond_to?(:dump) ? MultiJson.dump(string, options) : MultiJson.encode(string, options)
+ end
+
end # APN::Notification
View
4 lib/apn_on_rails/app/models/apn/notification.rb
@@ -54,7 +54,7 @@ def apple_hash
result['aps']['alert'] = self.alert if self.alert
result['aps']['badge'] = self.badge.to_i if self.badge
if self.sound
- result['aps']['sound'] = self.sound if self.sound.is_a? String
+ result['aps']['sound'] = self.sound if self.sound.is_a?(String) && self.sound.strip.present?
result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
end
if self.custom_properties
@@ -79,7 +79,7 @@ def to_apple_json
# Creates the binary message needed to send to Apple.
def message_for_sending
- json = self.to_apple_json
+ json = self.to_apple_json.gsub(/\\u([0-9a-z]{4})/) {|s| [$1.to_i(16)].pack("U")} # This will create non encoded string. Otherwise the string is encoded from utf8 to ascii with unicode representation (i.e. \\u05d2)
message = "\0\0 #{self.device.to_hexa}\0#{json.length.chr}#{json}"
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
message
View
3  lib/apn_on_rails/libs/connection.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
module APN
module Connection
@@ -59,7 +60,7 @@ def open(options = {}, &block) # :nodoc:
ssl.connect
yield ssl, sock if block_given?
-
+ ensure
ssl.close
sock.close
end
View
8 lib/apn_on_rails/libs/feedback.rb
@@ -13,10 +13,10 @@ class << self
def devices(cert, &block)
devices = []
return if cert.nil?
- APN::Connection.open_for_feedback({:cert => cert}) do |conn, sock|
+ APN::Connection.open_for_feedback({:cert => cert}) do |conn, sock|
while line = conn.read(38) # Read 38 bytes from the SSL socket
- feedback = line.unpack('N1n1H140')
- token = feedback[2].scan(/.{0,8}/).join(' ').strip
+ feedback = line.unpack('N1n1H140')
+ token = feedback[2].strip
device = APN::Device.find(:first, :conditions => {:token => token})
if device
device.feedback_at = Time.at(feedback[0])
@@ -25,7 +25,7 @@ def devices(cert, &block)
end
end
devices.each(&block) if block_given?
- return devices
+ devices
end # devices
def process_devices
Something went wrong with that request. Please try again.