apn_on_rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 markbates
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,88 @@
1
+ APN on Rails
2
+ (Apple Push Notifications on Rails)
3
+ =====================
4
+
5
+ Acknowledgements
6
+ ----------------
7
+ This gem is a re-write of a plugin that was written by Fabien Penso and Sam Soffes.
8
+ Their plugin was a great start, but it just didn't quite reach the level I hoped it would.
9
+ I've re-written, as a gem, added a ton of tests, and I would like to think that I made it
10
+ a little nicer and easier to use.
11
+
12
+ Converting Your Certificate
13
+ ---------------------------
14
+ Once you have the certificate from Apple for your application, export your key
15
+ and the apple certificate as p12 files. Here is a quick walkthrough on how to do this:
16
+
17
+ 1. Click the disclosure arrow next to your certificate in Keychain Access and select the certificate and the key.
18
+ 2. Right click and choose `Export 2 items...`.
19
+ 3. Choose the p12 format from the drop down and name it `cert.p12`.
20
+
21
+ Now covert the p12 file to a pem file:
22
+
23
+ $ openssl pkcs12 -in cert.p12 -out apple_push_notification_production.pem -nodes -clcerts
24
+
25
+ Put 'apple_push_notification_production.pem' in config/
26
+
27
+ If you are using a development certificate, then change the name to apple_push_notification_development.pem instead.
28
+
29
+ Installing
30
+ ----------
31
+
32
+ From RubyForge:
33
+ $ sudo gem install apn_on_rails
34
+
35
+ Or, if you like to live on the edge:
36
+ $ sudo gem install markbates-apn_on_rails --source=http://gems.github.com
37
+
38
+ Then you just add the following require, wherever it makes sense to you:
39
+ require 'apn_on_rails'
40
+
41
+ If you like to use the built in Rails gem management:
42
+ config.gem 'apn_on_rails'
43
+
44
+ Or, if you like to live on the edge:
45
+ config.gem 'markbates-apn_on_rails', :lib => 'apn_on_rails', :source => 'http://gems.github.com'
46
+
47
+ Setup and Configuration
48
+ -----------------------
49
+ Once you have the gem installed you need to add the following to your Rakefile so you can use the
50
+ Rake tasks that ship with APN on Rails:
51
+ begin
52
+ require 'apn_on_rails_tasks'
53
+ rescue MissingSourceFile => e
54
+ puts e.message
55
+ end
56
+
57
+ Now, to create the tables you need for APN on Rails, run the following task:
58
+ $ rake apn:db:migrate
59
+
60
+ APN on Rails uses the Configatron gem, http://github.com/markbates/configatron/tree/master,
61
+ to configure itself. APN on Rails has the following default configurations that you change as you
62
+ see fit:
63
+
64
+ configatron.apn.passphrase # => ''
65
+ configatron.apn.port # => 2195
66
+ configatron.apn.host # => 'gateway.sandbox.push.apple.com'
67
+ configatron.apn.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_development.pem')
68
+
69
+ # production:
70
+ configatron.apn.host # => 'gateway.push.apple.com'
71
+ configatron.apn.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_production.pem')
72
+
73
+ That's it, now you're ready to start creating notifications.
74
+
75
+ Example
76
+ -------
77
+
78
+ $ ./script/console
79
+ >> device = APN::Device.create(:token => "XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX")
80
+ >> notification = APN::Notification.new
81
+ >> notification.device = device
82
+ >> notification.badge = 5
83
+ >> notification.sound = true
84
+ >> notification.alert = "foobar"
85
+ >> notification.save
86
+ >> APN::Notification.send_notifications
87
+
88
+ Released under the MIT license.
@@ -0,0 +1,51 @@
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
+ configatron.apn.set_default(:host, 'gateway.sandbox.push.apple.com')
18
+ configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_development.pem'))
19
+
20
+ if rails_env == 'production'
21
+ configatron.apn.set_default(:host, 'gateway.push.apple.com')
22
+ configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_production.pem'))
23
+ end
24
+
25
+ module APN
26
+
27
+ module Errors
28
+
29
+ class ExceededMessageSizeError < StandardError
30
+
31
+ def initialize(message)
32
+ super("The maximum size allowed for a notification payload is 256 bytes: '#{message}'")
33
+ end
34
+
35
+ end
36
+
37
+ end # Errors
38
+
39
+ end # APN
40
+
41
+ Dir.glob(File.join(File.dirname(__FILE__), 'app', 'models', 'apn', '*.rb')).sort.each do |f|
42
+ require f
43
+ end
44
+
45
+ %w{ models controllers helpers }.each do |dir|
46
+ path = File.join(File.dirname(__FILE__), 'app', dir)
47
+ $LOAD_PATH << path
48
+ # puts "Adding #{path}"
49
+ ActiveSupport::Dependencies.load_paths << path
50
+ ActiveSupport::Dependencies.load_once_paths.delete(path)
51
+ end
@@ -0,0 +1,25 @@
1
+ class APN::Device < ActiveRecord::Base
2
+ set_table_name 'apn_devices'
3
+
4
+ has_many :notifications, :class_name => 'APN::Notification'
5
+
6
+ validates_uniqueness_of :token
7
+ 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}$/
8
+
9
+ # If the token comes in like this:
10
+ # '<5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz>'
11
+ # Then the '<' and '>' will be stripped off.
12
+ def token=(token)
13
+ res = token.scan(/\<(.+)\>/).first
14
+ unless res.nil? || res.empty?
15
+ token = res.first
16
+ end
17
+ write_attribute('token', token)
18
+ end
19
+
20
+ # Returns the hexadecimal representation of the device's token.
21
+ def to_hexa
22
+ [self.token.delete(' ')].pack('H*')
23
+ end
24
+
25
+ end
@@ -0,0 +1,84 @@
1
+ class APN::Notification < ActiveRecord::Base
2
+ include ::ActionView::Helpers::TextHelper
3
+ extend ::ActionView::Helpers::TextHelper
4
+
5
+ set_table_name 'apn_notifications'
6
+
7
+ belongs_to :device, :class_name => 'APN::Device'
8
+
9
+ def alert=(message)
10
+ if !message.blank? && message.size > 150
11
+ message = truncate(message, :length => 150)
12
+ end
13
+ write_attribute('alert', message)
14
+ end
15
+
16
+ # Creates a Hash that will be the payload of an APN.
17
+ #
18
+ # Example:
19
+ # apn = APN::Notification.new
20
+ # apn.badge = 5
21
+ # apn.sound = 'my_sound.aiff'
22
+ # apn.alert = 'Hello!'
23
+ # apn.apple_hash # => {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
24
+ def apple_hash
25
+ result = {}
26
+ result['aps'] = {}
27
+ result['aps']['alert'] = self.alert if self.alert
28
+ result['aps']['badge'] = self.badge.to_i if self.badge
29
+ if self.sound
30
+ result['aps']['sound'] = self.sound if self.sound.is_a? String
31
+ result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
32
+ end
33
+ result
34
+ end
35
+
36
+ # Creates the JSON string required for an APN message.
37
+ #
38
+ # Example:
39
+ # apn = APN::Notification.new
40
+ # apn.badge = 5
41
+ # apn.sound = 'my_sound.aiff'
42
+ # apn.alert = 'Hello!'
43
+ # apn.to_apple_json # => '{"aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}'
44
+ def to_apple_json
45
+ self.apple_hash.to_json
46
+ end
47
+
48
+ # Creates the binary message needed to send to Apple.
49
+ def message_for_sending
50
+ json = self.to_apple_json
51
+ message = "\0\0 #{self.device.to_hexa}\0#{json.length.chr}#{json}"
52
+ raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
53
+ message
54
+ end
55
+
56
+ class << self
57
+
58
+ def send_notifications(notifications = APN::Notification.all(:conditions => {:sent_at => nil}))
59
+ unless notifications.nil? || notifications.empty?
60
+ logger.info "APN: Attempting to deliver #{pluralize(notifications.size, 'notification')}."
61
+ cert = File.read(configatron.apn.cert)
62
+ ctx = OpenSSL::SSL::SSLContext.new
63
+ ctx.key = OpenSSL::PKey::RSA.new(cert, configatron.apn.passphrase)
64
+ ctx.cert = OpenSSL::X509::Certificate.new(cert)
65
+
66
+ s = TCPSocket.new(configatron.apn.host, configatron.apn.port)
67
+ ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
68
+ ssl.sync = true
69
+ ssl.connect
70
+
71
+ notifications.each do |noty|
72
+ ssl.write(noty.message_for_sending)
73
+ noty.sent_at = Time.now
74
+ noty.save
75
+ end
76
+
77
+ ssl.close
78
+ s.close
79
+ end
80
+ end
81
+
82
+ end # class << self
83
+
84
+ end # APN::Notification
@@ -0,0 +1,13 @@
1
+ class CreateApnDevices < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :apn_devices do |t|
4
+ t.text :token, :size => 71, :null => false
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :apn_devices
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ class CreateApnNotifications < ActiveRecord::Migration
2
+
3
+ def self.up
4
+
5
+ create_table :apn_notifications do |t|
6
+ t.integer :device_id, :null => false
7
+ t.integer :errors_nb, :default => 0 # used for storing errors from apple feedbacks
8
+ t.string :device_language, :size => 5 # if you don't want to send localized strings
9
+ t.string :sound
10
+ t.string :alert, :size => 150
11
+ t.integer :badge
12
+ t.datetime :sent_at
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :apn_notifications, :device_id
17
+ end
18
+
19
+ def self.down
20
+ drop_table :apn_notifications
21
+ end
22
+
23
+ end
@@ -0,0 +1,12 @@
1
+ namespace :apn do
2
+
3
+ namespace :notifications do
4
+
5
+ desc "Deliver all unsent APN notifications."
6
+ task :deliver => [:environment] do
7
+ APN::Notification.send_notifications
8
+ end
9
+
10
+ end # notifications
11
+
12
+ end # apn
@@ -0,0 +1,13 @@
1
+ namespace :apn do
2
+
3
+ namespace :db do
4
+
5
+ desc 'Runs the migrations for apn_on_rails.'
6
+ task :migrate => [:environment] do
7
+ puts File.join(File.dirname(__FILE__), '..', '..', 'apn_on_rails', 'db', 'migrate')
8
+ ActiveRecord::Migrator.up(File.join(File.dirname(__FILE__), '..', '..', 'apn_on_rails', 'db', 'migrate'))
9
+ end
10
+
11
+ end # db
12
+
13
+ end # apn
@@ -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,3 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'apn_on_rails', 'tasks', '**/*.rake')).each do |f|
2
+ load File.expand_path(f)
3
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apn_on_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - markbates
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-24 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: configatron
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: "apn_on_rails was developed by: markbates"
26
+ email: ""
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ - LICENSE
34
+ files:
35
+ - lib/apn_on_rails/apn_on_rails.rb
36
+ - lib/apn_on_rails/app/models/apn/device.rb
37
+ - lib/apn_on_rails/app/models/apn/notification.rb
38
+ - lib/apn_on_rails/db/migrate/20090723132058_create_apn_devices.rb
39
+ - lib/apn_on_rails/db/migrate/20090723132059_create_apn_notifications.rb
40
+ - lib/apn_on_rails/tasks/apn.rake
41
+ - lib/apn_on_rails/tasks/db.rake
42
+ - lib/apn_on_rails.rb
43
+ - lib/apn_on_rails_tasks.rb
44
+ - README
45
+ - LICENSE
46
+ has_rdoc: true
47
+ homepage: ""
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: magrathea
70
+ rubygems_version: 1.3.4
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: apn_on_rails
74
+ test_files: []
75
+