urbanairship_on_rails 0.0.1
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/Gemfile +4 -0
- data/Gemfile.lock +12 -0
- data/MIT-LICENSE +20 -0
- data/README +188 -0
- data/Rakefile +26 -0
- data/generators/apn_migrations/apn_migrations_generator.rb +31 -0
- data/generators/apn_migrations/templates/apn_migrations/001_create_apn_devices.rb +15 -0
- data/generators/apn_migrations/templates/apn_migrations/002_create_apn_notifications.rb +23 -0
- data/generators/apn_migrations/templates/apn_migrations/003_create_apn_feedbacks.rb +17 -0
- data/generators/apn_migrations/templates/apn_migrations/004_create_apn_broadcast_notifications.rb +20 -0
- data/generators/apn_migrations/templates/apn_migrations/005_change_badge_to_string_on_notifications.rb +11 -0
- data/generators/apn_migrations/templates/apn_migrations/006_rename_errors_nb_to_last_error_code_on_notifications.rb +11 -0
- data/generators/apn_migrations/templates/apn_migrations/007_rename_errors_nb_to_last_error_code_on_broadcast_notifications.rb +11 -0
- data/generators/apn_migrations/templates/apn_migrations/008_add_custom_properties_on_notifications.rb +9 -0
- data/generators/apn_migrations/templates/apn_migrations/009_add_custom_properties_on_broadcast_notifications.rb +9 -0
- data/generators/apn_migrations/templates/apn_migrations/010_create_apn_excluded_devices_for_notifications.rb +17 -0
- data/generators/apn_migrations/templates/apn_migrations/011_add_response_code_message_and_body_to_devices.rb +15 -0
- data/generators/apn_migrations/templates/apn_migrations/012_add_last_inactive_at_to_devices.rb +9 -0
- data/init.rb +2 -0
- data/install.rb +1 -0
- data/lib/urbanairship_on_rails.rb +4 -0
- data/lib/urbanairship_on_rails/libs/config.rb +29 -0
- data/lib/urbanairship_on_rails/models/apn/base.rb +124 -0
- data/lib/urbanairship_on_rails/models/apn/broadcast_notification.rb +95 -0
- data/lib/urbanairship_on_rails/models/apn/device.rb +129 -0
- data/lib/urbanairship_on_rails/models/apn/excluded_devices_for_notification.rb +21 -0
- data/lib/urbanairship_on_rails/models/apn/feedback.rb +74 -0
- data/lib/urbanairship_on_rails/models/apn/notification.rb +101 -0
- data/lib/urbanairship_on_rails/urbanairship_on_rails.rb +21 -0
- data/lib/urbanairship_on_rails/version.rb +3 -0
- data/tasks/urban_airship_on_rails_tasks.rake +22 -0
- data/test/test_helper.rb +3 -0
- data/test/urban_airship_on_rails_test.rb +8 -0
- data/uninstall.rb +1 -0
- data/urbanairship_on_rails.gemspec +21 -0
- metadata +92 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
===================
|
2
|
+
URBAN AIRSHIP ON RAILS
|
3
|
+
===================
|
4
|
+
|
5
|
+
Borrowed Liberally from APN on Rails Gem but designed to interface with urbanairship for simplicity, ease of use, and speedy deployment of push notifications
|
6
|
+
uses identical data schema to APN.. perhaps allowing future upgrade to APN
|
7
|
+
|
8
|
+
=======
|
9
|
+
DEPENDENCIES
|
10
|
+
=======
|
11
|
+
requires JSON gem
|
12
|
+
AASM
|
13
|
+
|
14
|
+
Note: If you use the aasm gem, you may need to install the gem before you 'config.gem "aasm"' in your environment.rb or include it in your Gemfile.
|
15
|
+
|
16
|
+
=======
|
17
|
+
HOW TO INSTALL
|
18
|
+
=======
|
19
|
+
ruby script/plugin install git://github.com/yonnage/urbanairship_on_rails
|
20
|
+
|
21
|
+
=======
|
22
|
+
HOW TO GENERATE MIGRATIONS
|
23
|
+
=======
|
24
|
+
ruby script/generate apn_migrations
|
25
|
+
rake db:migrate
|
26
|
+
|
27
|
+
=======
|
28
|
+
HOW TO CONFIGURE
|
29
|
+
=======
|
30
|
+
|
31
|
+
=====
|
32
|
+
Create an initializer named urbanairship.rb with the following content
|
33
|
+
require File.dirname(__FILE__) + '/../../vendor/plugins/urbanairship_on_rails/lib/urbanairship_on_rails/libs/config'
|
34
|
+
UA::Config::app_key = 'YOUR_APP_KEY'
|
35
|
+
UA::Config::app_secret = 'YOUR_APP_SECRET'
|
36
|
+
UA::Config::push_secret = 'YOUR_PUSH_SECRET' ### CURRENTLY KNOWN AS "Application Master Secret" on Urban Airship
|
37
|
+
UA::Config::push_host = 'go.urbanairship.com'
|
38
|
+
UA::Config::push_port = '443'
|
39
|
+
|
40
|
+
=====
|
41
|
+
Set up a daily cron job to set inactive devices to "inactive"
|
42
|
+
rake apn:feedback or
|
43
|
+
script/runner APN:Feedback.create().run
|
44
|
+
|
45
|
+
Set up a cron job to push and manage notifications
|
46
|
+
rake apn:push or
|
47
|
+
script/runner APN::Notification.process_pending
|
48
|
+
|
49
|
+
Set up a cron job to push and manage broadcast notifications
|
50
|
+
rake apn:broadcast_push or
|
51
|
+
script/runner APN::BroadcastNotification.process_pending
|
52
|
+
|
53
|
+
|
54
|
+
=== Examples of Use
|
55
|
+
|
56
|
+
== Create and register a device:
|
57
|
+
|
58
|
+
# Create device:
|
59
|
+
## Requires a unique token. Token format should be: 5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz
|
60
|
+
|
61
|
+
device = APN::Device.find_or_create_by_token(:token => token)
|
62
|
+
|
63
|
+
|
64
|
+
#Register the device with Urban Airship
|
65
|
+
|
66
|
+
device.register
|
67
|
+
|
68
|
+
# Your device is now registered and ready to receive notifications.
|
69
|
+
|
70
|
+
|
71
|
+
# Your mobile application should try to register itself each time the application is launched, which is why it's important
|
72
|
+
# to do "find_or_create_by_token"
|
73
|
+
|
74
|
+
|
75
|
+
== Create and send a single simple Notification:
|
76
|
+
|
77
|
+
# Basic Options for Notifications:
|
78
|
+
# alert - The message the device will receive.
|
79
|
+
# badge - The number of badges (red circle with number) on the device.
|
80
|
+
# sound - This is the sound heard on the device. "default" is the standard device notification sound.
|
81
|
+
# device - The device to send the notification to.
|
82
|
+
|
83
|
+
n = APN::Notification.create(:alert => "Hello, World", :badge => 1, :sound => "default", :device => device)
|
84
|
+
|
85
|
+
# Now send the notification to the device:
|
86
|
+
|
87
|
+
n.push
|
88
|
+
|
89
|
+
|
90
|
+
== Create and send a single Notification with more options:
|
91
|
+
|
92
|
+
# Adding more options to a notification.
|
93
|
+
# sound - You can also use specific sounds for the alert which are already included as part of the mobile app.
|
94
|
+
# badge - Urban Airship supports auto increment / decrement of badges. Pass "+1" or "-1".
|
95
|
+
# custom_properties - Extra information you need to pass to the device.
|
96
|
+
|
97
|
+
n = APN::Notification.create(:alert => "Hello, World", :badge => "+1", :device => device, :sound => "alert1.wav", :custom_properties => {:name => "Bobby", :person_id => 7})
|
98
|
+
|
99
|
+
|
100
|
+
# Now send the notification to the device
|
101
|
+
|
102
|
+
n.push
|
103
|
+
|
104
|
+
|
105
|
+
== Send all pending notifications:
|
106
|
+
|
107
|
+
# Instead of sending each notification as it is created, you can send them all at once.
|
108
|
+
|
109
|
+
APN::Notification.process_pending
|
110
|
+
|
111
|
+
|
112
|
+
# Or setup a cron rake task:
|
113
|
+
|
114
|
+
rake apn:push
|
115
|
+
|
116
|
+
|
117
|
+
== Broadcast Notifications
|
118
|
+
|
119
|
+
# Send the same notification to all devices.
|
120
|
+
# Use this to avoid making multiple connections if you are sending the same message to all devices.
|
121
|
+
|
122
|
+
APN::BroadcastNotification.create(:alert => "Hello, World", :badge => 1, :sound => "default")
|
123
|
+
|
124
|
+
|
125
|
+
# Push the BroadcastNotification
|
126
|
+
|
127
|
+
APN::BroadcastNotification.process_pending
|
128
|
+
|
129
|
+
|
130
|
+
== Broadcast Notifications with Device exclusions.
|
131
|
+
|
132
|
+
# You can exclude devices from receiving a broadcast notification.
|
133
|
+
|
134
|
+
n = APN::BroadcastNotification.create(:alert => "Hello, World", :badge => 1, :sound => "default")
|
135
|
+
|
136
|
+
|
137
|
+
# Exclude any devices that should not receive the above notification.
|
138
|
+
|
139
|
+
n.excluded_devices.create(:broadcast_notification => n, :device => APN::Device.find(1))
|
140
|
+
n.excluded_devices.create(:broadcast_notification => n, :device => APN::Device.find(2))
|
141
|
+
n.excluded_devices.create(:broadcast_notification => n, :device => APN::Device.find(3))
|
142
|
+
|
143
|
+
|
144
|
+
# Push the Broadcast Notification:
|
145
|
+
|
146
|
+
n.push
|
147
|
+
|
148
|
+
# -- OR -- Push all pending Broadcast Notifications:
|
149
|
+
|
150
|
+
APN::BroadcastNotification.process_pending
|
151
|
+
|
152
|
+
|
153
|
+
== Feedback for Devices
|
154
|
+
|
155
|
+
# When a device no longer accepts notifications from your application because of reasons such as the application being removed,
|
156
|
+
# Urban Airship will stop trying to send notifications to those devices. You should run the "feedback" task daily to find and
|
157
|
+
# mark all inactive devices as such.
|
158
|
+
|
159
|
+
rake apn:feedback
|
160
|
+
|
161
|
+
|
162
|
+
=======
|
163
|
+
API CALLS and current support level
|
164
|
+
=======
|
165
|
+
|
166
|
+
REGISTRATION
|
167
|
+
|
168
|
+
APN::Device.register => HTTP PUT to /api/device_tokens/<device_token>
|
169
|
+
APN::Device.read => HTTP GET to /api/device_tokens/<device_token>
|
170
|
+
APN::Device.unregister => HTTP DELETE to /api/device_tokens/<device_token>
|
171
|
+
|
172
|
+
PUSH
|
173
|
+
APN::Notifcation.push => HTTP POST to /api/push/
|
174
|
+
|
175
|
+
BATCH PUSH
|
176
|
+
NOT YET SUPPORTED
|
177
|
+
|
178
|
+
BROADCAST
|
179
|
+
APN::BroadcastNotifcation.push => HTTP POST to /api/push/broadcast/
|
180
|
+
|
181
|
+
FEEDBACK SERVICE
|
182
|
+
APN::Feedback.create().run => HTTP GET to /api/device_tokens/feedback/?since=<timestamp>
|
183
|
+
|
184
|
+
STATISTICS
|
185
|
+
NOT YET SUPPORTED
|
186
|
+
|
187
|
+
|
188
|
+
Copyright (c) 2009 [Russell Sherman(code-ronin@code-ronin.com)], released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
require 'bundler'
|
6
|
+
Bundler::GemHelper.install_tasks
|
7
|
+
|
8
|
+
desc 'Default: run unit tests.'
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
desc 'Test the urban_airship_on_rails plugin.'
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << 'lib'
|
14
|
+
t.libs << 'test'
|
15
|
+
t.pattern = 'test/**/*_test.rb'
|
16
|
+
t.verbose = true
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Generate documentation for the urban_airship_on_rails plugin.'
|
20
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
21
|
+
rdoc.rdoc_dir = 'rdoc'
|
22
|
+
rdoc.title = 'UrbanAirshipOnRails'
|
23
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
24
|
+
rdoc.rdoc_files.include('README')
|
25
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
26
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails_generator'
|
2
|
+
# Generates the migrations necessary for APN on Rails.
|
3
|
+
# This should be run upon install and upgrade of the
|
4
|
+
# APN on Rails gem.
|
5
|
+
#
|
6
|
+
# $ ruby script/generate apn_migrations
|
7
|
+
class ApnMigrationsGenerator < Rails::Generator::Base
|
8
|
+
|
9
|
+
def manifest # :nodoc:
|
10
|
+
record do |m|
|
11
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
db_migrate_path = File.join('db', 'migrate')
|
13
|
+
|
14
|
+
m.directory(db_migrate_path)
|
15
|
+
|
16
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'templates', 'apn_migrations', '*.rb')).sort.each_with_index do |f, i|
|
17
|
+
f = File.basename(f)
|
18
|
+
f.match(/\d+\_(.+)/)
|
19
|
+
timestamp = timestamp.succ
|
20
|
+
if Dir.glob(File.join(db_migrate_path, "*_#{$1}")).empty?
|
21
|
+
m.file(File.join('apn_migrations', f),
|
22
|
+
File.join(db_migrate_path, "#{timestamp}_#{$1}"),
|
23
|
+
{:collision => :skip})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end # record
|
28
|
+
|
29
|
+
end # manifest
|
30
|
+
|
31
|
+
end # ApnMigrationsGenerator
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateApnDevices < ActiveRecord::Migration # :nodoc:
|
2
|
+
def self.up
|
3
|
+
create_table :apn_devices do |t|
|
4
|
+
t.string :token, :size => 100, :null => false
|
5
|
+
t.datetime :last_registered_at
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
add_index :apn_devices, :token, :unique => true
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.down
|
12
|
+
remove_index :apn_devices, :column => :token
|
13
|
+
drop_table :apn_devices
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CreateApnNotifications < ActiveRecord::Migration # :nodoc:
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
create_table :apn_notifications do |t|
|
5
|
+
t.integer :device_id, :null => false
|
6
|
+
t.integer :errors_nb, :default => 0 # used for storing errors from apple feedbacks
|
7
|
+
t.string :device_language, :size => 5 # if you don't want to send localized strings
|
8
|
+
t.string :sound
|
9
|
+
t.string :alert, :size => 150
|
10
|
+
t.integer :badge
|
11
|
+
t.datetime :sent_at
|
12
|
+
t.string :state
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
add_index :apn_notifications, :device_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.down
|
19
|
+
remove_index :apn_notifications, :device_id
|
20
|
+
drop_table :apn_notifications
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateApnFeedbacks < ActiveRecord::Migration # :nodoc:
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
create_table :apn_feedbacks do |t|
|
5
|
+
t.string :code
|
6
|
+
t.string :message
|
7
|
+
t.text :body
|
8
|
+
t.string :state
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
drop_table :apn_feedbacks
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/generators/apn_migrations/templates/apn_migrations/004_create_apn_broadcast_notifications.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class CreateApnBroadcastNotifications < ActiveRecord::Migration # :nodoc:
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
create_table :apn_broadcast_notifications do |t|
|
5
|
+
t.integer :errors_nb, :default => 0 # used for storing errors from apple feedbacks
|
6
|
+
t.string :device_language, :size => 5 # if you don't want to send localized strings
|
7
|
+
t.string :sound
|
8
|
+
t.string :alert, :size => 150
|
9
|
+
t.string :badge
|
10
|
+
t.datetime :sent_at
|
11
|
+
t.string :state
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.down
|
17
|
+
drop_table :apn_broadcast_notifications
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class RenameErrorsNbToLastErrorCodeOnNotifications < ActiveRecord::Migration # :nodoc:
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
rename_column :apn_notifications, :errors_nb, :last_response_code
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
rename_column :apn_notifications, :last_response_code, :errors_nb
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class RenameErrorsNbToLastErrorCodeOnBroadcastNotifications < ActiveRecord::Migration # :nodoc:
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
rename_column :apn_broadcast_notifications, :errors_nb, :last_response_code
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
rename_column :apn_broadcast_notifications, :last_response_code, :errors_nb
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class AddCustomPropertiesOnBroadcastNotifications < ActiveRecord::Migration # :nodoc:
|
2
|
+
def self.up
|
3
|
+
add_column :apn_broadcast_notifications, :custom_properties, :text, :after => :badge
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.down
|
7
|
+
remove_column :apn_broadcast_notifications, :custom_properties
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateApnExcludedDevicesForNotifications < ActiveRecord::Migration # :nodoc:
|
2
|
+
def self.up
|
3
|
+
create_table :apn_excluded_devices_for_notifications do |t|
|
4
|
+
t.integer :device_id
|
5
|
+
t.integer :notification_id
|
6
|
+
t.integer :broadcast_notification_id
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
add_index(:apn_excluded_devices_for_notifications, [:device_id, :notification_id], :unique => true, :name => 'by_device_notfication')
|
10
|
+
add_index(:apn_excluded_devices_for_notifications, [:device_id, :broadcast_notification_id], :unique => true, :name => 'by_device_broadcast_notfication')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
remove_index :apn_excluded_devices_for_notifications, :device_id
|
15
|
+
drop_table :apn_excluded_devices_for_notifications
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class AddLastInactiveAtToDevices < ActiveRecord::Migration # :nodoc:
|
2
|
+
def self.up
|
3
|
+
add_column :apn_devices, :state, :string, :default => 'created', :after => :token
|
4
|
+
add_column :apn_devices, :response_code, :string, :after => :state
|
5
|
+
add_column :apn_devices, :response_message, :string, :after => :response_code
|
6
|
+
add_column :apn_devices, :response_body, :text, :after => :response_message
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
remove_column :apn_devices, :state
|
11
|
+
remove_column :apn_devices, :response_code
|
12
|
+
remove_column :apn_devices, :response_message
|
13
|
+
remove_column :apn_devices, :response_body
|
14
|
+
end
|
15
|
+
end
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module UA
|
2
|
+
|
3
|
+
module Config
|
4
|
+
|
5
|
+
@@app_key = 'YOUR_APP_KEY'
|
6
|
+
@@app_secret = 'YOUR_APP_SECRET'
|
7
|
+
@@push_secret = 'YOUR_PUSH_SECRET'
|
8
|
+
@@push_host = 'go.urbanairship.com'
|
9
|
+
@@push_port = 443
|
10
|
+
|
11
|
+
[:app_key, :app_secret, :push_secret, :push_host, :push_port].each do |sym|
|
12
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
13
|
+
def self.#{sym}
|
14
|
+
if defined?(#{sym.to_s.upcase})
|
15
|
+
#{sym.to_s.upcase}
|
16
|
+
else
|
17
|
+
@@#{sym}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
def self.#{sym}=(obj)
|
21
|
+
@@#{sym} = obj
|
22
|
+
end
|
23
|
+
EOS
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end # Config
|
28
|
+
|
29
|
+
end # UA
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
module APN
|
5
|
+
|
6
|
+
class ConnectionError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class RetriableConnectionError < ConnectionError
|
10
|
+
end
|
11
|
+
|
12
|
+
class Base < ActiveRecord::Base # :nodoc:
|
13
|
+
|
14
|
+
MAX_RETRIES = 3
|
15
|
+
OPEN_TIMEOUT = 60
|
16
|
+
READ_TIMEOUT = 60
|
17
|
+
|
18
|
+
def self.table_name # :nodoc:
|
19
|
+
self.to_s.gsub("::", "_").tableize
|
20
|
+
end
|
21
|
+
|
22
|
+
def http_get(url, data=nil, headers={}, push=false)
|
23
|
+
puts "APN::Base.http_get"
|
24
|
+
http_request(:get, url, data, headers, push)
|
25
|
+
end
|
26
|
+
|
27
|
+
def http_post(url, data=nil, headers={}, push=false)
|
28
|
+
puts "APN::Base.http_post"
|
29
|
+
http_request(:post, url, data, headers, push)
|
30
|
+
end
|
31
|
+
|
32
|
+
def http_put(url, data=nil, headers={}, push=false)
|
33
|
+
puts "APN::Base.http_put"
|
34
|
+
http_request(:put, url, data, headers, push)
|
35
|
+
end
|
36
|
+
|
37
|
+
def http_delete(url, data=nil, headers={}, push=false)
|
38
|
+
puts "APN::Base.http_delete"
|
39
|
+
http_request(:delete, url, data, headers, push)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def self.open_timeout
|
45
|
+
OPEN_TIMEOUT
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.read_timeout
|
49
|
+
READ_TIMEOUT
|
50
|
+
end
|
51
|
+
|
52
|
+
def retry_exceptions
|
53
|
+
retries = MAX_RETRIES
|
54
|
+
begin
|
55
|
+
yield
|
56
|
+
rescue RetriableConnectionError => e
|
57
|
+
puts e
|
58
|
+
retries -= 1
|
59
|
+
retry unless retries.zero?
|
60
|
+
raise ConnectionError, e.message
|
61
|
+
rescue ConnectionError
|
62
|
+
puts e
|
63
|
+
retries -= 1
|
64
|
+
retry if retry_safe && !retries.zero?
|
65
|
+
raise
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def http_request(method, url, data=nil, headers={}, push=false)
|
70
|
+
headers['Content-Type'] = 'application/json'
|
71
|
+
|
72
|
+
puts "APN::Base.http_request(#{method.inspect}, #{url.inspect}, #{data.inspect}, #{push.inspect})"
|
73
|
+
|
74
|
+
case method
|
75
|
+
when :get
|
76
|
+
req = Net::HTTP::Get.new(url, headers)
|
77
|
+
when :put
|
78
|
+
req = Net::HTTP::Put.new(url, headers)
|
79
|
+
when :post
|
80
|
+
req = Net::HTTP::Post.new(url, headers)
|
81
|
+
when :delete
|
82
|
+
req = Net::HTTP::Delete.new(url, headers)
|
83
|
+
end
|
84
|
+
|
85
|
+
if push
|
86
|
+
req.basic_auth(UA::Config::app_key, UA::Config::push_secret)
|
87
|
+
else
|
88
|
+
req.basic_auth(UA::Config::app_key, UA::Config::app_secret)
|
89
|
+
end
|
90
|
+
|
91
|
+
puts "auth #{UA::Config::app_key}:#{UA::Config::app_secret}"
|
92
|
+
|
93
|
+
if data
|
94
|
+
req.body = data.to_json
|
95
|
+
else
|
96
|
+
req.body = "{}"
|
97
|
+
end
|
98
|
+
puts "body #{req.body}"
|
99
|
+
|
100
|
+
puts "#{UA::Config::push_host}:#{UA::Config::push_port}"
|
101
|
+
http = Net::HTTP.new(UA::Config::push_host, UA::Config::push_port)
|
102
|
+
http.use_ssl = true
|
103
|
+
|
104
|
+
http.start {|it|
|
105
|
+
retry_exceptions do
|
106
|
+
begin
|
107
|
+
response = it.request(req)
|
108
|
+
puts "\nResponse #{response.code} #{response.message}:#{response.body}"
|
109
|
+
response
|
110
|
+
rescue EOFError => e
|
111
|
+
raise ConnectionError, "The remote server dropped the connection"
|
112
|
+
rescue Errno::ECONNRESET => e
|
113
|
+
raise ConnectionError, "The remote server reset the connection"
|
114
|
+
rescue Errno::ECONNREFUSED => e
|
115
|
+
raise RetriableConnectionError, "The remote server refused the connection"
|
116
|
+
rescue Timeout::Error, Errno::ETIMEDOUT => e
|
117
|
+
raise ConnectionError, "The connection to the remote server timed out"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# Represents the message you wish to send.
|
2
|
+
# An APN::Notification belongs to an APN::Device.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
# apn = APN::BroadcastNotification.new
|
6
|
+
# apn.badge = 5
|
7
|
+
# apn.sound = 'my_sound.aiff'
|
8
|
+
# apn.alert = 'Hello!'
|
9
|
+
# apn.custom_properties = {:email_id => "6", :link => "http://google.com"}
|
10
|
+
# apn.save
|
11
|
+
#
|
12
|
+
# To deliver call the following method:
|
13
|
+
# APN::BroadcastNotification.process_pending
|
14
|
+
#
|
15
|
+
# As each APN::BroadcastNotification is sent the <tt>sent_at</tt> column will be timestamped,
|
16
|
+
# so as to not be sent again.
|
17
|
+
class APN::BroadcastNotification < APN::Base
|
18
|
+
include AASM
|
19
|
+
include ::ActionView::Helpers::TextHelper
|
20
|
+
extend ::ActionView::Helpers::TextHelper
|
21
|
+
|
22
|
+
has_many :excluded_devices, :class_name => 'APN::ExcludedDevicesForNotification'
|
23
|
+
|
24
|
+
serialize :custom_properties
|
25
|
+
|
26
|
+
#
|
27
|
+
# MODEL STATE MACHINE
|
28
|
+
#
|
29
|
+
aasm_initial_state :pending
|
30
|
+
aasm_column :state
|
31
|
+
|
32
|
+
aasm_state :pending
|
33
|
+
aasm_state :processed, :enter => :update_sent_at
|
34
|
+
|
35
|
+
aasm_event :process do
|
36
|
+
transitions :from => :pending, :to => :processed, :guard => :check_response
|
37
|
+
end
|
38
|
+
|
39
|
+
def apple_hash
|
40
|
+
result = {}
|
41
|
+
result['aps'] = {}
|
42
|
+
result['aps']['alert'] = self.alert if self.alert
|
43
|
+
result['aps']['badge'] = convert(self.badge) if self.badge
|
44
|
+
if self.sound
|
45
|
+
result['aps']['sound'] = self.sound if self.sound
|
46
|
+
end
|
47
|
+
if self.custom_properties
|
48
|
+
self.custom_properties.each do |key,value|
|
49
|
+
result["#{key}"] = "#{value}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
result['exclude_tokens'] = []
|
54
|
+
self.excluded_devices.each do |excluded|
|
55
|
+
result['exclude_tokens'] << excluded.device.token_for_ua
|
56
|
+
end
|
57
|
+
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
def push
|
62
|
+
puts "process #{self.inspect}"
|
63
|
+
result = http_post("/api/push/broadcast/", apple_hash, {}, true)
|
64
|
+
self.last_response_code = result.code
|
65
|
+
self.save
|
66
|
+
self.process!
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_sent_at
|
70
|
+
self.sent_at = Time.now
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.process_pending
|
74
|
+
self.pending.each do |n|
|
75
|
+
n.push
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def check_response
|
80
|
+
self.last_response_code == 200 ? true : false
|
81
|
+
end
|
82
|
+
|
83
|
+
def convert(input)
|
84
|
+
if input.to_i > 0 && input.to_i.to_s == input
|
85
|
+
return input.to_i
|
86
|
+
else
|
87
|
+
return input
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def check_response
|
92
|
+
self.last_response_code == 200 ? true : false
|
93
|
+
end
|
94
|
+
|
95
|
+
end # APN::Notification
|
@@ -0,0 +1,129 @@
|
|
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
|
+
include AASM
|
13
|
+
|
14
|
+
before_destroy :unregister, :destroy_undelivered_notifications, :destroy_undelivered_broadcast_exclusions
|
15
|
+
# before_save :set_last_registered_at
|
16
|
+
|
17
|
+
belongs_to :user, :dependent => :delete
|
18
|
+
has_many :notifications, :class_name => 'APN::Notification'
|
19
|
+
has_many :exclusions_from_notifications, :class_name => 'APN::ExcludedDevicesForNotification'
|
20
|
+
|
21
|
+
validates_presence_of :token
|
22
|
+
validates_uniqueness_of :token
|
23
|
+
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}$/
|
24
|
+
|
25
|
+
# The <tt>feedback_at</tt> accessor is set when the
|
26
|
+
# device is marked as potentially disconnected from your
|
27
|
+
# application by Apple.
|
28
|
+
attr_accessor :feedback_at
|
29
|
+
|
30
|
+
aasm_initial_state :created
|
31
|
+
aasm_column :state
|
32
|
+
|
33
|
+
aasm_state :created
|
34
|
+
aasm_state :activated, :enter => :set_last_registered_at
|
35
|
+
aasm_state :inactive, :enter => :set_last_inactive_at
|
36
|
+
|
37
|
+
aasm_event :activate do
|
38
|
+
transitions :from => [:created, :inactive, :activated], :to => :activated, :guard => :check_response
|
39
|
+
end
|
40
|
+
|
41
|
+
aasm_event :deactivate do
|
42
|
+
transitions :from => [:created, :inactive, :activated], :to => :inactive
|
43
|
+
end
|
44
|
+
|
45
|
+
# Stores the token (Apple's device ID) of the iPhone (device).
|
46
|
+
#
|
47
|
+
# If the token comes in like this:
|
48
|
+
# '<5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz>'
|
49
|
+
# Then the '<' and '>' will be stripped off.
|
50
|
+
|
51
|
+
def token=(token)
|
52
|
+
res = token.scan(/\<(.+)\>/).first
|
53
|
+
unless res.nil? || res.empty?
|
54
|
+
token = res.first
|
55
|
+
end
|
56
|
+
write_attribute('token', token)
|
57
|
+
end
|
58
|
+
|
59
|
+
def register(options=nil)
|
60
|
+
puts "APN::Device.register"
|
61
|
+
# options = options.merge({:alias => self.user.id}) if self.user
|
62
|
+
result = http_put("/api/device_tokens/#{self.token_for_ua}", options)
|
63
|
+
self.response_code = result.code.to_s
|
64
|
+
self.response_message = result.message.to_s
|
65
|
+
self.response_body = result.body.to_s
|
66
|
+
self.save
|
67
|
+
self.activate!
|
68
|
+
end
|
69
|
+
|
70
|
+
# You can read a device token’s alias with an HTTP GET to /api/device_tokens/<device_token>, which returns application/json:
|
71
|
+
# {"device_token": "some device token","alias": "your_user_id"}
|
72
|
+
def read
|
73
|
+
puts "APN::Device.read"
|
74
|
+
http_get("/api/device_tokens/#{self.token_for_ua}")
|
75
|
+
end
|
76
|
+
|
77
|
+
# The DELETE returns HTTP 204 No Content, and needs no payload.
|
78
|
+
# When a token is DELETEd in this manner, any alias or tags will be cleared.
|
79
|
+
|
80
|
+
def token_for_ua
|
81
|
+
self.token.gsub(' ', '').upcase
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.find_by_ua_token(ua_token)
|
85
|
+
find_by_token(ua_token.downcase.scan(/.{8}/).join(" "))
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def unregister
|
91
|
+
puts "APN::Device.unregister"
|
92
|
+
http_delete("/api/device_tokens/#{self.token_for_ua}")
|
93
|
+
end
|
94
|
+
|
95
|
+
def destroy_undelivered_notifications
|
96
|
+
self.notifications.pending.each do |notification|
|
97
|
+
notification.excluded_devices.first.destroy unless notification.excluded_devices.first.nil?
|
98
|
+
notification.destroy
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def destroy_undelivered_broadcast_exclusions
|
103
|
+
self.exclusions_from_notifications.broadcasts.each do |exclusion|
|
104
|
+
unless exclusion.broadcast_notification.nil?
|
105
|
+
exclusion.destroy unless exclusion.broadcast_notification.processed?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_last_registered_at
|
111
|
+
self.last_registered_at = Time.now
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_last_inactive_at
|
115
|
+
self.last_inactive_at = Time.now
|
116
|
+
self.notifications.pending.each do |n|
|
117
|
+
n.set_to_inactive!
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def check_response
|
122
|
+
if self.response_code == "200" || self.response_code == "201"
|
123
|
+
return true
|
124
|
+
else
|
125
|
+
self.deactivate!
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class APN::ExcludedDevicesForNotification < APN::Base
|
2
|
+
|
3
|
+
belongs_to :broadcast_notification, :class_name => 'APN::BroadcastNotification'
|
4
|
+
belongs_to :notification, :class_name => 'APN::Notification'
|
5
|
+
belongs_to :device, :class_name => 'APN::Device'
|
6
|
+
|
7
|
+
validates_presence_of :device, :on => :create, :message => "is required"
|
8
|
+
validates_presence_of :notification, :on => :create, :message => "or BroadcastNotification is required", :unless => :has_broadcast_notification?
|
9
|
+
|
10
|
+
validates_uniqueness_of :notification_id, :scope => :device_id, :allow_nil => true
|
11
|
+
validates_uniqueness_of :broadcast_notification_id, :scope => :device_id, :allow_nil => true
|
12
|
+
|
13
|
+
named_scope :broadcasts, lambda {
|
14
|
+
{ :conditions => "broadcast_notification_id IS NOT NULL" }
|
15
|
+
}
|
16
|
+
|
17
|
+
|
18
|
+
def has_broadcast_notification?
|
19
|
+
broadcast_notification.nil? ? false : true
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'json'
|
2
|
+
#
|
3
|
+
# Apple notifies UA when a notification is sent to a device that has push notifications enabled (for at least one application)
|
4
|
+
# but has turned off or uninstalled your application. We immediate stop sending notifications through to avoid angering Apple.
|
5
|
+
#
|
6
|
+
# To query what device tokens are now invalid, do an HTTP GET to /api/device_tokens/feedback/?since=<timestamp> with the push secret;
|
7
|
+
# the query argument since is required, and is a timestamp in ISO 8601 format, e.g. /api/device_tokens/feedback/?since=2009-06-01+13:00:00.
|
8
|
+
# The return value is application/json with the following structure:
|
9
|
+
#
|
10
|
+
class APN::Feedback < APN::Base
|
11
|
+
include AASM
|
12
|
+
|
13
|
+
named_scope :last_feedback_request, lambda {
|
14
|
+
{ :conditions => "state = 'processed'", :order => "id DESC" }
|
15
|
+
}
|
16
|
+
|
17
|
+
#
|
18
|
+
# MODEL STATE MACHINE
|
19
|
+
#
|
20
|
+
aasm_initial_state :pending
|
21
|
+
aasm_column :state
|
22
|
+
|
23
|
+
aasm_state :pending
|
24
|
+
aasm_state :active
|
25
|
+
aasm_state :processed
|
26
|
+
|
27
|
+
aasm_event :pend do
|
28
|
+
transitions :from => [:active, :processed], :to => :pending
|
29
|
+
end
|
30
|
+
|
31
|
+
aasm_event :activate do
|
32
|
+
transitions :from => [:pending, :processed], :to => :active
|
33
|
+
end
|
34
|
+
|
35
|
+
aasm_event :process do
|
36
|
+
transitions :from => :active, :to => :processed
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
raise "save feedback record before running" if self.new_record?
|
41
|
+
get_feedback { |results|
|
42
|
+
|
43
|
+
puts results.inspect
|
44
|
+
if results.code.to_i == 200
|
45
|
+
result = JSON.parse(results.body) # parse json results
|
46
|
+
result.each do |item| # iterate results and delete devices that have been deactivated
|
47
|
+
d = APN::Device.find_by_ua_token(item['device_token'])
|
48
|
+
d.deactivate! if d
|
49
|
+
end
|
50
|
+
self.process!
|
51
|
+
end
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_feedback
|
56
|
+
self.activate!
|
57
|
+
# puts "APN::get_feedback"
|
58
|
+
# time = 1.day.ago.iso8601
|
59
|
+
time = last_feedback_time
|
60
|
+
# puts " since #{time}"
|
61
|
+
result = http_get("/api/device_tokens/feedback/?since=#{time}", nil, {}, true)
|
62
|
+
self.code = result.code.to_s
|
63
|
+
self.message = result.message.to_s
|
64
|
+
self.body = result.body.to_s
|
65
|
+
|
66
|
+
yield result if block_given?
|
67
|
+
end
|
68
|
+
|
69
|
+
def last_feedback_time
|
70
|
+
f = APN::Feedback.last_feedback_request.first
|
71
|
+
f.nil? ? Time.at(0).iso8601 : f.created_at.iso8601
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,101 @@
|
|
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.custom_properties = {:email_id => "6", :link => "http://google.com"}
|
10
|
+
# apn.device = APN::Device.find(1)
|
11
|
+
# apn.save
|
12
|
+
#
|
13
|
+
# To deliver call the following method:
|
14
|
+
# APN::Notification.process_pending
|
15
|
+
#
|
16
|
+
|
17
|
+
class APN::Notification < APN::Base
|
18
|
+
include AASM
|
19
|
+
include ::ActionView::Helpers::TextHelper
|
20
|
+
extend ::ActionView::Helpers::TextHelper
|
21
|
+
|
22
|
+
serialize :custom_properties
|
23
|
+
|
24
|
+
has_many :excluded_devices, :class_name => 'APN::ExcludedDevicesForNotification'
|
25
|
+
belongs_to :device, :class_name => 'APN::Device'
|
26
|
+
|
27
|
+
validates_presence_of :device, :on => :create, :message => "can't be blank"
|
28
|
+
validate_on_create :device_state
|
29
|
+
|
30
|
+
#
|
31
|
+
# MODEL STATE MACHINE
|
32
|
+
#
|
33
|
+
aasm_initial_state :pending
|
34
|
+
aasm_column :state
|
35
|
+
|
36
|
+
aasm_state :pending
|
37
|
+
aasm_state :processed, :enter => :update_sent_at
|
38
|
+
aasm_state :inactive_device
|
39
|
+
|
40
|
+
aasm_event :process do
|
41
|
+
transitions :from => :pending, :to => :processed, :guard => :check_response
|
42
|
+
end
|
43
|
+
|
44
|
+
aasm_event :set_to_inactive do
|
45
|
+
transitions :from => :pending, :to => :inactive_device
|
46
|
+
end
|
47
|
+
|
48
|
+
def apple_hash
|
49
|
+
result = {}
|
50
|
+
result['aps'] = {}
|
51
|
+
result['aps']['alert'] = self.alert if self.alert
|
52
|
+
result['aps']['badge'] = convert(self.badge) if self.badge
|
53
|
+
if self.sound
|
54
|
+
result['aps']['sound'] = self.sound if self.sound
|
55
|
+
end
|
56
|
+
if self.custom_properties
|
57
|
+
self.custom_properties.each do |key,value|
|
58
|
+
result["#{key}"] = "#{value}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
result['device_tokens'] = self.device.token_for_ua
|
62
|
+
result
|
63
|
+
end
|
64
|
+
|
65
|
+
def push
|
66
|
+
puts "process #{self.inspect}"
|
67
|
+
result = http_post("/api/push/", apple_hash, {}, true)
|
68
|
+
self.last_response_code = result.code
|
69
|
+
self.save
|
70
|
+
self.process!
|
71
|
+
end
|
72
|
+
|
73
|
+
def update_sent_at
|
74
|
+
self.sent_at = Time.now
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.process_pending
|
78
|
+
self.pending.each do |n|
|
79
|
+
unless n.device.inactive?
|
80
|
+
n.push
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def convert(input)
|
86
|
+
if input.to_i > 0 && input.to_i.to_s == input
|
87
|
+
return input.to_i
|
88
|
+
else
|
89
|
+
return input
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def check_response
|
94
|
+
self.last_response_code == 200 ? true : false
|
95
|
+
end
|
96
|
+
|
97
|
+
def device_state
|
98
|
+
errors.add(:device, "must not be marked as inactive") if device.inactive?
|
99
|
+
end
|
100
|
+
|
101
|
+
end # APN::Notification
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
# ############################# this belongs in initializer
|
4
|
+
# require File.dirname(__FILE__) + '/libs/config'
|
5
|
+
# UA::Config::app_key = 'YOUR_APP_KEY'
|
6
|
+
# UA::Config::app_secret = 'YOUR_APP_SECRET'
|
7
|
+
# UA::Config::push_secret = 'YOUR_PUSH_SECRET'
|
8
|
+
# UA::Config::push_host = 'https://go.urbanairship.com'
|
9
|
+
# #############################
|
10
|
+
|
11
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'app', 'models', 'apn', '*.rb')).sort.each do |f|
|
12
|
+
require f
|
13
|
+
end
|
14
|
+
|
15
|
+
%w{ models controllers helpers }.each do |dir|
|
16
|
+
path = File.join(File.dirname(__FILE__), 'app', dir)
|
17
|
+
$LOAD_PATH << path
|
18
|
+
# puts "Adding #{path}"
|
19
|
+
ActiveSupport::Dependencies.load_paths << path
|
20
|
+
ActiveSupport::Dependencies.load_once_paths.delete(path)
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# desc "Explaining what the task does"
|
2
|
+
# task :urban_airship_on_rails do
|
3
|
+
# # Task goes here
|
4
|
+
# end
|
5
|
+
|
6
|
+
namespace :apn do
|
7
|
+
desc "retreive and process list of inactive devices"
|
8
|
+
task :feedback => [:environment] do
|
9
|
+
APN::Feedback.create().run
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "send all pending notifications to devices"
|
13
|
+
task :push => [:environment] do
|
14
|
+
APN::Notification.process_pending
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "send all pending broadcast notifications to devices"
|
18
|
+
task :broadcast_push => [:environment] do
|
19
|
+
APN::BroadcastNotification.process_pending
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/test/test_helper.rb
ADDED
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "urbanairship_on_rails/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "urbanairship_on_rails"
|
7
|
+
s.version = UrbanairshipOnRails::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["John Kassimatis"]
|
10
|
+
s.email = ["yonnage@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{URBAN AIRSHIP ON RAILS}
|
13
|
+
s.description = %q{push notifications}
|
14
|
+
|
15
|
+
s.rubyforge_project = "urbanairship_on_rails"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: urbanairship_on_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- John Kassimatis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-03-23 00:00:00 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: push notifications
|
18
|
+
email:
|
19
|
+
- yonnage@gmail.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- Gemfile
|
28
|
+
- Gemfile.lock
|
29
|
+
- MIT-LICENSE
|
30
|
+
- README
|
31
|
+
- Rakefile
|
32
|
+
- generators/apn_migrations/apn_migrations_generator.rb
|
33
|
+
- generators/apn_migrations/templates/apn_migrations/001_create_apn_devices.rb
|
34
|
+
- generators/apn_migrations/templates/apn_migrations/002_create_apn_notifications.rb
|
35
|
+
- generators/apn_migrations/templates/apn_migrations/003_create_apn_feedbacks.rb
|
36
|
+
- generators/apn_migrations/templates/apn_migrations/004_create_apn_broadcast_notifications.rb
|
37
|
+
- generators/apn_migrations/templates/apn_migrations/005_change_badge_to_string_on_notifications.rb
|
38
|
+
- generators/apn_migrations/templates/apn_migrations/006_rename_errors_nb_to_last_error_code_on_notifications.rb
|
39
|
+
- generators/apn_migrations/templates/apn_migrations/007_rename_errors_nb_to_last_error_code_on_broadcast_notifications.rb
|
40
|
+
- generators/apn_migrations/templates/apn_migrations/008_add_custom_properties_on_notifications.rb
|
41
|
+
- generators/apn_migrations/templates/apn_migrations/009_add_custom_properties_on_broadcast_notifications.rb
|
42
|
+
- generators/apn_migrations/templates/apn_migrations/010_create_apn_excluded_devices_for_notifications.rb
|
43
|
+
- generators/apn_migrations/templates/apn_migrations/011_add_response_code_message_and_body_to_devices.rb
|
44
|
+
- generators/apn_migrations/templates/apn_migrations/012_add_last_inactive_at_to_devices.rb
|
45
|
+
- init.rb
|
46
|
+
- install.rb
|
47
|
+
- lib/urbanairship_on_rails.rb
|
48
|
+
- lib/urbanairship_on_rails/libs/config.rb
|
49
|
+
- lib/urbanairship_on_rails/models/apn/base.rb
|
50
|
+
- lib/urbanairship_on_rails/models/apn/broadcast_notification.rb
|
51
|
+
- lib/urbanairship_on_rails/models/apn/device.rb
|
52
|
+
- lib/urbanairship_on_rails/models/apn/excluded_devices_for_notification.rb
|
53
|
+
- lib/urbanairship_on_rails/models/apn/feedback.rb
|
54
|
+
- lib/urbanairship_on_rails/models/apn/notification.rb
|
55
|
+
- lib/urbanairship_on_rails/urbanairship_on_rails.rb
|
56
|
+
- lib/urbanairship_on_rails/version.rb
|
57
|
+
- tasks/urban_airship_on_rails_tasks.rake
|
58
|
+
- test/test_helper.rb
|
59
|
+
- test/urban_airship_on_rails_test.rb
|
60
|
+
- uninstall.rb
|
61
|
+
- urbanairship_on_rails.gemspec
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: ""
|
64
|
+
licenses: []
|
65
|
+
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project: urbanairship_on_rails
|
86
|
+
rubygems_version: 1.6.1
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: URBAN AIRSHIP ON RAILS
|
90
|
+
test_files:
|
91
|
+
- test/test_helper.rb
|
92
|
+
- test/urban_airship_on_rails_test.rb
|