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.
@@ -0,0 +1,4 @@
1
+ === 0.3.0 2010-01-22
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,11 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/pyapns.rb
7
+ script/console
8
+ script/destroy
9
+ script/generate
10
+ test/test_helper.rb
11
+ test/test_pyapns.rb
@@ -0,0 +1,7 @@
1
+
2
+ For more information on pyapns, see http://pyapns.rubyforge.org
3
+
4
+ NOTE: Change this information in PostInstall.txt
5
+ You can also delete it if you don't want it.
6
+
7
+
@@ -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.
@@ -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]
@@ -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
@@ -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"
@@ -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)
@@ -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)
@@ -0,0 +1,3 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/pyapns'
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestPYAPNS < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_truth
9
+ assert true
10
+ end
11
+ end
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