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

Fix for group notification #36

Open
wants to merge 15 commits 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ profile
spec/active_record/test.db
.bundle
**/.svn
.idea
8 changes: 4 additions & 4 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
8 changes: 4 additions & 4 deletions README.textile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions lib/apn_on_rails/apn_on_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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, '')
Expand Down
101 changes: 55 additions & 46 deletions lib/apn_on_rails/app/models/apn/app.rb
Original file line number Diff line number Diff line change
@@ -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.
#
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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|
Expand All @@ -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
2 changes: 2 additions & 0 deletions lib/apn_on_rails/app/models/apn/base.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/apn_on_rails/app/models/apn/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
61 changes: 47 additions & 14 deletions lib/apn_on_rails/app/models/apn/group_notification.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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
Loading