Class: PYAPNS::Client
Overview
PYAPNS::Client There’s python in my ruby!
This is a class used to send notifications, provision applications and retrieve feedback using the Apple Push Notification Service.
PYAPNS is a multi-application APS provider, meaning it is possible to send notifications to any number of different applications from the same application and same server. It is also possible to scale the client to any number of processes and servers, simply balanced behind a simple web proxy.
It may seem like overkill for such a bare interface - after all, the APS service is rather simplistic. However, PYAPNS takes no shortcuts when it comes to compleatness/compliance with the APNS protocol and allows the user many optimization and scaling vectors not possible with other libraries. No bandwidth is wasted, connections are persistent and the server is asynchronous therefore notifications are delivered immediately.
PYAPNS takes after the design of 3rd party push notification service that charge a fee each time you push a notification, and charge extra for so-called ‘premium’ service which supposedly gives you quicker access to the APS servers. However, PYAPNS is free, as in beer and offers more scaling oportunites without the financial draw.
Provisioning
To add your app to the PYAPNS server, it must be ‘provisioned` at least once. Normally this is done once upon the start-up of your application, be it a web service, desktop application or whatever… It must be done at least once to the server you’re connecting to. Multiple instances of PYAPNS will have to have their applications provisioned individually. To provision an application manually use the PYAPNS::Client#provision method.
require 'pyapns'
client = PYAPNS::Client.configure
client.provision :app_id => 'cf', :cert => '/home/ss/cert.pem', :env => 'sandbox', :timeout => 15
This basically says “add an app reference named ‘cf’ to the server and start a connection using the certification, and if it can’t within 15 seconds, raise a PYAPNS::TimeoutException
That’s all it takes to get started. Of course, this can be done automatically by using PYAPNS::ClientConfiguration middleware. PYAPNS::Client is a singleton class that is configured using the class method PYAPNS::Client#configure. It is sensibly configured by default, but can be customized by specifying a hash See the docs on PYAPNS::ClientConfiguration for a list of available configuration parameters (some of these are important, and you can specify initial applications) to be configured by default.
Sending Notifications
Once your client is configured, and application provisioned (again, these should be taken care of before you write notification code) you can begin sending notifications to users. If you’re wondering how to acquire a notification token, you’ve come to the wrong place… I recommend using google. However, if you want to send hundreds of millions of notifications to users, here’s how it’s done, one at a time…
The PYAPNS::Client#notify is a sort of polymorphic method which can notify any number of devices at a time. It’s basic form is as follows:
client.notify 'cf', 'long ass app token', {:aps=> {:alert => 'hello?'}}
However, as stated before, it is sort of polymorphic:
client.notify 'cf', ['token', 'token2', 'token3'], [alert, alert2, alert3]
client.notify :app_id => 'cf', :tokens => 'mah token', :notifications => alertHash
client.notify 'cf', 'token', PYAPNS::Notification('hello tits!')
As you can see, the method accepts paralell arrays of tokens and notifications meaning any number of notifications can be sent at once. Hashes will be automatically converted to PYAPNS::Notification objects so they can be optimized for the wire (nil values removed, etc…), and you can pass PYAPNS::Notification objects directly if you wish.
Retrieving Feedback
The APS service offers a feedback functionality that allows application servers to retrieve a list of device tokens it deems to be no longer in use, and the time it thinks they stopped being useful (the user uninstalled your app, better luck next time…) Sounds pretty straight forward, and it is. Apple recommends you do this at least once an hour. PYAPNS will return a list of 2-element lists with the date and the token:
feedbacks = client.feedback 'cf'
Asynchronous Calls
PYAPNS::Client will, by default, perform no funny stuff and operate entirely within the calling thread. This means that certain applications may hang when, say, sending a notification, if only for a fraction of a second. Obviously not a desirable trait, all ‘provision`, `feedback` and `notify` methods also take a block, which indicates to the method you want to call PYAPNS asynchronously, and it will be done so handily in another thread, calling back your block with a single argument when finished. Note that `notify` and `provision` return absolutely nothing (nil, for you rub–wait you are ruby developers!). It is probably wise to always use this form of operation so your calling thread is never blocked (especially important in UI-driven apps and asynchronous servers) Just pass a block to provision/notify/feedback like so:
PYAPNS::Client.instance.feedback do |feedbacks|
feedbacks.each { |f| trim_token f }
end
Class Method Summary collapse
Instance Method Summary collapse
- #configure(hash = {}) ⇒ Object
- #configured? ⇒ Boolean
- #feedback(*args, &block) ⇒ Object
- #get_args(splat, *args, &block) ⇒ Object
-
#initialize ⇒ Client
constructor
A new instance of Client.
- #notify(*args, &block) ⇒ Object
- #perform_call(method, splat, *args, &block) ⇒ Object
- #perform_call2(&block) ⇒ Object
- #provision(*args, &block) ⇒ Object
Constructor Details
#initialize ⇒ Client
Returns a new instance of Client.
125 126 127 |
# File 'lib/pyapns.rb', line 125 def initialize @configured = false end |
Class Method Details
.configure(hash = {}) ⇒ Object
120 121 122 123 |
# File 'lib/pyapns.rb', line 120 def self.configure(hash={}) y = self.instance y.configure(hash) end |
Instance Method Details
#configure(hash = {}) ⇒ Object
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/pyapns.rb', line 202 def configure(hash={}) if configured? return self end h = {} hash.each { |k,v| h[k.to_s.downcase] = v } @host = h['host'] || "localhost" @port = h['port'] || 7077 @path = h['path'] || '/' @timeout = h['timeout'] || 15 @client = XMLRPC::Client.new3( :host => @host, :port => @port, :timeout => @timeout, :path => @path) if not h['initial'].nil? h['initial'].each do |initial| provision(:app_id => initial[:app_id], :cert => initial[:cert], :env => initial[:env], :timeout => initial[:timeout] || 15) end end @configured = true self end |
#configured? ⇒ Boolean
198 199 200 |
# File 'lib/pyapns.rb', line 198 def configured? return @configured end |
#feedback(*args, &block) ⇒ Object
148 149 150 |
# File 'lib/pyapns.rb', line 148 def feedback(*args, &block) perform_call :feedback, args, :app_id, &block end |
#get_args(splat, *args, &block) ⇒ Object
170 171 172 173 174 175 176 177 178 179 |
# File 'lib/pyapns.rb', line 170 def get_args(splat, *args, &block) if splat.length == 1 && splat[0].class == Hash splat = args.map { |k| splat[0][k] } end if (splat.find_all { |l| not l.nil? }).length == args.length block.call(splat) else raise PYAPNS::InvalidArguments.new "Invalid args supplied #{args}" end end |
#notify(*args, &block) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/pyapns.rb', line 133 def notify(*args, &block) kwargs = [:app_id, :tokens, :notifications] get_args(args, *kwargs) do |splat| splat[2] = (splat[2].class == Array ? splat[2] : [splat[2]]).map do |note| if note.class != PYAPNS::Notification PYAPNS::Notification.encode note else note end end perform_call :notify, splat, *kwargs, &block end end |
#perform_call(method, splat, *args, &block) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/pyapns.rb', line 152 def perform_call(method, splat, *args, &block) if !configured? raise PYAPNS::NotConfigured.new end get_args(splat, *args) do |splat| if block_given? Thread.new do perform_call2 { block.call(@client.call_async(method.to_s, *splat)) } end nil else perform_call2 { @client.call_async(method.to_s, *splat) } end end end |
#perform_call2(&block) ⇒ Object
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/pyapns.rb', line 181 def perform_call2(&block) begin block.call() rescue XMLRPC::FaultException => fault case fault.faultCode when 404 raise PYAPNS::UnknownAppID.new fault.faultString when 401 raise PYAPNS::InvalidEnvironment.new fault.faultString when 500 raise PYAPNS::ServerTimeout.new fault.faultString else raise fault end end end |
#provision(*args, &block) ⇒ Object
129 130 131 |
# File 'lib/pyapns.rb', line 129 def provision(*args, &block) perform_call :provision, args, :app_id, :cert, :env, :timeout, &block end |