apn_sender 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6a84e566b3256a5f25795f6264af593041e7c800
4
+ data.tar.gz: cbe6ffd3cc326f919f702c883c1c608d9ed58535
5
+ SHA512:
6
+ metadata.gz: 8dc1ed6e420f195f9154c145b3a5c3ca53d0de4b76c4b55b64b8c77dcfdeee2dded1d2279d9c4cc643650681cba1225744550a3314cd8cb28ce6197b47eaab1b
7
+ data.tar.gz: 0782593c57733813f5ad1a9b6b6a50f50d4550550e0583ce2a4168b505a0d1bdd47fbfde2cd217b0dbcf16f66f8b7b8e8cf84d02c2fb2f3e6251db08e28e521e
data/lib/apn.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  require "openssl"
2
2
  require "socket"
3
+ require "active_support"
3
4
  require "active_support/core_ext"
4
5
  require "active_support/json"
5
6
  require 'connection_pool'
6
7
 
7
8
  require "apn/version"
8
- require 'apn/connection'
9
+ require "apn/connection"
9
10
 
10
11
  module APN
11
-
12
12
  class << self
13
13
  include APN::Connection
14
14
 
@@ -83,6 +83,7 @@ module APN
83
83
  end
84
84
  end
85
85
 
86
+ require 'apn/multiple_apps' if ENV['APN_MULTIPLE_APPS'] == 'true'
86
87
  require 'apn/notification'
87
88
  require 'apn/client'
88
89
  require 'apn/feedback'
@@ -0,0 +1,37 @@
1
+ require "apn/connection"
2
+
3
+ module APN
4
+ class Application
5
+ include Connection
6
+
7
+ APPS = {}
8
+ OPTION_KEYS = [:pool_size, :pool_timeout, :host, :port, :root, :full_certificate_path, :password, :certificate_name].freeze
9
+ DELEGATE_METHODS = [:with_connection, :connection_pool].concat(OPTION_KEYS)
10
+
11
+ attr_reader :name
12
+
13
+ def initialize(name, options = {})
14
+ @name = name.to_s
15
+
16
+ OPTION_KEYS.each do |key|
17
+ self.send("#{key}=", options.fetch(key) { APN.send("original_#{key}") } )
18
+ end
19
+ end
20
+
21
+ def to_h
22
+ Hash[OPTION_KEYS.zip(OPTION_KEYS.map(&method(:send)))]
23
+ end
24
+
25
+ def == other
26
+ if other.is_a?(APN::Application)
27
+ to_h == other.to_h
28
+ else
29
+ super(other)
30
+ end
31
+ end
32
+
33
+ def self.register(*args)
34
+ new(*args).tap { |app| APPS[app.name] = app if app.certificate }
35
+ end
36
+ end
37
+ end
@@ -1,11 +1,11 @@
1
1
  module APN::Jobs
2
2
  # This is the class that's actually enqueued via Sidekiq when user calls +APN.notify+.
3
- # It gets added to the +apple_server_notifications+ Sidekiq queue, which should only be operated on by
3
+ # It gets added to the +apple_push_notifications+ Sidekiq queue, which should only be operated on by
4
4
  # workers of the +APN::Sender+ class.
5
5
  class SidekiqNotificationJob
6
6
  include Sidekiq::Worker
7
7
  # Behind the scenes, this is the name of our Sidekiq queue
8
- @queue = QUEUE_NAME
8
+ sidekiq_options :queue => QUEUE_NAME
9
9
 
10
10
  # Build a notification from arguments and send to Apple
11
11
  def perform(token, opts)
@@ -0,0 +1,53 @@
1
+ require "apn/application"
2
+
3
+ module APN
4
+ module MultipleApps
5
+ def self.extended(mod)
6
+ class << mod
7
+ alias_method_chain :notify_sync, :app
8
+
9
+ delegate(*Application::DELEGATE_METHODS, to: :current_app, prefix: true, allow_nil: true)
10
+
11
+ Application::DELEGATE_METHODS.each do |method_name|
12
+ alias_method :"original_#{method_name}", method_name
13
+ alias_method method_name, :"current_app_#{method_name}"
14
+ end
15
+ end
16
+ end
17
+
18
+ def notify_sync_with_app(token, notification)
19
+ if notification.is_a?(Hash)
20
+ notification.symbolize_keys!
21
+ app_name = notification.delete(:app)
22
+ end
23
+
24
+ with_app(app_name) do
25
+ notify_sync_without_app(token, notification)
26
+ end
27
+ end
28
+
29
+ attr_writer :default_app_name
30
+
31
+ def default_app_name
32
+ @default_app_name || 'default'.freeze
33
+ end
34
+
35
+ def current_app_name
36
+ @_app_name || default_app_name
37
+ end
38
+
39
+ def current_app
40
+ Application::APPS[current_app_name] or \
41
+ raise NameError, "Unregistered APN::Application `#{current_app_name}'"
42
+ end
43
+
44
+ def with_app(app_name)
45
+ @_app_name, app_was = app_name.presence, @_app_name
46
+ yield if block_given?
47
+ ensure
48
+ @_app_name = app_was
49
+ end
50
+ end
51
+ end
52
+
53
+ APN.extend APN::MultipleApps
@@ -1,3 +1,5 @@
1
+ require 'apn/payload'
2
+
1
3
  module APN
2
4
  # Encapsulates the logic necessary to convert an iPhone token and an array of options into a string of the format required
3
5
  # by Apple's servers to send the notification. Much of the processing code here copied with many thanks from
@@ -19,9 +21,9 @@ module APN
19
21
  #
20
22
  class Notification
21
23
  # 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 (not including the token or other push data), see Apple specs at:
23
- # https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4
24
- DATA_MAX_BYTES = 256
24
+ # Each iPhone Notification payload must be 2047 or fewer characters (not including the token or other push data), see Apple specs at:
25
+ # https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1
26
+ DATA_MAX_BYTES = 2047
25
27
 
26
28
  attr_accessor :options, :token
27
29
  def initialize(token, opts)
@@ -47,9 +49,23 @@ module APN
47
49
 
48
50
  # Completed encoded notification, ready to send down the wire to Apple
49
51
  def packaged_notification
50
- pt = packaged_token
51
- pm = packaged_message
52
- [0, 0, 32, pt, 0, payload_size, pm].pack("ccca*cca*")
52
+ [2, frame_length, packaged_frame].pack("cl>a*")
53
+ end
54
+
55
+ def frame_length
56
+ packaged_frame.bytesize
57
+ end
58
+
59
+ def packaged_frame
60
+ [token_frame, payload_frame].pack('a*a*')
61
+ end
62
+
63
+ def token_frame
64
+ [1, 32, packaged_token].pack('cs>a*')
65
+ end
66
+
67
+ def payload_frame
68
+ [2, payload_size, packaged_message].pack('cs>a*')
53
69
  end
54
70
 
55
71
  # Device token, compressed and hex-ified
@@ -58,7 +74,7 @@ module APN
58
74
  end
59
75
 
60
76
  # Converts the supplied options into the JSON needed for Apple's push notification servers.
61
- # Extracts :alert, :badge, and :sound keys into the 'aps' hash, merges any other hash data
77
+ # Extracts :alert, :badge, :sound and :category keys into the 'aps' hash, merges any other hash data
62
78
  # into the root of the hash to encode and send to apple.
63
79
  def packaged_message
64
80
  @packaged_message ||=
@@ -70,6 +86,7 @@ module APN
70
86
  hsh['aps']['alert'] = alert
71
87
  end
72
88
  hsh['aps']['badge'] = opts.delete(:badge).to_i if opts[:badge]
89
+ hsh['aps']['category'] = opts.delete(:category).to_s if opts[:category]
73
90
  if sound = opts.delete(:sound)
74
91
  hsh['aps']['sound'] = sound.is_a?(TrueClass) ? 'default' : sound.to_s
75
92
  end
@@ -77,45 +94,8 @@ module APN
77
94
  hsh['aps']['content-available'] = 1 if [1,true].include? content_available
78
95
  end
79
96
  hsh.merge!(opts)
80
- payload(hsh)
97
+ Payload.new(hsh, DATA_MAX_BYTES).package
81
98
  end
82
99
  end
83
-
84
- private
85
-
86
- def payload(hash)
87
- str = ActiveSupport::JSON::encode(hash)
88
-
89
- if APN.truncate_alert && str.bytesize > DATA_MAX_BYTES
90
- if hash['aps']['alert'].is_a?(Hash)
91
- alert = hash['aps']['alert']['loc-args'][0]
92
- else
93
- alert = hash['aps']['alert']
94
- end
95
- max_bytesize = DATA_MAX_BYTES - (str.bytesize - alert.bytesize)
96
-
97
- raise "Even truncating the alert wont be enought to have a #{DATA_MAX_BYTES} message" if max_bytesize <= 0
98
- alert = truncate_alert(alert, max_bytesize)
99
-
100
- if hash['aps']['alert'].is_a?(Hash)
101
- hash['aps']['alert']['loc-args'][0] = alert
102
- else
103
- hash['aps']['alert'] = alert
104
- end
105
- str = ActiveSupport::JSON::encode(hash)
106
- end
107
- str
108
- end
109
-
110
- def truncate_alert(alert, max_size)
111
- alert.each_char.each_with_object('') do |char, result|
112
- if result.bytesize + char.bytesize > max_size
113
- break result
114
- else
115
- result << char
116
- end
117
- end
118
- end
119
-
120
100
  end
121
101
  end
@@ -0,0 +1,70 @@
1
+ class Payload
2
+
3
+ def initialize(notification_hash, max_bytes)
4
+ @notification_hash = notification_hash
5
+ @max_bytes = max_bytes
6
+ end
7
+
8
+ def package
9
+ str = encode(@notification_hash)
10
+
11
+ if APN.truncate_alert && str.bytesize > @max_bytes
12
+ max_bytesize = @max_bytes - (str.bytesize - alert.bytesize)
13
+
14
+ if max_bytesize <= 0
15
+ escaped_max_bytesize = @max_bytes - (str.bytesize - encode(alert).bytesize)
16
+ raise "Even truncating the alert won't be enough to have a #{@max_bytes} message" if escaped_max_bytesize <= 0
17
+ truncate_escaped!(escaped_max_bytesize)
18
+ else
19
+ truncate_alert!(max_bytesize)
20
+ end
21
+ str = encode(@notification_hash)
22
+ end
23
+ str
24
+ end
25
+
26
+ private
27
+
28
+ def alert
29
+ @alert ||=
30
+ if hash_alert?
31
+ @notification_hash['aps']['alert']['loc-args'][0]
32
+ else
33
+ @notification_hash['aps']['alert']
34
+ end
35
+ end
36
+
37
+ def alert=(value)
38
+ if hash_alert?
39
+ @notification_hash['aps']['alert']['loc-args'][0] = value
40
+ else
41
+ @notification_hash['aps']['alert'] = value
42
+ end
43
+ end
44
+
45
+ def hash_alert?
46
+ @hash_alert ||= @notification_hash['aps']['alert'].is_a?(Hash)
47
+ end
48
+
49
+ def truncate_alert!(max_size)
50
+ self.alert = alert.mb_chars.limit(max_size).to_s
51
+ end
52
+
53
+ def truncate_escaped!(max_size)
54
+ self.alert = alert.each_char.each_with_object('') do |char, result|
55
+ if encoded_size(result) + encoded_size(char) > max_size
56
+ break result
57
+ else
58
+ result << char
59
+ end
60
+ end
61
+ end
62
+
63
+ def encode(obj)
64
+ ActiveSupport::JSON.encode(obj)
65
+ end
66
+
67
+ def encoded_size(str)
68
+ encode(str).bytesize - 2
69
+ end
70
+ end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module APN
3
- VERSION = "2.0.2"
3
+ VERSION = "2.1.0"
4
4
  end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apn_sender
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
5
- prerelease:
4
+ version: 2.1.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Kali Donovan
@@ -14,40 +13,52 @@ date: 2011-05-15 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: connection_pool
17
- requirement: &1 !ruby/object:Gem::Requirement
18
- none: false
16
+ requirement: !ruby/object:Gem::Requirement
19
17
  requirements:
20
18
  - - ">="
21
19
  - !ruby/object:Gem::Version
22
20
  version: '0'
23
21
  type: :runtime
24
22
  prerelease: false
25
- version_requirements: *1
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
26
28
  - !ruby/object:Gem::Dependency
27
29
  name: activesupport
28
- requirement: &2 !ruby/object:Gem::Requirement
29
- none: false
30
+ requirement: !ruby/object:Gem::Requirement
30
31
  requirements:
31
32
  - - ">="
32
33
  - !ruby/object:Gem::Version
33
34
  version: '3.1'
34
35
  - - "<"
35
36
  - !ruby/object:Gem::Version
36
- version: 4.1.0
37
+ version: '5'
37
38
  type: :runtime
38
39
  prerelease: false
39
- version_requirements: *2
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '3.1'
45
+ - - "<"
46
+ - !ruby/object:Gem::Version
47
+ version: '5'
40
48
  - !ruby/object:Gem::Dependency
41
49
  name: daemons
42
- requirement: &3 !ruby/object:Gem::Requirement
43
- none: false
50
+ requirement: !ruby/object:Gem::Requirement
44
51
  requirements:
45
52
  - - ">="
46
53
  - !ruby/object:Gem::Version
47
54
  version: '0'
48
55
  type: :runtime
49
56
  prerelease: false
50
- version_requirements: *3
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
51
62
  description: Background worker to send Apple Push Notifications over a persistent
52
63
  TCP socket. Includes Resque tweaks to allow persistent sockets between jobs, helper
53
64
  methods for enqueueing APN notifications, and a background daemon to send them.
@@ -56,49 +67,47 @@ executables: []
56
67
  extensions: []
57
68
  extra_rdoc_files: []
58
69
  files:
70
+ - lib/apn.rb
71
+ - lib/apn/application.rb
72
+ - lib/apn/backend.rb
59
73
  - lib/apn/backend/resque.rb
60
74
  - lib/apn/backend/sidekiq.rb
61
- - lib/apn/backend.rb
62
75
  - lib/apn/client.rb
63
76
  - lib/apn/connection.rb
64
77
  - lib/apn/feedback.rb
65
78
  - lib/apn/jobs/resque_notification_job.rb
66
79
  - lib/apn/jobs/sidekiq_notification_job.rb
80
+ - lib/apn/multiple_apps.rb
67
81
  - lib/apn/notification.rb
82
+ - lib/apn/payload.rb
68
83
  - lib/apn/railtie.rb
69
84
  - lib/apn/sender_daemon.rb
70
85
  - lib/apn/tasks.rb
71
86
  - lib/apn/version.rb
72
- - lib/apn.rb
73
87
  - lib/apn_sender.rb
74
- - CHANGELOG.md
75
- - LICENSE
76
- - README.md
77
- - Rakefile
78
88
  homepage: http://github.com/arthurnn/apn_sender
79
89
  licenses:
80
90
  - MIT
91
+ metadata: {}
81
92
  post_install_message:
82
93
  rdoc_options: []
83
94
  require_paths:
84
95
  - lib
85
96
  required_ruby_version: !ruby/object:Gem::Requirement
86
- none: false
87
97
  requirements:
88
98
  - - ">="
89
99
  - !ruby/object:Gem::Version
90
100
  version: '1.9'
91
101
  required_rubygems_version: !ruby/object:Gem::Requirement
92
- none: false
93
102
  requirements:
94
103
  - - ">="
95
104
  - !ruby/object:Gem::Version
96
105
  version: 1.3.6
97
106
  requirements: []
98
107
  rubyforge_project:
99
- rubygems_version: 1.8.11
108
+ rubygems_version: 2.5.1
100
109
  signing_key:
101
- specification_version: 3
110
+ specification_version: 4
102
111
  summary: Background worker to send Apple Push Notifications over a persistent TCP
103
112
  socket.
104
113
  test_files: []
@@ -1,28 +0,0 @@
1
- # Version 2.0
2
- ## 2.0.2
3
- - Add default file so we dont need to require 'apn' anymore
4
- - Change backend switch:
5
- Use simple backend per default, also allow changes.
6
- Now you can change the backend using:
7
- APN.backend = :sidekiq
8
-
9
- ## 2.0.1
10
- - Use bytesize to truncate alert when necessary
11
- - Better calculation on payload size. (botvinik)
12
- - Fix generating payload should use bytesize. (piotr-sokolowski)
13
- - Rescuing and repairing broken connections (Arseniy Ivanov)
14
-
15
- ## 2.0.0
16
- - adding connection_pool for handle apple sockets
17
- - removing resque hard dependency
18
- - adding support for sending sync messages
19
- - adding Thread support
20
- - adding support to sidekiq (Caue Guerra)
21
- - truncation messages when payload is greater than 256 option (Caue Guerra)
22
-
23
- # Version 1.0
24
- ## 1.0.6
25
- - Added support for password-protected .pem files
26
- - Read feedback data in 38-byte chunks
27
- - Support passing dictionary as :alert key
28
- - Logging to STDOUT if no other loggers present
data/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright (c) 2013 Arthur Nogueira Neves
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.md DELETED
@@ -1,151 +0,0 @@
1
- [![Code Climate](http://img.shields.io/codeclimate/github/arthurnn/apn_sender.svg)](https://codeclimate.com/github/arthurnn/apn_sender)
2
- [![Build Status](https://travis-ci.org/arthurnn/apn_sender.svg?branch=master)](https://travis-ci.org/arthurnn/apn_sender)
3
-
4
- ## Synopsis
5
-
6
- Need to send background notifications to an iPhone application over a <em>persistent</em> connection in Ruby? Keep reading...
7
-
8
- ## The Story
9
-
10
- 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. This 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. Since Rails has no easy way to maintain a persistent connection internally, things start to look complicated.
11
-
12
- This 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.
13
-
14
- ## Yet another ApplePushNotification interface?
15
-
16
- 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.
17
-
18
- ## Current Status
19
-
20
- This gem has been used in production, on [500px](http://500px.com), sending hundreds of millions, if not, billions of notifications.
21
-
22
- ## Usage
23
-
24
- APN sender can use [Resque](http://github.com/defunkt/resque) or [Sidekiq](https://github.com/mperham/sidekiq) to send asynchronous messages, if none of them are installed it creates a new thread to send messages.
25
-
26
- ### 1. Use a background processor or not.
27
-
28
- You can either use Resque or Sidekiq, I strongly advice using Sidekiq, as apn_sender uses a connection pool for the apple socks. To use apn_sender with one of them you dont have to do anything, just include the background processor gem into your gemfile and it will all work.
29
-
30
- ### 2. Queueing Messages From Your Application
31
-
32
- To queue a message for sending through Apple's Push Notification service from your Rails application:
33
-
34
- ```
35
- APN.notify_async(token, opts_hash)
36
- ```
37
-
38
- Where ```token``` is the unique identifier of the iPhone to receive the notification and ```opts_hash``` can have any of the following keys:
39
-
40
- * :alert ## The alert to send
41
- * :badge ## The badge number to send
42
- * :sound ## The sound file to play on receipt, or true to play the default sound installed with your app
43
-
44
- If any other keys are present they'll be be passed along as custom data to your application.
45
-
46
- ### 3. Sending Queued Messages
47
-
48
- Put your ```apn_development.pem``` and ```apn_production.pem``` certificates from Apple in your ```RAILS_ROOT/config/certs``` directory.
49
-
50
- You also can configure some extra settings:
51
-
52
- ```
53
- APN.root = 'RAILS_ROOT/config/certs' # root to certificates folder
54
- APN.certificate_name = 'apn_production.pem' # certificate filename
55
- APN.host = 'apple host (on development sandbox url is used by default)'
56
- APN.password = 'certificate_password'
57
- APN.pool_size = 1 # number of connections on the pool
58
- APN.pool_timeout = 5 # timeout in seconds for connection pool
59
- APN.logger = Logger.new(File.join(Rails.root, 'log', 'apn_sender.log'))
60
- ```
61
-
62
- 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.
63
- On latest versions apn_sender will use Rails.logger as the default logger.
64
-
65
-
66
- ### 4. Checking Apple's Feedback Service
67
-
68
- 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.
69
-
70
- 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:
71
-
72
- ```
73
- # APN::Feedback accepts the same optional :environment
74
- # and :cert_path / :full_cert_path options as APN::Sender
75
- feedback = APN::Feedback.new()
76
-
77
- tokens = feedback.tokens # Array of device tokens
78
- tokens.each do |token|
79
- # ... custom logic here to stop you app from
80
- # sending further notifications to this token
81
- end
82
- ```
83
-
84
- 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):
85
-
86
- ```
87
- items = feedback.data # Array of APN::FeedbackItem elements
88
- items.each do |item|
89
- item.token
90
- item.timestamp
91
- # ... custom logic here
92
- end
93
- ```
94
-
95
- 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).
96
-
97
- 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.
98
-
99
-
100
- #### Warning: No really, check Apple's Feedback Service occasionally
101
-
102
- 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)).
103
-
104
- Just for the record, this is essentially what you want to have whenever run periodically for you:
105
- ```
106
- def self.clear_uninstalled_applications
107
- feedback_data = APN::Feedback.new(:environment #> :production).data
108
-
109
- feedback_data.each do |item|
110
- user = User.find_by_iphone_token( item.token )
111
-
112
- if user.iphone_token_updated_at && user.iphone_token_updated_at > item.timestamp
113
- return true # App has been reregistered since Apple determined it'd been uninstalled
114
- else
115
- user.update_attributes(iphone_token: nil, iphone_token_updated_at: Time.now)
116
- end
117
- end
118
- end
119
- ```
120
-
121
-
122
- ### Keeping Your Workers Working
123
-
124
- There's also an included sample ```apn_sender.monitrc``` file in the ```contrib/``` folder to help monit handle server restarts and unexpected disasters.
125
-
126
-
127
- ## Installation
128
-
129
- Add this line to your application's Gemfile:
130
-
131
- gem 'apn_sender', require: 'apn'
132
-
133
- And then execute:
134
-
135
- $ bundle
136
-
137
- Or install it yourself as:
138
-
139
- $ gem install apn_sender
140
-
141
- To add a few useful rake tasks for running workers, add the following line to your Rakefile:
142
-
143
- ```
144
- require 'apn/tasks'
145
- ```
146
-
147
- ## License
148
-
149
- APN Sender is released under the [MIT License](http://www.opensource.org/licenses/MIT).
150
-
151
-
data/Rakefile DELETED
@@ -1,35 +0,0 @@
1
- require "bundler"
2
- Bundler.setup
3
-
4
- require "rake"
5
- require "rspec/core/rake_task"
6
-
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"
13
- end
14
-
15
- task :install => :build do
16
- system "gem install apn_sender-#{APN::VERSION}.gem"
17
- end
18
-
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
25
-
26
- RSpec::Core::RakeTask.new("spec") do |spec|
27
- spec.pattern = "spec/**/*_spec.rb"
28
- end
29
-
30
- RSpec::Core::RakeTask.new('spec:progress') do |spec|
31
- spec.rspec_opts = %w(--format progress)
32
- spec.pattern = "spec/**/*_spec.rb"
33
- end
34
-
35
- task :default => :spec