apn_sender 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/LICENSE +1 -1
- data/README.md +36 -45
- data/lib/apn.rb +2 -3
- data/lib/apn/client.rb +13 -0
- data/lib/apn/connection.rb +1 -2
- data/lib/apn/jobs/sidekiq_notification_job.rb +2 -2
- data/lib/apn/notification.rb +57 -27
- data/lib/apn/version.rb +1 -1
- metadata +15 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9297870e306966d00d3d0acf01a3022dbbb1636
|
4
|
+
data.tar.gz: 25b8a81befe782fba5a0b7f0376ce9491f674a86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5048150d58ffba87b276ebe50d24bd1ab744f6eefa58ac94662cf22b5b773fde19fa66b840c1c982303693460d0d53283606aa9a0af7f99f04a6e4eb6802e634
|
7
|
+
data.tar.gz: ef4fc9e0ce014ac4bef75d1da4f858ae6317a86bed03aa09e8ca1f248e9b9cffef8f9a5ec2939024fb7db4ee89cfe87e91dcc0bb2c302a581cfcb8a38466f126
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
# Version 2.0
|
2
|
+
## 2.0.1
|
3
|
+
- Use bytesize to truncate alert when necessary
|
4
|
+
- Better calculation on payload size. (botvinik)
|
5
|
+
- Fix generating payload should use bytesize. (piotr-sokolowski)
|
6
|
+
- Rescuing and repairing broken connections (Arseniy Ivanov)
|
7
|
+
|
2
8
|
## 2.0.0
|
3
9
|
- adding connection_pool for handle apple sockets
|
4
10
|
- removing resque hard dependency
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,32 +1,33 @@
|
|
1
1
|
[![Code Climate](https://codeclimate.com/github/arthurnn/apn_sender.png)](https://codeclimate.com/github/arthurnn/apn_sender)
|
2
2
|
[![Build Status](https://travis-ci.org/arthurnn/apn_sender.png)](https://travis-ci.org/arthurnn/apn_sender)
|
3
3
|
|
4
|
-
## UPDATE May 3, 2013: current status
|
5
|
-
|
6
|
-
This project was not supported for a while, but we are going to support it again.
|
7
|
-
|
8
|
-
-----
|
9
|
-
|
10
4
|
## Synopsis
|
11
5
|
|
12
6
|
Need to send background notifications to an iPhone application over a <em>persistent</em> connection in Ruby? Keep reading...
|
13
7
|
|
14
8
|
## The Story
|
15
9
|
|
16
|
-
So you're building the server component of an iPhone application in Ruby
|
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.
|
17
11
|
|
18
|
-
|
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.
|
19
13
|
|
20
14
|
## Yet another ApplePushNotification interface?
|
21
15
|
|
22
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.
|
23
17
|
|
24
18
|
## Current Status
|
25
|
-
|
19
|
+
|
20
|
+
This gem has been used in production, on [500px](http://500px.com), sending hundreds of millions, if not, billions of notifications.
|
26
21
|
|
27
22
|
## Usage
|
28
23
|
|
29
|
-
|
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
|
30
31
|
|
31
32
|
To queue a message for sending through Apple's Push Notification service from your Rails application:
|
32
33
|
|
@@ -34,44 +35,33 @@ To queue a message for sending through Apple's Push Notification service from yo
|
|
34
35
|
APN.notify_async(token, opts_hash)
|
35
36
|
```
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
Options:
|
38
|
+
Where ```token``` is the unique identifier of the iPhone to receive the notification and ```opts_hash``` can have any of the following keys:
|
40
39
|
|
41
40
|
* :alert ## The alert to send
|
42
41
|
* :badge ## The badge number to send
|
43
42
|
* :sound ## The sound file to play on receipt, or true to play the default sound installed with your app
|
44
43
|
|
45
|
-
|
46
44
|
If any other keys are present they'll be be passed along as custom data to your application.
|
47
45
|
|
48
|
-
###
|
46
|
+
### 3. Sending Queued Messages
|
49
47
|
|
50
48
|
Put your ```apn_development.pem``` and ```apn_production.pem``` certificates from Apple in your ```RAILS_ROOT/config/certs``` directory.
|
51
49
|
|
52
|
-
|
50
|
+
You also can configure some extra settings:
|
53
51
|
|
54
52
|
```
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
# To generate daemon
|
62
|
-
./script/generate apn_sender
|
63
|
-
|
64
|
-
# To run daemon. Pass --help to print all options
|
65
|
-
./script/apn_sender start
|
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
|
66
59
|
```
|
67
60
|
|
68
|
-
|
69
|
-
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.
|
70
|
-
|
71
61
|
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.
|
72
62
|
|
73
63
|
|
74
|
-
###
|
64
|
+
### 4. Checking Apple's Feedback Service
|
75
65
|
|
76
66
|
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.
|
77
67
|
|
@@ -134,27 +124,28 @@ There's also an included sample ```apn_sender.monitrc``` file in the ```contrib/
|
|
134
124
|
|
135
125
|
## Installation
|
136
126
|
|
137
|
-
|
127
|
+
Add this line to your application's Gemfile:
|
138
128
|
|
139
|
-
|
140
|
-
$ gem install apn_sender
|
141
|
-
```
|
142
|
-
In your Rails app, add (2.3.x):
|
129
|
+
gem 'apn_sender', require: 'apn'
|
143
130
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
131
|
+
And then execute:
|
132
|
+
|
133
|
+
$ bundle
|
134
|
+
|
135
|
+
Or install it yourself as:
|
136
|
+
|
137
|
+
$ gem install apn_sender
|
148
138
|
|
149
|
-
```
|
150
|
-
gem 'apn_sender', require: 'apn'
|
151
|
-
```
|
152
139
|
To add a few useful rake tasks for running workers, add the following line to your Rakefile:
|
153
140
|
|
154
141
|
```
|
155
142
|
require 'apn/tasks'
|
156
143
|
```
|
157
144
|
|
158
|
-
##
|
145
|
+
## License
|
146
|
+
|
147
|
+
APN Sender is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
148
|
+
|
149
|
+
|
150
|
+
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/arthurnn/apn_sender/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
159
151
|
|
160
|
-
Copyright (c) 2013 Arthur Nogueira Neves. See LICENSE for details.
|
data/lib/apn.rb
CHANGED
@@ -34,8 +34,10 @@ module APN
|
|
34
34
|
def backend
|
35
35
|
@backend ||=
|
36
36
|
if defined?(Sidekiq)
|
37
|
+
require 'apn/jobs/sidekiq_notification_job'
|
37
38
|
APN::Backend::Sidekiq.new
|
38
39
|
elsif defined?(Resque)
|
40
|
+
require 'apn/jobs/resque_notification_job'
|
39
41
|
APN::Backend::Resque.new
|
40
42
|
else
|
41
43
|
APN::Backend::Simple.new
|
@@ -89,8 +91,5 @@ module APN::Jobs
|
|
89
91
|
QUEUE_NAME = :apple_push_notifications
|
90
92
|
end
|
91
93
|
|
92
|
-
require 'apn/jobs/sidekiq_notification_job' if defined?(Sidekiq)
|
93
|
-
require 'apn/jobs/resque_notification_job' if defined?(Resque)
|
94
94
|
require "apn/railtie" if defined?(Rails)
|
95
|
-
|
96
95
|
require 'apn/backend'
|
data/lib/apn/client.rb
CHANGED
@@ -13,6 +13,7 @@ module APN
|
|
13
13
|
def push(message)
|
14
14
|
socket.write(message.to_s)
|
15
15
|
socket.flush
|
16
|
+
|
16
17
|
if IO.select([socket], nil, nil, 1) && error = socket.read(6)
|
17
18
|
error = error.unpack("ccN")
|
18
19
|
APN.log(:error, "Error on message: #{error}")
|
@@ -21,6 +22,11 @@ module APN
|
|
21
22
|
|
22
23
|
APN.log(:debug, "Message sent.")
|
23
24
|
true
|
25
|
+
rescue OpenSSL::SSL::SSLError, Errno::EPIPE => e
|
26
|
+
APN.log(:error, "[##{self.object_id}] Exception occurred: #{e.inspect}, socket state: #{socket.inspect}")
|
27
|
+
reset_socket
|
28
|
+
APN.log(:debug, "[##{self.object_id}] Socket reestablished, socket state: #{socket.inspect}")
|
29
|
+
retry
|
24
30
|
end
|
25
31
|
|
26
32
|
def feedback
|
@@ -35,6 +41,7 @@ module APN
|
|
35
41
|
end
|
36
42
|
|
37
43
|
private
|
44
|
+
|
38
45
|
# Open socket to Apple's servers
|
39
46
|
def setup_socket
|
40
47
|
ctx = setup_certificate
|
@@ -48,6 +55,12 @@ module APN
|
|
48
55
|
end
|
49
56
|
end
|
50
57
|
|
58
|
+
def reset_socket
|
59
|
+
@socket.close if @socket
|
60
|
+
@socket = nil
|
61
|
+
socket
|
62
|
+
end
|
63
|
+
|
51
64
|
def setup_certificate
|
52
65
|
ctx = OpenSSL::SSL::SSLContext.new
|
53
66
|
ctx.cert = OpenSSL::X509::Certificate.new(@apn_cert)
|
data/lib/apn/connection.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module APN
|
2
2
|
module Connection
|
3
|
-
|
4
|
-
# responsibilities so APN::Sender and APN::Feedback and focus on their respective specialties.
|
3
|
+
|
5
4
|
def connection_pool
|
6
5
|
@pool ||= ConnectionPool.new(size: (pool_size || 1), timeout: (pool_timeout || 5)) do
|
7
6
|
APN::Client.new(host: host,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module APN::Jobs
|
2
|
-
# This is the class that's actually enqueued via
|
3
|
-
# It gets added to the +apple_server_notifications+
|
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
|
4
4
|
# workers of the +APN::Sender+ class.
|
5
5
|
class SidekiqNotificationJob
|
6
6
|
include Sidekiq::Worker
|
data/lib/apn/notification.rb
CHANGED
@@ -19,9 +19,8 @@ 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
|
23
|
-
#
|
24
|
-
MAX_ALERT_LENGTH = 199
|
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
|
25
24
|
DATA_MAX_BYTES = 256
|
26
25
|
|
27
26
|
attr_accessor :options, :token
|
@@ -29,15 +28,17 @@ module APN
|
|
29
28
|
@options = opts.is_a?(Hash) ? opts.symbolize_keys : {:alert => opts}
|
30
29
|
@token = token
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
raise "The maximum size allowed for a notification payload is 256 bytes." if packaged_notification.size.to_i > 256
|
31
|
+
raise "The maximum size allowed for a notification payload is #{DATA_MAX_BYTES} bytes." if payload_size > DATA_MAX_BYTES
|
35
32
|
end
|
36
33
|
|
37
34
|
def to_s
|
38
35
|
packaged_notification
|
39
36
|
end
|
40
37
|
|
38
|
+
def payload_size
|
39
|
+
packaged_message.bytesize
|
40
|
+
end
|
41
|
+
|
41
42
|
# Ensures at least one of <code>%w(alert badge sound)</code> is present
|
42
43
|
def valid?
|
43
44
|
return true if [:alert, :badge, :sound].any?{|key| options.keys.include?(key) }
|
@@ -48,7 +49,7 @@ module APN
|
|
48
49
|
def packaged_notification
|
49
50
|
pt = packaged_token
|
50
51
|
pm = packaged_message
|
51
|
-
[0, 0, 32, pt, 0,
|
52
|
+
[0, 0, 32, pt, 0, payload_size, pm].pack("ccca*cca*")
|
52
53
|
end
|
53
54
|
|
54
55
|
# Device token, compressed and hex-ified
|
@@ -60,29 +61,58 @@ module APN
|
|
60
61
|
# Extracts :alert, :badge, and :sound keys into the 'aps' hash, merges any other hash data
|
61
62
|
# into the root of the hash to encode and send to apple.
|
62
63
|
def packaged_message
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
64
|
+
@packaged_message ||=
|
65
|
+
begin
|
66
|
+
opts = @options.dup
|
67
|
+
hsh = {'aps' => {}}
|
68
|
+
if alert = opts.delete(:alert)
|
69
|
+
alert = alert.to_s unless alert.is_a?(Hash)
|
70
|
+
hsh['aps']['alert'] = alert
|
71
|
+
end
|
72
|
+
hsh['aps']['badge'] = opts.delete(:badge).to_i if opts[:badge]
|
73
|
+
if sound = opts.delete(:sound)
|
74
|
+
hsh['aps']['sound'] = sound.is_a?(TrueClass) ? 'default' : sound.to_s
|
75
|
+
end
|
76
|
+
hsh.merge!(opts)
|
77
|
+
payload(hsh)
|
78
|
+
end
|
75
79
|
end
|
76
80
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
private
|
82
|
+
|
83
|
+
def payload(hash)
|
84
|
+
str = ActiveSupport::JSON::encode(hash)
|
85
|
+
|
86
|
+
if APN.truncate_alert && str.bytesize > DATA_MAX_BYTES
|
87
|
+
if hash['aps']['alert'].is_a?(Hash)
|
88
|
+
alert = hash['aps']['alert']['loc-args'][0]
|
89
|
+
else
|
90
|
+
alert = hash['aps']['alert']
|
91
|
+
end
|
92
|
+
max_bytesize = DATA_MAX_BYTES - (str.bytesize - alert.bytesize)
|
93
|
+
|
94
|
+
raise "Even truncating the alert wont be enought to have a #{DATA_MAX_BYTES} message" if max_bytesize <= 0
|
95
|
+
alert = truncate_alert(alert, max_bytesize)
|
96
|
+
|
97
|
+
if hash['aps']['alert'].is_a?(Hash)
|
98
|
+
hash['aps']['alert']['loc-args'][0] = alert
|
99
|
+
else
|
100
|
+
hash['aps']['alert'] = alert
|
101
|
+
end
|
102
|
+
str = ActiveSupport::JSON::encode(hash)
|
84
103
|
end
|
104
|
+
str
|
85
105
|
end
|
86
|
-
|
106
|
+
|
107
|
+
def truncate_alert(alert, max_size)
|
108
|
+
alert.each_char.each_with_object('') do |char, result|
|
109
|
+
if result.bytesize + char.bytesize > max_size
|
110
|
+
break result
|
111
|
+
else
|
112
|
+
result << char
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
87
117
|
end
|
88
118
|
end
|
data/lib/apn/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apn_sender
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kali Donovan
|
@@ -15,42 +15,42 @@ dependencies:
|
|
15
15
|
name: connection_pool
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- -
|
18
|
+
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: '0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- -
|
25
|
+
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: activesupport
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
|
-
- -
|
32
|
+
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
34
|
version: '3.1'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
|
-
- -
|
39
|
+
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '3.1'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: daemons
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- -
|
46
|
+
- - ">="
|
47
47
|
- !ruby/object:Gem::Version
|
48
48
|
version: '0'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- -
|
53
|
+
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
56
|
description: Background worker to send Apple Push Notifications over a persistent
|
@@ -61,6 +61,11 @@ executables: []
|
|
61
61
|
extensions: []
|
62
62
|
extra_rdoc_files: []
|
63
63
|
files:
|
64
|
+
- CHANGELOG.md
|
65
|
+
- LICENSE
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/apn.rb
|
64
69
|
- lib/apn/backend.rb
|
65
70
|
- lib/apn/client.rb
|
66
71
|
- lib/apn/connection.rb
|
@@ -72,11 +77,6 @@ files:
|
|
72
77
|
- lib/apn/sender_daemon.rb
|
73
78
|
- lib/apn/tasks.rb
|
74
79
|
- lib/apn/version.rb
|
75
|
-
- lib/apn.rb
|
76
|
-
- CHANGELOG.md
|
77
|
-
- LICENSE
|
78
|
-
- README.md
|
79
|
-
- Rakefile
|
80
80
|
homepage: http://github.com/arthurnn/apn_sender
|
81
81
|
licenses:
|
82
82
|
- MIT
|
@@ -87,17 +87,17 @@ require_paths:
|
|
87
87
|
- lib
|
88
88
|
required_ruby_version: !ruby/object:Gem::Requirement
|
89
89
|
requirements:
|
90
|
-
- -
|
90
|
+
- - ">="
|
91
91
|
- !ruby/object:Gem::Version
|
92
92
|
version: '1.9'
|
93
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
|
-
- -
|
95
|
+
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: 1.3.6
|
98
98
|
requirements: []
|
99
99
|
rubyforge_project:
|
100
|
-
rubygems_version: 2.0
|
100
|
+
rubygems_version: 2.2.0
|
101
101
|
signing_key:
|
102
102
|
specification_version: 4
|
103
103
|
summary: Background worker to send Apple Push Notifications over a persistent TCP
|