gwong-apn_on_rails 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|