apns-pressplane 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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