racoon 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -28,8 +28,7 @@ persistent connection to Apple.
28
28
 
29
29
  ## Remaining Tasks & Issues
30
30
 
31
- You can see progress by looking at the [issue tracker](https://www.pivotaltracker.com/projects/251991) page for fracas. Any labels related to
32
- *apnserver* or *racoon* are related to this subproject.
31
+ You can see progress by looking at the [issue tracker](https://www.pivotaltracker.com/projects/279053).
33
32
 
34
33
  ## Preparing Certificates
35
34
 
@@ -122,11 +121,16 @@ Finally within we can send a push notification using the following code:
122
121
  ```ruby
123
122
  beanstalk.yput({ :project => project,
124
123
  :notification => notification,
124
+ :send_at => Time.mktime(2012, 12, 21, 4, 15)
125
125
  :device_token => "binary encoded token",
126
126
  :sandbox => true
127
127
  })
128
128
  ```
129
129
 
130
+ This will construct a notification which is to be scheduled to be delivered on December 21st,
131
+ 2012 at 4:15 AM localtime to the server. That is, if the server's timezone is UTC, you must
132
+ account for that in your client application.
133
+
130
134
  Note that the `sandbox` parameter is used to indicate whether or not we're using a development
131
135
  certificate, and as such, should contact Apple's sandbox APN service instead of the production
132
136
  certificate. If left out, we assume production.
@@ -72,5 +72,9 @@ else
72
72
  %w{use watch}.each { |s| bs.send(s, "racoon-apns") }
73
73
  bs.ignore("default")
74
74
  project = { :name => "test", :certificate => certificate }
75
- bs.yput({ :project => project, :notification => notification.payload, :device_token => notification.device_token, :sandbox => true })
75
+ bs.yput({ :project => project,
76
+ :notification => notification.payload,
77
+ :device_token => notification.device_token,
78
+ :sandbox => true
79
+ })
76
80
  end
@@ -6,5 +6,5 @@ require 'racoon/client'
6
6
  require 'racoon/feedback_client'
7
7
 
8
8
  module Racoon
9
- VERSION = "0.3.2"
9
+ VERSION = "0.4.0"
10
10
  end
@@ -6,7 +6,12 @@ module Racoon
6
6
  class Notification
7
7
  include Racoon::Payload
8
8
 
9
- attr_accessor :device_token, :alert, :badge, :sound, :custom
9
+ attr_accessor :identifier, :expiry, :device_token, :alert, :badge, :sound, :custom, :send_at, :expiry
10
+
11
+ def initialize
12
+ @expiry = 0
13
+ @send_at = Time.now
14
+ end
10
15
 
11
16
  def payload
12
17
  p = Hash.new
@@ -23,24 +28,9 @@ module Racoon
23
28
  j
24
29
  end
25
30
 
26
- =begin
27
- def push
28
- if Config.pem.nil?
29
- socket = TCPSocket.new(Config.host || 'localhost', Config.port.to_i || 22195)
30
- socket.write(to_bytes)
31
- socket.close
32
- else
33
- client = Racoon::Client.new(Config.pem, Config.host || 'gateway.push.apple.com', Config.port.to_i || 2195)
34
- client.connect!
35
- client.write(self)
36
- client.disconnect!
37
- end
38
- end
39
- =end
40
-
41
31
  def to_bytes
42
32
  j = json_payload
43
- [0, 0, device_token.size, device_token, 0, j.size, j].pack("ccca*cca*")
33
+ [1, identifier, expiry.to_i, device_token.size, device_token, j.size, j].pack("cNNna*na*")
44
34
  end
45
35
 
46
36
  def self.valid?(p)
@@ -62,23 +52,25 @@ module Racoon
62
52
  buffer = p.dup
63
53
  notification = Notification.new
64
54
 
65
- header = buffer.slice!(0, 3).unpack('ccc')
66
- if header[0] != 0
67
- raise RuntimeError.new("Header of notification is invalid: #{header.inspect}")
68
- end
55
+ header = buffer.slice!(0, 11).unpack("cNNn")
56
+ raise RuntimeError.new("Header of notification is invalid: #{header.inspect}") if header[0] != 1
57
+
58
+ # identifier
59
+ notification.identifier = header[1]
60
+ notification.expiry = header[2]
69
61
 
70
- # parse token
71
- notification.device_token = buffer.slice!(0, 32).unpack('a*').first
62
+ # device token
63
+ notification.device_token = buffer.slice!(0, 32).unpack("a*").first
72
64
 
73
- # parse json payload
74
- payload_len = buffer.slice!(0, 2).unpack('CC')
65
+ # JSON payload
66
+ payload_len = buffer.slice!(0, 2).unpack("n")
75
67
  j = buffer.slice!(0, payload_len.last)
76
68
  result = Yajl::Parser.parse(j)
77
69
 
78
70
  ['alert', 'badge', 'sound'].each do |k|
79
71
  notification.send("#{k}=", result['aps'][k]) if result['aps'] && result['aps'][k]
80
72
  end
81
- result.delete('aps')
73
+ result.delete("aps")
82
74
  notification.custom = result
83
75
 
84
76
  notification
@@ -50,15 +50,17 @@ module Racoon
50
50
  end
51
51
  end
52
52
 
53
- EventMachine::PeriodicTimer.new(1) do
53
+ EventMachine::PeriodicTimer.new(2) do
54
54
  begin
55
55
  b = beanstalk('apns')
56
56
  %w{watch use}.each { |s| b.send(s, "racoon-apns") }
57
57
  b.ignore('default')
58
- if b.peek_ready
58
+ jobs = []
59
+ until b.peek_ready.nil?
59
60
  item = b.reserve(1)
60
- handle_job item
61
+ jobs << item
61
62
  end
63
+ handle_jobs jobs if jobs.count > 0
62
64
  rescue Beanstalk::TimedOut
63
65
  Config.logger.info "(Beanstalkd) Unable to secure a job, operation timed out."
64
66
  end
@@ -75,42 +77,47 @@ module Racoon
75
77
  # :certificate => "contents of a certificate.pem"
76
78
  # },
77
79
  # :device_token => "0f21ab...def",
78
- # :notification => notification.json_payload,
80
+ # :notification => notification.payload,
79
81
  # :sandbox => true # Development environment?
80
82
  # }
81
- def handle_job(job)
82
- packet = job.ybody
83
- project = packet[:project]
83
+ def handle_jobs(jobs)
84
+ connections = {}
85
+ jobs.each do |job|
86
+ packet = job.ybody
87
+ project = packet[:project]
84
88
 
85
- aps = packet[:notification][:aps]
89
+ client = get_client(project[:name], project[:certificate], packet[:sandbox])
90
+ conn = client[:connection]
91
+ connections[conn] ||= []
86
92
 
87
- notification = Notification.new
88
- notification.device_token = packet[:device_token]
89
- notification.badge = aps[:badge] if aps.has_key? :badge
90
- notification.alert = aps[:alert] if aps.has_key? :alert
91
- notification.sound = aps[:sound] if aps.has_key? :sound
92
- notification.custom = aps[:custom] if aps.has_key? :custom
93
+ notification = create_notification_from_packet(packet)
93
94
 
94
- if notification
95
- client = get_client(project[:name], project[:certificate], packet[:sandbox])
96
- connection = client[:connection]
97
- begin
98
- connection.connect! unless connection.connected?
99
- connection.write(notification)
100
- @clients[project[:name]][:timestamp] = Time.now
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]
101
103
 
102
- job.delete
103
- rescue Errno::EPIPE, OpenSSL::SSL::SSLError, Errno::ECONNRESET
104
- Config.logger.error "Caught Error, closing connecting and adding notification back to queue"
104
+ begin
105
+ conn.write(notif)
106
+ @clients[project[:name]][:timestamp] = Time.now
105
107
 
106
- connection.disconnect!
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"
107
112
 
108
- # Queue back up the notification
109
- job.release
110
- rescue RuntimeError => e
111
- Config.logger.error "Unable to handle: #{e}"
113
+ connection.disconnect!
112
114
 
113
- job.delete
115
+ job.release
116
+ rescue RuntimeError => e
117
+ Config.logger.error "Unable to handle: #{e}"
118
+
119
+ job.delete
120
+ end
114
121
  end
115
122
  end
116
123
  end
@@ -167,5 +174,20 @@ module Racoon
167
174
  @clients[project_name] = nil
168
175
  job.delete
169
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
170
192
  end
171
193
  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.3.2
4
+ version: 0.4.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: &70102541072720 !ruby/object:Gem::Requirement
17
+ requirement: &70324876524860 !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: *70102541072720
25
+ version_requirements: *70324876524860
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: beanstalk-client
28
- requirement: &70102541072160 !ruby/object:Gem::Requirement
28
+ requirement: &70324876524360 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 1.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70102541072160
36
+ version_requirements: *70324876524360
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: bundler
39
- requirement: &70102541071700 !ruby/object:Gem::Requirement
39
+ requirement: &70324876523860 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ~>
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 1.0.0
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70102541071700
47
+ version_requirements: *70324876523860
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: eventmachine
50
- requirement: &70102541071120 !ruby/object:Gem::Requirement
50
+ requirement: &70324876523360 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,7 +55,7 @@ dependencies:
55
55
  version: 0.12.8
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *70102541071120
58
+ version_requirements: *70324876523360
59
59
  description: A toolkit for proxying and sending Apple Push Notifications prepared
60
60
  for a hosted environment
61
61
  email: jeremy.tregunna@me.com