apns-pressplane 0.9.10

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,2 @@
1
+ .DS_Store
2
+ pkg
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2009 James Pozdena
2
+ Copyright (c) 2010-2011 Thierry Passeron
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,199 @@
1
+ h1. APNS
2
+
3
+ A plugin/Gem for the Apple Push Notification Service.
4
+
5
+ The connections to Apple are done on demand (per process) and last until they are either closed by the system or are timed out.
6
+ This is the prefered way for communicating with Apple's push servers.
7
+
8
+ Works in Ruby on Rails 3.
9
+
10
+ h2. Install
11
+
12
+ <pre>
13
+ <code>
14
+ sudo gem install apns
15
+ or
16
+ rails plugin install git://...
17
+ </code>
18
+ </pre>
19
+
20
+ h2. Setup:
21
+
22
+ Convert your certificates
23
+
24
+ In Keychain access export your push certificate(s) as a .p12. Then run the following command to convert each .p12 to a .pem
25
+
26
+ <pre>
27
+ <code>
28
+ openssl pkcs12 -in cert.p12 -out cert.pem -nodes -clcerts
29
+ </code>
30
+ </pre>
31
+
32
+ After you have your .pem files, copy them to a place where APNS can access them.
33
+ You will need to register them with APNS before being able to send Notifications
34
+
35
+ In a Rails project, you can add an initializer to configure the pem(s), with for example:
36
+
37
+ <pre>
38
+ <code>
39
+ In file ./config/initializers/APNS.rb:
40
+
41
+ # Initialize the APNS environment
42
+ APNS.pem = Rails.root.join("config", Rails.env + ".pem") # => ./config/{development,production}.pem
43
+
44
+ </code>
45
+ </pre>
46
+
47
+ h2. Creating a Payload:
48
+
49
+ Sending a push notification is sending a Payload to Apple's servers.
50
+
51
+ You may create payloads with APNS::Payload.new(<device-token> [,<message>])
52
+ A payload is composed of a device-token and a message all mixed and encoded together.
53
+ Payload message can either just be a alert string or a hash that lets you specify the alert, badge, sound and any custom field.
54
+
55
+ <pre>
56
+ <code>
57
+ device_token = '123abc456def'
58
+ p1 = APNS::Payload.new(device_token, 'Hello iPhone!')
59
+ p2 = APNS::Payload.new(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default')
60
+ p3 = APNS::Payload.new(device_token).badge(4).alert("Hello iPhone!").sound('bird.aiff')
61
+
62
+ # with custom data:
63
+ p4 = APNS::Payload.new(device_token, :badge => 2, :my_custom_field => 'blah')
64
+ p5 = APNS::Payload.new(device_token, :badge => 2).custom(:my_custom_field => 'blah')
65
+ </code>
66
+ </pre>
67
+
68
+
69
+ h2. Truncating payload informations
70
+
71
+ Only valid Payloads will be sent to Apple. From APNS point of view, a valid payload has a size lesser or equal to 256 bytes.
72
+ REM: Apple may find a APNS valid payload invalid because it doesn't contain mandatory fields in the message part.
73
+ For instance, a message must at least contain a non empty badge or sound or alert.
74
+
75
+ You can check whether a payload is valid with the Payload#valid? method.
76
+ In case you know a payload message field is too large and wish to have it truncated you can either use Payload#payload_with_truncated_alert or a more generic Payload#payload_with_truncated_string_at_keypath. These two method will try to truncate the value of the alert field or any custom field at a keypath.
77
+
78
+ Truncate the alert field:
79
+
80
+ <pre>
81
+ <code>
82
+ p = APNS::Payload.new("a-device-token", "A very long message "*15)
83
+ p.valid?
84
+ => false
85
+ p.size
86
+ => 331
87
+ p.payload_with_truncated_alert.size
88
+ => 256
89
+ p.payload_with_truncated_alert.valid?
90
+ => true
91
+ </code>
92
+ </pre>
93
+
94
+ Truncate a custom field:
95
+
96
+ <pre>
97
+ <code>
98
+ p = APNS::Payload.new("a-device-token", :alert => "Hello from APNS", :custom => {:foo => "Bar "*80})
99
+ p.valid?
100
+ => false
101
+ p.size
102
+ => 387
103
+ p.payload_with_truncated_string_at_keypath("custom.foo").size
104
+ => 256
105
+ p.payload_with_truncated_string_at_keypath("custom.foo").valid?
106
+ => true
107
+ </code>
108
+ </pre>
109
+
110
+
111
+ h2. Sending Notifications to a single application:
112
+
113
+ Before sending notifications, you _must_ have setup the pem file(s) so that Apple knows which application you are sending a notification to.
114
+
115
+ <pre>
116
+ <code>
117
+ APNS.pem = "/path/to/my/development.pem"
118
+ </code>
119
+ </pre>
120
+
121
+ Now we can send some payloads either with:
122
+
123
+ * APNS.send_payloads(<payloads>)
124
+ * APNS.send(<payloads>) # same as APNS.send_payloads
125
+
126
+ <pre>
127
+ <code>
128
+ APNS.send(p1, p2, p3)
129
+ </code>
130
+ </pre>
131
+
132
+ h2. Sending Notifications to multiple applications:
133
+
134
+ You may want to handle push notifications for many applications at once. In this case you have to setup multiple pem streams:
135
+
136
+ <pre>
137
+ <code>
138
+ @streams = [:voodoo, :child]
139
+
140
+ @streams.each do |stream|
141
+ APNS.pem(stream, "/path/to/#{stream}/development.pem"
142
+ end
143
+ </code>
144
+ </pre>
145
+
146
+ Now you can send the notifications to any stream with:
147
+
148
+ * APNS.send_stream(<stream>, <payloads>)
149
+
150
+ <pre>
151
+ <code>
152
+ APNS.send_stream(@streams.first, p1, p2, p3)
153
+ APNS.send_stream(@streams.last, p4, p5)
154
+ </code>
155
+ </pre>
156
+
157
+
158
+ h2. Feedback queue:
159
+
160
+ You should check the feedback queue of your application on Apple's servers to avoid sending notifications to obsolete devices
161
+
162
+ For single pem:
163
+
164
+ <pre>
165
+ <code>
166
+ APNS.feedback.each do |time, token|
167
+ # remove the device registered with this token ?
168
+ end
169
+ </code>
170
+ </pre>
171
+
172
+ For multiple pems:
173
+
174
+ <pre>
175
+ <code>
176
+ APNS.feedback(@streams.first).each do |time, token|
177
+ # remove the device registered with this token ?
178
+ end
179
+ </code>
180
+ </pre>
181
+
182
+
183
+ h2. Getting your iPhone's device token
184
+
185
+ After you setup push notification for your application with Apple. You need to ask Apple for you application specific device token.
186
+
187
+ In the UIApplicationDelegate
188
+ <pre>
189
+ <code>
190
+ - (void)applicationDidFinishLaunching:(UIApplication *)application {
191
+ [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
192
+ (UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)];
193
+ }
194
+
195
+ - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
196
+ // Do something with the device token
197
+ }
198
+ </code>
199
+ </pre>
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'rspec/core/rake_task'
6
+
7
+ GEM = 'apns'
8
+ GEM_NAME = 'apns'
9
+ GEM_VERSION = '0.9.9'
10
+ AUTHORS = ['James Pozdena', 'Thierry Passeron']
11
+ EMAIL = "thierry.passeron@gmail.com"
12
+ HOMEPAGE = "http://github.com/Orion98MC/APNS"
13
+ SUMMARY = "Simple Apple push notification service gem"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = GEM
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["MIT-LICENSE"]
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.authors = AUTHORS
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.require_path = 'lib'
27
+ s.autorequire = GEM
28
+ s.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{lib}/**/*")
29
+ end
30
+
31
+ task :default => :spec
32
+
33
+ desc "Run specs"
34
+ RSpec::Core::RakeTask.new do |t|
35
+ t.pattern = 'spec/**/*_spec.rb'
36
+ t.rspec_opts = %w(-fs --color)
37
+ end
38
+
39
+ Rake::GemPackageTask.new(spec) do |pkg|
40
+ pkg.gem_spec = spec
41
+ end
42
+
43
+ desc "install the gem locally"
44
+ task :install => [:package] do
45
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
46
+ end
47
+
48
+ desc "create a gemspec file"
49
+ task :make_spec do
50
+ File.open("#{GEM}.gemspec", "w") do |file|
51
+ file.puts spec.to_ruby
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{apns-pressplane}
5
+ s.version = "0.9.10"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["James Pozdena", "Thierry Passeron", "Pressplane Inc."]
9
+ s.autorequire = %q{apns}
10
+ s.date = %q{2010-03-22}
11
+ s.description = %q{Simple Apple push notification service gem}
12
+ s.email = %q{thierry.passeron@gmail.com}
13
+ s.extra_rdoc_files = ["MIT-LICENSE"]
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.homepage = %q{http://github.com/Orion98MC/APNS}
18
+ s.require_paths = ["lib"]
19
+ s.rubygems_version = %q{1.3.5}
20
+ s.summary = %q{Simple Apple push notification service gem}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ else
28
+ end
29
+ else
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ require 'apns/core'
2
+ require 'apns/device'
3
+ require 'apns/payload'
@@ -0,0 +1,162 @@
1
+ module APNS
2
+ require 'socket'
3
+ require 'openssl'
4
+ require 'json'
5
+
6
+ class PemPathError < RuntimeError;end
7
+ class PemFileError < RuntimeError;end
8
+
9
+ ## Host for push notification service
10
+ #
11
+ # production: gateway.push.apple.com
12
+ # development: gateway.sandbox.apple.com
13
+ #
14
+ # You may set the correct host with:
15
+ # APNS.host = <host> or use the default one
16
+ @host = 'gateway.sandbox.push.apple.com'
17
+ @port = 2195
18
+
19
+ ## Host for feedback service
20
+ #
21
+ # production: feedback.push.apple.com
22
+ # development: feedback.sandbox.apple.com
23
+ #
24
+ # You may set the correct feedback host with:
25
+ # APNS.feedback_host = <host> or use the default one
26
+ @feedback_host = @host.gsub('gateway','feedback')
27
+ @feedback_port = 2196
28
+
29
+ # openssl pkcs12 -in mycert.p12 -out client-cert.pem -nodes -clcerts
30
+ @pem = {} # this should be the path of the pem file not the contents
31
+ @pass = {}
32
+
33
+ # Persistent connection
34
+ @@ssl = {}
35
+ @@sock = {}
36
+
37
+ class << self
38
+ attr_accessor :host, :port, :feedback_host, :feedback_port
39
+ def pem(stream = :_global, new_pem = nil)
40
+ @pem[stream] = new_pem if new_pem
41
+ @pem[stream]
42
+ end
43
+ def pem=(new_pem); @pem[:_global] = new_pem; end
44
+
45
+ def pass(stream = :_global, new_pass = nil)
46
+ @pass[stream] = new_pass if new_pass
47
+ @pass[stream]
48
+ end
49
+ def pass=(new_pass); @pass[:_global] = new_pass; end
50
+ end
51
+
52
+ # send one or many payloads
53
+ #
54
+ # Connection
55
+ # The connection is made only if needed and is persisted until it times out or is closed by the system
56
+ #
57
+ # Errors
58
+ # If an error occures during the write operation, after 3 retries, the socket and ssl connections are closed and an exception is raised
59
+ #
60
+ # Example:
61
+ #
62
+ # single payload
63
+ # payload = APNS::Payload.new(device_token, 'Hello iPhone!')
64
+ # APNS.send_payloads(payload)
65
+ #
66
+ # or with multiple payloads
67
+ # APNS.send_payloads([payload1, payload2])
68
+
69
+
70
+ # Send to a pem stream
71
+ def self.send_stream(stream, *payloads)
72
+ payloads.flatten!
73
+
74
+ # retain valid payloads only
75
+ payloads.reject!{ |p| !(p.is_a?(APNS::Payload) && p.valid?) }
76
+
77
+ return if (payloads.nil? || payloads.count < 1)
78
+
79
+ # loop through each payloads
80
+ payloads.each do |payload|
81
+ retry_delay = 2
82
+
83
+ # !ToDo! do a better job by using a select to poll the socket for a possible response from apple to inform us about an error in the sent payload
84
+ #
85
+ begin
86
+ @@sock[stream], @@ssl[stream] = self.push_connection(stream) if @@ssl[stream].nil?
87
+ @@ssl[stream].write(payload.to_ssl); @@ssl[stream].flush
88
+ rescue PemPathError, PemFileError => e
89
+ raise e
90
+ rescue
91
+ @@ssl[stream].close; @@sock[stream].close
92
+ @@ssl[stream] = nil; @@sock[stream] = nil # cleanup
93
+
94
+ retry_delay *= 2
95
+ if retry_delay <= 8
96
+ sleep retry_delay
97
+ retry
98
+ else
99
+ raise
100
+ end
101
+ end # begin block
102
+
103
+ end # each payloads
104
+ end
105
+
106
+ def self.send_payloads(*payloads)
107
+ self.send(payloads)
108
+ end
109
+
110
+ def self.send(*payloads)
111
+ self.send_stream(:_global, payloads)
112
+ end
113
+
114
+
115
+ def self.feedback(stream = :_global)
116
+ sock, ssl = self.feedback_connection(stream)
117
+
118
+ apns_feedback = []
119
+
120
+ while line = sock.gets # Read lines from the socket
121
+ line.strip!
122
+ f = line.unpack('N1n1H140')
123
+ apns_feedback << [Time.at(f[0]), f[2]]
124
+ end
125
+
126
+ ssl.close
127
+ sock.close
128
+
129
+ return apns_feedback
130
+ end
131
+
132
+
133
+ protected
134
+
135
+ def self.ssl_context(stream = :_global)
136
+ raise PemPathError, "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless self.pem(stream)
137
+ raise PemFileError, "The path to your pem file does not exist!" unless File.exist?(self.pem(stream))
138
+
139
+ context = OpenSSL::SSL::SSLContext.new
140
+ context.cert = OpenSSL::X509::Certificate.new(File.read(self.pem(stream)))
141
+ context.key = OpenSSL::PKey::RSA.new(File.read(self.pem(stream)), self.pass(stream))
142
+ context
143
+ end
144
+
145
+ def self.connect_to(aps_host, aps_port, stream = :_global)
146
+ context = self.ssl_context(stream)
147
+ sock = TCPSocket.new(aps_host, aps_port)
148
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, context)
149
+ ssl.connect
150
+
151
+ return sock, ssl
152
+ end
153
+
154
+ def self.push_connection(stream = :_global)
155
+ self.connect_to(self.host, self.port, stream)
156
+ end
157
+
158
+ def self.feedback_connection(stream = :_global)
159
+ self.connect_to(self.feedback_host, self.feedback_port, stream)
160
+ end
161
+
162
+ end
@@ -0,0 +1,16 @@
1
+ module APNS
2
+
3
+ class Device
4
+ attr_accessor :token
5
+
6
+ def initialize(device_token)
7
+ # cleanup token
8
+ self.token = device_token.gsub(/[\s|<|>]/,'')
9
+ end
10
+
11
+ def to_payload
12
+ [token].pack('H*')
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,149 @@
1
+ module APNS
2
+
3
+ class Payload
4
+ attr_accessor :device, :message
5
+
6
+ APS_ROOT = :aps
7
+ APS_KEYS = [:alert, :badge, :sound]
8
+
9
+ PAYLOAD_MAX_SIZE = 256
10
+
11
+ def initialize(device_token, message_string_or_hash = {})
12
+ self.device = APNS::Device.new(device_token)
13
+ if message_string_or_hash.is_a?(String)
14
+ self.message = {:alert => message_string_or_hash.strip}
15
+ elsif message_string_or_hash.is_a?(Hash)
16
+ self.message = message_string_or_hash.each_value { |val| val.strip! if val.respond_to? :strip! }
17
+ else
18
+ raise "Payload message argument needs to be either a hash or a string"
19
+ end
20
+ self
21
+ end
22
+
23
+
24
+ # Batch payloads
25
+ # Ex: APNS::Payload.batch(Device.all.collect{|d|d.token}, :alert => "Hello")
26
+ # or with a block
27
+ # APNS::Payload.batch(Device.all.collect{|d|d.token}, :alert => custom_big_alert) do |payload|
28
+ # payload.payload_with_truncated_alert
29
+ # end
30
+ def self.batch(device_tokens, message_string_or_hash = {})
31
+ raise unless device_tokens.is_a?(Array)
32
+ payloads = []
33
+ device_tokens.each do |device|
34
+ payload = self.new(device, message_string_or_hash)
35
+ payload = yield(payload) if block_given?
36
+ payloads << payload
37
+ end
38
+ payloads
39
+ end
40
+
41
+ #
42
+ # Handy chainable setters
43
+ #
44
+ # Ex: APNS::Payload.new(token).badge(3).sound("bipbip").alert("Roadrunner!").custom(:foo => :bar)
45
+ #
46
+ def badge(number)
47
+ message[:badge] = number
48
+ self
49
+ end
50
+
51
+ def sound(filename)
52
+ message[:sound] = filename
53
+ self
54
+ end
55
+
56
+ def alert(string)
57
+ message[:alert] = string
58
+ self
59
+ end
60
+
61
+ def custom(hash)
62
+ return nil unless hash.is_a? Hash
63
+ return nil if hash.any?{|k,v| APS_KEYS.include?(k.to_sym) || (k.to_sym == APS_ROOT)}
64
+ message.merge!(hash)
65
+ self
66
+ end
67
+
68
+
69
+ #
70
+ def to_ssl
71
+ pm = self.apn_message.to_json
72
+ [0, 0, 32, self.device.to_payload, 0, pm.size, pm].pack("ccca*cca*")
73
+ end
74
+
75
+ def size
76
+ self.to_ssl.size
77
+ end
78
+
79
+ # Validity checking only checks that the payload size is valid. We do not check the message content.
80
+ def valid?
81
+ self.size <= PAYLOAD_MAX_SIZE
82
+ end
83
+
84
+ def apn_message
85
+ message_hash = message.dup
86
+ apnm = { APS_ROOT => {} }
87
+ APS_KEYS.each do |k|
88
+ apnm[APS_ROOT][k] = message_hash.delete(k) if message_hash.has_key?(k)
89
+ end
90
+ apnm.merge!(message_hash)
91
+ apnm
92
+ end
93
+
94
+ # Returns a new payload with the alert truncated to fit in the payload size requirement (PAYLOAD_MAX_SIZE)
95
+ # Rem: It's a best effort since the alert may not be the one string responsible for the oversized payload
96
+ # also, if your alert is a Hash containing loc-* keys it won't work, in this case you should use the #payload_with_truncated_string_at_keypath
97
+ def payload_with_truncated_alert
98
+ payload_with_truncated_string_at_keypath([:alert])
99
+ end
100
+
101
+
102
+ # payload_with_truncated_string_at_keypath("alert") or payload_with_truncated_string_at_keypath([:alert])
103
+ # or
104
+ # payload_with_truncated_string_at_keypath("custom1.custom2") or payload_with_truncated_string_at_keypath([:custom1, :custom2])
105
+ # Rem: Truncation only works on String values...
106
+ def payload_with_truncated_string_at_keypath(array_or_dotted_string)
107
+ return self if valid? # You can safely call it on a valid payload
108
+ return unless message.at_key_path(array_or_dotted_string).is_a?(String)
109
+
110
+ # Rem: I'm using Marshall to make a deep copy of the message hash. Of course this would only work with "standard" values like Hash/String/Array
111
+ payload_with_empty_string = APNS::Payload.new(device.token, Marshal.load(Marshal.dump(message)).at_key_path(array_or_dotted_string){|obj, key| obj[key] = ""})
112
+ wanted_length = PAYLOAD_MAX_SIZE - payload_with_empty_string.size
113
+
114
+ # Return a new payload with truncated value
115
+ APNS::Payload.new(device.token, Marshal.load(Marshal.dump(message)).at_key_path(array_or_dotted_string) {|obj, key| obj[key] = obj[key].truncate(wanted_length) })
116
+ end
117
+
118
+ end #Payload
119
+
120
+ end #module
121
+
122
+ class Hash
123
+ def at_key_path(array_or_dotted_string, &block)
124
+ keypath = array_or_dotted_string.is_a?(Array) ? array_or_dotted_string.dup : array_or_dotted_string.split('.')
125
+ obj = self
126
+ while (keypath.count > 0) do
127
+ key = keypath.shift.to_s
128
+ key = key.to_sym if !obj.has_key?(key) && obj.has_key?(key.to_sym)
129
+ next unless keypath.count > 0 # exit the while loop
130
+ obj = obj.has_key?(key) ? obj[key] : raise("No key #{key} in Object (#{obj.inspect})")
131
+ end
132
+
133
+ raise("No key #{key} in Object (#{obj.inspect})") unless obj.has_key?(key)
134
+ if block_given?
135
+ block.call(obj, key)
136
+ return self
137
+ else
138
+ return obj[key]
139
+ end
140
+ end
141
+ end
142
+
143
+ class String
144
+ if !String.new.respond_to? :truncate
145
+ def truncate(len)
146
+ (len > 4 && length > 5) ? self[0..(len - 1) - 3] + '...' : self
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe APNS do
4
+
5
+ def valid_payload
6
+ APNS::Payload.new("a-device-token", "my message")
7
+ end
8
+
9
+ describe "send_payload" do
10
+ it "should complain about no pem file path" do
11
+ lambda{APNS.send_payloads(valid_payload)}.should raise_error(APNS::PemPathError)
12
+ end
13
+
14
+ it "should complain about pem file inexistence" do
15
+ APNS.pem = "test.pem"
16
+ lambda{APNS.send_payloads(valid_payload)}.should raise_error(APNS::PemFileError)
17
+ APNS.pem = nil # cleanup for next tests
18
+ end
19
+
20
+ end
21
+
22
+ describe "feedback" do
23
+ it "should complain about no pem file path" do
24
+ lambda{APNS.feedback()}.should raise_error(APNS::PemPathError)
25
+ end
26
+
27
+ it "should complain about pem file inexistence" do
28
+ APNS.pem = "test.pem"
29
+ lambda{APNS.feedback()}.should raise_error(APNS::PemFileError)
30
+ APNS.pem = nil # cleanup for next tests
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,16 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe APNS::Device do
4
+ before do
5
+ @device = APNS::Device.new('<5b51030d d5bad758 fbad5004 bad35c31 e4e0f550 f77f20d4 f737bf8d 3d5524c6>')
6
+ end
7
+
8
+ it "should cleanup the token string" do
9
+ @device.token.should == "5b51030dd5bad758fbad5004bad35c31e4e0f550f77f20d4f737bf8d3d5524c6"
10
+ end
11
+
12
+ it "should package the token for the payload" do
13
+ @device.to_payload.should == "[Q\003\r\325\272\327X\373\255P\004\272\323\\1\344\340\365P\367\177 \324\3677\277\215=U$\306"
14
+ end
15
+
16
+ end
@@ -0,0 +1,137 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe APNS::Payload do
4
+
5
+ def payload(message, token = "a-device-token")
6
+ APNS::Payload.new(token, message)
7
+ end
8
+ it "should take a string as the message" do
9
+ p = payload('Hello')
10
+ p.should be_valid
11
+ end
12
+
13
+ it "should take a hash as the message" do
14
+ p = payload({:alert => 'Hello iPhone', :badge => 3})
15
+ p.should be_valid
16
+ end
17
+
18
+ it "should not validate a message longer than 256 bytes" do
19
+ p = payload({:alert => 'A' * 250})
20
+ p.size.should > 256
21
+ p.should_not be_valid
22
+ end
23
+
24
+ it "should validate a message of 256 bytes or less" do
25
+ p = payload({:alert => 'A' * 224})
26
+ p.size.should == 256
27
+ p.should be_valid
28
+ end
29
+
30
+ it "should strip whitespace from payload string" do
31
+ p = payload("Hello iPhone \n")
32
+ p.should be_valid
33
+ p.apn_message.should == { :aps => { :alert => 'Hello iPhone' } }
34
+ end
35
+
36
+ it "should allow chaining of setters for badge alert and sound" do
37
+ p = APNS::Payload.new("a-device-token").badge(2).sound("bipbip.aiff").alert("hello")
38
+ p.message[:badge].should == 2
39
+ p.message[:sound].should == "bipbip.aiff"
40
+ p.message[:alert].should == "hello"
41
+ p.should be_valid
42
+ end
43
+
44
+ it "should allow truncation of alert and return a valid payload when alert is too big" do
45
+ p = payload("A"*300)
46
+ p.size.should > 256
47
+ p.should_not be_valid
48
+
49
+ tp = p.payload_with_truncated_alert
50
+ tp.should be_valid
51
+ tp.size.should == 256
52
+ end
53
+
54
+ it "should not change other fields but the alert message when truncating alert" do
55
+ p = payload({
56
+ :alert => "A" * 300,
57
+ :badge => 2,
58
+ :sound => "sound.aiff",
59
+ :custom => "my custom data"
60
+ })
61
+ p.should_not be_valid
62
+ p.size.should > 256
63
+ p.message[:badge].should == 2
64
+ p.message[:sound].should == "sound.aiff"
65
+ p.message[:custom].should == "my custom data"
66
+
67
+ pn = p.payload_with_truncated_alert
68
+ pn.should be_valid
69
+ pn.size.should == 256
70
+ pn.message[:badge].should == p.message[:badge]
71
+ pn.message[:sound].should == p.message[:sound]
72
+ pn.message[:custom].should == p.message[:custom]
73
+ end
74
+
75
+ it "should not change the alert when truncating alert and alert is not a String" do
76
+ p = payload({
77
+ :alert => {'foo' => 'bar'*80, 'baz' => 'blah'},
78
+ :badge => 2,
79
+ :sound => "sound.aiff",
80
+ :custom => "my custom data"
81
+ })
82
+ p.should_not be_valid
83
+
84
+ pn = p.payload_with_truncated_alert
85
+ pn.should be_nil
86
+ end
87
+
88
+
89
+ it "should allow truncating a custom field" do
90
+ p = payload({
91
+ :alert => "my alert",
92
+ :badge => 2,
93
+ :sound => "sound.aiff",
94
+ :custom => {:message => "A"*300}
95
+ })
96
+ p.should_not be_valid
97
+ p.size.should > 256
98
+ p.message[:alert].should == "my alert"
99
+ p.message[:badge].should == 2
100
+ p.message[:sound].should == "sound.aiff"
101
+
102
+ pn = p.payload_with_truncated_string_at_keypath("custom.message")
103
+ pn.should be_valid
104
+ pn.size.should == 256
105
+ pn.message[:badge].should == p.message[:badge]
106
+ pn.message[:sound].should == p.message[:sound]
107
+ pn.message[:alert].should == p.message[:alert]
108
+ end
109
+
110
+
111
+ describe '#packaged_message' do
112
+
113
+ it "should return JSON with payload informations" do
114
+ p = payload({:alert => 'Hello iPhone', :badge => 2, :custom => "custom-string"})
115
+ p.apn_message.should == { :aps => { :badge => 2, :alert => 'Hello iPhone' }, :custom => "custom-string" }
116
+ end
117
+
118
+ it "should return JSON with payload informations with whitespace stripped" do
119
+ p = payload({:alert => " Hello iPhone \n", :badge => 2, :custom => " custom-string \n"})
120
+ p.apn_message.should == { :aps => { :badge => 2, :alert => 'Hello iPhone' }, :custom => "custom-string" }
121
+ end
122
+
123
+ it "should not include keys that are empty in the JSON" do
124
+ p = payload({:badge => 3})
125
+ p.apn_message.should == { :aps => { :badge => 3 } }
126
+ end
127
+
128
+ end
129
+
130
+ describe '#to_ssl' do
131
+ it "should package the token and message" do
132
+ p = APNS::Payload.new('<5b51030d d5bad758 fbad5004 bad35c31 e4e0f550 f77f20d4 f737bf8d 3d5524c6>', {:alert => 'Hello iPhone'})
133
+ Base64.encode64(p.to_ssl).should == "AAAgW1EDDdW611j7rVAEutNcMeTg9VD3fyDU9ze/jT1VJMYAIHsiYXBzIjp7\nImFsZXJ0IjoiSGVsbG8gaVBob25lIn19\n"
134
+ end
135
+ end
136
+
137
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ gem 'rspec', '>= 2.0.0'
3
+ require 'rspec'
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'apns')
5
+ require 'base64'
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apns-pressplane
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.10
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - James Pozdena
9
+ - Thierry Passeron
10
+ - Pressplane Inc.
11
+ autorequire: apns
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2010-03-22 00:00:00.000000000Z
15
+ dependencies: []
16
+ description: Simple Apple push notification service gem
17
+ email: thierry.passeron@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files:
21
+ - MIT-LICENSE
22
+ files:
23
+ - .gitignore
24
+ - MIT-LICENSE
25
+ - README.textile
26
+ - Rakefile
27
+ - apns.gemspec
28
+ - lib/apns.rb
29
+ - lib/apns/core.rb
30
+ - lib/apns/device.rb
31
+ - lib/apns/payload.rb
32
+ - spec/apns/core_spec.rb
33
+ - spec/apns/device_spec.rb
34
+ - spec/apns/payload_spec.rb
35
+ - spec/spec_helper.rb
36
+ homepage: http://github.com/Orion98MC/APNS
37
+ licenses: []
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.11
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Simple Apple push notification service gem
60
+ test_files:
61
+ - spec/apns/core_spec.rb
62
+ - spec/apns/device_spec.rb
63
+ - spec/apns/payload_spec.rb
64
+ - spec/spec_helper.rb