apnd 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ # 0.2.0 - 2011-04-04
2
+
3
+ Note that v0.2.0 fixes a critical error, and that previous releases of APND
4
+ should not be used.
5
+
6
+ * Fixes a critical error in `APND::Daemon::Protocol` which caused problems
7
+ with notifications containing new line characters in their message. Props to
8
+ [mwotton](https://github.com/mwotton).
9
+ * Merged `apnd` and `apnd-push` CLI tools into `apnd`.
10
+ * Added support for Apple Push Notification Feedback Service
@@ -12,8 +12,8 @@ are then sent to Apple over a single connection.
12
12
 
13
13
  Within ruby applications, `APND::Notification` can be used to send
14
14
  notifications to a running APND instance (see **APND Notification** below) or
15
- directly to Apple. A command line utility, `apnd-push`, can be used to send
16
- single notifications for testing purposes (see **APND Client** below).
15
+ directly to Apple. The command line can be used to send single notifications
16
+ for testing purposes (see **APND Client** below).
17
17
 
18
18
 
19
19
  ## General Usage
@@ -25,7 +25,7 @@ Apple over a single connection as explained above. The `apnd` command line
25
25
  utility is used to start APND.
26
26
 
27
27
  Usage:
28
- apnd [OPTIONS] --apple-cert </path/to/cert>
28
+ apnd daemon [OPTIONS] --apple-cert </path/to/cert>
29
29
 
30
30
  Required Arguments:
31
31
  --apple-cert [PATH] PATH to APN certificate from Apple
@@ -41,18 +41,17 @@ utility is used to start APND.
41
41
  --foreground Run APND in foreground without daemonizing
42
42
 
43
43
  Help:
44
- --version Show version
45
44
  --help Show this message
46
45
 
47
46
 
48
47
  ### APND Client
49
48
 
50
- APND includes a command line utility, `apnd-push`, which can be used to send
51
- notifications to a running APND instance, or Apple directly. It is only
52
- recommended to send notifications directly to Apple for testing purposes.
49
+ APND includes a command line client which can be used to send notifications to
50
+ a running APND instance. It is only recommended to send notifications via
51
+ `apnd push` for testing purposes.
53
52
 
54
53
  Usage:
55
- apnd-push [OPTIONS] --token <token>
54
+ apnd push [OPTIONS] --token <token>
56
55
 
57
56
  Required Arguments:
58
57
  --token [TOKEN] Set Notification's iPhone token to TOKEN
@@ -66,7 +65,6 @@ recommended to send notifications directly to Apple for testing purposes.
66
65
  --port [PORT] Send Notification on PORT (default is 22195)
67
66
 
68
67
  Help:
69
- --version Show version
70
68
  --help Show this message
71
69
 
72
70
 
@@ -118,6 +116,40 @@ push notifications to APND.
118
116
  )
119
117
 
120
118
 
119
+ ### APND Feedback
120
+
121
+ Apple Push Notification Service keeps a log when you attempt to deliver
122
+ a notification to a device that has removed your application. A Feedback
123
+ Service is provided which applications should periodically check to remove
124
+ from their databases.
125
+
126
+ The `APND::Feedback` class can be used within your application to retrieve
127
+ a list of device tokens that you are sending notifications to but have
128
+ removed your application.
129
+
130
+ APND::Feedback.upstream_host = 'feedback.push.apple.com'
131
+ APND::Feedback.upstream_port = 2196
132
+
133
+ # Block form
134
+ APND::Feedback.find_stale_devices do |token, removed_at|
135
+ device = YourApp::Device.find_by_token(token)
136
+ unless device.registered_at > removed_at
137
+ device.push_enabled = 0
138
+ device.save
139
+ end
140
+ end
141
+
142
+ # Array form
143
+ stale = APND::Feedback.find_stale_devices
144
+ stale.each do |(token, removed_at)|
145
+ device = YourApp::Device.find_by_token(token)
146
+ unless device.registered_at > removed_at
147
+ device.push_enabled = 0
148
+ device.save
149
+ end
150
+ end
151
+
152
+
121
153
  ## Prerequisites
122
154
 
123
155
  You must have a valid Apple Push Notification Certificate for your iPhone
@@ -156,6 +188,11 @@ wanted, so I rolled my own using theirs as starting points. If APND doesn't
156
188
  suit you, check them out instead.
157
189
 
158
190
 
191
+ ## Contributors
192
+
193
+ * Mark Wotton ([mwotton](https://github.com/mwotton))
194
+
195
+
159
196
  ## Copyright
160
197
 
161
- Copyright (c) 2010 Joshua Priddle. See LICENSE for details.
198
+ Copyright (c) 2010-2011 Joshua Priddle. See LICENSE for details.
data/bin/apnd CHANGED
@@ -1,112 +1,34 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
3
+ ARGV << '--help' if ARGV.empty?
4
4
 
5
- require 'apnd'
6
-
7
- require 'daemons'
8
- require 'optparse'
9
-
10
- help = <<HELP
11
- Usage:
12
- apnd [OPTIONS] --apple-cert </path/to/cert>
13
-
14
- HELP
15
-
16
- options = {}
17
-
18
- opts = OptionParser.new do |opt|
19
- opt.banner = help
20
-
21
- opt.separator "Required Arguments:\n"
22
-
23
- opt.on('--apple-cert [PATH]', 'PATH to APN certificate from Apple') do |cert|
24
- options[:apple_cert] = cert
25
- end
26
-
27
- opt.separator "\nOptional Arguments:\n"
28
-
29
- opt.on('--apple-host [HOST]', "Connect to Apple at HOST (default is gateway.sandbox.push.apple.com)") do |host|
30
- options[:apple_host] = host
31
- end
32
-
33
- opt.on('--apple-port [PORT]', 'Connect to Apple on PORT (default is 2195)') do |port|
34
- options[:apple_port] = port.to_i
35
- end
36
-
37
- opt.on('--apple-cert-pass [PASSWORD]', 'PASSWORD for APN certificate from Apple') do |pass|
38
- options[:apple_cert_pass] = pass
39
- end
40
-
41
- opt.on('--daemon-port [PORT]', 'Run APND on PORT (default is 22195)') do |port|
42
- options[:daemon_port] = port.to_i
43
- end
44
-
45
- opt.on('--daemon-bind [ADDRESS]', 'Bind APND to ADDRESS (default is 0.0.0.0)') do |bind|
46
- options[:daemon_bind] = bind
47
- end
48
-
49
- opt.on('--daemon-log-file [PATH]', 'PATH to APND log file (default is /var/log/apnd.log)') do |log|
50
- options[:daemon_log_file] = log
51
- end
52
-
53
- opt.on('--daemon-timer [SECONDS]', 'Set APND queue refresh time to SECONDS (default is 30)') do |seconds|
54
- options[:daemon_timer] = seconds.to_i
55
- end
56
-
57
- opt.on('--foreground', 'Run APND in foreground without daemonizing') do
58
- options[:foreground] = true
59
- end
60
-
61
- opt.separator "\nHelp:\n"
62
-
63
- opt.on('--version', 'Show version') do
64
- puts "APND #{APND::Version}"
65
- exit
66
- end
67
-
68
- opt.on('--help', 'Show this message') do
69
- puts opt
70
- exit
71
- end
72
- end
73
-
74
- begin
75
- opts.parse!
76
- if options.empty?
77
- puts opts
78
- exit
79
- end
80
-
81
- unless options[:apple_cert]
82
- raise OptionParser::MissingArgument, "must specify --apple-cert"
83
- end
84
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument
85
- puts "#{$0}: #{$!.message}"
86
- puts "#{$0}: try '#{$0} --help' for more information"
87
- exit
5
+ if $0 == __FILE__
6
+ require 'rubygems'
7
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
88
8
  end
89
9
 
90
- APND.configure do |config|
91
- # Setup AppleConnection
92
- config.apple.cert = options[:apple_cert] if options[:apple_cert]
93
- config.apple.cert_pass = options[:apple_cert_pass] if options[:apple_cert_pass]
94
- config.apple.host = options[:apple_host] if options[:apple_host]
95
- config.apple.port = options[:apple_port] if options[:apple_port]
10
+ require 'apnd'
96
11
 
97
- # Setup Daemon
98
- config.daemon.bind = options[:daemon_bind] if options[:daemon_bind]
99
- config.daemon.port = options[:daemon_port] if options[:daemon_port]
100
- config.daemon.log_file = options[:daemon_log_file] if options[:daemon_log_file]
101
- config.daemon.timer = options[:daemon_timer] if options[:daemon_timer]
102
- end
12
+ command = ARGV.shift
103
13
 
104
- if APND.settings.apple.cert.nil?
105
- puts opts
106
- exit
14
+ case command
15
+ when 'daemon'
16
+ APND::CLI.daemon(ARGV)
17
+ when 'push'
18
+ APND::CLI.push(ARGV)
19
+ when '--version', '-v'
20
+ puts "APND v#{APND::Version}"
107
21
  else
108
- unless options[:foreground]
109
- Daemonize.daemonize(APND.settings.daemon.log_file, 'apnd')
110
- end
111
- APND::Daemon.run!
22
+ puts "Error: Invalid command" unless %w(-h --help).include?(command)
23
+ puts <<-HELP
24
+ Usage: apnd COMMAND [ARGS]
25
+
26
+ Command list:
27
+ daemon Start the APND Daemon
28
+ push Send a single push notification (for development use only)
29
+
30
+ Help:
31
+ --version Show version
32
+ --help Show this message
33
+ HELP
112
34
  end
@@ -2,10 +2,12 @@ require 'json'
2
2
 
3
3
  module APND
4
4
  autoload :Version, 'apnd/version'
5
+ autoload :CLI, 'apnd/cli'
5
6
  autoload :Errors, 'apnd/errors'
6
7
  autoload :Settings, 'apnd/settings'
7
8
  autoload :Daemon, 'apnd/daemon'
8
9
  autoload :Notification, 'apnd/notification'
10
+ autoload :Feedback, 'apnd/feedback'
9
11
 
10
12
  #
11
13
  # Returns APND::Settings
@@ -24,7 +26,7 @@ module APND
24
26
  #
25
27
  # Write message to stdout with date
26
28
  #
27
- def self.ohai(message) #:nodoc:
29
+ def self.logger(message) #:nodoc:
28
30
  puts "[%s] %s" % [Time.now.strftime("%Y-%m-%d %H:%M:%S"), message]
29
31
  end
30
32
 
@@ -0,0 +1,195 @@
1
+ require 'daemons'
2
+ require 'optparse'
3
+
4
+ module APND
5
+ class CLI #:nodoc: all
6
+
7
+ #
8
+ # Run apnd push
9
+ #
10
+ def self.push(argv)
11
+ help = <<-HELP
12
+ Usage:
13
+ apnd push [OPTIONS] --token <token>
14
+
15
+ HELP
16
+
17
+ options = {}
18
+
19
+ opts = OptionParser.new do |opt|
20
+ opt.banner = help
21
+
22
+ opt.separator "Required Arguments:\n"
23
+
24
+ opt.on('--token [TOKEN]', "Set Notification's iPhone token to TOKEN") do |token|
25
+ options[:token] = token
26
+ end
27
+
28
+ opt.separator "\nOptional Arguments:\n"
29
+
30
+ opt.on('--alert [MESSAGE]', "Set Notification's alert to MESSAGE") do |alert|
31
+ options[:alert] = alert
32
+ end
33
+
34
+ opt.on('--sound [SOUND]', "Set Notification's sound to SOUND") do |sound|
35
+ options[:sound] = sound
36
+ end
37
+
38
+ opt.on('--badge [NUMBER]', "Set Notification's badge number to NUMBER") do |badge|
39
+ options[:badge] = badge.to_i
40
+ end
41
+
42
+ opt.on('--custom [JSON]', "Set Notification's custom data to JSON") do |custom|
43
+ begin
44
+ options[:custom] = JSON.parse(custom)
45
+ rescue JSON::ParserError => e
46
+ puts "Invalid JSON: #{e}"
47
+ exit -1
48
+ end
49
+ end
50
+
51
+ opt.on('--host [HOST]', "Send Notification to HOST, usually the one running APND (default is 'localhost')") do |host|
52
+ options[:host] = host
53
+ end
54
+
55
+ opt.on('--port [PORT]', 'Send Notification on PORT (default is 22195)') do |port|
56
+ options[:port] = port.to_i
57
+ end
58
+
59
+ opt.separator "\nHelp:\n"
60
+
61
+ opt.on('--help', 'Show this message') do
62
+ puts opt
63
+ exit
64
+ end
65
+ end
66
+
67
+ begin
68
+ opts.parse!
69
+ if options.empty?
70
+ puts opts
71
+ exit
72
+ end
73
+
74
+ unless options[:token]
75
+ raise OptionParser::MissingArgument, "must specify --token"
76
+ end
77
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
78
+ puts "#{$0}: #{$!.message}"
79
+ puts "#{$0}: try '#{$0} --help' for more information"
80
+ exit
81
+ end
82
+
83
+ # Configure Notification upstream host/port
84
+ APND::Notification.upstream_host = options.delete(:host) if options[:host]
85
+ APND::Notification.upstream_port = options.delete(:port) if options[:port]
86
+
87
+ APND::Notification.create(options)
88
+ end
89
+
90
+ #
91
+ # Run apnd daemon
92
+ #
93
+ def self.daemon(argv)
94
+ help = <<-HELP
95
+ Usage:
96
+ apnd daemon --apple-cert </path/to/cert>
97
+
98
+ HELP
99
+
100
+ options = {}
101
+
102
+ opts = OptionParser.new do |opt|
103
+ opt.banner = help
104
+
105
+ opt.separator "Required Arguments:\n"
106
+
107
+ opt.on('--apple-cert [PATH]', 'PATH to APN certificate from Apple') do |cert|
108
+ options[:apple_cert] = cert
109
+ end
110
+
111
+ opt.separator "\nOptional Arguments:\n"
112
+
113
+ opt.on('--apple-host [HOST]', "Connect to Apple at HOST (default is gateway.sandbox.push.apple.com)") do |host|
114
+ options[:apple_host] = host
115
+ end
116
+
117
+ opt.on('--apple-port [PORT]', 'Connect to Apple on PORT (default is 2195)') do |port|
118
+ options[:apple_port] = port.to_i
119
+ end
120
+
121
+ opt.on('--apple-cert-pass [PASSWORD]', 'PASSWORD for APN certificate from Apple') do |pass|
122
+ options[:apple_cert_pass] = pass
123
+ end
124
+
125
+ opt.on('--daemon-port [PORT]', 'Run APND on PORT (default is 22195)') do |port|
126
+ options[:daemon_port] = port.to_i
127
+ end
128
+
129
+ opt.on('--daemon-bind [ADDRESS]', 'Bind APND to ADDRESS (default is 0.0.0.0)') do |bind|
130
+ options[:daemon_bind] = bind
131
+ end
132
+
133
+ opt.on('--daemon-log-file [PATH]', 'PATH to APND log file (default is /var/log/apnd.log)') do |log|
134
+ options[:daemon_log_file] = log
135
+ end
136
+
137
+ opt.on('--daemon-timer [SECONDS]', 'Set APND queue refresh time to SECONDS (default is 30)') do |seconds|
138
+ options[:daemon_timer] = seconds.to_i
139
+ end
140
+
141
+ opt.on('--foreground', 'Run APND in foreground without daemonizing') do
142
+ options[:foreground] = true
143
+ end
144
+
145
+ opt.separator "\nHelp:\n"
146
+
147
+ opt.on('--help', 'Show this message') do
148
+ puts opt
149
+ exit
150
+ end
151
+ end
152
+
153
+ begin
154
+ opts.parse!
155
+ if options.empty?
156
+ puts opts
157
+ exit
158
+ end
159
+
160
+ unless options[:apple_cert]
161
+ raise OptionParser::MissingArgument, "must specify --apple-cert"
162
+ end
163
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
164
+ puts "#{$0}: #{$!.message}"
165
+ puts "#{$0}: try '#{$0} --help' for more information"
166
+ exit
167
+ end
168
+
169
+ APND.configure do |config|
170
+ # Setup AppleConnection
171
+ config.apple.cert = options[:apple_cert] if options[:apple_cert]
172
+ config.apple.cert_pass = options[:apple_cert_pass] if options[:apple_cert_pass]
173
+ config.apple.host = options[:apple_host] if options[:apple_host]
174
+ config.apple.port = options[:apple_port] if options[:apple_port]
175
+
176
+ # Setup Daemon
177
+ config.daemon.bind = options[:daemon_bind] if options[:daemon_bind]
178
+ config.daemon.port = options[:daemon_port] if options[:daemon_port]
179
+ config.daemon.log_file = options[:daemon_log_file] if options[:daemon_log_file]
180
+ config.daemon.timer = options[:daemon_timer] if options[:daemon_timer]
181
+ end
182
+
183
+ if APND.settings.apple.cert.nil?
184
+ puts opts
185
+ exit
186
+ else
187
+ unless options[:foreground]
188
+ Daemonize.daemonize(APND.settings.daemon.log_file, 'apnd')
189
+ end
190
+ APND::Daemon.run!
191
+ end
192
+
193
+ end
194
+ end
195
+ end
@@ -35,7 +35,7 @@ module APND
35
35
  #
36
36
  def run!
37
37
  EventMachine::run do
38
- APND.ohai "Starting APND Daemon v#{APND::Version} on #{@bind}:#{@port}"
38
+ APND.logger "Starting APND Daemon v#{APND::Version} on #{@bind}:#{@port}"
39
39
  EventMachine::start_server(@bind, @port, APND::Daemon::ServerConnection) do |server|
40
40
  server.queue = @queue
41
41
  end
@@ -54,19 +54,19 @@ module APND
54
54
  def process_notifications!
55
55
  count = @queue.size
56
56
  if count > 0
57
- APND.ohai "Queue has #{count} item#{count == 1 ? '' : 's'}"
57
+ APND.logger "Queue has #{count} item#{count == 1 ? '' : 's'}"
58
58
  @apple.connect!
59
59
  count.times do
60
60
  @queue.pop do |notification|
61
61
  begin
62
- APND.ohai "Sending notification for #{notification.token}"
62
+ APND.logger "Sending notification for #{notification.token}"
63
63
  @apple.write(notification.to_bytes)
64
64
  rescue Errno::EPIPE, OpenSSL::SSL::SSLError
65
- APND.ohai "Error, notification has been added back to the queue"
65
+ APND.logger "Error, notification has been added back to the queue"
66
66
  @queue.push(notification)
67
67
  @apple.reconnect!
68
68
  rescue RuntimeError => error
69
- APND.ohai "Error: #{error}"
69
+ APND.logger "Error: #{error}"
70
70
  end
71
71
  end
72
72
  end
@@ -11,7 +11,7 @@ module APND
11
11
  #
12
12
  def post_init
13
13
  @address = ::Socket.unpack_sockaddr_in(self.get_peername)
14
- APND.ohai "#{@address.last}:#{@address.first} opened connection"
14
+ APND.logger "#{@address.last}:#{@address.first} opened connection"
15
15
  end
16
16
 
17
17
  #
@@ -21,22 +21,33 @@ module APND
21
21
  # queued
22
22
  #
23
23
  def unbind
24
- @buffer.chomp.split("\n").each do |line|
25
- if notification = APND::Notification.valid?(line)
26
- APND.ohai "#{@address.last}:#{@address.first} added new Notification to queue"
24
+ # totally broken.
25
+ @buffer.chomp!
26
+ while(@buffer.length > 0) do
27
+ # 3 bytes for header
28
+ # 32 bytes for token
29
+ # 2 bytes for json length
30
+
31
+ # taking the last is acceptable because we know it's never
32
+ # longer than 256 bytes from the apple documentation.
33
+ json_length = @buffer.slice(35,37).unpack('CC').last
34
+ chunk = @buffer.slice!(0,json_length + 3 + 32 + 2)
35
+ if notification = APND::Notification.valid?(chunk)
36
+ APND.logger "#{@address.last}:#{@address.first} added new Notification to queue"
27
37
  queue.push(notification)
28
38
  else
29
- APND.ohai "#{@address.last}:#{@address.first} submitted invalid Notification"
39
+ APND.logger "#{@address.last}:#{@address.first} submitted invalid Notification"
30
40
  end
41
+ @buffer.strip!
31
42
  end
32
- APND.ohai "#{@address.last}:#{@address.first} closed connection"
43
+ APND.logger "#{@address.last}:#{@address.first} closed connection"
33
44
  end
34
45
 
35
46
  #
36
47
  # Add incoming notification(s) to @buffer
37
48
  #
38
49
  def receive_data(data)
39
- APND.ohai "#{@address.last}:#{@address.first} buffering data"
50
+ APND.logger "#{@address.last}:#{@address.first} buffering data"
40
51
  (@buffer ||= "") << data
41
52
  end
42
53
  end
@@ -0,0 +1,65 @@
1
+ module APND
2
+ #
3
+ # APND::Feedback receives feedback from Apple when notifications are
4
+ # being rejected for a specific token. This is usually due to the user
5
+ # uninstalling your application.
6
+ #
7
+ class Feedback
8
+
9
+ class << self
10
+ #
11
+ # The host to receive feedback from, usually apple
12
+ #
13
+ attr_accessor :upstream_host
14
+
15
+ #
16
+ # The port to connect to upstream_host on
17
+ #
18
+ attr_accessor :upstream_port
19
+ end
20
+
21
+ #
22
+ # Set upstream host/port to default values
23
+ #
24
+ self.upstream_host = APND.settings.feedback.host
25
+ self.upstream_port = APND.settings.feedback.port.to_i
26
+
27
+ #
28
+ # Connect to Apple's Feedback Service and return an array of device
29
+ # tokens that should no longer receive push notifications
30
+ #
31
+ def self.find_stale_devices(&block)
32
+ feedback = self.new
33
+ devices = feedback.receive
34
+ devices.each { |device| yield *device } if block_given?
35
+ devices
36
+ end
37
+
38
+ #
39
+ # Create a connection to Apple's Feedback Service
40
+ #
41
+ def initialize
42
+ @apple = APND::Daemon::AppleConnection.new({
43
+ :cert => APND.settings.apple.cert,
44
+ :cert_pass => APND.settings.apple.cert_pass,
45
+ :host => self.class.upstream_host,
46
+ :port => self.class.upstream_port.to_i
47
+ })
48
+ end
49
+
50
+ #
51
+ # Receive feedback from Apple and return an array of device tokens
52
+ #
53
+ def receive
54
+ tokens = []
55
+ @apple.open do |sock|
56
+ while line = sock.gets
57
+ payload = line.strip.unpack('N1n1H140')
58
+ tokens << [payload[2].strip, Time.at(payload[0])]
59
+ end
60
+ end
61
+ tokens
62
+ end
63
+
64
+ end
65
+ end
@@ -104,6 +104,32 @@ module APND
104
104
  end
105
105
  end
106
106
 
107
+ #
108
+ # Settings for APND::Feedback
109
+ #
110
+ class Feedback
111
+
112
+ #
113
+ # Host used to connect to Apple
114
+ #
115
+ # Development: feedback.sandbox.push.apple.com
116
+ # Production: feedback.push.apple.com
117
+ #
118
+ attr_accessor :host
119
+
120
+ #
121
+ # Port used to connect to Apple
122
+ #
123
+ # Default: 2196
124
+ #
125
+ attr_accessor :port
126
+
127
+ def initialize
128
+ @host = 'feedback.sandbox.push.apple.com'
129
+ @port = 2196
130
+ end
131
+ end
132
+
107
133
  #
108
134
  # Returns the AppleConnection settings
109
135
  #
@@ -158,5 +184,22 @@ module APND
158
184
  notification.host = options[:host] if options[:host]
159
185
  end
160
186
  end
187
+
188
+ #
189
+ # Returns the Feedback settings
190
+ #
191
+ def feedback
192
+ @feedback ||= APND::Settings::Feedback.new
193
+ end
194
+
195
+ #
196
+ # Mass assign Feedback settings
197
+ #
198
+ def feedback=(options = {})
199
+ if options.respond_to?(:keys)
200
+ feedback.port = options[:port] if options[:port]
201
+ feedback.host = options[:host] if options[:host]
202
+ end
203
+ end
161
204
  end
162
205
  end
@@ -1,8 +1,8 @@
1
1
  module APND
2
2
  class Version #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 1
5
- TINY = 8
4
+ MINOR = 2
5
+ TINY = 0
6
6
 
7
7
  def self.to_s
8
8
  [MAJOR, MINOR, TINY].join('.')
@@ -1,6 +1,8 @@
1
1
  require File.dirname(__FILE__) + '/test_helper.rb'
2
2
 
3
3
  class APNDTest < Test::Unit::TestCase
4
+ # FIXME: Tests fail if the hash keys here arent in this order. It shouldn't
5
+ # be so fragile.
4
6
  @@bytes = %|\000\000 \376\025\242}]\363\303Gx\336\373\037O8\200&\\\305,\f\004v\202\";\345\237\266\205\000\251\242\000\\{\"location\":\"New York\",\"aps\":{\"badge\":10,\"sound\":\"default\",\"alert\":\"Red Alert, Numba One!\"}}|
5
7
 
6
8
  context "APND Notification" do
@@ -51,6 +53,8 @@ class APNDTest < Test::Unit::TestCase
51
53
  end
52
54
  end
53
55
 
56
+
57
+
54
58
  end
55
59
 
56
60
  context "APND Daemon" do
@@ -77,6 +81,25 @@ class APNDTest < Test::Unit::TestCase
77
81
  end
78
82
  assert_equal 0, @daemon.queue.size
79
83
  end
84
+
85
+
86
+ context "newlines" do
87
+ should "be able to parse a notification with an embedded newline character" do
88
+ @newline_notification = APND::Notification.new({
89
+ # :token => 'fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2',
90
+ :token => '74b2a2197d7727a70f939de05a4c7fe8bd4a7d960a77ef4701a80cb7b293ee23',
91
+ :alert => 'Red Alert, Numba One!',
92
+ :sound => 'default',
93
+ :badge => 10,
94
+ :custom => { 'location' => 'New York' }
95
+ })
96
+ @daemon.receive_data(@newline_notification.to_bytes)
97
+ @daemon.unbind
98
+ assert_equal 1, @daemon.queue.size
99
+
100
+ end
101
+ end
102
+
80
103
  end
81
104
  end
82
105
 
@@ -23,5 +23,5 @@ class TestDaemon
23
23
 
24
24
  end
25
25
 
26
- # Silence APND.ohai in testing
27
- def APND.ohai(*args); end
26
+ # Silence APND.logger in testing
27
+ def APND.logger(*args); end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apnd
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 8
10
- version: 0.1.8
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Joshua Priddle
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-18 00:00:00 -05:00
18
+ date: 2011-04-04 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -84,7 +84,6 @@ description: " APND (Apple Push Notification Daemon) is a ruby library to sen
84
84
  email: jpriddle@nevercraft.net
85
85
  executables:
86
86
  - apnd
87
- - apnd-push
88
87
  extensions: []
89
88
 
90
89
  extra_rdoc_files:
@@ -92,13 +91,15 @@ extra_rdoc_files:
92
91
  files:
93
92
  - Rakefile
94
93
  - README.markdown
94
+ - HISTORY.markdown
95
95
  - bin/apnd
96
- - bin/apnd-push
96
+ - lib/apnd/cli.rb
97
97
  - lib/apnd/daemon/apple_connection.rb
98
98
  - lib/apnd/daemon/protocol.rb
99
99
  - lib/apnd/daemon/server_connection.rb
100
100
  - lib/apnd/daemon.rb
101
101
  - lib/apnd/errors.rb
102
+ - lib/apnd/feedback.rb
102
103
  - lib/apnd/notification.rb
103
104
  - lib/apnd/settings.rb
104
105
  - lib/apnd/version.rb
@@ -135,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
136
  requirements: []
136
137
 
137
138
  rubyforge_project:
138
- rubygems_version: 1.5.0
139
+ rubygems_version: 1.6.2
139
140
  signing_key:
140
141
  specification_version: 3
141
142
  summary: "APND: Apple Push Notification Daemon sends Apple Push Notifications to iPhones"
@@ -1,90 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
-
5
- require 'apnd'
6
-
7
- require 'optparse'
8
-
9
- help = <<HELP
10
- Usage:
11
- apnd-push [OPTIONS] --token <token>
12
-
13
- HELP
14
-
15
- options = {}
16
-
17
- opts = OptionParser.new do |opt|
18
- opt.banner = help
19
-
20
- opt.separator "Required Arguments:\n"
21
-
22
- opt.on('--token [TOKEN]', "Set Notification's iPhone token to TOKEN") do |token|
23
- options[:token] = token
24
- end
25
-
26
- opt.separator "\nOptional Arguments:\n"
27
-
28
- opt.on('--alert [MESSAGE]', "Set Notification's alert to MESSAGE") do |alert|
29
- options[:alert] = alert
30
- end
31
-
32
- opt.on('--sound [SOUND]', "Set Notification's sound to SOUND") do |sound|
33
- options[:sound] = sound
34
- end
35
-
36
- opt.on('--badge [NUMBER]', "Set Notification's badge number to NUMBER") do |badge|
37
- options[:badge] = badge.to_i
38
- end
39
-
40
- opt.on('--custom [JSON]', "Set Notification's custom data to JSON") do |custom|
41
- begin
42
- options[:custom] = JSON.parse(custom)
43
- rescue JSON::ParserError => e
44
- puts "Invalid JSON: #{e}"
45
- exit -1
46
- end
47
- end
48
-
49
- opt.on('--host [HOST]', "Send Notification to HOST, usually the one running APND (default is 'localhost')") do |host|
50
- options[:host] = host
51
- end
52
-
53
- opt.on('--port [PORT]', 'Send Notification on PORT (default is 22195)') do |port|
54
- options[:port] = port.to_i
55
- end
56
-
57
- opt.separator "\nHelp:\n"
58
-
59
- opt.on('--version', 'Show version') do
60
- puts "APND #{APND::Version}"
61
- exit
62
- end
63
-
64
- opt.on('--help', 'Show this message') do
65
- puts opt
66
- exit
67
- end
68
- end
69
-
70
- begin
71
- opts.parse!
72
- if options.empty?
73
- puts opts
74
- exit
75
- end
76
-
77
- unless options[:token]
78
- raise OptionParser::MissingArgument, "must specify --token"
79
- end
80
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument
81
- puts "#{$0}: #{$!.message}"
82
- puts "#{$0}: try '#{$0} --help' for more information"
83
- exit
84
- end
85
-
86
- # Configure Notification upstream host/port
87
- APND::Notification.upstream_host = options.delete(:host) if options[:host]
88
- APND::Notification.upstream_port = options.delete(:port) if options[:port]
89
-
90
- APND::Notification.create(options)