racoon 0.3.2 → 0.4.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.
@@ -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