Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Better notification sending. Added Feedback Service support.
- Loading branch information
Showing
7 changed files
with
285 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
require 'socket' | ||
require 'openssl' | ||
require 'resque' | ||
|
||
module APN | ||
module Connection | ||
module Base | ||
attr_accessor :opts, :logger | ||
|
||
def initialize(opts = {}) | ||
@opts = opts | ||
|
||
setup_logger | ||
apn_log(:info, "APN::Sender initializing. Establishing connections first...") if @opts[:verbose] | ||
setup_paths | ||
|
||
super( APN::QUEUE_NAME ) if self.class.ancestors.include?(Resque::Worker) | ||
end | ||
|
||
# Lazy-connect the socket once we try to access it in some way | ||
def socket | ||
setup_connection unless @socket | ||
return @socket | ||
end | ||
|
||
protected | ||
|
||
def setup_logger | ||
@logger = if defined?(Merb::Logger) | ||
Merb.logger | ||
elsif defined?(RAILS_DEFAULT_LOGGER) | ||
RAILS_DEFAULT_LOGGER | ||
end | ||
end | ||
|
||
def apn_log(level, message) | ||
return false unless self.logger | ||
self.logger.send(level, "#{Time.now}: #{message}") | ||
end | ||
|
||
def apn_production? | ||
@opts[:environment] && @opts[:environment] != '' && :production == @opts[:environment].to_sym | ||
end | ||
|
||
# Get a fix on the .pem certificate we'll be using for SSL | ||
def setup_paths | ||
# Set option defaults | ||
@opts[:cert_path] ||= File.join(File.expand_path(RAILS_ROOT), "config", "certs") if defined?(RAILS_ROOT) | ||
@opts[:environment] ||= RAILS_ENV if defined?(RAILS_ENV) | ||
|
||
raise "Missing certificate path. Please specify :cert_path when initializing class." unless @opts[:cert_path] | ||
cert_name = apn_production? ? "apn_production.pem" : "apn_development.pem" | ||
cert_path = File.join(@opts[:cert_path], cert_name) | ||
|
||
@apn_cert = File.exists?(cert_path) ? File.read(cert_path) : nil | ||
raise "Missing apple push notification certificate in #{cert_path}" unless @apn_cert | ||
end | ||
|
||
# Open socket to Apple's servers | ||
def setup_connection | ||
raise "Missing apple push notification certificate" unless @apn_cert | ||
return true if @socket && @socket_tcp | ||
raise "Trying to open half-open connection" if @socket || @socket_tcp | ||
|
||
ctx = OpenSSL::SSL::SSLContext.new | ||
ctx.cert = OpenSSL::X509::Certificate.new(@apn_cert) | ||
ctx.key = OpenSSL::PKey::RSA.new(@apn_cert) | ||
|
||
@socket_tcp = TCPSocket.new(apn_host, apn_port) | ||
@socket = OpenSSL::SSL::SSLSocket.new(@socket_tcp, ctx) | ||
@socket.sync = true | ||
@socket.connect | ||
rescue SocketError => error | ||
apn_log(:error, "Error with connection to #{apn_host}: #{error}") | ||
raise "Error with connection to #{apn_host}: #{error}" | ||
end | ||
|
||
# Close open sockets | ||
def teardown_connection | ||
apn_log(:info, "Closing connections...") if @opts[:verbose] | ||
|
||
begin | ||
@socket.close if @socket | ||
rescue Exception => e | ||
apn_log(:error, "Error closing SSL Socket: #{e}") | ||
end | ||
|
||
begin | ||
@socket_tcp.close if @socket_tcp | ||
rescue Exception => e | ||
apn_log(:error, "Error closing TCP Socket: #{e}") | ||
end | ||
end | ||
|
||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
require File.expand_path(File.dirname(__FILE__) + '/connection/base') | ||
|
||
module APN | ||
# Encapsulates data returned from the {APN Feedback Service}[http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3]. | ||
# Possesses +timestamp+ and +token+ attributes. | ||
class FeedbackItem | ||
attr_accessor :timestamp, :token | ||
|
||
def initialize(time, token) | ||
@timestamp = time | ||
@token = token | ||
end | ||
|
||
# For convenience, return the token on to_s | ||
def to_s | ||
token | ||
end | ||
end | ||
|
||
# When supplied with the certificate path and the desired environment, connects to the {APN Feedback Service}[http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3] | ||
# and returns any response as an array of APN::FeedbackItem elements. | ||
# | ||
# See README for usage and details. | ||
class Feedback | ||
include APN::Connection::Base | ||
|
||
# Returns array of APN::FeedbackItem elements read from Apple. Connects to Apple once and caches the | ||
# data, continues to returns cached data unless called with <code>data(true)</code>, which clears the | ||
# existing feedback array. Note that once you force resetting the cache you loose all previous feedback, | ||
# so be sure you've already processed it. | ||
def data(force = nil) | ||
@feedback = nil if force | ||
@feedback ||= receive | ||
end | ||
|
||
# Wrapper around +data+ returning just an array of token strings. | ||
def tokens(force = nil) | ||
data(force).map(&:token) | ||
end | ||
|
||
# Prettify to return meaningful status information when printed. Can't add these directly to connection/base, because Resque depends on decoding to_s | ||
def inspect | ||
"#<#{self.class.name}: #{to_s}>" | ||
end | ||
|
||
# Prettify to return meaningful status information when printed. Can't add these directly to connection/base, because Resque depends on decoding to_s | ||
def to_s | ||
"#{@socket ? 'Connected' : 'Connection not currently established'} to #{apn_host} on #{apn_port}" | ||
end | ||
|
||
protected | ||
|
||
# Connects to Apple's Feedback Service and checks if there's anything there for us. | ||
# Returns an array of APN::FeedbackItem pairs | ||
def receive | ||
feedback = [] | ||
|
||
# Hi Apple | ||
setup_connection | ||
|
||
# Unpacking code borrowed from http://github.com/jpoz/APNS/blob/master/lib/apns/core.rb | ||
while line = socket.gets # Read lines from the socket | ||
line.strip! | ||
f = line.unpack('N1n1H140') | ||
feedback << APN::FeedbackItem.new(Time.at(f[0]), f[2]) | ||
end | ||
|
||
# Bye Apple | ||
teardown_connection | ||
|
||
return feedback | ||
end | ||
|
||
|
||
def apn_host | ||
@apn_host ||= apn_production? ? "feedback.push.apple.com" : "feedback.sandbox.push.apple.com" | ||
end | ||
|
||
def apn_port | ||
2196 | ||
end | ||
|
||
end | ||
end | ||
|
||
|
||
|
||
__END__ | ||
# Testing from irb | ||
irb -r 'lib/apn/feedback' | ||
|
||
a=APN::Feedback.new(:cert_path => '/Users/kali/Code/insurrection/certs/', :environment => :production) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.