gwong-apn_on_rails 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/.rspec +2 -0
  2. data/.specification +80 -0
  3. data/Gemfile +19 -0
  4. data/Gemfile.lock +47 -0
  5. data/LICENSE +21 -0
  6. data/README +179 -0
  7. data/README.textile +209 -0
  8. data/Rakefile +49 -0
  9. data/VERSION +1 -0
  10. data/apn_on_rails.gemspec +144 -0
  11. data/autotest/discover.rb +1 -0
  12. data/generators/apn_migrations_generator.rb +31 -0
  13. data/generators/templates/apn_migrations/001_create_apn_devices.rb +13 -0
  14. data/generators/templates/apn_migrations/002_create_apn_notifications.rb +23 -0
  15. data/generators/templates/apn_migrations/003_alter_apn_devices.rb +25 -0
  16. data/generators/templates/apn_migrations/004_create_apn_apps.rb +18 -0
  17. data/generators/templates/apn_migrations/005_create_groups.rb +23 -0
  18. data/generators/templates/apn_migrations/006_alter_apn_groups.rb +11 -0
  19. data/generators/templates/apn_migrations/007_create_device_groups.rb +27 -0
  20. data/generators/templates/apn_migrations/008_create_apn_group_notifications.rb +23 -0
  21. data/generators/templates/apn_migrations/009_create_pull_notifications.rb +16 -0
  22. data/generators/templates/apn_migrations/010_alter_apn_notifications.rb +21 -0
  23. data/generators/templates/apn_migrations/011_make_device_token_index_nonunique.rb +11 -0
  24. data/generators/templates/apn_migrations/012_add_launch_notification_to_apn_pull_notifications.rb +9 -0
  25. data/lib/apn_on_rails.rb +4 -0
  26. data/lib/apn_on_rails/apn_on_rails.rb +81 -0
  27. data/lib/apn_on_rails/app/models/apn/app.rb +150 -0
  28. data/lib/apn_on_rails/app/models/apn/base.rb +9 -0
  29. data/lib/apn_on_rails/app/models/apn/device.rb +49 -0
  30. data/lib/apn_on_rails/app/models/apn/device_grouping.rb +16 -0
  31. data/lib/apn_on_rails/app/models/apn/group.rb +12 -0
  32. data/lib/apn_on_rails/app/models/apn/group_notification.rb +79 -0
  33. data/lib/apn_on_rails/app/models/apn/notification.rb +93 -0
  34. data/lib/apn_on_rails/app/models/apn/pull_notification.rb +28 -0
  35. data/lib/apn_on_rails/libs/connection.rb +70 -0
  36. data/lib/apn_on_rails/libs/feedback.rb +39 -0
  37. data/lib/apn_on_rails/tasks/apn.rake +30 -0
  38. data/lib/apn_on_rails/tasks/db.rake +19 -0
  39. data/lib/apn_on_rails_tasks.rb +3 -0
  40. data/spec/active_record/setup_ar.rb +19 -0
  41. data/spec/apn_on_rails/app/models/apn/app_spec.rb +226 -0
  42. data/spec/apn_on_rails/app/models/apn/device_spec.rb +60 -0
  43. data/spec/apn_on_rails/app/models/apn/group_notification_spec.rb +66 -0
  44. data/spec/apn_on_rails/app/models/apn/notification_spec.rb +71 -0
  45. data/spec/apn_on_rails/app/models/apn/pull_notification_spec.rb +100 -0
  46. data/spec/apn_on_rails/libs/connection_spec.rb +40 -0
  47. data/spec/apn_on_rails/libs/feedback_spec.rb +43 -0
  48. data/spec/extensions/string.rb +10 -0
  49. data/spec/factories/app_factory.rb +27 -0
  50. data/spec/factories/device_factory.rb +29 -0
  51. data/spec/factories/device_grouping_factory.rb +22 -0
  52. data/spec/factories/group_factory.rb +27 -0
  53. data/spec/factories/group_notification_factory.rb +22 -0
  54. data/spec/factories/notification_factory.rb +22 -0
  55. data/spec/factories/pull_notification_factory.rb +22 -0
  56. data/spec/fixtures/hexa.bin +1 -0
  57. data/spec/fixtures/message_for_sending.bin +0 -0
  58. data/spec/rails_root/config/apple_push_notification_development.pem +19 -0
  59. data/spec/spec_helper.rb +55 -0
  60. 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
@@ -0,0 +1,9 @@
1
+ class AddLaunchNotificationToApnPullNotifications < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :apn_pull_notifications, :launch_notification, :boolean
4
+ end
5
+
6
+ def self.down
7
+ remove_column :apn_pull_notifications, :launch_notification
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'apn_on_rails', '**/*.rb')).sort.each do |f|
2
+ require File.expand_path(f)
3
+ end
4
+
@@ -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,9 @@
1
+ module APN
2
+ class Base < ActiveRecord::Base # :nodoc:
3
+
4
+ def self.table_name # :nodoc:
5
+ self.to_s.gsub("::", "_").tableize
6
+ end
7
+
8
+ end
9
+ 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