markbates-apn_on_rails 0.0.1.20090723180641
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 +21 -0
- data/README +76 -0
- data/lib/apn_on_rails/apn_on_rails.rb +51 -0
- data/lib/apn_on_rails/app/models/apn/device.rb +25 -0
- data/lib/apn_on_rails/app/models/apn/notification.rb +79 -0
- data/lib/apn_on_rails/app/models/apple_push_notification.rb +94 -0
- data/lib/apn_on_rails/db/migrate/20090723132058_create_apn_devices.rb +13 -0
- data/lib/apn_on_rails/db/migrate/20090723132059_create_apn_notifications.rb +23 -0
- data/lib/apn_on_rails/tasks/apn.rake +17 -0
- data/lib/apn_on_rails/tasks/db.rake +13 -0
- data/lib/apn_on_rails.rb +4 -0
- data/lib/apn_on_rails_tasks.rb +3 -0
- metadata +74 -0
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,76 @@
|
|
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
|
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
|
+
That's it, now you're ready to start creating notifications.
|
61
|
+
|
62
|
+
Example
|
63
|
+
-------
|
64
|
+
|
65
|
+
*Note: the spaces in `device_token` are optional.*
|
66
|
+
|
67
|
+
$ ./script/console
|
68
|
+
>> a = ApplePushNotification.new
|
69
|
+
>> a.device_token = "XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX"
|
70
|
+
>> a.badge = 5
|
71
|
+
>> a.sound = true
|
72
|
+
>> a.alert = "foobar"
|
73
|
+
>> a.send_notification
|
74
|
+
=> nil
|
75
|
+
|
76
|
+
Copyright (c) 2009 Fabien Penso. Released under the MIT license. Modified by [Sam Soffes](http://samsoff.es).
|
@@ -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,79 @@
|
|
1
|
+
class APN::Notification < ActiveRecord::Base
|
2
|
+
include ::ActionView::Helpers::TextHelper
|
3
|
+
set_table_name 'apn_notifications'
|
4
|
+
|
5
|
+
belongs_to :device, :class_name => 'APN::Device'
|
6
|
+
|
7
|
+
def alert=(message)
|
8
|
+
if !message.blank? && message.size > 150
|
9
|
+
message = truncate(message, :length => 150)
|
10
|
+
end
|
11
|
+
write_attribute('alert', message)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a Hash that will be the payload of an APN.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
# apn = APN::Notification.new
|
18
|
+
# apn.badge = 5
|
19
|
+
# apn.sound = 'my_sound.aiff'
|
20
|
+
# apn.alert = 'Hello!'
|
21
|
+
# apn.apple_hash # => {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
|
22
|
+
def apple_hash
|
23
|
+
result = {}
|
24
|
+
result['aps'] = {}
|
25
|
+
result['aps']['alert'] = self.alert if self.alert
|
26
|
+
result['aps']['badge'] = self.badge.to_i if self.badge
|
27
|
+
if self.sound
|
28
|
+
result['aps']['sound'] = self.sound if self.sound.is_a? String
|
29
|
+
result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
|
30
|
+
end
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates the JSON string required for an APN message.
|
35
|
+
#
|
36
|
+
# Example:
|
37
|
+
# apn = APN::Notification.new
|
38
|
+
# apn.badge = 5
|
39
|
+
# apn.sound = 'my_sound.aiff'
|
40
|
+
# apn.alert = 'Hello!'
|
41
|
+
# apn.to_apple_json # => '{"aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}'
|
42
|
+
def to_apple_json
|
43
|
+
self.apple_hash.to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates the binary message needed to send to Apple.
|
47
|
+
def message_for_sending
|
48
|
+
json = self.to_apple_json
|
49
|
+
message = "\0\0 #{self.device.to_hexa}\0#{json.length.chr}#{json}"
|
50
|
+
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
|
51
|
+
message
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
|
56
|
+
def send_notifications(notifications)
|
57
|
+
cert = File.read(configatron.apn.cert)
|
58
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
59
|
+
ctx.key = OpenSSL::PKey::RSA.new(cert, configatron.apn.passphrase)
|
60
|
+
ctx.cert = OpenSSL::X509::Certificate.new(cert)
|
61
|
+
|
62
|
+
s = TCPSocket.new(configatron.apn.host, configatron.apn.port)
|
63
|
+
ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
|
64
|
+
ssl.sync = true
|
65
|
+
ssl.connect
|
66
|
+
|
67
|
+
notifications.each do |noty|
|
68
|
+
ssl.write(noty.message_for_sending)
|
69
|
+
noty.sent_at = Time.now
|
70
|
+
noty.save
|
71
|
+
end
|
72
|
+
|
73
|
+
ssl.close
|
74
|
+
s.close
|
75
|
+
end
|
76
|
+
|
77
|
+
end # class << self
|
78
|
+
|
79
|
+
end # APN::Notification
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# #
|
2
|
+
# # Fabien Penso <fabien.penso@conovae.com>
|
3
|
+
# # April 6th, 2009.
|
4
|
+
# #
|
5
|
+
#
|
6
|
+
# require 'socket'
|
7
|
+
# require 'openssl'
|
8
|
+
#
|
9
|
+
# class ApplePushNotification < ActiveRecord::Base
|
10
|
+
#
|
11
|
+
# HOST = "gateway.sandbox.push.apple.com"
|
12
|
+
# PATH = '/'
|
13
|
+
# PORT = 2195
|
14
|
+
# path = File.join(RAILS_ROOT, 'config', 'apple_push_notification_development.pem')
|
15
|
+
# CERT = File.read(path) if File.exists?(path)
|
16
|
+
# PASSPHRASE = "foobar"
|
17
|
+
# CACERT = File.expand_path(File.dirname(__FILE__) + "certs/ca.gateway.sandbox.push.apple.com.crt")
|
18
|
+
# USERAGENT = 'Mozilla/5.0 (apple_push_notification Ruby on Rails 0.1)'
|
19
|
+
#
|
20
|
+
# # attr_accessor :paylod, :sound, :badge, :alert, :appdata
|
21
|
+
# attr_accessible :device_token
|
22
|
+
#
|
23
|
+
# # validates_uniqueness_of :device_token
|
24
|
+
#
|
25
|
+
# # def send_notification
|
26
|
+
# #
|
27
|
+
# # ctx = OpenSSL::SSL::SSLContext.new
|
28
|
+
# # ctx.key = OpenSSL::PKey::RSA.new(CERT, PASSPHRASE)
|
29
|
+
# # ctx.cert = OpenSSL::X509::Certificate.new(CERT)
|
30
|
+
# #
|
31
|
+
# # s = TCPSocket.new(HOST, PORT)
|
32
|
+
# # ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
|
33
|
+
# # ssl.sync = true
|
34
|
+
# # ssl.connect
|
35
|
+
# #
|
36
|
+
# # ssl.write(self.apn_message_for_sending)
|
37
|
+
# #
|
38
|
+
# # ssl.close
|
39
|
+
# # s.close
|
40
|
+
# #
|
41
|
+
# # rescue SocketError => error
|
42
|
+
# # raise "Error while sending notifications: #{error}"
|
43
|
+
# # end
|
44
|
+
#
|
45
|
+
# def self.send_notifications(notifications)
|
46
|
+
# ctx = OpenSSL::SSL::SSLContext.new
|
47
|
+
# ctx.key = OpenSSL::PKey::RSA.new(CERT, PASSPHRASE)
|
48
|
+
# ctx.cert = OpenSSL::X509::Certificate.new(CERT)
|
49
|
+
#
|
50
|
+
# s = TCPSocket.new(HOST, PORT)
|
51
|
+
# ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
|
52
|
+
# ssl.sync = true
|
53
|
+
# ssl.connect
|
54
|
+
#
|
55
|
+
# for notif in notifications do
|
56
|
+
# ssl.write(notif.apn_message_for_sending)
|
57
|
+
# notif.sent_at = Time.now
|
58
|
+
# notif.save
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# ssl.close
|
62
|
+
# s.close
|
63
|
+
# rescue SocketError => error
|
64
|
+
# raise "Error while sending notifications: #{error}"
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# def to_apple_json
|
68
|
+
# logger.debug "Sending #{self.apple_hash.to_json}"
|
69
|
+
# self.apple_hash.to_json
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# def apn_message_for_sending
|
73
|
+
# json = self.to_apple_json
|
74
|
+
# message = "\0\0 #{self.device_token_hexa}\0#{json.length.chr}#{json}"
|
75
|
+
# raise "The maximum size allowed for a notification payload is 256 bytes." if message.size.to_i > 256
|
76
|
+
# message
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# def device_token_hexa
|
80
|
+
# [self.device_token.delete(' ')].pack('H*')
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# def apple_hash
|
84
|
+
# result = {}
|
85
|
+
# result['aps'] = {}
|
86
|
+
# result['aps']['alert'] = alert if alert
|
87
|
+
# result['aps']['badge'] = badge.to_i if badge
|
88
|
+
# result['aps']['sound'] = sound if sound and sound.is_a? String
|
89
|
+
# result['aps']['sound'] = "1.aiff" if sound and sound.is_a?(TrueClass)
|
90
|
+
# result.merge appdata if appdata
|
91
|
+
#
|
92
|
+
# result
|
93
|
+
# end
|
94
|
+
# 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,17 @@
|
|
1
|
+
namespace :apn do
|
2
|
+
|
3
|
+
namespace :notifications do
|
4
|
+
|
5
|
+
desc "Deliver all unsent APN notifications."
|
6
|
+
task :deliver => [:environment] do
|
7
|
+
notifications = APN::Notification.all(:conditions => {:sent_at => nil})
|
8
|
+
unless notifications.empty?
|
9
|
+
include ActionView::Helpers::TextHelper
|
10
|
+
RAILS_DEFAULT_LOGGER.info "APN: Attempting to deliver #{pluralize(notifications.size, 'notification')}."
|
11
|
+
APN::Notification.send_notifications(notifications)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end # notifications
|
16
|
+
|
17
|
+
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
|
data/lib/apn_on_rails.rb
ADDED
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: markbates-apn_on_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.20090723180641
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- markbates
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-23 00:00:00 -07: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/app/models/apple_push_notification.rb
|
39
|
+
- lib/apn_on_rails/db/migrate/20090723132058_create_apn_devices.rb
|
40
|
+
- lib/apn_on_rails/db/migrate/20090723132059_create_apn_notifications.rb
|
41
|
+
- lib/apn_on_rails/tasks/apn.rake
|
42
|
+
- lib/apn_on_rails/tasks/db.rake
|
43
|
+
- lib/apn_on_rails.rb
|
44
|
+
- lib/apn_on_rails_tasks.rb
|
45
|
+
- README
|
46
|
+
- LICENSE
|
47
|
+
has_rdoc: false
|
48
|
+
homepage: ""
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: magrathea
|
69
|
+
rubygems_version: 1.2.0
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: apn_on_rails
|
73
|
+
test_files: []
|
74
|
+
|