apnd 0.1.8 → 0.2.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.
@@ -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)