apn_on_rails 0.1.0

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/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
+