gwong-apn_on_rails 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/.specification +80 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +47 -0
- data/LICENSE +21 -0
- data/README +179 -0
- data/README.textile +209 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/apn_on_rails.gemspec +144 -0
- data/autotest/discover.rb +1 -0
- data/generators/apn_migrations_generator.rb +31 -0
- data/generators/templates/apn_migrations/001_create_apn_devices.rb +13 -0
- data/generators/templates/apn_migrations/002_create_apn_notifications.rb +23 -0
- data/generators/templates/apn_migrations/003_alter_apn_devices.rb +25 -0
- data/generators/templates/apn_migrations/004_create_apn_apps.rb +18 -0
- data/generators/templates/apn_migrations/005_create_groups.rb +23 -0
- data/generators/templates/apn_migrations/006_alter_apn_groups.rb +11 -0
- data/generators/templates/apn_migrations/007_create_device_groups.rb +27 -0
- data/generators/templates/apn_migrations/008_create_apn_group_notifications.rb +23 -0
- data/generators/templates/apn_migrations/009_create_pull_notifications.rb +16 -0
- data/generators/templates/apn_migrations/010_alter_apn_notifications.rb +21 -0
- data/generators/templates/apn_migrations/011_make_device_token_index_nonunique.rb +11 -0
- data/generators/templates/apn_migrations/012_add_launch_notification_to_apn_pull_notifications.rb +9 -0
- data/lib/apn_on_rails.rb +4 -0
- data/lib/apn_on_rails/apn_on_rails.rb +81 -0
- data/lib/apn_on_rails/app/models/apn/app.rb +150 -0
- data/lib/apn_on_rails/app/models/apn/base.rb +9 -0
- data/lib/apn_on_rails/app/models/apn/device.rb +49 -0
- data/lib/apn_on_rails/app/models/apn/device_grouping.rb +16 -0
- data/lib/apn_on_rails/app/models/apn/group.rb +12 -0
- data/lib/apn_on_rails/app/models/apn/group_notification.rb +79 -0
- data/lib/apn_on_rails/app/models/apn/notification.rb +93 -0
- data/lib/apn_on_rails/app/models/apn/pull_notification.rb +28 -0
- data/lib/apn_on_rails/libs/connection.rb +70 -0
- data/lib/apn_on_rails/libs/feedback.rb +39 -0
- data/lib/apn_on_rails/tasks/apn.rake +30 -0
- data/lib/apn_on_rails/tasks/db.rake +19 -0
- data/lib/apn_on_rails_tasks.rb +3 -0
- data/spec/active_record/setup_ar.rb +19 -0
- data/spec/apn_on_rails/app/models/apn/app_spec.rb +226 -0
- data/spec/apn_on_rails/app/models/apn/device_spec.rb +60 -0
- data/spec/apn_on_rails/app/models/apn/group_notification_spec.rb +66 -0
- data/spec/apn_on_rails/app/models/apn/notification_spec.rb +71 -0
- data/spec/apn_on_rails/app/models/apn/pull_notification_spec.rb +100 -0
- data/spec/apn_on_rails/libs/connection_spec.rb +40 -0
- data/spec/apn_on_rails/libs/feedback_spec.rb +43 -0
- data/spec/extensions/string.rb +10 -0
- data/spec/factories/app_factory.rb +27 -0
- data/spec/factories/device_factory.rb +29 -0
- data/spec/factories/device_grouping_factory.rb +22 -0
- data/spec/factories/group_factory.rb +27 -0
- data/spec/factories/group_notification_factory.rb +22 -0
- data/spec/factories/notification_factory.rb +22 -0
- data/spec/factories/pull_notification_factory.rb +22 -0
- data/spec/fixtures/hexa.bin +1 -0
- data/spec/fixtures/message_for_sending.bin +0 -0
- data/spec/rails_root/config/apple_push_notification_development.pem +19 -0
- data/spec/spec_helper.rb +55 -0
- metadata +282 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
class MakeDeviceTokenIndexNonunique < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
remove_index :apn_devices, :column => :token
|
4
|
+
add_index :apn_devices, :token
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
remove_index :apn_devices, :column => :token
|
9
|
+
add_index :apn_devices, :token, :unique => true
|
10
|
+
end
|
11
|
+
end
|
data/lib/apn_on_rails.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'openssl'
|
3
|
+
require 'configatron'
|
4
|
+
|
5
|
+
rails_root = File.join(FileUtils.pwd, 'rails_root')
|
6
|
+
if defined?(RAILS_ROOT)
|
7
|
+
rails_root = RAILS_ROOT
|
8
|
+
end
|
9
|
+
|
10
|
+
rails_env = 'development'
|
11
|
+
if defined?(RAILS_ENV)
|
12
|
+
rails_env = RAILS_ENV
|
13
|
+
end
|
14
|
+
|
15
|
+
configatron.apn.set_default(:passphrase, '')
|
16
|
+
configatron.apn.set_default(:port, 2195)
|
17
|
+
|
18
|
+
configatron.apn.feedback.set_default(:passphrase, configatron.apn.passphrase)
|
19
|
+
configatron.apn.feedback.set_default(:port, 2196)
|
20
|
+
|
21
|
+
if rails_env == 'production'
|
22
|
+
configatron.apn.set_default(:host, 'gateway.push.apple.com')
|
23
|
+
configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_production.pem'))
|
24
|
+
|
25
|
+
configatron.apn.feedback.set_default(:host, 'feedback.push.apple.com')
|
26
|
+
configatron.apn.feedback.set_default(:cert, configatron.apn.cert)
|
27
|
+
else
|
28
|
+
configatron.apn.set_default(:host, 'gateway.sandbox.push.apple.com')
|
29
|
+
configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_development.pem'))
|
30
|
+
|
31
|
+
configatron.apn.feedback.set_default(:host, 'feedback.sandbox.push.apple.com')
|
32
|
+
configatron.apn.feedback.set_default(:cert, configatron.apn.cert)
|
33
|
+
end
|
34
|
+
|
35
|
+
module APN # :nodoc:
|
36
|
+
|
37
|
+
module Errors # :nodoc:
|
38
|
+
|
39
|
+
# Raised when a notification message to Apple is longer than 256 bytes.
|
40
|
+
class ExceededMessageSizeError < StandardError
|
41
|
+
|
42
|
+
def initialize(message) # :nodoc:
|
43
|
+
super("The maximum size allowed for a notification payload is 256 bytes: '#{message}'")
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
class MissingCertificateError < StandardError
|
49
|
+
def initialize
|
50
|
+
super("This app has no certificate")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end # Errors
|
55
|
+
|
56
|
+
end # APN
|
57
|
+
|
58
|
+
base = File.join(File.dirname(__FILE__), 'app', 'models', 'apn', 'base.rb')
|
59
|
+
require base
|
60
|
+
|
61
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'app', 'models', 'apn', '*.rb')).sort.each do |f|
|
62
|
+
require f
|
63
|
+
end
|
64
|
+
|
65
|
+
%w{ models controllers helpers }.each do |dir|
|
66
|
+
path = File.join(File.dirname(__FILE__), 'app', dir)
|
67
|
+
$LOAD_PATH << path
|
68
|
+
# puts "Adding #{path}"
|
69
|
+
begin
|
70
|
+
if ActiveSupport::Dependencies.respond_to? :autoload_paths
|
71
|
+
ActiveSupport::Dependencies.autoload_paths << path
|
72
|
+
ActiveSupport::Dependencies.autoload_once_paths.delete(path)
|
73
|
+
else
|
74
|
+
ActiveSupport::Dependencies.load_paths << path
|
75
|
+
ActiveSupport::Dependencies.load_once_paths.delete(path)
|
76
|
+
end
|
77
|
+
rescue NameError
|
78
|
+
Dependencies.load_paths << path
|
79
|
+
Dependencies.load_once_paths.delete(path)
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
class APN::App < APN::Base
|
2
|
+
|
3
|
+
has_many :groups, :class_name => 'APN::Group', :dependent => :destroy
|
4
|
+
has_many :devices, :class_name => 'APN::Device', :dependent => :destroy
|
5
|
+
has_many :notifications, :through => :devices, :dependent => :destroy
|
6
|
+
has_many :unsent_notifications, :through => :devices
|
7
|
+
has_many :group_notifications, :through => :groups
|
8
|
+
has_many :unsent_group_notifications, :through => :groups
|
9
|
+
|
10
|
+
def cert
|
11
|
+
(RAILS_ENV == 'production' ? apn_prod_cert : apn_dev_cert)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Opens a connection to the Apple APN server and attempts to batch deliver
|
15
|
+
# an Array of group notifications.
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# As each APN::GroupNotification is sent the <tt>sent_at</tt> column will be timestamped,
|
19
|
+
# so as to not be sent again.
|
20
|
+
#
|
21
|
+
def send_notifications
|
22
|
+
if self.cert.nil?
|
23
|
+
raise APN::Errors::MissingCertificateError.new
|
24
|
+
return
|
25
|
+
end
|
26
|
+
APN::App.send_notifications_for_cert(self.cert, self.id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.send_notifications
|
30
|
+
apps = APN::App.all
|
31
|
+
apps.each do |app|
|
32
|
+
app.send_notifications
|
33
|
+
end
|
34
|
+
if !configatron.apn.cert.blank?
|
35
|
+
global_cert = File.read(configatron.apn.cert)
|
36
|
+
send_notifications_for_cert(global_cert, nil)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.send_notifications_for_cert(the_cert, app_id)
|
41
|
+
# unless self.unsent_notifications.nil? || self.unsent_notifications.empty?
|
42
|
+
if (app_id == nil)
|
43
|
+
conditions = "app_id is null and sent_at is null"
|
44
|
+
else
|
45
|
+
conditions = ["app_id = ? and sent_at is null", app_id]
|
46
|
+
end
|
47
|
+
begin
|
48
|
+
APN::Connection.open_for_delivery({:cert => the_cert}) do |conn, sock|
|
49
|
+
notifications = APN::Notification.find(:all, :select => "apn_notifications.*", :conditions => conditions, :joins => " INNER JOIN apn_devices ON apn_devices.id = apn_notifications.device_id")
|
50
|
+
notifications.each do |noty|
|
51
|
+
conn.write(noty.message_for_sending)
|
52
|
+
noty.sent_at = Time.now
|
53
|
+
noty.save
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue Exception => e
|
57
|
+
log_connection_exception(e)
|
58
|
+
end
|
59
|
+
# end
|
60
|
+
end
|
61
|
+
|
62
|
+
def send_group_notifications
|
63
|
+
if self.cert.nil?
|
64
|
+
raise APN::Errors::MissingCertificateError.new
|
65
|
+
return
|
66
|
+
end
|
67
|
+
unless self.unsent_group_notifications.nil? || self.unsent_group_notifications.empty?
|
68
|
+
APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
|
69
|
+
unsent_group_notifications.each do |gnoty|
|
70
|
+
gnoty.devices.find_each do |device|
|
71
|
+
conn.write(gnoty.message_for_sending(device))
|
72
|
+
end
|
73
|
+
gnoty.sent_at = Time.now
|
74
|
+
gnoty.save
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def send_group_notification(gnoty)
|
81
|
+
if self.cert.nil?
|
82
|
+
raise APN::Errors::MissingCertificateError.new
|
83
|
+
return
|
84
|
+
end
|
85
|
+
unless gnoty.nil?
|
86
|
+
APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
|
87
|
+
gnoty.devices.find_each do |device|
|
88
|
+
conn.write(gnoty.message_for_sending(device))
|
89
|
+
end
|
90
|
+
gnoty.sent_at = Time.now
|
91
|
+
gnoty.save
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.send_group_notifications
|
97
|
+
apps = APN::App.all
|
98
|
+
apps.each do |app|
|
99
|
+
app.send_group_notifications
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Retrieves a list of APN::Device instnces from Apple using
|
104
|
+
# the <tt>devices</tt> method. It then checks to see if the
|
105
|
+
# <tt>last_registered_at</tt> date of each APN::Device is
|
106
|
+
# before the date that Apple says the device is no longer
|
107
|
+
# accepting notifications then the device is deleted. Otherwise
|
108
|
+
# it is assumed that the application has been re-installed
|
109
|
+
# and is available for notifications.
|
110
|
+
#
|
111
|
+
# This can be run from the following Rake task:
|
112
|
+
# $ rake apn:feedback:process
|
113
|
+
def process_devices
|
114
|
+
if self.cert.nil?
|
115
|
+
raise APN::Errors::MissingCertificateError.new
|
116
|
+
return
|
117
|
+
end
|
118
|
+
APN::App.process_devices_for_cert(self.cert)
|
119
|
+
end # process_devices
|
120
|
+
|
121
|
+
def self.process_devices
|
122
|
+
apps = APN::App.all
|
123
|
+
apps.each do |app|
|
124
|
+
app.process_devices
|
125
|
+
end
|
126
|
+
if !configatron.apn.cert.blank?
|
127
|
+
global_cert = File.read(configatron.apn.cert)
|
128
|
+
APN::App.process_devices_for_cert(global_cert)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.process_devices_for_cert(the_cert)
|
133
|
+
puts "in APN::App.process_devices_for_cert"
|
134
|
+
APN::Feedback.devices(the_cert).each do |device|
|
135
|
+
if device.last_registered_at < device.feedback_at
|
136
|
+
puts "device #{device.id} -> #{device.last_registered_at} < #{device.feedback_at}"
|
137
|
+
device.destroy
|
138
|
+
else
|
139
|
+
puts "device #{device.id} -> #{device.last_registered_at} not < #{device.feedback_at}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
protected
|
146
|
+
def log_connection_exception(ex)
|
147
|
+
puts ex.message
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Represents an iPhone (or other APN enabled device).
|
2
|
+
# An APN::Device can have many APN::Notification.
|
3
|
+
#
|
4
|
+
# In order for the APN::Feedback system to work properly you *MUST*
|
5
|
+
# touch the <tt>last_registered_at</tt> column everytime someone opens
|
6
|
+
# your application. If you do not, then it is possible, and probably likely,
|
7
|
+
# that their device will be removed and will no longer receive notifications.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# Device.create(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
|
11
|
+
class APN::Device < APN::Base
|
12
|
+
|
13
|
+
belongs_to :app, :class_name => 'APN::App'
|
14
|
+
has_many :notifications, :class_name => 'APN::Notification'
|
15
|
+
has_many :unsent_notifications, :class_name => 'APN::Notification', :conditions => 'sent_at is null'
|
16
|
+
|
17
|
+
validates_uniqueness_of :token, :scope => :app_id
|
18
|
+
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}$/
|
19
|
+
|
20
|
+
before_create :set_last_registered_at
|
21
|
+
|
22
|
+
# The <tt>feedback_at</tt> accessor is set when the
|
23
|
+
# device is marked as potentially disconnected from your
|
24
|
+
# application by Apple.
|
25
|
+
attr_accessor :feedback_at
|
26
|
+
|
27
|
+
# Stores the token (Apple's device ID) of the iPhone (device).
|
28
|
+
#
|
29
|
+
# If the token comes in like this:
|
30
|
+
# '<5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz>'
|
31
|
+
# Then the '<' and '>' will be stripped off.
|
32
|
+
def token=(token)
|
33
|
+
res = token.scan(/\<(.+)\>/).first
|
34
|
+
unless res.nil? || res.empty?
|
35
|
+
token = res.first
|
36
|
+
end
|
37
|
+
write_attribute('token', token)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the hexadecimal representation of the device's token.
|
41
|
+
def to_hexa
|
42
|
+
[self.token.delete(' ')].pack('H*')
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_last_registered_at
|
46
|
+
self.last_registered_at = Time.now #if self.last_registered_at.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class APN::DeviceGrouping < APN::Base
|
2
|
+
|
3
|
+
belongs_to :group, :class_name => 'APN::Group'
|
4
|
+
belongs_to :device, :class_name => 'APN::Device'
|
5
|
+
|
6
|
+
validates_presence_of :device_id, :group_id
|
7
|
+
validate :same_app_id
|
8
|
+
validates_uniqueness_of :device_id, :scope => :group_id
|
9
|
+
|
10
|
+
def same_app_id
|
11
|
+
unless self.group and self.device and self.group.app_id == self.device.app_id
|
12
|
+
errors.add_to_base("device and group must belong to the same app")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class APN::Group < APN::Base
|
2
|
+
|
3
|
+
belongs_to :app, :class_name => 'APN::App'
|
4
|
+
has_many :device_groupings, :class_name => "APN::DeviceGrouping", :dependent => :destroy
|
5
|
+
has_many :devices, :class_name => 'APN::Device', :through => :device_groupings
|
6
|
+
has_many :group_notifications, :class_name => 'APN::GroupNotification'
|
7
|
+
has_many :unsent_group_notifications, :class_name => 'APN::GroupNotification', :conditions => 'sent_at is null'
|
8
|
+
|
9
|
+
validates_presence_of :app_id
|
10
|
+
validates_uniqueness_of :name, :scope => :app_id
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class APN::GroupNotification < APN::Base
|
2
|
+
include ::ActionView::Helpers::TextHelper
|
3
|
+
extend ::ActionView::Helpers::TextHelper
|
4
|
+
serialize :custom_properties
|
5
|
+
|
6
|
+
belongs_to :group, :class_name => 'APN::Group'
|
7
|
+
has_one :app, :class_name => 'APN::App', :through => :group
|
8
|
+
has_many :device_groupings, :through => :group
|
9
|
+
|
10
|
+
validates_presence_of :group_id
|
11
|
+
|
12
|
+
def devices
|
13
|
+
self.group.devices
|
14
|
+
end
|
15
|
+
|
16
|
+
# Stores the text alert message you want to send to the device.
|
17
|
+
#
|
18
|
+
# If the message is over 150 characters long it will get truncated
|
19
|
+
# to 150 characters with a <tt>...</tt>
|
20
|
+
def alert=(message)
|
21
|
+
if !message.blank? && message.size > 150
|
22
|
+
message = truncate(message, :length => 150)
|
23
|
+
end
|
24
|
+
write_attribute('alert', message)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates a Hash that will be the payload of an APN.
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
# apn = APN::GroupNotification.new
|
31
|
+
# apn.badge = 5
|
32
|
+
# apn.sound = 'my_sound.aiff'
|
33
|
+
# apn.alert = 'Hello!'
|
34
|
+
# apn.apple_hash # => {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
|
35
|
+
#
|
36
|
+
# Example 2:
|
37
|
+
# apn = APN::GroupNotification.new
|
38
|
+
# apn.badge = 0
|
39
|
+
# apn.sound = true
|
40
|
+
# apn.custom_properties = {"typ" => 1}
|
41
|
+
# apn.apple_hash # => {"aps" => {"badge" => 0, "sound" => 1.aiff},"typ" => "1"}
|
42
|
+
def apple_hash
|
43
|
+
result = {}
|
44
|
+
result['aps'] = {}
|
45
|
+
result['aps']['alert'] = self.alert if self.alert
|
46
|
+
result['aps']['badge'] = self.badge.to_i if self.badge
|
47
|
+
if self.sound
|
48
|
+
result['aps']['sound'] = self.sound if self.sound.is_a? String
|
49
|
+
result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
|
50
|
+
end
|
51
|
+
if self.custom_properties
|
52
|
+
self.custom_properties.each do |key,value|
|
53
|
+
result["#{key}"] = "#{value}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates the JSON string required for an APN message.
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
# apn = APN::Notification.new
|
63
|
+
# apn.badge = 5
|
64
|
+
# apn.sound = 'my_sound.aiff'
|
65
|
+
# apn.alert = 'Hello!'
|
66
|
+
# apn.to_apple_json # => '{"aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}'
|
67
|
+
def to_apple_json
|
68
|
+
self.apple_hash.to_json
|
69
|
+
end
|
70
|
+
|
71
|
+
# Creates the binary message needed to send to Apple.
|
72
|
+
def message_for_sending(device)
|
73
|
+
json = self.to_apple_json
|
74
|
+
message = "\0\0 #{device.to_hexa}\0#{json.length.chr}#{json}"
|
75
|
+
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
|
76
|
+
message
|
77
|
+
end
|
78
|
+
|
79
|
+
end # APN::Notification
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Represents the message you wish to send.
|
2
|
+
# An APN::Notification belongs to an APN::Device.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
# apn = APN::Notification.new
|
6
|
+
# apn.badge = 5
|
7
|
+
# apn.sound = 'my_sound.aiff'
|
8
|
+
# apn.alert = 'Hello!'
|
9
|
+
# apn.device = APN::Device.find(1)
|
10
|
+
# apn.save
|
11
|
+
#
|
12
|
+
# To deliver call the following method:
|
13
|
+
# APN::Notification.send_notifications
|
14
|
+
#
|
15
|
+
# As each APN::Notification is sent the <tt>sent_at</tt> column will be timestamped,
|
16
|
+
# so as to not be sent again.
|
17
|
+
class APN::Notification < APN::Base
|
18
|
+
include ::ActionView::Helpers::TextHelper
|
19
|
+
extend ::ActionView::Helpers::TextHelper
|
20
|
+
serialize :custom_properties
|
21
|
+
|
22
|
+
belongs_to :device, :class_name => 'APN::Device'
|
23
|
+
has_one :app, :class_name => 'APN::App', :through => :device
|
24
|
+
|
25
|
+
# Stores the text alert message you want to send to the device.
|
26
|
+
#
|
27
|
+
# If the message is over 150 characters long it will get truncated
|
28
|
+
# to 150 characters with a <tt>...</tt>
|
29
|
+
def alert=(message)
|
30
|
+
if !message.blank? && message.size > 150
|
31
|
+
message = truncate(message, :length => 150)
|
32
|
+
end
|
33
|
+
write_attribute('alert', message)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a Hash that will be the payload of an APN.
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
# apn = APN::Notification.new
|
40
|
+
# apn.badge = 5
|
41
|
+
# apn.sound = 'my_sound.aiff'
|
42
|
+
# apn.alert = 'Hello!'
|
43
|
+
# apn.apple_hash # => {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
|
44
|
+
#
|
45
|
+
# Example 2:
|
46
|
+
# apn = APN::Notification.new
|
47
|
+
# apn.badge = 0
|
48
|
+
# apn.sound = true
|
49
|
+
# apn.custom_properties = {"typ" => 1}
|
50
|
+
# apn.apple_hash # => {"aps" => {"badge" => 0, "sound" => "1.aiff"}, "typ" => "1"}
|
51
|
+
def apple_hash
|
52
|
+
result = {}
|
53
|
+
result['aps'] = {}
|
54
|
+
result['aps']['alert'] = self.alert if self.alert
|
55
|
+
result['aps']['badge'] = self.badge.to_i if self.badge
|
56
|
+
if self.sound
|
57
|
+
result['aps']['sound'] = self.sound if self.sound.is_a? String
|
58
|
+
result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
|
59
|
+
end
|
60
|
+
if self.custom_properties
|
61
|
+
self.custom_properties.each do |key,value|
|
62
|
+
result["#{key}"] = "#{value}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
# Creates the JSON string required for an APN message.
|
69
|
+
#
|
70
|
+
# Example:
|
71
|
+
# apn = APN::Notification.new
|
72
|
+
# apn.badge = 5
|
73
|
+
# apn.sound = 'my_sound.aiff'
|
74
|
+
# apn.alert = 'Hello!'
|
75
|
+
# apn.to_apple_json # => '{"aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}'
|
76
|
+
def to_apple_json
|
77
|
+
self.apple_hash.to_json
|
78
|
+
end
|
79
|
+
|
80
|
+
# Creates the binary message needed to send to Apple.
|
81
|
+
def message_for_sending
|
82
|
+
json = self.to_apple_json
|
83
|
+
message = "\0\0 #{self.device.to_hexa}\0#{json.length.chr}#{json}"
|
84
|
+
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
|
85
|
+
message
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.send_notifications
|
89
|
+
ActiveSupport::Deprecation.warn("The method APN::Notification.send_notifications is deprecated. Use APN::App.send_notifications instead.")
|
90
|
+
APN::App.send_notifications
|
91
|
+
end
|
92
|
+
|
93
|
+
end # APN::Notification
|