apn_sender 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ * 1.0.6
2
+ - Added support for password-protected .pem files
3
+ - Read feedback data in 38-byte chunks
4
+ - Support passing dictionary as :alert key
5
+ - Logging to STDOUT if no other loggers present
data/README.md ADDED
@@ -0,0 +1,158 @@
1
+ ## UPDATE May 3, 2013: current status
2
+
3
+ This project was not supported for a while, but we are going to support it again.
4
+
5
+ -----
6
+
7
+ ## Synopsis
8
+
9
+ Need to send background notifications to an iPhone application over a <em>persistent</em> connection in Ruby? Keep reading...
10
+
11
+ ## The Story
12
+
13
+ So you're building the server component of an iPhone application in Ruby. And you want to send background notifications through the Apple Push Notification servers, which doesn't seem too bad at first. But then you read in the [Apple Documentation](https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction.html) that Apple's servers may treat non-persistent connections as a Denial of Service attack, and you realize that Rails has no easy way to maintain a persistent connection internally, and things start looking more complicated.
14
+
15
+ The apn_sender gem includes a background daemon which processes background messages from your application and sends them along to Apple <em>over a single, persistent socket</em>. It also includes the ability to query the Feedback service, helper methods for enqueueing your jobs, and a sample monit config to make sure the background worker is around when you need it.
16
+
17
+ ## Yet another ApplePushNotification interface?
18
+
19
+ Yup. There's some great code out there already, but we didn't like the idea of getting banned from the APN gateway for establishing a new connection each time we needed to send a batch of messages, and none of the libraries I found handled maintaining a persistent connection.
20
+
21
+ ## Current Status
22
+ This gem has been in production on 500px,sending million of notifications.
23
+
24
+ ## Usage
25
+
26
+ ### 1. Queueing Messages From Your Application
27
+
28
+ To queue a message for sending through Apple's Push Notification service from your Rails application:
29
+
30
+ ```
31
+ APN.notify(token, opts_hash)
32
+ ```
33
+
34
+ where ```token``` is the unique identifier of the iPhone to receive the notification and ```opts_hash``` can have any of the following keys:
35
+
36
+ Options:
37
+
38
+ * :alert ## The alert to send
39
+ * :badge ## The badge number to send
40
+ * :sound ## The sound file to play on receipt, or true to play the default sound installed with your app
41
+
42
+
43
+ If any other keys are present they'll be be passed along as custom data to your application.
44
+
45
+ ### 2. Sending Queued Messages
46
+
47
+ Put your ```apn_development.pem``` and ```apn_production.pem``` certificates from Apple in your ```RAILS_ROOT/config/certs``` directory.
48
+
49
+ Once this is done, you can fire off a background worker with
50
+
51
+ ```
52
+ $ rake apn:sender
53
+ ```
54
+
55
+ For production, you're probably better off running a dedicated daemon and setting up monit to watch over it for you. Luckily, that's pretty easy:
56
+
57
+ ```
58
+ # To generate daemon
59
+ ./script/generate apn_sender
60
+
61
+ # To run daemon. Pass --help to print all options
62
+ ./script/apn_sender --environment#production --verbose start
63
+ ```
64
+
65
+ Note the --environment must be explicitly set (separately from your <code>Rails.env</code>) to production in order to send messages via the production APN servers. Any other environment sends messages through Apple's sandbox servers at <code>gateway.sandbox.push.apple.com</code>.
66
+
67
+ Also, there are two similar options: ```:cert_path``` and ```:full_cert_path```. The former specifies the directory in which to find the .pem file (either apn_production.pem or apn_development.pem, depending on the environment). The latter specifies a .pem file explicitly, allowing customized certificate names if needed.
68
+
69
+ Check ```logs/apn_sender.log``` for debugging output. In addition to logging any major errors there, apn_sender hooks into the Resque::Worker logging to display any verbose or very_verbose worker output in apn_sender.log file as well.
70
+
71
+
72
+ ### 3. Checking Apple's Feedback Service
73
+
74
+ Since push notifications are a fire-and-forget sorta deal, where you get no indication if your message was received (or if the specified recipient even exists), Apple needed to come up with some other way to ensure their network isn't clogged with thousands of bogus messages (e.g. from developers sending messages to phones where their application <em>used</em> to be installed, but where the user has since removed it). Hence, the Feedback Service.
75
+
76
+ It's actually really simple - you connect to them periodically and they give you a big dump of tokens you shouldn't send to anymore. The gem wraps this up nicely -- just call:
77
+
78
+ ```
79
+ # APN::Feedback accepts the same optional :environment
80
+ # and :cert_path / :full_cert_path options as APN::Sender
81
+ feedback # APN::Feedback.new()
82
+
83
+ tokens # feedback.tokens # #> Array of device tokens
84
+ tokens.each do |token|
85
+ # ... custom logic here to stop you app from
86
+ # sending further notifications to this token
87
+ end
88
+ ```
89
+
90
+ If you're interested in knowing exactly <em>when</em> Apple determined each token was expired (which can be useful in determining if the application re-registered with your service since it first appeared in the expired queue):
91
+
92
+ ```
93
+ items # feedback.data # #> Array of APN::FeedbackItem elements
94
+ items.each do |item|
95
+ item.token
96
+ item.timestamp
97
+ # ... custom logic here
98
+ end
99
+ ```
100
+
101
+ The Feedback Service works as a big queue. When you connect it pops off all its data and sends it over the wire at once, which means connecting a second time will return an empty array, so for ease of use a call to either +tokens+ or +data+ will connect once and cache the data. If you call either one again it'll continue to use its cached version (rather than connecting to Apple a second time to retrieve an empty array, which is probably not what you want).
102
+
103
+ Forcing a reconnect is as easy as calling either method with the single parameter +true+, but be sure you've already used the existing data because you'll never get it back.
104
+
105
+
106
+ #### Warning: No really, check Apple's Feedback Service occasionally
107
+
108
+ If you're sending notifications, you should definitely call one of the ```receive``` methods periodically, as Apple's policies require it and they apparently monitor providers for compliance. I'd definitely recommend throwing together a quick rake task to take care of this for you (the [whenever library](http://github.com/javan/whenever) provides a nice wrapper around scheduling tasks to run at certain times (for systems with cron enabled)).
109
+
110
+ Just for the record, this is essentially what you want to have whenever run periodically for you:
111
+ ```
112
+ def self.clear_uninstalled_applications
113
+ feedback_data # APN::Feedback.new(:environment #> :production).data
114
+
115
+ feedback_data.each do |item|
116
+ user # User.find_by_iphone_token( item.token )
117
+
118
+ if user.iphone_token_updated_at && user.iphone_token_updated_at > item.timestamp
119
+ return true # App has been reregistered since Apple determined it'd been uninstalled
120
+ else
121
+ user.update_attributes(:iphone_token #> nil, :iphone_token_updated_at #> Time.now)
122
+ end
123
+ end
124
+ end
125
+ ```
126
+
127
+
128
+ ### Keeping Your Workers Working
129
+
130
+ There's also an included sample ```apn_sender.monitrc``` file in the ```contrib/``` folder to help monit handle server restarts and unexpected disasters.
131
+
132
+
133
+ ## Installation
134
+
135
+ APN is built on top of [Resque](http://github.com/defunkt/resque) (an awesome [Redis](http://code.google.com/p/redis/)-based background runner similar to [delayed_job](http://github.com/collectiveidea/delayed_job)). Read through the [Resque README](http://github.com/defunkt/resque#readme) to get a feel for what's going on, follow the installation instructions there, and then run:
136
+
137
+ ```
138
+ $ gem install apn_sender
139
+ ```
140
+ In your Rails app, add (2.3.x):
141
+
142
+ ```
143
+ config.gem 'apn_sender', :lib => 'apn'
144
+ ```
145
+ or (3.x) to your Gemfile:
146
+
147
+ ```
148
+ gem 'apn_sender', require: 'apn'
149
+ ```
150
+ To add a few useful rake tasks for running workers, add the following line to your Rakefile:
151
+
152
+ ```
153
+ require 'apn/tasks'
154
+ ```
155
+
156
+ ## Copyright
157
+
158
+ Copyright (c) 2010 Kali Donovan. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,56 +1,35 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- load 'lib/apn/tasks.rb'
5
-
6
- begin
7
- require 'jeweler'
8
- Jeweler::Tasks.new do |gem|
9
- gem.name = "apn_sender"
10
- gem.summary = %Q{Resque-based background worker to send Apple Push Notifications over a persistent TCP socket.}
11
- gem.description = %Q{Resque-based background worker to send Apple Push Notifications over a persistent TCP socket. Includes Resque tweaks to allow persistent sockets between jobs, helper methods for enqueueing APN notifications, and a background daemon to send them.}
12
- gem.email = "kali.donovan@gmail.com"
13
- gem.homepage = "http://github.com/kdonovan/apple_push_notification"
14
- gem.authors = ["Kali Donovan"]
15
- gem.add_dependency 'resque'
16
- gem.add_dependency 'resque-access_worker_from_job'
17
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
- end
19
- Jeweler::GemcutterTasks.new
20
- rescue LoadError
21
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
- end
1
+ require "bundler"
2
+ Bundler.setup
23
3
 
24
- require 'rake/testtask'
25
- Rake::TestTask.new(:test) do |test|
26
- test.libs << 'lib' << 'test'
27
- test.pattern = 'test/**/test_*.rb'
28
- test.verbose = true
29
- end
4
+ require "rake"
5
+ require "rspec/core/rake_task"
30
6
 
31
- begin
32
- require 'rcov/rcovtask'
33
- Rcov::RcovTask.new do |test|
34
- test.libs << 'test'
35
- test.pattern = 'test/**/test_*.rb'
36
- test.verbose = true
37
- end
38
- rescue LoadError
39
- task :rcov do
40
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
41
- end
7
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
8
+ require "apn/version"
9
+
10
+ task :gem => :build
11
+ task :build do
12
+ system "gem build apn_sender.gemspec"
42
13
  end
43
14
 
44
- task :test => :check_dependencies
15
+ task :install => :build do
16
+ system "gem install apn_sender-#{APN::VERSION}.gem"
17
+ end
45
18
 
46
- task :default => :test
19
+ task :release => :build do
20
+ system "git tag -a v#{APN::VERSION} -m 'Tagging #{APN::VERSION}'"
21
+ system "git push --tags"
22
+ system "gem push apn_sender-#{APN::VERSION}.gem"
23
+ system "rm apn_sender-#{APN::VERSION}.gem"
24
+ end
47
25
 
48
- require 'rake/rdoctask'
49
- Rake::RDocTask.new do |rdoc|
50
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
26
+ RSpec::Core::RakeTask.new("spec") do |spec|
27
+ spec.pattern = "spec/**/*_spec.rb"
28
+ end
51
29
 
52
- rdoc.rdoc_dir = 'rdoc'
53
- rdoc.title = "apple_push_notification #{version}"
54
- rdoc.rdoc_files.include('README*')
55
- rdoc.rdoc_files.include('lib/**/*.rb')
30
+ RSpec::Core::RakeTask.new('spec:progress') do |spec|
31
+ spec.rspec_opts = %w(--format progress)
32
+ spec.pattern = "spec/**/*_spec.rb"
56
33
  end
34
+
35
+ task :default => :spec
@@ -29,11 +29,12 @@ module APN
29
29
 
30
30
  # Default to Rails or Merg logger, if available
31
31
  def setup_logger
32
- @logger = if defined?(::Merb::Logger)
33
- ::Merb.logger
32
+ @logger = if defined?(Merb::Logger)
33
+ Merb.logger
34
34
  elsif defined?(::Rails.logger)
35
35
  ::Rails.logger
36
36
  end
37
+ @logger ||= Logger.new(STDOUT)
37
38
  end
38
39
 
39
40
  # Log message to any logger provided by the user (e.g. the Rails logger).
@@ -89,7 +90,12 @@ module APN
89
90
 
90
91
  ctx = OpenSSL::SSL::SSLContext.new
91
92
  ctx.cert = OpenSSL::X509::Certificate.new(@apn_cert)
92
- ctx.key = OpenSSL::PKey::RSA.new(@apn_cert)
93
+
94
+ if @opts[:cert_pass]
95
+ ctx.key = OpenSSL::PKey::RSA.new(@apn_cert, @opts[:cert_pass])
96
+ else
97
+ ctx.key = OpenSSL::PKey::RSA.new(@apn_cert)
98
+ end
93
99
 
94
100
  @socket_tcp = TCPSocket.new(apn_host, apn_port)
95
101
  @socket = OpenSSL::SSL::SSLSocket.new(@socket_tcp, ctx)
data/lib/apn/feedback.rb CHANGED
@@ -16,14 +16,14 @@ module APN
16
16
  token
17
17
  end
18
18
  end
19
-
19
+
20
20
  # When supplied with the certificate path and the desired environment, connects to the {APN Feedback Service}[http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3]
21
21
  # and returns any response as an array of APN::FeedbackItem elements.
22
- #
22
+ #
23
23
  # See README for usage and details.
24
24
  class Feedback
25
25
  include APN::Connection::Base
26
-
26
+
27
27
  # Returns array of APN::FeedbackItem elements read from Apple. Connects to Apple once and caches the
28
28
  # data, continues to returns cached data unless called with <code>data(true)</code>, which clears the
29
29
  # existing feedback array. Note that once you force resetting the cache you loose all previous feedback,
@@ -32,25 +32,25 @@ module APN
32
32
  @feedback = nil if force
33
33
  @feedback ||= receive
34
34
  end
35
-
35
+
36
36
  # Wrapper around +data+ returning just an array of token strings.
37
37
  def tokens(force = nil)
38
38
  data(force).map(&:token)
39
39
  end
40
-
40
+
41
41
  # Prettify to return meaningful status information when printed. Can't add these directly to connection/base, because Resque depends on decoding to_s
42
42
  def inspect
43
43
  "#<#{self.class.name}: #{to_s}>"
44
44
  end
45
-
45
+
46
46
  # Prettify to return meaningful status information when printed. Can't add these directly to connection/base, because Resque depends on decoding to_s
47
47
  def to_s
48
48
  "#{@socket ? 'Connected' : 'Connection not currently established'} to #{apn_host} on #{apn_port}"
49
49
  end
50
-
50
+
51
51
  protected
52
-
53
- # Connects to Apple's Feedback Service and checks if there's anything there for us.
52
+
53
+ # Connects to Apple's Feedback Service and checks if there's anything there for us.
54
54
  # Returns an array of APN::FeedbackItem pairs
55
55
  def receive
56
56
  feedback = []
@@ -59,9 +59,8 @@ module APN
59
59
  setup_connection
60
60
 
61
61
  # Unpacking code borrowed from http://github.com/jpoz/APNS/blob/master/lib/apns/core.rb
62
- while line = socket.gets # Read lines from the socket
63
- line.strip!
64
- f = line.unpack('N1n1H140')
62
+ while bunch = socket.read(38) # Read data from the socket
63
+ f = bunch.strip.unpack('N1n1H140')
65
64
  feedback << APN::FeedbackItem.new(Time.at(f[0]), f[2])
66
65
  end
67
66
 
@@ -70,23 +69,15 @@ module APN
70
69
 
71
70
  return feedback
72
71
  end
73
-
74
-
72
+
73
+
75
74
  def apn_host
76
75
  @apn_host ||= apn_production? ? "feedback.push.apple.com" : "feedback.sandbox.push.apple.com"
77
76
  end
78
-
77
+
79
78
  def apn_port
80
79
  2196
81
80
  end
82
-
81
+
83
82
  end
84
83
  end
85
-
86
-
87
-
88
- __END__
89
- # Testing from irb
90
- irb -r 'lib/apn/feedback'
91
-
92
- a=APN::Feedback.new(:cert_path => '/Users/kali/Code/insurrection/certs/', :environment => :production)
@@ -10,8 +10,8 @@ module APN
10
10
  #
11
11
  # APN::Notification.new(token, {:alert => 'Stuff', :custom => {:code => 23}})
12
12
  # # Writes this JSON to servers: {"aps" => {"alert" => "Stuff"}, "custom" => {"code" => 23}}
13
- #
14
- # As a shortcut, APN::Notification.new also accepts a string as the second argument, which it converts into the alert to send. The
13
+ #
14
+ # As a shortcut, APN::Notification.new also accepts a string as the second argument, which it converts into the alert to send. The
15
15
  # following two lines are equivalent:
16
16
  #
17
17
  # APN::Notification.new(token, 'Some Alert')
@@ -19,13 +19,13 @@ module APN
19
19
  #
20
20
  class Notification
21
21
  # Available to help clients determine before they create the notification if their message will be too large.
22
- # Each iPhone Notification payload must be 256 or fewer characters. Encoding a null message has a 57
22
+ # Each iPhone Notification payload must be 256 or fewer characters. Encoding a null message has a 57
23
23
  # character overhead, so there are 199 characters available for the alert string.
24
- MAX_ALERT_LENGTH = 199
25
-
24
+ MAX_ALERT_LENGTH = 199
25
+
26
26
  attr_accessor :options, :token
27
27
  def initialize(token, opts)
28
- @options = hash_as_symbols(opts.is_a?(Hash) ? opts : {:alert => opts})
28
+ @options = opts.is_a?(Hash) ? opts.symbolize_keys : {:alert => opts}
29
29
  @token = token
30
30
 
31
31
  raise "The maximum size allowed for a notification payload is 256 bytes." if packaged_notification.size.to_i > 256
@@ -34,20 +34,20 @@ module APN
34
34
  def to_s
35
35
  packaged_notification
36
36
  end
37
-
37
+
38
38
  # Ensures at least one of <code>%w(alert badge sound)</code> is present
39
39
  def valid?
40
- return true if %w(alert badge sound).any?{|key| options.keys.include?(key.to_sym) }
40
+ return true if [:alert, :badge, :sound].any?{|key| options.keys.include?(key) }
41
41
  false
42
42
  end
43
-
43
+
44
44
  protected
45
45
 
46
46
  # Completed encoded notification, ready to send down the wire to Apple
47
47
  def packaged_notification
48
48
  pt = packaged_token
49
49
  pm = packaged_message
50
- [0, 0, 32, pt, 0, pm.size, pm].pack("ccca*cca*")
50
+ [0, 0, 32, pt, 0, pm.size, pm].pack("ccca*cca*")
51
51
  end
52
52
 
53
53
  # Device token, compressed and hex-ified
@@ -61,7 +61,10 @@ module APN
61
61
  def packaged_message
62
62
  opts = @options.clone # Don't destroy our pristine copy
63
63
  hsh = {'aps' => {}}
64
- hsh['aps']['alert'] = opts.delete(:alert).to_s if opts[:alert]
64
+ if alert = opts.delete(:alert)
65
+ alert = alert.to_s unless alert.is_a?(Hash)
66
+ hsh['aps']['alert'] = alert
67
+ end
65
68
  hsh['aps']['badge'] = opts.delete(:badge).to_i if opts[:badge]
66
69
  if sound = opts.delete(:sound)
67
70
  hsh['aps']['sound'] = sound.is_a?(TrueClass) ? 'default' : sound.to_s
@@ -69,18 +72,6 @@ module APN
69
72
  hsh.merge!(opts)
70
73
  ActiveSupport::JSON::encode(hsh)
71
74
  end
72
-
73
- # Symbolize keys, using ActiveSupport if available
74
- def hash_as_symbols(hash)
75
- if hash.respond_to?(:symbolize_keys)
76
- return hash.symbolize_keys
77
- else
78
- hash.inject({}) do |opt, (key, value)|
79
- opt[(key.to_sym rescue key) || key] = value
80
- opt
81
- end
82
- end
83
- end
84
-
85
- end
86
- end
75
+
76
+ end
77
+ end
@@ -11,7 +11,8 @@ module APN
11
11
  msg = APN::Notification.new(token, opts)
12
12
  raise "Invalid notification options (did you provide :alert, :badge, or :sound?): #{opts.inspect}" unless msg.valid?
13
13
 
14
- worker.send_to_apple( msg )
14
+ raise "APN::NotificationJob was picked up by a non-APN:Sender resque worker. Aborting." unless worker
15
+ worker.send_to_apple(msg)
15
16
  end
16
17
 
17
18
 
data/lib/apn/sender.rb CHANGED
@@ -6,14 +6,14 @@ module APN
6
6
  # End result: single persistent TCP connection to Apple, so they don't ban you for frequently opening and closing connections,
7
7
  # which they apparently view as a DOS attack.
8
8
  #
9
- # Accepts <code>:environment</code> (production vs anything else) and <code>:cert_path</code> options on initialization. If called in a
10
- # Rails context, <code>:cert_path</code> will default to "#{Rails.root}/config/certs" and <code>:environment</code>'s default will be set
11
- # from Rails.env (production if Rails.env is production, development for any other Rails.env value).
12
- #
9
+ # Accepts <code>:environment</code> (production vs anything else), <code>:cert_pass</code> and <code>:cert_path</code> options on initialization. If called in a
10
+ # Rails context, will default to RAILS_ENV and RAILS_ROOT/config/certs. :environment will default to development.
13
11
  # APN::Sender expects two files to exist in the specified <code>:cert_path</code> directory:
14
12
  # <code>apn_production.pem</code> and <code>apn_development.pem</code>.
15
13
  #
16
- # If a socket error is encountered, it will teardown the connection and retry again twice before admitting defeat.
14
+ # Use the <code>:cert_pass</code> option if your certificates require a password
15
+ #
16
+ # If a socket error is encountered, will teardown the connection and retry again twice before admitting defeat.
17
17
  class Sender < ::Resque::Worker
18
18
  include APN::Connection::Base
19
19
  TIMES_TO_RETRY_SOCKET_ERROR = 2
@@ -32,6 +32,9 @@ module APN
32
32
  opts.on('c', '--full-cert-path=NAME', 'Full path to desired .pem certificate (overrides environment selector).') do |path|
33
33
  @options[:full_cert_path] = path
34
34
  end
35
+ opts.on('--cert-pass=PASSWORD', 'Password for the apn .pem certificates.') do |pass|
36
+ @options[:cert_pass] = pass
37
+ end
35
38
  opts.on('-n', '--number-of-workers=WORKERS', "Number of unique workers to spawn") do |worker_count|
36
39
  @options[:worker_count] = worker_count.to_i rescue 1
37
40
  end
data/lib/apn/tasks.rb CHANGED
@@ -8,16 +8,9 @@ namespace :apn do
8
8
  task :sender => :setup do
9
9
  require 'apn'
10
10
 
11
- worker = nil
12
-
13
- begin
14
- worker = APN::Sender.new(:full_cert_path => ENV['FULL_CERT_PATH'], :cert_path => ENV['CERT_PATH'], :environment => ENV['ENVIRONMENT'])
15
- worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
16
- worker.very_verbose = ENV['VVERBOSE']
17
- rescue Exception => e
18
- raise e
19
- # abort "set QUEUE env var, e.g. $ QUEUE=critical,high rake resque:work"
20
- end
11
+ worker = APN::Sender.new(:full_cert_path => ENV['FULL_CERT_PATH'], :cert_path => ENV['CERT_PATH'], :environment => ENV['ENVIRONMENT'], :cert_pass => ENV['CERT_PASS'])
12
+ worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
13
+ worker.very_verbose = ENV['VVERBOSE']
21
14
 
22
15
  puts "*** Starting worker to send apple notifications in the background from #{worker}"
23
16
 
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module APN
3
+ VERSION = "1.0.6"
4
+ end
metadata CHANGED
@@ -1,72 +1,74 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: apn_sender
3
- version: !ruby/object:Gem::Version
4
- hash: 29
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.6
5
5
  prerelease:
6
- segments:
7
- - 1
8
- - 0
9
- - 5
10
- version: 1.0.5
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Kali Donovan
9
+ - Arthur Neves
14
10
  autorequire:
15
11
  bindir: bin
16
12
  cert_chain: []
17
-
18
- date: 2011-05-15 00:00:00 -07:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
13
+ date: 2011-05-15 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
22
16
  name: resque
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ requirement: !ruby/object:Gem::Requirement
25
18
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
33
23
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
36
32
  name: resque-access_worker_from_job
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
37
40
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: active_support
49
+ requirement: !ruby/object:Gem::Requirement
39
50
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
47
55
  type: :runtime
48
- version_requirements: *id002
49
- description: Resque-based background worker to send Apple Push Notifications over a persistent TCP socket. Includes Resque tweaks to allow persistent sockets between jobs, helper methods for enqueueing APN notifications, and a background daemon to send them.
50
- email: kali.donovan@gmail.com
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ description: Resque-based background worker to send Apple Push Notifications over
64
+ a persistent TCP socket. Includes Resque tweaks to allow persistent sockets between
65
+ jobs, helper methods for enqueueing APN notifications, and a background daemon to
66
+ send them.
67
+ email: arthurnn@gmail.com
51
68
  executables: []
52
-
53
69
  extensions: []
54
-
55
- extra_rdoc_files:
56
- - LICENSE
57
- - README.rdoc
58
- files:
59
- - .document
60
- - LICENSE
61
- - README.rdoc
62
- - Rakefile
63
- - VERSION
64
- - apn_sender.gemspec
65
- - contrib/apn_sender.monitrc
66
- - generators/apn_sender_generator.rb
67
- - generators/templates/script
68
- - init.rb
69
- - lib/apn.rb
70
+ extra_rdoc_files: []
71
+ files:
70
72
  - lib/apn/connection/base.rb
71
73
  - lib/apn/feedback.rb
72
74
  - lib/apn/notification.rb
@@ -76,44 +78,37 @@ files:
76
78
  - lib/apn/sender.rb
77
79
  - lib/apn/sender_daemon.rb
78
80
  - lib/apn/tasks.rb
81
+ - lib/apn/version.rb
82
+ - lib/apn.rb
79
83
  - lib/resque/hooks/before_unregister_worker.rb
80
- - rails/init.rb
81
- - test/helper.rb
82
- - test/test_apple_push_notification.rb
83
- has_rdoc: true
84
- homepage: http://github.com/kdonovan/apple_push_notification
85
- licenses: []
86
-
84
+ - CHANGELOG
85
+ - LICENSE
86
+ - README.md
87
+ - Rakefile
88
+ homepage: http://github.com/arthurnn/apn_sender
89
+ licenses:
90
+ - MIT
87
91
  post_install_message:
88
92
  rdoc_options: []
89
-
90
- require_paths:
93
+ require_paths:
91
94
  - lib
92
- required_ruby_version: !ruby/object:Gem::Requirement
95
+ required_ruby_version: !ruby/object:Gem::Requirement
93
96
  none: false
94
- requirements:
95
- - - ">="
96
- - !ruby/object:Gem::Version
97
- hash: 3
98
- segments:
99
- - 0
100
- version: "0"
101
- required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '1.9'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
102
  none: false
103
- requirements:
104
- - - ">="
105
- - !ruby/object:Gem::Version
106
- hash: 3
107
- segments:
108
- - 0
109
- version: "0"
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: 1.3.6
110
107
  requirements: []
111
-
112
108
  rubyforge_project:
113
- rubygems_version: 1.5.2
109
+ rubygems_version: 1.8.24
114
110
  signing_key:
115
111
  specification_version: 3
116
- summary: Resque-based background worker to send Apple Push Notifications over a persistent TCP socket.
117
- test_files:
118
- - test/helper.rb
119
- - test/test_apple_push_notification.rb
112
+ summary: Resque-based background worker to send Apple Push Notifications over a persistent
113
+ TCP socket.
114
+ test_files: []
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE
data/README.rdoc DELETED
@@ -1,137 +0,0 @@
1
- == Synopsis
2
-
3
- Need to send background notifications to an iPhone application over a <em>persistent</em> connection in Ruby? Keep reading...
4
-
5
- == The Story
6
-
7
- So you're building the server component of an iPhone application in Ruby. And you want to send background notifications through the Apple Push Notification servers, which doesn't seem too bad at first. But then you read in the {Apple Documentation}[https://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/WhatAreRemoteNotif/WhatAreRemoteNotif.html#//apple_ref/doc/uid/TP40008194-CH102-SW7] that Apple's servers may treat non-persistent connections as a Denial of Service attack, and you realize that Rails has no easy way to maintain a persistent connection internally, and things start looking more complicated.
8
-
9
- The apn_sender gem includes a background daemon which processes background messages from your application and sends them along to Apple <em>over a single, persistent socket</em>. It also includes the ability to query the Feedback service, helper methods for enqueueing your jobs, and a sample monit config to make sure the background worker is around when you need it.
10
-
11
- == Yet another ApplePushNotification interface?
12
-
13
- Yup. There's some great code out there already, but we didn't like the idea of getting banned from the APN gateway for establishing a new connection each time we needed to send a batch of messages, and none of the libraries I found handled maintaining a persistent connection.
14
-
15
- == Current Status
16
-
17
- This gem has been in production use since early May, 2010. There are no guarantees of complete functionality, of course, but it's working for us. :)
18
- ** UPDATE: our site is now defunct (for completely-unrelated reasons), but the many forks and watchers make me think something's going right. **
19
-
20
- == Usage
21
-
22
- === 1. Queueing Messages From Your Application
23
-
24
- To queue a message for sending through Apple's Push Notification service from your Rails application:
25
-
26
- APN.notify(token, opts_hash)
27
-
28
- where +token+ is the unique identifier of the iPhone to receive the notification and +opts_hash+ can have any of the following keys:
29
-
30
- # :alert #=> The alert to send
31
- # :badge #=> The badge number to send
32
- # :sound #=> The sound file to play on receipt, or true to play the default sound installed with your app
33
-
34
- If any other keys are present they'll be be passed along as custom data to your application.
35
-
36
- === 2. Sending Queued Messages
37
-
38
- Put your <code>apn_development.pem</code> and <code>apn_production.pem</code> certificates from Apple in your <code>RAILS_ROOT/config/certs</code> directory.
39
-
40
- Once this is done, you can fire off a background worker with
41
-
42
- $ rake apn:sender
43
-
44
- For production, you're probably better off running a dedicated daemon and setting up monit to watch over it for you. Luckily, that's pretty easy:
45
-
46
- # To generate daemon
47
- ./script/generate apn_sender
48
-
49
- # To run daemon. Pass --help to print all options
50
- ./script/apn_sender --environment=production --verbose start
51
-
52
- Note the --environment must be explicitly set (separately from your <code>Rails.env</code>) to production in order to send messages via the production APN servers. Any other environment sends messages through Apple's sandbox servers at <code>gateway.sandbox.push.apple.com</code>.
53
-
54
- Also, there are two similar options <code>:cert_path</code> and <code>:full_cert_path</code>. The former specifies the directory in which to file the .pem file (either apn_production.pem or apn_development.pem, depending on the environment). The latter specifies a .pem file explicitly, allowing customized certificate names for those that need them.
55
-
56
- Check <code>logs/apn_sender.log</code> for debugging output. In addition to logging any major errors there, apn_sender hooks into the Resque::Worker logging to display any verbose or very_verbose worker output in apn_sender.log file as well.
57
-
58
-
59
- === 3. Checking Apple's Feedback Service
60
-
61
- Since push notifications are a fire-and-forget sorta deal, where you get no indication if your message was received (or if the specified recipient even exists), Apple needed to come up with some other way to ensure their network isn't clogged with thousands of bogus messages (e.g. from developers sending messages to phones where their application <em>used</em> to be installed, but where the user has since removed it). Hence, the Feedback Service.
62
-
63
- It's actually really simple - you connect to them periodically and they give you a big dump of tokens you shouldn't send to anymore. The gem wraps this up nicely -- just call:
64
-
65
- # APN::Feedback accepts the same optional :environment and :cert_path / :full_cert_path options as APN::Sender
66
- feedback = APN::Feedback.new()
67
-
68
- tokens = feedback.tokens # => Array of device tokens
69
- tokens.each do |token|
70
- # ... custom logic here to stop you app from
71
- # sending further notifications to this token
72
- end
73
-
74
- If you're interested in knowing exactly <em>when</em> Apple determined each token was expired (which can be useful in determining if the application re-registered with your service since it first appeared in the expired queue):
75
-
76
- items = feedback.data # => Array of APN::FeedbackItem elements
77
- items.each do |item|
78
- item.token
79
- item.timestamp
80
- # ... custom logic here
81
- end
82
-
83
- The Feedback Service works as a big queue. When you connect it pops off all its data and sends it over the wire at once, which means connecting a second time will return an empty array, so for ease of use a call to either +tokens+ or +data+ will connect once and cache the data. If you call either one again it'll continue to use its cached version (rather than connecting to Apple a second time to retrieve an empty array, which is probably not what you want).
84
-
85
- Forcing a reconnect is as easy as calling either method with the single parameter +true+, but be sure you've already used the existing data because you'll never get it back.
86
-
87
-
88
- ==== Warning: No really, check Apple's Feedback Service occasionally
89
-
90
- If you're sending notifications, you should definitely call one of the <code>receive</code> methods periodically, as Apple's policies require it and they apparently monitor providers for compliance. I'd definitely recommend throwing together a quick rake task to take care of this for you (the {whenever library}[http://github.com/javan/whenever] provides a nice wrapper around scheduling tasks to run at certain times (for systems with cron enabled)).
91
-
92
- Just for the record, this is essentially what you want to have whenever run periodically for you:
93
-
94
- def self.clear_uninstalled_applications
95
- feedback_data = APN::Feedback.new(:environment => :production).data
96
-
97
- feedback_data.each do |item|
98
- user = User.find_by_iphone_token( item.token )
99
-
100
- if user.iphone_token_updated_at && user.iphone_token_updated_at > item.timestamp
101
- return true # App has been reregistered since Apple determined it'd been uninstalled
102
- else
103
- user.update_attributes(:iphone_token => nil, :iphone_token_updated_at => Time.now)
104
- end
105
- end
106
- end
107
-
108
-
109
-
110
-
111
- === Keeping Your Workers Working
112
-
113
- There's also an included sample <code>apn_sender.monitrc</code> file in the <code>contrib/</code> folder to help monit handle server restarts and unexpected disasters.
114
-
115
-
116
- == Installation
117
-
118
- APN is built on top of {Resque}[http://github.com/defunkt/resque] (an awesome {Redis}[http://code.google.com/p/redis/]-based background runner similar to {delayed_job}[http://github.com/collectiveidea/delayed_job]). Read through the {Resque README}[http://github.com/defunkt/resque#readme] to get a feel for what's going on, follow the installation instructions there, and then run:
119
-
120
- $ gem install apn_sender
121
-
122
- In your Rails app, add (2.3.x):
123
-
124
- config.gem 'apn_sender', :lib => 'apn'
125
-
126
- or (3.x) to your Gemfile:
127
-
128
- gem 'apn_sender', :require => 'apn'
129
-
130
- To add a few useful rake tasks for running workers, add the following line to your Rakefile:
131
-
132
- require 'apn/tasks'
133
-
134
-
135
- == Copyright
136
-
137
- Copyright (c) 2010 Kali Donovan. See LICENSE for details.
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.0.5
data/apn_sender.gemspec DELETED
@@ -1,69 +0,0 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
5
-
6
- Gem::Specification.new do |s|
7
- s.name = %q{apn_sender}
8
- s.version = "1.0.5"
9
-
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Kali Donovan"]
12
- s.date = %q{2011-05-15}
13
- s.description = %q{Resque-based background worker to send Apple Push Notifications over a persistent TCP socket. Includes Resque tweaks to allow persistent sockets between jobs, helper methods for enqueueing APN notifications, and a background daemon to send them.}
14
- s.email = %q{kali.donovan@gmail.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE",
17
- "README.rdoc"
18
- ]
19
- s.files = [
20
- ".document",
21
- "LICENSE",
22
- "README.rdoc",
23
- "Rakefile",
24
- "VERSION",
25
- "apn_sender.gemspec",
26
- "contrib/apn_sender.monitrc",
27
- "generators/apn_sender_generator.rb",
28
- "generators/templates/script",
29
- "init.rb",
30
- "lib/apn.rb",
31
- "lib/apn/connection/base.rb",
32
- "lib/apn/feedback.rb",
33
- "lib/apn/notification.rb",
34
- "lib/apn/notification_job.rb",
35
- "lib/apn/queue_manager.rb",
36
- "lib/apn/queue_name.rb",
37
- "lib/apn/sender.rb",
38
- "lib/apn/sender_daemon.rb",
39
- "lib/apn/tasks.rb",
40
- "lib/resque/hooks/before_unregister_worker.rb",
41
- "rails/init.rb",
42
- "test/helper.rb",
43
- "test/test_apple_push_notification.rb"
44
- ]
45
- s.homepage = %q{http://github.com/kdonovan/apple_push_notification}
46
- s.require_paths = ["lib"]
47
- s.rubygems_version = %q{1.5.2}
48
- s.summary = %q{Resque-based background worker to send Apple Push Notifications over a persistent TCP socket.}
49
- s.test_files = [
50
- "test/helper.rb",
51
- "test/test_apple_push_notification.rb"
52
- ]
53
-
54
- if s.respond_to? :specification_version then
55
- s.specification_version = 3
56
-
57
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
- s.add_runtime_dependency(%q<resque>, [">= 0"])
59
- s.add_runtime_dependency(%q<resque-access_worker_from_job>, [">= 0"])
60
- else
61
- s.add_dependency(%q<resque>, [">= 0"])
62
- s.add_dependency(%q<resque-access_worker_from_job>, [">= 0"])
63
- end
64
- else
65
- s.add_dependency(%q<resque>, [">= 0"])
66
- s.add_dependency(%q<resque-access_worker_from_job>, [">= 0"])
67
- end
68
- end
69
-
@@ -1,22 +0,0 @@
1
- # An example Monit configuration file for running the apn_sender background daemon
2
- #
3
- # 1. Replace #{app_name} with your application name
4
- # 2. Add any arguments between apn_sender the and start/stop command
5
- # 3. Install as a monitrc file (paste to bottom of /etc/monit/monitrc, or save as a .monitrc file and include in the main config)
6
-
7
- check process redis
8
- with pidfile /var/run/redis.pid
9
- group apn_sender
10
- start program = "/usr/bin/redis-server /etc/redis/redis.conf"
11
- stop program = "/bin/echo SHUTDOWN | nc localhost 6379"
12
- if failed host 127.0.0.1 port 6379 then restart
13
- if 5 restarts within 5 cycles then timeout
14
-
15
-
16
- check process apn_sender
17
- with pidfile /var/www/#{app_name}/shared/pids/apn_sender.pid
18
- group apn_sender
19
- start program = "/var/www/#{app_name}/current/script/apn_sender --environment=production --verbose start"
20
- stop program = "/var/www/{#app_name}/current/script/apn_sender --environment=production --verbose stop"
21
- if 2 restarts within 3 cycles then timeout
22
- depends_on redis
@@ -1,9 +0,0 @@
1
- class ApnSenderGenerator < Rails::Generator::Base
2
-
3
- def manifest
4
- record do |m|
5
- m.template 'script', 'script/apn_sender', :chmod => 0755
6
- end
7
- end
8
-
9
- end
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # Daemons sets pwd to /, so we have to explicitly set RAILS_ROOT
4
- RAILS_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
5
-
6
- require 'rubygems'
7
- require 'apn'
8
- require 'apn/sender_daemon'
9
-
10
- APN::SenderDaemon.new(ARGV).daemonize
data/init.rb DELETED
@@ -1 +0,0 @@
1
- require File.dirname(__FILE__) + "/rails/init.rb"
data/rails/init.rb DELETED
@@ -1 +0,0 @@
1
- require "apn"
data/test/helper.rb DELETED
@@ -1,10 +0,0 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
- require 'shoulda'
4
-
5
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
- $LOAD_PATH.unshift(File.dirname(__FILE__))
7
- require 'apple_push_notification'
8
-
9
- class Test::Unit::TestCase
10
- end
@@ -1,7 +0,0 @@
1
- require 'helper'
2
-
3
- class TestAPN < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
7
- end