pyapns 0.3.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.
- data/History.txt +4 -0
- data/Manifest.txt +11 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +212 -0
- data/Rakefile +23 -0
- data/lib/pyapns.rb +365 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/test_helper.rb +3 -0
- data/test/test_pyapns.rb +11 -0
- metadata +256 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/PostInstall.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
= pyapns
|
2
|
+
|
3
|
+
* http://pyapns.org
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
:title: The Ruby API
|
7
|
+
|
8
|
+
:section: PYAPNS::Client
|
9
|
+
There's python in my ruby!
|
10
|
+
|
11
|
+
This is a class used to send notifications, provision applications and
|
12
|
+
retrieve feedback using the Apple Push Notification Service.
|
13
|
+
|
14
|
+
PYAPNS is a multi-application APS provider, meaning it is possible to send
|
15
|
+
notifications to any number of different applications from the same application
|
16
|
+
and same server. It is also possible to scale the client to any number
|
17
|
+
of processes and servers, simply balanced behind a simple web proxy.
|
18
|
+
|
19
|
+
It may seem like overkill for such a bare interface - after all, the
|
20
|
+
APS service is rather simplistic. However, PYAPNS takes no shortcuts when it
|
21
|
+
comes to completeness/compliance with the APNS protocol and allows the
|
22
|
+
user many optimization and scaling vectors not possible with other libraries.
|
23
|
+
No bandwidth is wasted, connections are persistent and the server is
|
24
|
+
asynchronous therefore notifications are delivered immediately.
|
25
|
+
|
26
|
+
PYAPNS takes after the design of 3rd party push notification service that
|
27
|
+
charge a fee each time you push a notification, and charge extra for so-called
|
28
|
+
'premium' service which supposedly gives you quicker access to the APS servers.
|
29
|
+
However, PYAPNS is free, as in beer and offers more scaling opportunities without
|
30
|
+
the financial draw.
|
31
|
+
|
32
|
+
:section: Provisioning
|
33
|
+
|
34
|
+
To add your app to the PYAPNS server, it must be `provisioned` at least once.
|
35
|
+
Normally this is done once upon the start-up of your application, be it a web
|
36
|
+
service, desktop application or whatever... It must be done at least once
|
37
|
+
to the server you're connecting to. Multiple instances of PYAPNS will have
|
38
|
+
to have their applications provisioned individually. To provision an application
|
39
|
+
manually use the `PYAPNS::Client#provision` method.
|
40
|
+
|
41
|
+
require 'pyapns'
|
42
|
+
client = PYAPNS::Client.configure
|
43
|
+
client.provision :app_id => 'cf', :cert => '/home/ss/cert.pem', :env => 'sandbox', :timeout => 15
|
44
|
+
|
45
|
+
This basically says "add an app reference named 'cf' to the server and start
|
46
|
+
a connection using the certification, and if it can't within 15 seconds,
|
47
|
+
raise a `PYAPNS::TimeoutException`
|
48
|
+
|
49
|
+
That's all it takes to get started. Of course, this can be done automatically
|
50
|
+
by using PYAPNS::ClientConfiguration middleware. `PYAPNS::Client` is a singleton
|
51
|
+
class that is configured using the class method `PYAPNS::Client#configure`. It
|
52
|
+
is sensibly configured by default, but can be customized by specifying a hash
|
53
|
+
See the docs on `PYAPNS::ClientConfiguration` for a list of available configuration
|
54
|
+
parameters (some of these are important, and you can specify initial applications)
|
55
|
+
to be configured by default.
|
56
|
+
|
57
|
+
:section: Sending Notifications
|
58
|
+
|
59
|
+
Once your client is configured, and application provisioned (again, these
|
60
|
+
should be taken care of before you write notification code) you can begin
|
61
|
+
sending notifications to users. If you're wondering how to acquire a notification
|
62
|
+
token, you've come to the wrong place... I recommend using google. However,
|
63
|
+
if you want to send hundreds of millions of notifications to users, here's how
|
64
|
+
it's done, one at a time...
|
65
|
+
|
66
|
+
The `PYAPNS::Client#notify` is a sort of polymorphic method which can notify
|
67
|
+
any number of devices at a time. It's basic form is as follows:
|
68
|
+
|
69
|
+
client.notify 'cf', 'long ass app token', {:aps=> {:alert => 'hello?'}}
|
70
|
+
|
71
|
+
However, as stated before, it is sort of polymorphic:
|
72
|
+
|
73
|
+
client.notify 'cf', ['token', 'token2', 'token3'], [alert, alert2, alert3]
|
74
|
+
|
75
|
+
client.notify :app_id => 'cf', :tokens => 'mah token', :notifications => alertHash
|
76
|
+
|
77
|
+
client.notify 'cf', 'token', PYAPNS::Notification('hello tits!')
|
78
|
+
|
79
|
+
As you can see, the method accepts paralell arrays of tokens and notifications
|
80
|
+
meaning any number of notifications can be sent at once. Hashes will be automatically
|
81
|
+
converted to `PYAPNS::Notification` objects so they can be optimized for the wire
|
82
|
+
(nil values removed, etc...), and you can pass `PYAPNS::Notification` objects
|
83
|
+
directly if you wish.
|
84
|
+
|
85
|
+
:section: Retrieving Feedback
|
86
|
+
|
87
|
+
The APS service offers a feedback functionality that allows application servers
|
88
|
+
to retrieve a list of device tokens it deems to be no longer in use, and the
|
89
|
+
time it thinks they stopped being useful (the user uninstalled your app, better
|
90
|
+
luck next time...) Sounds pretty straight forward, and it is. Apple recommends
|
91
|
+
you do this at least once an hour. PYAPNS will return a list of 2-element lists
|
92
|
+
with the date and the token:
|
93
|
+
|
94
|
+
feedbacks = client.feedback 'cf'
|
95
|
+
|
96
|
+
:section: Asynchronous Calls
|
97
|
+
|
98
|
+
PYAPNS::Client will, by default, perform no funny stuff and operate entirely
|
99
|
+
within the calling thread. This means that certain applications may hang when,
|
100
|
+
say, sending a notification, if only for a fraction of a second. Obviously
|
101
|
+
not a desirable trait, all `provision`, `feedback` and `notify`
|
102
|
+
methods also take a block, which indicates to the method you want to call
|
103
|
+
PYAPNS asynchronously, and it will be done so handily in another thread, calling
|
104
|
+
back your block with a single argument when finished. Note that `notify` and `provision`
|
105
|
+
return absolutely nothing (nil, for you rub--wait you are ruby developers!).
|
106
|
+
It is probably wise to always use this form of operation so your calling thread
|
107
|
+
is never blocked (especially important in UI-driven apps and asynchronous servers)
|
108
|
+
Just pass a block to provision/notify/feedback like so:
|
109
|
+
|
110
|
+
PYAPNS::Client.instance.feedback do |feedbacks|
|
111
|
+
feedbacks.each { |f| trim_token f }
|
112
|
+
end
|
113
|
+
|
114
|
+
:section: PYAPNS::ClientConfiguration
|
115
|
+
A middleware class to make `PYAPNS::Client` easy to use in web contexts
|
116
|
+
|
117
|
+
Automates configuration of the client in Rack environments
|
118
|
+
using a simple confiuration middleware. To use `PYAPNS::Client` in
|
119
|
+
Rack environments with the least code possible `use PYAPNS::ClientConfiguration`
|
120
|
+
(no, really, in some cases, that's all you need!) middleware with an optional
|
121
|
+
hash specifying the client variables. Options are as follows:
|
122
|
+
|
123
|
+
use PYAPNS::ClientConfiguration(
|
124
|
+
:host => 'http://localhost/'
|
125
|
+
:port => 7077,
|
126
|
+
:initial => [{
|
127
|
+
:app_id => 'myapp',
|
128
|
+
:cert => '/home/myuser/apps/myapp/cert.pem',
|
129
|
+
:env => 'sandbox',
|
130
|
+
:timeout => 15
|
131
|
+
}])
|
132
|
+
|
133
|
+
Where the configuration variables are defined:
|
134
|
+
|
135
|
+
:host String the host where the server can be found
|
136
|
+
:port Number the port to which the client should connect
|
137
|
+
:initial Array OPTIONAL - an array of INITIAL hashes
|
138
|
+
|
139
|
+
INITIAL HASHES:
|
140
|
+
|
141
|
+
:app_id String the id used to send messages with this certification
|
142
|
+
can be a totally arbitrary value
|
143
|
+
:cert String a path to the certification or the certification file
|
144
|
+
as a string
|
145
|
+
:env String the environment to connect to apple with, always
|
146
|
+
either 'sandbox' or 'production'
|
147
|
+
:timoeut Number The timeout for the server to use when connecting
|
148
|
+
to the apple servers
|
149
|
+
|
150
|
+
:section: PYAPNS::Notification
|
151
|
+
An APNS Notification
|
152
|
+
|
153
|
+
You can construct notification objects ahead of time by using this class.
|
154
|
+
However unnecessary, it allows you to programmatically generate a Notification
|
155
|
+
like so:
|
156
|
+
|
157
|
+
note = PYAPNS::Notification.new 'alert text', 9, 'flynn.caf', {:extra => 'guid'}
|
158
|
+
|
159
|
+
-- or --
|
160
|
+
note = PYAPNS::Notification.new 'alert text'
|
161
|
+
|
162
|
+
These can be passed to `PYAPNS::Client#notify` the same as hashes
|
163
|
+
|
164
|
+
== FEATURES/PROBLEMS:
|
165
|
+
|
166
|
+
* XML-RPC Based, works with any client in any language
|
167
|
+
* Native Python API with Django and Pylons support
|
168
|
+
* Native Ruby API with Rails/Rack support
|
169
|
+
* Scalable, fast and easy to distribute behind a proxy
|
170
|
+
* Based on Twisted
|
171
|
+
* Multi-application and dual environment support
|
172
|
+
* Simplified feedback interface
|
173
|
+
|
174
|
+
== SYNOPSIS:
|
175
|
+
|
176
|
+
require 'pyapns'
|
177
|
+
c = PYAPNS::Client.configure
|
178
|
+
c.notify('myapp', 'token', 'notification')
|
179
|
+
|
180
|
+
== REQUIREMENTS:
|
181
|
+
|
182
|
+
* pyapns >=0.3.0
|
183
|
+
|
184
|
+
== INSTALL:
|
185
|
+
|
186
|
+
sudo gem install pyapns
|
187
|
+
sudo easy_install-2.6 pyapns
|
188
|
+
|
189
|
+
== LICENSE:
|
190
|
+
|
191
|
+
(The MIT License)
|
192
|
+
|
193
|
+
Copyright (c) 2010 Samuel Webster Sutch
|
194
|
+
|
195
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
196
|
+
a copy of this software and associated documentation files (the
|
197
|
+
'Software'), to deal in the Software without restriction, including
|
198
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
199
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
200
|
+
permit persons to whom the Software is furnished to do so, subject to
|
201
|
+
the following conditions:
|
202
|
+
|
203
|
+
The above copyright notice and this permission notice shall be
|
204
|
+
included in all copies or substantial portions of the Software.
|
205
|
+
|
206
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
207
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
208
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
209
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
210
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
211
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
212
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/pyapns'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'pyapns' do
|
14
|
+
self.developer 'Samuel Webster Sutch', 'samuraiblog@gmail.com'
|
15
|
+
self.rubyforge_name = self.name
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'newgem/tasks'
|
19
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
20
|
+
|
21
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
22
|
+
# remove_task :default
|
23
|
+
# task :default => [:spec, :features]
|
data/lib/pyapns.rb
ADDED
@@ -0,0 +1,365 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'singleton'
|
5
|
+
require 'xmlrpc/client'
|
6
|
+
|
7
|
+
|
8
|
+
module PYAPNS
|
9
|
+
VERSION = "0.3.0"
|
10
|
+
|
11
|
+
## PYAPNS::Client
|
12
|
+
## There's python in my ruby!
|
13
|
+
##
|
14
|
+
## This is a class used to send notifications, provision applications and
|
15
|
+
## retrieve feedback using the Apple Push Notification Service.
|
16
|
+
##
|
17
|
+
## PYAPNS is a multi-application APS provider, meaning it is possible to send
|
18
|
+
## notifications to any number of different applications from the same application
|
19
|
+
## and same server. It is also possible to scale the client to any number
|
20
|
+
## of processes and servers, simply balanced behind a simple web proxy.
|
21
|
+
##
|
22
|
+
## It may seem like overkill for such a bare interface - after all, the
|
23
|
+
## APS service is rather simplistic. However, PYAPNS takes no shortcuts when it
|
24
|
+
## comes to compleatness/compliance with the APNS protocol and allows the
|
25
|
+
## user many optimization and scaling vectors not possible with other libraries.
|
26
|
+
## No bandwidth is wasted, connections are persistent and the server is
|
27
|
+
## asynchronous therefore notifications are delivered immediately.
|
28
|
+
##
|
29
|
+
## PYAPNS takes after the design of 3rd party push notification service that
|
30
|
+
## charge a fee each time you push a notification, and charge extra for so-called
|
31
|
+
## 'premium' service which supposedly gives you quicker access to the APS servers.
|
32
|
+
## However, PYAPNS is free, as in beer and offers more scaling oportunites without
|
33
|
+
## the financial draw.
|
34
|
+
##
|
35
|
+
## Provisioning
|
36
|
+
##
|
37
|
+
## To add your app to the PYAPNS server, it must be `provisioned` at least once.
|
38
|
+
## Normally this is done once upon the start-up of your application, be it a web
|
39
|
+
## service, desktop application or whatever... It must be done at least once
|
40
|
+
## to the server you're connecting to. Multiple instances of PYAPNS will have
|
41
|
+
## to have their applications provisioned individually. To provision an application
|
42
|
+
## manually use the PYAPNS::Client#provision method.
|
43
|
+
##
|
44
|
+
## require 'pyapns'
|
45
|
+
## client = PYAPNS::Client.configure
|
46
|
+
## client.provision :app_id => 'cf', :cert => '/home/ss/cert.pem', :env => 'sandbox', :timeout => 15
|
47
|
+
##
|
48
|
+
## This basically says "add an app reference named 'cf' to the server and start
|
49
|
+
## a connection using the certification, and if it can't within 15 seconds,
|
50
|
+
## raise a PYAPNS::TimeoutException
|
51
|
+
##
|
52
|
+
## That's all it takes to get started. Of course, this can be done automatically
|
53
|
+
## by using PYAPNS::ClientConfiguration middleware. PYAPNS::Client is a singleton
|
54
|
+
## class that is configured using the class method PYAPNS::Client#configure. It
|
55
|
+
## is sensibly configured by default, but can be customized by specifying a hash
|
56
|
+
## See the docs on PYAPNS::ClientConfiguration for a list of available configuration
|
57
|
+
## parameters (some of these are important, and you can specify initial applications)
|
58
|
+
## to be configured by default.
|
59
|
+
##
|
60
|
+
## Sending Notifications
|
61
|
+
##
|
62
|
+
## Once your client is configured, and application provisioned (again, these
|
63
|
+
## should be taken care of before you write notification code) you can begin
|
64
|
+
## sending notifications to users. If you're wondering how to acquire a notification
|
65
|
+
## token, you've come to the wrong place... I recommend using google. However,
|
66
|
+
## if you want to send hundreds of millions of notifications to users, here's how
|
67
|
+
## it's done, one at a time...
|
68
|
+
##
|
69
|
+
## The PYAPNS::Client#notify is a sort of polymorphic method which can notify
|
70
|
+
## any number of devices at a time. It's basic form is as follows:
|
71
|
+
##
|
72
|
+
## client.notify 'cf', 'long ass app token', {:aps=> {:alert => 'hello?'}}
|
73
|
+
##
|
74
|
+
## However, as stated before, it is sort of polymorphic:
|
75
|
+
##
|
76
|
+
## client.notify 'cf', ['token', 'token2', 'token3'], [alert, alert2, alert3]
|
77
|
+
##
|
78
|
+
## client.notify :app_id => 'cf', :tokens => 'mah token', :notifications => alertHash
|
79
|
+
##
|
80
|
+
## client.notify 'cf', 'token', PYAPNS::Notification('hello tits!')
|
81
|
+
##
|
82
|
+
## As you can see, the method accepts paralell arrays of tokens and notifications
|
83
|
+
## meaning any number of notifications can be sent at once. Hashes will be automatically
|
84
|
+
## converted to PYAPNS::Notification objects so they can be optimized for the wire
|
85
|
+
## (nil values removed, etc...), and you can pass PYAPNS::Notification objects
|
86
|
+
## directly if you wish.
|
87
|
+
##
|
88
|
+
## Retrieving Feedback
|
89
|
+
##
|
90
|
+
## The APS service offers a feedback functionality that allows application servers
|
91
|
+
## to retrieve a list of device tokens it deems to be no longer in use, and the
|
92
|
+
## time it thinks they stopped being useful (the user uninstalled your app, better
|
93
|
+
## luck next time...) Sounds pretty straight forward, and it is. Apple recommends
|
94
|
+
## you do this at least once an hour. PYAPNS will return a list of 2-element lists
|
95
|
+
## with the date and the token:
|
96
|
+
##
|
97
|
+
## feedbacks = client.feedback 'cf'
|
98
|
+
##
|
99
|
+
## Asynchronous Calls
|
100
|
+
##
|
101
|
+
## PYAPNS::Client will, by default, perform no funny stuff and operate entirely
|
102
|
+
## within the calling thread. This means that certain applications may hang when,
|
103
|
+
## say, sending a notification, if only for a fraction of a second. Obviously
|
104
|
+
## not a desirable trait, all `provision`, `feedback` and `notify`
|
105
|
+
## methods also take a block, which indicates to the method you want to call
|
106
|
+
## PYAPNS asynchronously, and it will be done so handily in another thread, calling
|
107
|
+
## back your block with a single argument when finished. Note that `notify` and `provision`
|
108
|
+
## return absolutely nothing (nil, for you rub--wait you are ruby developers!).
|
109
|
+
## It is probably wise to always use this form of operation so your calling thread
|
110
|
+
## is never blocked (especially important in UI-driven apps and asynchronous servers)
|
111
|
+
## Just pass a block to provision/notify/feedback like so:
|
112
|
+
##
|
113
|
+
## PYAPNS::Client.instance.feedback do |feedbacks|
|
114
|
+
## feedbacks.each { |f| trim_token f }
|
115
|
+
## end
|
116
|
+
##
|
117
|
+
class Client
|
118
|
+
include Singleton
|
119
|
+
|
120
|
+
def self.configure(hash={})
|
121
|
+
y = self.instance
|
122
|
+
y.configure(hash)
|
123
|
+
end
|
124
|
+
|
125
|
+
def initialize
|
126
|
+
@configured = false
|
127
|
+
end
|
128
|
+
|
129
|
+
def provision(*args, &block)
|
130
|
+
perform_call :provision, args, :app_id, :cert, :env, :timeout, &block
|
131
|
+
end
|
132
|
+
|
133
|
+
def notify(*args, &block)
|
134
|
+
kwargs = [:app_id, :tokens, :notifications]
|
135
|
+
get_args(args, *kwargs) do |splat|
|
136
|
+
splat[2] = (splat[2].class == Array ?
|
137
|
+
splat[2] : [splat[2]]).map do |note|
|
138
|
+
if note.class != PYAPNS::Notification
|
139
|
+
PYAPNS::Notification.encode note
|
140
|
+
else
|
141
|
+
note
|
142
|
+
end
|
143
|
+
end
|
144
|
+
perform_call :notify, splat, *kwargs, &block
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def feedback(*args, &block)
|
149
|
+
perform_call :feedback, args, :app_id, &block
|
150
|
+
end
|
151
|
+
|
152
|
+
def perform_call(method, splat, *args, &block)
|
153
|
+
if !configured?
|
154
|
+
raise PYAPNS::NotConfigured.new
|
155
|
+
end
|
156
|
+
get_args(splat, *args) do |splat|
|
157
|
+
if block_given?
|
158
|
+
Thread.new do
|
159
|
+
perform_call2 {
|
160
|
+
block.call(@client.call_async(method.to_s, *splat))
|
161
|
+
}
|
162
|
+
end
|
163
|
+
nil
|
164
|
+
else
|
165
|
+
perform_call2 { @client.call_async(method.to_s, *splat) }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def get_args(splat, *args, &block)
|
171
|
+
if splat.length == 1 && splat[0].class == Hash
|
172
|
+
splat = args.map { |k| splat[0][k] }
|
173
|
+
end
|
174
|
+
if (splat.find_all { |l| not l.nil? }).length == args.length
|
175
|
+
block.call(splat)
|
176
|
+
else
|
177
|
+
raise PYAPNS::InvalidArguments.new "Invalid args supplied #{args}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def perform_call2(&block)
|
182
|
+
begin
|
183
|
+
block.call()
|
184
|
+
rescue XMLRPC::FaultException => fault
|
185
|
+
case fault.faultCode
|
186
|
+
when 404
|
187
|
+
raise PYAPNS::UnknownAppID.new fault.faultString
|
188
|
+
when 401
|
189
|
+
raise PYAPNS::InvalidEnvironment.new fault.faultString
|
190
|
+
when 500
|
191
|
+
raise PYAPNS::ServerTimeout.new fault.faultString
|
192
|
+
else
|
193
|
+
raise fault
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def configured?
|
199
|
+
return @configured
|
200
|
+
end
|
201
|
+
|
202
|
+
def configure(hash={})
|
203
|
+
if configured?
|
204
|
+
return self
|
205
|
+
end
|
206
|
+
h = {}
|
207
|
+
hash.each { |k,v| h[k.to_s.downcase] = v }
|
208
|
+
@host = h['host'] || "localhost"
|
209
|
+
@port = h['port'] || 7077
|
210
|
+
@path = h['path'] || '/'
|
211
|
+
@timeout = h['timeout'] || 15
|
212
|
+
@client = XMLRPC::Client.new3(
|
213
|
+
:host => @host,
|
214
|
+
:port => @port,
|
215
|
+
:timeout => @timeout,
|
216
|
+
:path => @path)
|
217
|
+
if not h['initial'].nil?
|
218
|
+
h['initial'].each do |initial|
|
219
|
+
provision(:app_id => initial[:app_id],
|
220
|
+
:cert => initial[:cert],
|
221
|
+
:env => initial[:env],
|
222
|
+
:timeout => initial[:timeout] || 15)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
@configured = true
|
226
|
+
self
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
## PYAPNS::ClientConfiguration
|
231
|
+
## A middleware class to make PYAPNS::Client easy to use in web contexts
|
232
|
+
##
|
233
|
+
## Automates configuration of the client in Rack environments
|
234
|
+
## using a simple confiuration middleware. To use PYAPNS::Client in
|
235
|
+
## Rack environments with the least code possible use PYAPNS::ClientConfiguration
|
236
|
+
## (no, really, in some cases, that's all you need!) middleware with an optional
|
237
|
+
## hash specifying the client variables. Options are as follows:
|
238
|
+
##
|
239
|
+
## use PYAPNS::ClientConfiguration(
|
240
|
+
## :host => 'http://localhost/'
|
241
|
+
## :port => 7077,
|
242
|
+
## :initial => [{
|
243
|
+
## :app_id => 'myapp',
|
244
|
+
## :cert => '/home/myuser/apps/myapp/cert.pem',
|
245
|
+
## :env => 'sandbox',
|
246
|
+
## :timeout => 15
|
247
|
+
## }])
|
248
|
+
##
|
249
|
+
## Where the configuration variables are defined:
|
250
|
+
##
|
251
|
+
## :host String the host where the server can be found
|
252
|
+
## :port Number the port to which the client should connect
|
253
|
+
## :initial Array OPTIONAL - an array of INITIAL hashes
|
254
|
+
##
|
255
|
+
## INITIAL HASHES:
|
256
|
+
##
|
257
|
+
## :app_id String the id used to send messages with this certification
|
258
|
+
## can be a totally arbitrary value
|
259
|
+
## :cert String a path to the certification or the certification file
|
260
|
+
## as a string
|
261
|
+
## :env String the environment to connect to apple with, always
|
262
|
+
## either 'sandbox' or 'production'
|
263
|
+
## :timoeut Number The timeout for the server to use when connecting
|
264
|
+
## to the apple servers
|
265
|
+
class ClientConfiguration
|
266
|
+
def initialize(app, hash={})
|
267
|
+
@app = app
|
268
|
+
PYAPNS::Client.configure(hash)
|
269
|
+
end
|
270
|
+
|
271
|
+
def call(env)
|
272
|
+
@app.call(env)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
## PYAPNS::Notification
|
277
|
+
## An APNS Notification
|
278
|
+
##
|
279
|
+
## You can construct notification objects ahead of time by using this class.
|
280
|
+
## However unnecessary, it allows you to programatically generate a Notification
|
281
|
+
## like so:
|
282
|
+
##
|
283
|
+
## note = PYAPNS::Notification.new 'alert text', 9, 'flynn.caf', {:extra => 'guid'}
|
284
|
+
##
|
285
|
+
## -- or --
|
286
|
+
## note = PYAPNS::Notification.new 'alert text'
|
287
|
+
##
|
288
|
+
## These can be passed to PYAPNS::Client#notify the same as hashes
|
289
|
+
##
|
290
|
+
class Notification
|
291
|
+
def initialize(*args)
|
292
|
+
kwargs = [:alert, :badge, :sound]
|
293
|
+
extra = nil
|
294
|
+
if args.length == 1 && args[0].class == Hash
|
295
|
+
args = kwargs.map { |k| args[0][k] }
|
296
|
+
end
|
297
|
+
@note = {
|
298
|
+
:aps => {
|
299
|
+
:alert => args[0].nil? ? nil : args[0].to_s,
|
300
|
+
:badge => args[1].nil? ? nil : args[1].to_i,
|
301
|
+
:sound => args[2].nil? ? nil : args[2].to_s
|
302
|
+
}
|
303
|
+
}
|
304
|
+
if args.length == 4
|
305
|
+
@note = @note.merge(args[3] || {})
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def self.aps_attr(*symbols)
|
310
|
+
symbols.each do |sy|
|
311
|
+
define_method sy do
|
312
|
+
instance_variable_get(:@note)[:aps][sy]
|
313
|
+
end
|
314
|
+
define_method "#{sy}=".to_sym do |val|
|
315
|
+
instance_variable_get(:@note)[:aps][sy] = val
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
aps_attr :alert, :badge, :sound
|
321
|
+
|
322
|
+
def extra key
|
323
|
+
@note[key]
|
324
|
+
end
|
325
|
+
|
326
|
+
def set_extra key, val
|
327
|
+
@note[key] = val
|
328
|
+
end
|
329
|
+
|
330
|
+
def encode
|
331
|
+
PYAPNS::Notification.encode(@note)
|
332
|
+
end
|
333
|
+
|
334
|
+
def self.encode note
|
335
|
+
ret = {}
|
336
|
+
if !note[:aps].nil?
|
337
|
+
ret['aps'] = {}
|
338
|
+
note[:aps].each do |k, v|
|
339
|
+
if !v.nil?
|
340
|
+
ret['aps'][k.to_s] = v
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
note.keys.find_all { |k| !note[k].nil? && k != :aps }.each do |k|
|
345
|
+
ret[k.to_s] = note[k]
|
346
|
+
end
|
347
|
+
ret
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
class UnknownAppID < Exception
|
352
|
+
end
|
353
|
+
|
354
|
+
class NotConfigured < Exception
|
355
|
+
end
|
356
|
+
|
357
|
+
class InvalidEnvironment < Exception
|
358
|
+
end
|
359
|
+
|
360
|
+
class ServerTimeout < Exception
|
361
|
+
end
|
362
|
+
|
363
|
+
class InvalidArguments < Exception
|
364
|
+
end
|
365
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/pyapns.rb'}"
|
9
|
+
puts "Loading pyapns gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/test/test_helper.rb
ADDED
data/test/test_pyapns.rb
ADDED
metadata
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pyapns
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Webster Sutch
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-29 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rubyforge
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.0.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: gemcutter
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.0
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: hoe
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.5.0
|
44
|
+
version:
|
45
|
+
description: |-
|
46
|
+
:title: The Ruby API
|
47
|
+
|
48
|
+
:section: PYAPNS::Client
|
49
|
+
There's python in my ruby!
|
50
|
+
|
51
|
+
This is a class used to send notifications, provision applications and
|
52
|
+
retrieve feedback using the Apple Push Notification Service.
|
53
|
+
|
54
|
+
PYAPNS is a multi-application APS provider, meaning it is possible to send
|
55
|
+
notifications to any number of different applications from the same application
|
56
|
+
and same server. It is also possible to scale the client to any number
|
57
|
+
of processes and servers, simply balanced behind a simple web proxy.
|
58
|
+
|
59
|
+
It may seem like overkill for such a bare interface - after all, the
|
60
|
+
APS service is rather simplistic. However, PYAPNS takes no shortcuts when it
|
61
|
+
comes to completeness/compliance with the APNS protocol and allows the
|
62
|
+
user many optimization and scaling vectors not possible with other libraries.
|
63
|
+
No bandwidth is wasted, connections are persistent and the server is
|
64
|
+
asynchronous therefore notifications are delivered immediately.
|
65
|
+
|
66
|
+
PYAPNS takes after the design of 3rd party push notification service that
|
67
|
+
charge a fee each time you push a notification, and charge extra for so-called
|
68
|
+
'premium' service which supposedly gives you quicker access to the APS servers.
|
69
|
+
However, PYAPNS is free, as in beer and offers more scaling opportunities without
|
70
|
+
the financial draw.
|
71
|
+
|
72
|
+
:section: Provisioning
|
73
|
+
|
74
|
+
To add your app to the PYAPNS server, it must be `provisioned` at least once.
|
75
|
+
Normally this is done once upon the start-up of your application, be it a web
|
76
|
+
service, desktop application or whatever... It must be done at least once
|
77
|
+
to the server you're connecting to. Multiple instances of PYAPNS will have
|
78
|
+
to have their applications provisioned individually. To provision an application
|
79
|
+
manually use the `PYAPNS::Client#provision` method.
|
80
|
+
|
81
|
+
require 'pyapns'
|
82
|
+
client = PYAPNS::Client.configure
|
83
|
+
client.provision :app_id => 'cf', :cert => '/home/ss/cert.pem', :env => 'sandbox', :timeout => 15
|
84
|
+
|
85
|
+
This basically says "add an app reference named 'cf' to the server and start
|
86
|
+
a connection using the certification, and if it can't within 15 seconds,
|
87
|
+
raise a `PYAPNS::TimeoutException`
|
88
|
+
|
89
|
+
That's all it takes to get started. Of course, this can be done automatically
|
90
|
+
by using PYAPNS::ClientConfiguration middleware. `PYAPNS::Client` is a singleton
|
91
|
+
class that is configured using the class method `PYAPNS::Client#configure`. It
|
92
|
+
is sensibly configured by default, but can be customized by specifying a hash
|
93
|
+
See the docs on `PYAPNS::ClientConfiguration` for a list of available configuration
|
94
|
+
parameters (some of these are important, and you can specify initial applications)
|
95
|
+
to be configured by default.
|
96
|
+
|
97
|
+
:section: Sending Notifications
|
98
|
+
|
99
|
+
Once your client is configured, and application provisioned (again, these
|
100
|
+
should be taken care of before you write notification code) you can begin
|
101
|
+
sending notifications to users. If you're wondering how to acquire a notification
|
102
|
+
token, you've come to the wrong place... I recommend using google. However,
|
103
|
+
if you want to send hundreds of millions of notifications to users, here's how
|
104
|
+
it's done, one at a time...
|
105
|
+
|
106
|
+
The `PYAPNS::Client#notify` is a sort of polymorphic method which can notify
|
107
|
+
any number of devices at a time. It's basic form is as follows:
|
108
|
+
|
109
|
+
client.notify 'cf', 'long ass app token', {:aps=> {:alert => 'hello?'}}
|
110
|
+
|
111
|
+
However, as stated before, it is sort of polymorphic:
|
112
|
+
|
113
|
+
client.notify 'cf', ['token', 'token2', 'token3'], [alert, alert2, alert3]
|
114
|
+
|
115
|
+
client.notify :app_id => 'cf', :tokens => 'mah token', :notifications => alertHash
|
116
|
+
|
117
|
+
client.notify 'cf', 'token', PYAPNS::Notification('hello tits!')
|
118
|
+
|
119
|
+
As you can see, the method accepts paralell arrays of tokens and notifications
|
120
|
+
meaning any number of notifications can be sent at once. Hashes will be automatically
|
121
|
+
converted to `PYAPNS::Notification` objects so they can be optimized for the wire
|
122
|
+
(nil values removed, etc...), and you can pass `PYAPNS::Notification` objects
|
123
|
+
directly if you wish.
|
124
|
+
|
125
|
+
:section: Retrieving Feedback
|
126
|
+
|
127
|
+
The APS service offers a feedback functionality that allows application servers
|
128
|
+
to retrieve a list of device tokens it deems to be no longer in use, and the
|
129
|
+
time it thinks they stopped being useful (the user uninstalled your app, better
|
130
|
+
luck next time...) Sounds pretty straight forward, and it is. Apple recommends
|
131
|
+
you do this at least once an hour. PYAPNS will return a list of 2-element lists
|
132
|
+
with the date and the token:
|
133
|
+
|
134
|
+
feedbacks = client.feedback 'cf'
|
135
|
+
|
136
|
+
:section: Asynchronous Calls
|
137
|
+
|
138
|
+
PYAPNS::Client will, by default, perform no funny stuff and operate entirely
|
139
|
+
within the calling thread. This means that certain applications may hang when,
|
140
|
+
say, sending a notification, if only for a fraction of a second. Obviously
|
141
|
+
not a desirable trait, all `provision`, `feedback` and `notify`
|
142
|
+
methods also take a block, which indicates to the method you want to call
|
143
|
+
PYAPNS asynchronously, and it will be done so handily in another thread, calling
|
144
|
+
back your block with a single argument when finished. Note that `notify` and `provision`
|
145
|
+
return absolutely nothing (nil, for you rub--wait you are ruby developers!).
|
146
|
+
It is probably wise to always use this form of operation so your calling thread
|
147
|
+
is never blocked (especially important in UI-driven apps and asynchronous servers)
|
148
|
+
Just pass a block to provision/notify/feedback like so:
|
149
|
+
|
150
|
+
PYAPNS::Client.instance.feedback do |feedbacks|
|
151
|
+
feedbacks.each { |f| trim_token f }
|
152
|
+
end
|
153
|
+
|
154
|
+
:section: PYAPNS::ClientConfiguration
|
155
|
+
A middleware class to make `PYAPNS::Client` easy to use in web contexts
|
156
|
+
|
157
|
+
Automates configuration of the client in Rack environments
|
158
|
+
using a simple confiuration middleware. To use `PYAPNS::Client` in
|
159
|
+
Rack environments with the least code possible `use PYAPNS::ClientConfiguration`
|
160
|
+
(no, really, in some cases, that's all you need!) middleware with an optional
|
161
|
+
hash specifying the client variables. Options are as follows:
|
162
|
+
|
163
|
+
use PYAPNS::ClientConfiguration(
|
164
|
+
:host => 'http://localhost/'
|
165
|
+
:port => 7077,
|
166
|
+
:initial => [{
|
167
|
+
:app_id => 'myapp',
|
168
|
+
:cert => '/home/myuser/apps/myapp/cert.pem',
|
169
|
+
:env => 'sandbox',
|
170
|
+
:timeout => 15
|
171
|
+
}])
|
172
|
+
|
173
|
+
Where the configuration variables are defined:
|
174
|
+
|
175
|
+
:host String the host where the server can be found
|
176
|
+
:port Number the port to which the client should connect
|
177
|
+
:initial Array OPTIONAL - an array of INITIAL hashes
|
178
|
+
|
179
|
+
INITIAL HASHES:
|
180
|
+
|
181
|
+
:app_id String the id used to send messages with this certification
|
182
|
+
can be a totally arbitrary value
|
183
|
+
:cert String a path to the certification or the certification file
|
184
|
+
as a string
|
185
|
+
:env String the environment to connect to apple with, always
|
186
|
+
either 'sandbox' or 'production'
|
187
|
+
:timoeut Number The timeout for the server to use when connecting
|
188
|
+
to the apple servers
|
189
|
+
|
190
|
+
:section: PYAPNS::Notification
|
191
|
+
An APNS Notification
|
192
|
+
|
193
|
+
You can construct notification objects ahead of time by using this class.
|
194
|
+
However unnecessary, it allows you to programmatically generate a Notification
|
195
|
+
like so:
|
196
|
+
|
197
|
+
note = PYAPNS::Notification.new 'alert text', 9, 'flynn.caf', {:extra => 'guid'}
|
198
|
+
|
199
|
+
-- or --
|
200
|
+
note = PYAPNS::Notification.new 'alert text'
|
201
|
+
|
202
|
+
These can be passed to `PYAPNS::Client#notify` the same as hashes
|
203
|
+
email:
|
204
|
+
- samuraiblog@gmail.com
|
205
|
+
executables: []
|
206
|
+
|
207
|
+
extensions: []
|
208
|
+
|
209
|
+
extra_rdoc_files:
|
210
|
+
- History.txt
|
211
|
+
- Manifest.txt
|
212
|
+
- PostInstall.txt
|
213
|
+
files:
|
214
|
+
- History.txt
|
215
|
+
- Manifest.txt
|
216
|
+
- PostInstall.txt
|
217
|
+
- README.rdoc
|
218
|
+
- Rakefile
|
219
|
+
- lib/pyapns.rb
|
220
|
+
- script/console
|
221
|
+
- script/destroy
|
222
|
+
- script/generate
|
223
|
+
- test/test_helper.rb
|
224
|
+
- test/test_pyapns.rb
|
225
|
+
has_rdoc: true
|
226
|
+
homepage: http://pyapns.org
|
227
|
+
licenses: []
|
228
|
+
|
229
|
+
post_install_message:
|
230
|
+
rdoc_options:
|
231
|
+
- --main
|
232
|
+
- README.rdoc
|
233
|
+
require_paths:
|
234
|
+
- lib
|
235
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
236
|
+
requirements:
|
237
|
+
- - ">="
|
238
|
+
- !ruby/object:Gem::Version
|
239
|
+
version: "0"
|
240
|
+
version:
|
241
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
242
|
+
requirements:
|
243
|
+
- - ">="
|
244
|
+
- !ruby/object:Gem::Version
|
245
|
+
version: "0"
|
246
|
+
version:
|
247
|
+
requirements: []
|
248
|
+
|
249
|
+
rubyforge_project: pyapns
|
250
|
+
rubygems_version: 1.3.5
|
251
|
+
signing_key:
|
252
|
+
specification_version: 3
|
253
|
+
summary: ":title: The Ruby API :section: 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"
|
254
|
+
test_files:
|
255
|
+
- test/test_helper.rb
|
256
|
+
- test/test_pyapns.rb
|