racoon 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.mdown +96 -83
- data/bin/racoon-firehose +59 -0
- data/bin/racoon-send +11 -5
- data/bin/{racoond → racoon-worker} +16 -12
- data/lib/racoon.rb +5 -3
- data/lib/racoon/apns/connection.rb +52 -0
- data/lib/racoon/apns/feedback_connection.rb +29 -0
- data/lib/racoon/config.rb +5 -0
- data/lib/racoon/firehose.rb +70 -0
- data/lib/racoon/notification.rb +21 -2
- data/lib/racoon/payload.rb +5 -0
- data/lib/racoon/worker.rb +76 -0
- metadata +31 -17
- data/lib/racoon/client.rb +0 -44
- data/lib/racoon/feedback_client.rb +0 -24
- data/lib/racoon/server.rb +0 -193
data/README.mdown
CHANGED
@@ -6,25 +6,28 @@ has since taken on a different path. How does it differ from apnserver? By a few
|
|
6
6
|
1. It implements the APNS feedback service;
|
7
7
|
2. Uses Yajl for JSON encoding/decoding rather than ActiveSupport;
|
8
8
|
3. Expects certificates as strings instead of paths to files;
|
9
|
-
4. Does not assume there is only one certificate; and
|
10
|
-
5. Receives packets containing notifications from beanstalkd instead of a listening socket
|
9
|
+
4. Does not assume there is only one certificate (read: supports multiple projects); and
|
10
|
+
5. Receives packets containing notifications from beanstalkd instead of a listening socket;
|
11
|
+
6. Operates on a distributed architecture (many parallel workers, one firehose.
|
11
12
|
|
12
|
-
The above changes were made because of the need
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
The above changes were made because of the need to replace an existing APNs provider with something
|
14
|
+
more robust, and better suited to scaling upwards. This APNs provider had a couple requirements:
|
15
|
+
|
16
|
+
1. Support fully the APNs protocol (including feedback)
|
17
|
+
2. Scale outwards horizontally
|
18
|
+
3. Support multiple projects
|
16
19
|
|
17
20
|
It should be noted that the development of this project is independent of the work bpoweski
|
18
21
|
is doing on apnserver. If you're looking for that project, [go here](https://github.com/bpoweski/apnserver).
|
19
22
|
|
20
23
|
## Description
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
Racoon consists of a firehose, which maintains the connections to Apple's APNs service. It also
|
26
|
+
consists of a worker, which works on a beanstalk tube to pop notifications off and process them,
|
27
|
+
before sending them off to the firehose. You can run many workers, they all run in parallel to
|
28
|
+
one another. Additionally, it includes a command line tool to send (test) the system.
|
29
|
+
|
30
|
+
At this time, Racoon only supports Apple's APNs service.
|
28
31
|
|
29
32
|
## Remaining Tasks & Issues
|
30
33
|
|
@@ -46,99 +49,109 @@ using frac.as, this is the file you would upload to the web service.
|
|
46
49
|
If you're not using frac.as, then the contents of this file are what you need to use as
|
47
50
|
your certificate, not the path to the file.
|
48
51
|
|
49
|
-
##
|
50
|
-
|
51
|
-
<pre>
|
52
|
-
Usage: racoond [options]
|
53
|
-
--beanstalk <csv ip:port mappings>
|
54
|
-
The comma-separated list of ip:port for beanstalk servers
|
55
|
-
|
56
|
-
--pid <pid file path>
|
57
|
-
Path used for the PID file. Defaults to /var/run/racoon.pid
|
52
|
+
## Firehose
|
58
53
|
|
59
|
-
|
60
|
-
|
54
|
+
The firehose sits at the end of the pipeline. All the workers deliver messages to this
|
55
|
+
part of racoon. Think of it as the drain in your sink.
|
61
56
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
57
|
+
<pre>
|
58
|
+
Usage: racoon-firehose [switches]
|
59
|
+
--pid </var/run/racoon-firehose.pid> the path to store the pid
|
60
|
+
--log </var/log/racoon-firehosed.log> the path to store the log
|
61
|
+
--daemon to daemonize the server
|
62
|
+
--help this message
|
67
63
|
</pre>
|
68
64
|
|
69
|
-
##
|
70
|
-
|
71
|
-
TODO: Document this
|
65
|
+
## Worker
|
72
66
|
|
73
|
-
|
67
|
+
The worker is the part of the system which interacts with a beanstalk cluster. Each worker
|
68
|
+
can talk to more than one beanstalk server, thus forming the "cluster". Since beanstalk
|
69
|
+
clustering is all done client side, it's not a traditional cluster, but I'm going to call
|
70
|
+
it that way anyway.
|
74
71
|
|
75
|
-
|
76
|
-
|
72
|
+
The worker pops items that are ready off of the beanstalk cluster. It grabs those packets,
|
73
|
+
constructs a message out of it, and then forms another packet, suitable for sending to
|
74
|
+
Apple, before sending that packet to the firehose.
|
77
75
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
@beanstalk
|
84
|
-
end
|
85
|
-
```
|
76
|
+
You may run as many workers as your little heart desires. Keep in mind however, there will
|
77
|
+
come a point when having only one firehose is not suitable to handle the amount of traffic
|
78
|
+
you will be passing through from the workers. At this point, a second firehose will need
|
79
|
+
to be started. I doubt anyone will ever be processing enough messages a second to warrant
|
80
|
+
a second firehose.
|
86
81
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
82
|
+
<pre>
|
83
|
+
Usage: racoon-worker [switches]
|
84
|
+
--beanstalk <127.0.0.1:11300> csv list of ip:port for beanstalk servers
|
85
|
+
--pid </var/run/racoon-worker.pid> the path to store the pid
|
86
|
+
--log </var/log/racoon-worker.log> the path to store the log
|
87
|
+
--daemon to daemonize the server
|
88
|
+
--help this message
|
89
|
+
</pre>
|
91
90
|
|
92
|
-
|
93
|
-
|
91
|
+
It should be noted that the worker's `--beanstalk` parameter requires comma separated values
|
92
|
+
in `IP:port` pairings. Where `IP` is either an IP address, or a hostname to a host running a
|
93
|
+
beanstalkd server on the associated port `port`. In the future, I will create a config file
|
94
|
+
instead of having to specify this information on the command line each time. My apologies for
|
95
|
+
the inconvenience.
|
94
96
|
|
95
|
-
|
97
|
+
## Sender
|
96
98
|
|
97
|
-
|
98
|
-
|
99
|
+
The sender is the program used to form a packet, place it on beanstalk for racoon to consume.
|
100
|
+
It is useful during testing.
|
99
101
|
|
100
|
-
|
101
|
-
|
102
|
-
|
102
|
+
<pre>
|
103
|
+
Usage: racoon-send [switches] (--b64-token | --hex-token) <token>
|
104
|
+
--beanstalk <127.0.0.1:11300> csv of ip:port for beanstalk servers
|
105
|
+
--pem <path> the path to the pem file, if a pem is supplied the server defaults to gateway.push.apple.com:2195
|
106
|
+
--alert <message> the message to send
|
107
|
+
--sound <default> the sound to play, defaults to 'default'
|
108
|
+
--badge <number> the badge number
|
109
|
+
--custom <json string> a custom json string to be added to the main object
|
110
|
+
--b64-token <token> a base 64 encoded device token
|
111
|
+
--hex-token <token> a hex encoded device token
|
112
|
+
--help this message
|
113
|
+
</pre>
|
103
114
|
|
104
|
-
|
115
|
+
## Preparing packets to place on beanstalk
|
105
116
|
|
106
|
-
|
107
|
-
|
117
|
+
Until an API can be built that you can tie into with your own applications, please take care
|
118
|
+
to construct your notifications as YAML payload with the following format. I will use ruby
|
119
|
+
syntax for this example:
|
108
120
|
|
109
121
|
```ruby
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
122
|
+
{
|
123
|
+
:project => { :name => "My awesome app", :certificate => "...", :sandbox => true },
|
124
|
+
:identifier => 12345,
|
125
|
+
:notification => { :aps => { :alert => "text",
|
126
|
+
:sound => "default",
|
127
|
+
:badge => 1,
|
128
|
+
:custom => { ... }
|
129
|
+
}
|
130
|
+
},
|
131
|
+
:device_token => "..."
|
132
|
+
}
|
117
133
|
```
|
118
134
|
|
119
|
-
|
135
|
+
A few key points need to be raised here. For starters, the `sandbox` key should only be true if
|
136
|
+
you desire to work in the sandbox, and your `certificate` contains the text of your development
|
137
|
+
certificate.
|
120
138
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
:send_at => Time.mktime(2012, 12, 21, 4, 15)
|
125
|
-
:device_token => "binary encoded token",
|
126
|
-
:sandbox => true
|
127
|
-
})
|
128
|
-
```
|
139
|
+
Secondly, the `identifier` must be a unique 32-bit number identifying your message should you
|
140
|
+
choose to want useful error messages. (This feature is not presently written, as such, you can
|
141
|
+
supply anything you want, just make sure it falls within the range of `0` to `4294967295`.
|
129
142
|
|
130
|
-
|
131
|
-
|
132
|
-
|
143
|
+
The `notification` key represents the payload we'll send to Apple. The `custom` key must be
|
144
|
+
present if you intend to send custom data. Note however, that `custom` will be removed, and the
|
145
|
+
items you place in its hash will be substituted in with the payload when the message is passed
|
146
|
+
to Apple. As such, if you want a custom key -> value pair of: `"foo" => "bar"`, you would ensure
|
147
|
+
you have: `:custom => { "foo" => "bar" }` in the notification. Your application should just look
|
148
|
+
for "foo" in the payload delivered to the app.
|
133
149
|
|
134
|
-
|
135
|
-
|
136
|
-
certificate. If left out, we assume production.
|
150
|
+
Finally, the ```device_token``` is a binary encoded representation of your devices token. Your
|
151
|
+
app gets it in hex, please ensure you convert it to binary before sending it to beanstalk.
|
137
152
|
|
138
|
-
|
139
|
-
|
140
|
-
queue up items, and during peak times, add another **N** more racoon servers to make up any
|
141
|
-
backlog, to ensure our messages are sent fast, and that we can scale.
|
153
|
+
TODO: Document new way of handling scheduling notifications in the future. Abstract beanstalk
|
154
|
+
away from users of the library, give them a proper client interface to use instead.
|
142
155
|
|
143
156
|
## Installation
|
144
157
|
|
data/bin/racoon-firehose
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'getoptlong'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'daemons'
|
7
|
+
require 'racoon'
|
8
|
+
require 'csv'
|
9
|
+
|
10
|
+
def usage
|
11
|
+
puts "Usage: racoon-firehose [switches]"
|
12
|
+
puts " --pid </var/run/racoon-firehose.pid> the path to store the pid"
|
13
|
+
puts " --log </var/log/racoon-firehosed.log> the path to store the log"
|
14
|
+
puts " --daemon to daemonize the server"
|
15
|
+
puts " --help this message"
|
16
|
+
end
|
17
|
+
|
18
|
+
def daemonize
|
19
|
+
Daemonize.daemonize(@log_file, 'racoon-firehose')
|
20
|
+
open(@pid_file,"w") { |f| f.write(Process.pid) }
|
21
|
+
open(@pid_file,"w") do |f|
|
22
|
+
f.write(Process.pid)
|
23
|
+
File.chmod(0644, @pid_file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
opts = GetoptLong.new(
|
28
|
+
["--pid", "-i", GetoptLong::REQUIRED_ARGUMENT],
|
29
|
+
["--log", "-l", GetoptLong::REQUIRED_ARGUMENT],
|
30
|
+
["--help", "-h", GetoptLong::NO_ARGUMENT],
|
31
|
+
["--daemon", "-d", GetoptLong::NO_ARGUMENT]
|
32
|
+
)
|
33
|
+
|
34
|
+
@pid_file = '/var/run/racoon-firehose.pid'
|
35
|
+
@log_file = '/var/log/racoon-firehose.log'
|
36
|
+
daemon = false
|
37
|
+
|
38
|
+
opts.each do |opt, arg|
|
39
|
+
case opt
|
40
|
+
when '--help'
|
41
|
+
usage
|
42
|
+
exit 1
|
43
|
+
when '--pid'
|
44
|
+
@pid_file = arg
|
45
|
+
when '--log'
|
46
|
+
@log_file = arg
|
47
|
+
when '--daemon'
|
48
|
+
daemon = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
Racoon::Config.logger = Logger.new(@log_file)
|
53
|
+
|
54
|
+
if daemon
|
55
|
+
daemonize
|
56
|
+
else
|
57
|
+
puts "Starting racoon worker."
|
58
|
+
end
|
59
|
+
Racoon::Firehose.new.start!
|
data/bin/racoon-send
CHANGED
@@ -14,6 +14,7 @@ require 'beanstalk-client'
|
|
14
14
|
def usage
|
15
15
|
puts "Usage: racoon-send [switches] (--b64-token | --hex-token) <token>"
|
16
16
|
puts " --beanstalk <127.0.0.1:11300> csv of ip:port for beanstalk servers"
|
17
|
+
puts " --tube <racoon> the beanstalk tube to use"
|
17
18
|
puts " --pem <path> the path to the pem file, if a pem is supplied the server defaults to gateway.push.apple.com:2195"
|
18
19
|
puts " --alert <message> the message to send"
|
19
20
|
puts " --sound <default> the sound to play, defaults to 'default'"
|
@@ -26,6 +27,7 @@ end
|
|
26
27
|
|
27
28
|
opts = GetoptLong.new(
|
28
29
|
["--beanstalk", "-b", GetoptLong::REQUIRED_ARGUMENT],
|
30
|
+
["--tube", "-t", GetoptLong::REQUIRED_ARGUMENT],
|
29
31
|
["--pem", "-c", GetoptLong::REQUIRED_ARGUMENT],
|
30
32
|
["--alert", "-a", GetoptLong::REQUIRED_ARGUMENT],
|
31
33
|
["--sound", "-S", GetoptLong::REQUIRED_ARGUMENT],
|
@@ -37,6 +39,7 @@ opts = GetoptLong.new(
|
|
37
39
|
)
|
38
40
|
|
39
41
|
beanstalks = ["127.0.0.1:11300"]
|
42
|
+
tube = 'racoon'
|
40
43
|
certificate = nil
|
41
44
|
notification = Racoon::Notification.new
|
42
45
|
|
@@ -47,6 +50,8 @@ opts.each do |opt, arg|
|
|
47
50
|
exit
|
48
51
|
when '--beanstalk'
|
49
52
|
beanstalks = CSV.parse(arg)[0]
|
53
|
+
when '--tube'
|
54
|
+
tube = arg
|
50
55
|
when '--pem'
|
51
56
|
certificate = File.read(arg)
|
52
57
|
when '--alert'
|
@@ -69,12 +74,13 @@ if notification.device_token.nil?
|
|
69
74
|
exit
|
70
75
|
else
|
71
76
|
bs = Beanstalk::Pool.new beanstalks
|
72
|
-
%w{use watch}.each { |s| bs.send(s,
|
77
|
+
%w{use watch}.each { |s| bs.send(s, tube) }
|
73
78
|
bs.ignore("default")
|
74
|
-
project = { :name => "test", :certificate => certificate }
|
75
|
-
|
79
|
+
project = { :name => "test", :certificate => certificate, :sandbox => true }
|
80
|
+
notif = { :project => project,
|
81
|
+
:identifier => 1,
|
76
82
|
:notification => notification.payload,
|
77
83
|
:device_token => notification.device_token,
|
78
|
-
|
79
|
-
|
84
|
+
}
|
85
|
+
bs.yput(notif)
|
80
86
|
end
|
@@ -4,22 +4,21 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
4
4
|
require 'getoptlong'
|
5
5
|
require 'rubygems'
|
6
6
|
require 'daemons'
|
7
|
-
require 'eventmachine'
|
8
7
|
require 'racoon'
|
9
|
-
require 'racoon/server'
|
10
8
|
require 'csv'
|
11
9
|
|
12
10
|
def usage
|
13
|
-
puts "Usage:
|
11
|
+
puts "Usage: racoon-worker [switches]"
|
14
12
|
puts " --beanstalk <127.0.0.1:11300> csv list of ip:port for beanstalk servers"
|
15
|
-
puts " --
|
16
|
-
puts " --
|
13
|
+
puts " --tube <racoon> the beanstalk tube to use"
|
14
|
+
puts " --pid </var/run/racoon-worker.pid> the path to store the pid"
|
15
|
+
puts " --log </var/log/racoon-worker.log> the path to store the log"
|
17
16
|
puts " --daemon to daemonize the server"
|
18
17
|
puts " --help this message"
|
19
18
|
end
|
20
19
|
|
21
20
|
def daemonize
|
22
|
-
Daemonize.daemonize(@log_file, '
|
21
|
+
Daemonize.daemonize(@log_file, 'racoon-worker')
|
23
22
|
open(@pid_file,"w") { |f| f.write(Process.pid) }
|
24
23
|
open(@pid_file,"w") do |f|
|
25
24
|
f.write(Process.pid)
|
@@ -29,6 +28,7 @@ end
|
|
29
28
|
|
30
29
|
opts = GetoptLong.new(
|
31
30
|
["--beanstalk", "-b", GetoptLong::REQUIRED_ARGUMENT],
|
31
|
+
["--tube", "-t", GetoptLong::REQUIRED_ARGUMENT],
|
32
32
|
["--pid", "-i", GetoptLong::REQUIRED_ARGUMENT],
|
33
33
|
["--log", "-l", GetoptLong::REQUIRED_ARGUMENT],
|
34
34
|
["--help", "-h", GetoptLong::NO_ARGUMENT],
|
@@ -36,8 +36,9 @@ opts = GetoptLong.new(
|
|
36
36
|
)
|
37
37
|
|
38
38
|
beanstalks = ["127.0.0.1:11300"]
|
39
|
-
|
40
|
-
@
|
39
|
+
tube = 'racoon'
|
40
|
+
@pid_file = '/var/run/racoon-worker.pid'
|
41
|
+
@log_file = '/var/log/racoon-worker.log'
|
41
42
|
daemon = false
|
42
43
|
|
43
44
|
opts.each do |opt, arg|
|
@@ -47,6 +48,8 @@ opts.each do |opt, arg|
|
|
47
48
|
exit 1
|
48
49
|
when '--beanstalk'
|
49
50
|
beanstalks = CSV.parse(arg)[0]
|
51
|
+
when '--tube'
|
52
|
+
tube = arg
|
50
53
|
when '--pid'
|
51
54
|
@pid_file = arg
|
52
55
|
when '--log'
|
@@ -58,8 +61,9 @@ end
|
|
58
61
|
|
59
62
|
Racoon::Config.logger = Logger.new(@log_file)
|
60
63
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
+
if daemon
|
65
|
+
daemonize
|
66
|
+
else
|
67
|
+
puts "Starting racoon worker."
|
64
68
|
end
|
65
|
-
|
69
|
+
Racoon::Worker.new(beanstalks, tube).start!
|
data/lib/racoon.rb
CHANGED
@@ -2,9 +2,11 @@ require 'logger'
|
|
2
2
|
require 'racoon/config'
|
3
3
|
require 'racoon/payload'
|
4
4
|
require 'racoon/notification'
|
5
|
-
require 'racoon/
|
6
|
-
require 'racoon/
|
5
|
+
require 'racoon/apns/connection'
|
6
|
+
require 'racoon/apns/feedback_connection'
|
7
|
+
require 'racoon/worker'
|
8
|
+
require 'racoon/firehose'
|
7
9
|
|
8
10
|
module Racoon
|
9
|
-
VERSION = "0.
|
11
|
+
VERSION = "0.5.0"
|
10
12
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Racoon - A distributed APNs provider
|
2
|
+
# Copyright (c) 2011, Jeremy Tregunna, All Rights Reserved.
|
3
|
+
#
|
4
|
+
# This module contains the connection to the APNs service.
|
5
|
+
|
6
|
+
require 'openssl'
|
7
|
+
require 'socket'
|
8
|
+
|
9
|
+
module Racoon
|
10
|
+
module APNS
|
11
|
+
class Connection
|
12
|
+
attr_accessor :pem, :host, :port, :password
|
13
|
+
|
14
|
+
def initialize(pem, host = 'gateway.push.apple.com', port = 2195, pass = nil)
|
15
|
+
@pem, @host, @port, @password = pem, host, port, pass
|
16
|
+
end
|
17
|
+
|
18
|
+
def connect!
|
19
|
+
raise "Your certificate is not set." unless self.pem
|
20
|
+
|
21
|
+
@context = OpenSSL::SSL::SSLContext.new
|
22
|
+
@context.cert = OpenSSL::X509::Certificate.new(self.pem)
|
23
|
+
@context.key = OpenSSL::PKey::RSA.new(self.pem, self.password)
|
24
|
+
|
25
|
+
@sock = TCPSocket.new(self.host, self.port.to_i)
|
26
|
+
@ssl = OpenSSL::SSL::SSLSocket.new(@sock, @context)
|
27
|
+
@ssl.connect
|
28
|
+
|
29
|
+
return @sock, @ssl
|
30
|
+
end
|
31
|
+
|
32
|
+
def disconnect!
|
33
|
+
@ssl.close
|
34
|
+
@sock.close
|
35
|
+
@ssl = nil
|
36
|
+
@sock = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def write(bytes)
|
40
|
+
if host.include? "sandbox"
|
41
|
+
notification = Notification.parse(bytes)
|
42
|
+
Config.logger.debug "#{Time.now} [#{host}:#{port}] Device: #{notification.device_token.unpack('H*')} sending #{notification.json_payload}"
|
43
|
+
end
|
44
|
+
@ssl.write(notification.to_bytes)
|
45
|
+
end
|
46
|
+
|
47
|
+
def connected?
|
48
|
+
@ssl
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Racoon - A distributed APNs provider
|
2
|
+
# Copyright (c) 2011, Jeremy Tregunna, All Rights Reserved.
|
3
|
+
#
|
4
|
+
# This module contains the code that connects to the feedback service.
|
5
|
+
|
6
|
+
module Racoon
|
7
|
+
module APNS
|
8
|
+
class FeedbackConnection < Connection
|
9
|
+
def initialize(pem, host = 'feedback.push.apple.com', port = 2196, pass = nil)
|
10
|
+
@pem, @host, @port, @pass = pem, host, port, pass
|
11
|
+
end
|
12
|
+
|
13
|
+
def read
|
14
|
+
records ||= []
|
15
|
+
while record = @ssl.read(38)
|
16
|
+
records << parse_tuple(record)
|
17
|
+
end
|
18
|
+
records
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parse_tuple(data)
|
24
|
+
feedback = data.unpack("N1n1H*")
|
25
|
+
{ :feedback_at => Time.at(feedback[0]), :length => feedback[1], :device_token => feedback[2] }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/racoon/config.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Racoon - A distributed APNs provider
|
2
|
+
# Copyright (c) 2011, Jeremy Tregunna, All Rights Reserved.
|
3
|
+
#
|
4
|
+
# This module contains the firehose which is responsible for maintaining all the open
|
5
|
+
# connections to Apple, and sending data over the right ones.
|
6
|
+
|
7
|
+
require 'digest/sha1'
|
8
|
+
require 'eventmachine'
|
9
|
+
require 'ffi-rzmq'
|
10
|
+
|
11
|
+
module Racoon
|
12
|
+
class Firehose
|
13
|
+
def initialize(address = "tcp://*:11555", context = ZMQ::Context.new(1), &feedback_callback)
|
14
|
+
@connections = {}
|
15
|
+
@context = context
|
16
|
+
@firehose = context.socket(ZMQ::PULL)
|
17
|
+
@address = address
|
18
|
+
@feedback_callback = feedback_callback
|
19
|
+
end
|
20
|
+
|
21
|
+
def start!
|
22
|
+
EventMachine::run do
|
23
|
+
@firehose.bind(@address)
|
24
|
+
|
25
|
+
EventMachine::PeriodicTimer.new(28800) do
|
26
|
+
@connections.each_pair do |key, data|
|
27
|
+
begin
|
28
|
+
uri = "gateway.#{project[:sandbox] ? 'sandbox.' : ''}push.apple.com"
|
29
|
+
feedback = Racoon::APNS::FeedbackConnection.new(data[:certificate], uri)
|
30
|
+
feedback.connect!
|
31
|
+
feedback.read.each do |record|
|
32
|
+
@feedback_callback.call(record) if @feedback_callback
|
33
|
+
end
|
34
|
+
rescue Errno::EPIPE, OpenSSL::SSL::SSLError, Errno::ECONNRESET
|
35
|
+
feedback.disconnect!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
EventMachine::PeriodicTimer.new(0.1) do
|
41
|
+
received_message = ZMQ::Message.new
|
42
|
+
@firehose.recv(received_message, ZMQ::NOBLOCK)
|
43
|
+
yaml_string = received_message.copy_out_string
|
44
|
+
|
45
|
+
if yaml_string and yaml_string != ""
|
46
|
+
packet = YAML::load(yaml_string)
|
47
|
+
|
48
|
+
apns(packet[:project], packet[:bytes])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def apns(project, bytes, retries=2)
|
55
|
+
uri = "gateway.#{project[:sandbox] ? 'sandbox.' : ''}push.apple.com"
|
56
|
+
hash = Digest::SHA1.hexdigest("#{project[:name]}-#{project[:certificate]}")
|
57
|
+
|
58
|
+
begin
|
59
|
+
connection = Racoon::APNS::Connection.new(project[:certificate], uri)
|
60
|
+
@connections[hash] ||= { :connection => connection, :certificate => project[:certificate], :sandbox => project[:sandbox] }
|
61
|
+
|
62
|
+
connection.connect! unless connection.connected?
|
63
|
+
connection.write(bytes)
|
64
|
+
rescue Errno::EPIPE, OpenSSL::SSL::SSLError, Errno::ECONNRESET
|
65
|
+
connection.disconnect!
|
66
|
+
retry if (retries -= 1) > 0
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/racoon/notification.rb
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# Racoon - A distributed APNs provider
|
2
|
+
# Copyright (c) 2011, Jeremy Tregunna, All Rights Reserved.
|
3
|
+
#
|
4
|
+
# This module contains the class that represents notifications and all their details.
|
5
|
+
|
1
6
|
require 'racoon/payload'
|
2
7
|
require 'base64'
|
3
8
|
require 'yajl'
|
@@ -6,11 +11,10 @@ module Racoon
|
|
6
11
|
class Notification
|
7
12
|
include Racoon::Payload
|
8
13
|
|
9
|
-
attr_accessor :identifier, :expiry, :device_token, :alert, :badge, :sound, :custom, :
|
14
|
+
attr_accessor :identifier, :expiry, :device_token, :alert, :badge, :sound, :custom, :expiry
|
10
15
|
|
11
16
|
def initialize
|
12
17
|
@expiry = 0
|
13
|
-
@send_at = Time.now
|
14
18
|
end
|
15
19
|
|
16
20
|
def payload
|
@@ -75,5 +79,20 @@ module Racoon
|
|
75
79
|
|
76
80
|
notification
|
77
81
|
end
|
82
|
+
|
83
|
+
def self.create_from_packet(packet)
|
84
|
+
aps = packet[:notification][:aps]
|
85
|
+
|
86
|
+
notification = Notification.new
|
87
|
+
notification.identifier = packet[:identifier]
|
88
|
+
notification.expiry = packet[:expiry] || 0
|
89
|
+
notification.device_token = packet[:device_token]
|
90
|
+
notification.badge = aps[:badge] if aps.has_key? :badge
|
91
|
+
notification.alert = aps[:alert] if aps.has_key? :alert
|
92
|
+
notification.sound = aps[:sound] if aps.has_key? :sound
|
93
|
+
notification.custom = aps[:custom] if aps.has_key? :custom
|
94
|
+
|
95
|
+
notification
|
96
|
+
end
|
78
97
|
end
|
79
98
|
end
|
data/lib/racoon/payload.rb
CHANGED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Racoon - A distributed APNs provider
|
2
|
+
# Copyright (c) 2011, Jeremy Tregunna, All Rights Reserved.
|
3
|
+
#
|
4
|
+
# This module contains the worker which processes notifications before sending them off
|
5
|
+
# down to the firehose.
|
6
|
+
|
7
|
+
require 'beanstalk-client'
|
8
|
+
require 'eventmachine'
|
9
|
+
require 'ffi-rzmq'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
module Racoon
|
13
|
+
class Worker
|
14
|
+
def initialize(beanstalk_uris, tube = "racoon", address = "tcp://*:11555", context = ZMQ::Context.new(1))
|
15
|
+
@beanstalk_uris = beanstalk_uris
|
16
|
+
@context = context
|
17
|
+
@firehose = context.socket(ZMQ::PUSH)
|
18
|
+
@tube = tube
|
19
|
+
@address = address
|
20
|
+
# First packet, send something silly, the firehose ignores it
|
21
|
+
@send_batch = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def start!
|
25
|
+
EventMachine::run do
|
26
|
+
@firehose.connect(@address)
|
27
|
+
|
28
|
+
if @send_batch
|
29
|
+
@send_batch = false
|
30
|
+
@firehose.send_string("")
|
31
|
+
end
|
32
|
+
|
33
|
+
EventMachine::PeriodicTimer.new(0.5) do
|
34
|
+
begin
|
35
|
+
if beanstalk.peek_ready
|
36
|
+
job = beanstalk.reserve(1)
|
37
|
+
process job
|
38
|
+
job.delete
|
39
|
+
end
|
40
|
+
rescue Beanstalk::TimedOut
|
41
|
+
Config.logger.info "[Beanstalk] Unable to secure job, operation timed out."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def beanstalk
|
50
|
+
return @beanstalk if @beanstalk
|
51
|
+
@beanstalk ||= Beanstalk::Pool.new(@beanstalk_uris)
|
52
|
+
%w{use watch}.each { |s| @beanstalk.send(s, @tube) }
|
53
|
+
@beanstalk.ignore('default')
|
54
|
+
@beanstalk
|
55
|
+
end
|
56
|
+
|
57
|
+
# Expects json ala:
|
58
|
+
# json = {
|
59
|
+
# "project":{
|
60
|
+
# "name":"foobar",
|
61
|
+
# "certificate":"...",
|
62
|
+
# "sandbox":false
|
63
|
+
# },
|
64
|
+
# "bytes":"..."
|
65
|
+
# }
|
66
|
+
def process(job)
|
67
|
+
packet = job.ybody
|
68
|
+
project = packet[:project]
|
69
|
+
|
70
|
+
notification = Notification.create_from_packet(packet)
|
71
|
+
|
72
|
+
data = { :project => project, :bytes => notification.to_bytes }
|
73
|
+
@firehose.send_string(YAML::dump(data))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: racoon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -14,7 +14,7 @@ default_executable:
|
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: yajl-ruby
|
17
|
-
requirement: &
|
17
|
+
requirement: &70355758737600 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 0.7.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70355758737600
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: beanstalk-client
|
28
|
-
requirement: &
|
28
|
+
requirement: &70355758737040 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,21 @@ dependencies:
|
|
33
33
|
version: 1.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70355758737040
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: ffi-rzmq
|
39
|
+
requirement: &70355758736520 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 0.8.0
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *70355758736520
|
37
48
|
- !ruby/object:Gem::Dependency
|
38
49
|
name: bundler
|
39
|
-
requirement: &
|
50
|
+
requirement: &70355758735740 !ruby/object:Gem::Requirement
|
40
51
|
none: false
|
41
52
|
requirements:
|
42
53
|
- - ~>
|
@@ -44,10 +55,10 @@ dependencies:
|
|
44
55
|
version: 1.0.0
|
45
56
|
type: :development
|
46
57
|
prerelease: false
|
47
|
-
version_requirements: *
|
58
|
+
version_requirements: *70355758735740
|
48
59
|
- !ruby/object:Gem::Dependency
|
49
60
|
name: eventmachine
|
50
|
-
requirement: &
|
61
|
+
requirement: &70355758734300 !ruby/object:Gem::Requirement
|
51
62
|
none: false
|
52
63
|
requirements:
|
53
64
|
- - ! '>='
|
@@ -55,27 +66,30 @@ dependencies:
|
|
55
66
|
version: 0.12.8
|
56
67
|
type: :development
|
57
68
|
prerelease: false
|
58
|
-
version_requirements: *
|
59
|
-
description: A
|
60
|
-
for
|
69
|
+
version_requirements: *70355758734300
|
70
|
+
description: A distributed Apple Push Notification Service (APNs) provider developed
|
71
|
+
for hosting multiple projects.
|
61
72
|
email: jeremy.tregunna@me.com
|
62
73
|
executables:
|
63
74
|
- racoon-send
|
64
|
-
-
|
75
|
+
- racoon-worker
|
76
|
+
- racoon-firehose
|
65
77
|
extensions: []
|
66
78
|
extra_rdoc_files:
|
67
79
|
- README.mdown
|
68
80
|
files:
|
69
81
|
- bin/apnserverd.fedora.init
|
70
82
|
- bin/apnserverd.ubuntu.init
|
83
|
+
- bin/racoon-firehose
|
71
84
|
- bin/racoon-send
|
72
|
-
- bin/
|
73
|
-
- lib/racoon/
|
85
|
+
- bin/racoon-worker
|
86
|
+
- lib/racoon/apns/connection.rb
|
87
|
+
- lib/racoon/apns/feedback_connection.rb
|
74
88
|
- lib/racoon/config.rb
|
75
|
-
- lib/racoon/
|
89
|
+
- lib/racoon/firehose.rb
|
76
90
|
- lib/racoon/notification.rb
|
77
91
|
- lib/racoon/payload.rb
|
78
|
-
- lib/racoon/
|
92
|
+
- lib/racoon/worker.rb
|
79
93
|
- lib/racoon.rb
|
80
94
|
- README.mdown
|
81
95
|
- spec/models/client_spec.rb
|
@@ -108,7 +122,7 @@ rubyforge_project: racoon
|
|
108
122
|
rubygems_version: 1.6.2
|
109
123
|
signing_key:
|
110
124
|
specification_version: 3
|
111
|
-
summary: Apple Push Notification
|
125
|
+
summary: Distributed Apple Push Notification provider suitable for multi-project hosting.
|
112
126
|
test_files:
|
113
127
|
- spec/models/client_spec.rb
|
114
128
|
- spec/models/feedback_client_spec.rb
|
data/lib/racoon/client.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'openssl'
|
2
|
-
require 'socket'
|
3
|
-
|
4
|
-
module Racoon
|
5
|
-
class Client
|
6
|
-
attr_accessor :pem, :host, :port, :password
|
7
|
-
|
8
|
-
def initialize(pem, host = 'gateway.push.apple.com', port = 2195, pass = nil)
|
9
|
-
@pem, @host, @port, @password = pem, host, port, pass
|
10
|
-
end
|
11
|
-
|
12
|
-
def connect!
|
13
|
-
raise "Your certificate is not set." unless self.pem
|
14
|
-
|
15
|
-
@context = OpenSSL::SSL::SSLContext.new
|
16
|
-
@context.cert = OpenSSL::X509::Certificate.new(self.pem)
|
17
|
-
@context.key = OpenSSL::PKey::RSA.new(self.pem, self.password)
|
18
|
-
|
19
|
-
@sock = TCPSocket.new(self.host, self.port.to_i)
|
20
|
-
@ssl = OpenSSL::SSL::SSLSocket.new(@sock, @context)
|
21
|
-
@ssl.connect
|
22
|
-
|
23
|
-
return @sock, @ssl
|
24
|
-
end
|
25
|
-
|
26
|
-
def disconnect!
|
27
|
-
@ssl.close
|
28
|
-
@sock.close
|
29
|
-
@ssl = nil
|
30
|
-
@sock = nil
|
31
|
-
end
|
32
|
-
|
33
|
-
def write(notification)
|
34
|
-
if host.include? "sandbox"
|
35
|
-
Config.logger.debug "#{Time.now} [#{host}:#{port}] Device: #{notification.device_token.unpack('H*')} sending #{notification.json_payload}"
|
36
|
-
end
|
37
|
-
@ssl.write(notification.to_bytes)
|
38
|
-
end
|
39
|
-
|
40
|
-
def connected?
|
41
|
-
@ssl
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# Feedback service
|
2
|
-
|
3
|
-
module Racoon
|
4
|
-
class FeedbackClient < Client
|
5
|
-
def initialize(pem, host = 'feedback.push.apple.com', port = 2196, pass = nil)
|
6
|
-
@pem, @host, @port, @pass = pem, host, port, pass
|
7
|
-
end
|
8
|
-
|
9
|
-
def read
|
10
|
-
records ||= []
|
11
|
-
while record = @ssl.read(38)
|
12
|
-
records << parse_tuple(record)
|
13
|
-
end
|
14
|
-
records
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def parse_tuple(data)
|
20
|
-
feedback = data.unpack("N1n1H*")
|
21
|
-
{ :feedback_at => Time.at(feedback[0]), :length => feedback[1], :device_token => feedback[2] }
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
data/lib/racoon/server.rb
DELETED
@@ -1,193 +0,0 @@
|
|
1
|
-
require 'beanstalk-client'
|
2
|
-
|
3
|
-
module Racoon
|
4
|
-
class Server
|
5
|
-
attr_accessor :beanstalkd_uris, :feedback_callback
|
6
|
-
|
7
|
-
def initialize(beanstalkd_uris = ["127.0.0.1:11300"], &feedback_blk)
|
8
|
-
@beanstalks = {}
|
9
|
-
@clients = {}
|
10
|
-
@feedback_callback = feedback_blk
|
11
|
-
@beanstalkd_uris = beanstalkd_uris
|
12
|
-
end
|
13
|
-
|
14
|
-
def beanstalk(arg)
|
15
|
-
tube = "racoon-#{arg}"
|
16
|
-
return @beanstalks[tube] if @beanstalks[tube]
|
17
|
-
@beanstalks[tube] = Beanstalk::Pool.new @beanstalkd_uris
|
18
|
-
@beanstalks[tube]
|
19
|
-
end
|
20
|
-
|
21
|
-
def start!
|
22
|
-
EventMachine::run do
|
23
|
-
EventMachine::PeriodicTimer.new(3600) do
|
24
|
-
begin
|
25
|
-
b = beanstalk('feedback')
|
26
|
-
%w{watch use}.each { |s| b.send(s, "racoon-feedback") }
|
27
|
-
b.ignore('default')
|
28
|
-
if b.peek_ready
|
29
|
-
item = b.reserve(1)
|
30
|
-
handle_feedback(item)
|
31
|
-
end
|
32
|
-
rescue Beanstalk::TimedOut
|
33
|
-
Config.logger.info "(Beanstalkd) Unable to secure a job, operation timed out."
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Every minute,poll all the clients, ensuring they've been inactive for 20+ minutes.
|
38
|
-
EventMachine::PeriodicTimer.new(60) do
|
39
|
-
remove_clients = []
|
40
|
-
|
41
|
-
@clients.each_pair do |project_name, packet|
|
42
|
-
if Time.now - packet[:timestamp] >= 1200 # 20 minutes
|
43
|
-
packet[:connection].disconnect!
|
44
|
-
remove_clients << project_name
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
remove_clients.each do |project_name|
|
49
|
-
@clients[project_name] = nil
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
EventMachine::PeriodicTimer.new(2) do
|
54
|
-
begin
|
55
|
-
b = beanstalk('apns')
|
56
|
-
%w{watch use}.each { |s| b.send(s, "racoon-apns") }
|
57
|
-
b.ignore('default')
|
58
|
-
jobs = []
|
59
|
-
until b.peek_ready.nil?
|
60
|
-
item = b.reserve(1)
|
61
|
-
jobs << item
|
62
|
-
end
|
63
|
-
handle_jobs jobs if jobs.count > 0
|
64
|
-
rescue Beanstalk::TimedOut
|
65
|
-
Config.logger.info "(Beanstalkd) Unable to secure a job, operation timed out."
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
# Received a notification. job is YAML encoded hash in the following format:
|
74
|
-
# job = {
|
75
|
-
# :project => {
|
76
|
-
# :name => "Foo",
|
77
|
-
# :certificate => "contents of a certificate.pem"
|
78
|
-
# },
|
79
|
-
# :device_token => "0f21ab...def",
|
80
|
-
# :notification => notification.payload,
|
81
|
-
# :sandbox => true # Development environment?
|
82
|
-
# }
|
83
|
-
def handle_jobs(jobs)
|
84
|
-
connections = {}
|
85
|
-
jobs.each do |job|
|
86
|
-
packet = job.ybody
|
87
|
-
project = packet[:project]
|
88
|
-
|
89
|
-
client = get_client(project[:name], project[:certificate], packet[:sandbox])
|
90
|
-
conn = client[:connection]
|
91
|
-
connections[conn] ||= []
|
92
|
-
|
93
|
-
notification = create_notification_from_packet(packet)
|
94
|
-
|
95
|
-
connections[conn] << { :job => job, :notification => notification }
|
96
|
-
end
|
97
|
-
|
98
|
-
connections.each_pair do |conn, tasks|
|
99
|
-
conn.connect! unless conn.connected?
|
100
|
-
tasks.each do |data|
|
101
|
-
job = data[:job]
|
102
|
-
notif = data[:notification]
|
103
|
-
|
104
|
-
begin
|
105
|
-
conn.write(notif)
|
106
|
-
@clients[project[:name]][:timestamp] = Time.now
|
107
|
-
|
108
|
-
# TODO: Listen for error responses from Apple
|
109
|
-
job.delete
|
110
|
-
rescue Errno::EPIPE, OpenSSL::SSL::SSLError, Errno::ECONNRESET
|
111
|
-
Config.logger.error "Caught error, closing connection and adding notification back to queue"
|
112
|
-
|
113
|
-
connection.disconnect!
|
114
|
-
|
115
|
-
job.release
|
116
|
-
rescue RuntimeError => e
|
117
|
-
Config.logger.error "Unable to handle: #{e}"
|
118
|
-
|
119
|
-
job.delete
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# Will be a hash with two keys:
|
126
|
-
# :certificate and :sandbox.
|
127
|
-
def handle_feedback(job)
|
128
|
-
begin
|
129
|
-
packet = job.ybody
|
130
|
-
uri = "feedback.#{packet[:sandbox] ? 'sandbox.' : ''}push.apple.com"
|
131
|
-
feedback_client = Racoon::FeedbackClient.new(packet[:certificate], uri)
|
132
|
-
feedback_client.connect!
|
133
|
-
feedback_client.read.each do |record|
|
134
|
-
feedback_callback.call record
|
135
|
-
end
|
136
|
-
feedback_client.disconnect!
|
137
|
-
job.delete
|
138
|
-
rescue Errno::EPIPE, OpenSSL::SSL::SSLError, Errno::ECONNRESET
|
139
|
-
Config.logger.error "(Feedback) Caught Error, closing connection"
|
140
|
-
feedback_client.disconnect!
|
141
|
-
job.release
|
142
|
-
rescue RuntimeError => e
|
143
|
-
Config.logger.error "(Feedback) Unable to handle: #{e}"
|
144
|
-
job.delete
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# Returns a hash containing a timestamp referring to when the connection was opened.
|
149
|
-
# This timestamp will be updated to reflect when there was last activity over the socket.
|
150
|
-
def get_client(project_name, certificate, sandbox = false)
|
151
|
-
uri = "gateway.#{sandbox ? 'sandbox.' : ''}push.apple.com"
|
152
|
-
unless @clients[project_name]
|
153
|
-
@clients[project_name] = { :timestamp => Time.now, :connection => Racoon::Client.new(certificate, uri) }
|
154
|
-
end
|
155
|
-
@clients[project_name] ||= { :timestamp => Time.now, :connection => Racoon::Client.new(certificate, uri) }
|
156
|
-
client = @clients[project_name]
|
157
|
-
connection = client[:connection]
|
158
|
-
|
159
|
-
# If the certificate has changed, but we still are connected using the old certificate,
|
160
|
-
# disconnect and reconnect.
|
161
|
-
unless connection.pem.eql?(certificate)
|
162
|
-
connection.disconnect! if connection.connected?
|
163
|
-
@clients[project_name] = { :timestamp => Time.now, :connection => Racoon::Client.new(certificate, uri) }
|
164
|
-
client = @clients[project_name]
|
165
|
-
end
|
166
|
-
|
167
|
-
client
|
168
|
-
end
|
169
|
-
|
170
|
-
def purge_client(job)
|
171
|
-
project_name = job.ybody
|
172
|
-
client = @clients[project_name]
|
173
|
-
client.disconnect! if client
|
174
|
-
@clients[project_name] = nil
|
175
|
-
job.delete
|
176
|
-
end
|
177
|
-
|
178
|
-
def create_notification_from_packet(packet)
|
179
|
-
aps = packet[:notification][:aps]
|
180
|
-
|
181
|
-
notification = Notification.new
|
182
|
-
notification.identifier = packet[:identifier]
|
183
|
-
notification.expiry = packet[:expiry]
|
184
|
-
notification.device_token = packet[:device_token]
|
185
|
-
notification.badge = aps[:badge] if aps.has_key? :badge
|
186
|
-
notification.alert = aps[:alert] if aps.has_key? :alert
|
187
|
-
notification.sound = aps[:sound] if aps.has_key? :sound
|
188
|
-
notification.custom = aps[:custom] if aps.has_key? :custom
|
189
|
-
|
190
|
-
notification
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|