rapns 3.0.1 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +10 -2
- data/README.md +26 -3
- data/bin/rapns +2 -7
- data/lib/generators/templates/add_gcm.rb +3 -1
- data/lib/generators/templates/rapns.rb +42 -11
- data/lib/rapns/apns/notification.rb +8 -2
- data/lib/rapns/app.rb +3 -3
- data/lib/rapns/configuration.rb +46 -13
- data/lib/rapns/daemon/apns/connection.rb +12 -9
- data/lib/rapns/daemon/apns/delivery_handler.rb +1 -1
- data/lib/rapns/daemon/apns/feedback_receiver.rb +6 -2
- data/lib/rapns/daemon/app_runner.rb +23 -7
- data/lib/rapns/daemon/delivery.rb +5 -1
- data/lib/rapns/daemon/delivery_handler.rb +4 -0
- data/lib/rapns/daemon/feeder.rb +26 -5
- data/lib/rapns/daemon/reflectable.rb +13 -0
- data/lib/rapns/daemon.rb +33 -22
- data/lib/rapns/embed.rb +28 -0
- data/lib/rapns/gcm/notification.rb +7 -2
- data/lib/rapns/gcm/payload_data_size_validator.rb +13 -0
- data/lib/rapns/gcm/registration_ids_count_validator.rb +13 -0
- data/lib/rapns/push.rb +12 -0
- data/lib/rapns/reflection.rb +44 -0
- data/lib/rapns/version.rb +1 -1
- data/lib/rapns.rb +11 -1
- data/spec/support/cert_with_password.pem +90 -0
- data/spec/support/cert_without_password.pem +59 -0
- data/spec/unit/apns/app_spec.rb +15 -1
- data/spec/unit/apns/notification_spec.rb +16 -1
- data/spec/unit/configuration_spec.rb +10 -1
- data/spec/unit/daemon/apns/connection_spec.rb +11 -2
- data/spec/unit/daemon/apns/delivery_handler_spec.rb +1 -1
- data/spec/unit/daemon/apns/delivery_spec.rb +10 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +16 -7
- data/spec/unit/daemon/delivery_handler_shared.rb +8 -0
- data/spec/unit/daemon/feeder_spec.rb +37 -6
- data/spec/unit/daemon/gcm/delivery_spec.rb +33 -1
- data/spec/unit/daemon/reflectable_spec.rb +27 -0
- data/spec/unit/daemon_spec.rb +55 -9
- data/spec/unit/embed_spec.rb +44 -0
- data/spec/unit/gcm/notification_spec.rb +9 -3
- data/spec/unit/push_spec.rb +28 -0
- data/spec/unit/reflection_spec.rb +34 -0
- data/spec/unit_spec_helper.rb +4 -62
- metadata +22 -5
- data/lib/rapns/gcm/payload_size_validator.rb +0 -13
data/lib/rapns/daemon.rb
CHANGED
@@ -5,6 +5,7 @@ require 'openssl'
|
|
5
5
|
|
6
6
|
require 'net/http/persistent'
|
7
7
|
|
8
|
+
require 'rapns/daemon/reflectable'
|
8
9
|
require 'rapns/daemon/interruptible_sleep'
|
9
10
|
require 'rapns/daemon/delivery_error'
|
10
11
|
require 'rapns/daemon/database_reconnectable'
|
@@ -37,9 +38,10 @@ module Rapns
|
|
37
38
|
def self.start
|
38
39
|
self.logger = Logger.new(:foreground => Rapns.config.foreground,
|
39
40
|
:airbrake_notify => Rapns.config.airbrake_notify)
|
40
|
-
setup_signal_hooks
|
41
41
|
|
42
|
-
|
42
|
+
setup_signal_traps if trap_signals?
|
43
|
+
|
44
|
+
if daemonize?
|
43
45
|
daemonize
|
44
46
|
reconnect_database
|
45
47
|
end
|
@@ -47,11 +49,22 @@ module Rapns
|
|
47
49
|
write_pid_file
|
48
50
|
ensure_upgraded
|
49
51
|
AppRunner.sync
|
50
|
-
Feeder.start
|
52
|
+
Feeder.start
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.shutdown(quiet = false)
|
56
|
+
puts "\nShutting down..." unless quiet
|
57
|
+
Feeder.stop
|
58
|
+
AppRunner.stop
|
59
|
+
delete_pid_file
|
51
60
|
end
|
52
61
|
|
53
62
|
protected
|
54
63
|
|
64
|
+
def self.daemonize?
|
65
|
+
!(Rapns.config.foreground || Rapns.config.embedded || Rapns.config.push || defined?(JRUBY_VERSION))
|
66
|
+
end
|
67
|
+
|
55
68
|
def self.ensure_upgraded
|
56
69
|
count = 0
|
57
70
|
|
@@ -64,7 +77,7 @@ module Rapns
|
|
64
77
|
puts "Please run 'rails g rapns' to generate the new migrations and create your app."
|
65
78
|
puts "See https://github.com/ileitch/rapns for further instructions."
|
66
79
|
puts
|
67
|
-
exit 1
|
80
|
+
exit 1 unless Rapns.config.embedded || Rapns.config.push
|
68
81
|
end
|
69
82
|
|
70
83
|
if count == 0
|
@@ -80,7 +93,11 @@ Remove config/rapns/rapns.yml to avoid this warning.
|
|
80
93
|
end
|
81
94
|
end
|
82
95
|
|
83
|
-
def self.
|
96
|
+
def self.trap_signals?
|
97
|
+
!(Rapns.config.embedded || Rapns.config.push)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.setup_signal_traps
|
84
101
|
@shutting_down = false
|
85
102
|
|
86
103
|
Signal.trap('SIGHUP') { AppRunner.sync }
|
@@ -97,13 +114,6 @@ Remove config/rapns/rapns.yml to avoid this warning.
|
|
97
114
|
shutdown
|
98
115
|
end
|
99
116
|
|
100
|
-
def self.shutdown
|
101
|
-
puts "\nShutting down..."
|
102
|
-
Feeder.stop
|
103
|
-
AppRunner.stop
|
104
|
-
delete_pid_file
|
105
|
-
end
|
106
|
-
|
107
117
|
def self.write_pid_file
|
108
118
|
if !Rapns.config.pid_file.blank?
|
109
119
|
begin
|
@@ -121,16 +131,17 @@ Remove config/rapns/rapns.yml to avoid this warning.
|
|
121
131
|
|
122
132
|
# :nocov:
|
123
133
|
def self.daemonize
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
+
if RUBY_VERSION < "1.9"
|
135
|
+
exit if fork
|
136
|
+
Process.setsid
|
137
|
+
exit if fork
|
138
|
+
Dir.chdir "/"
|
139
|
+
STDIN.reopen "/dev/null"
|
140
|
+
STDOUT.reopen "/dev/null", "a"
|
141
|
+
STDERR.reopen "/dev/null", "a"
|
142
|
+
else
|
143
|
+
Process.daemon
|
144
|
+
end
|
134
145
|
end
|
135
146
|
end
|
136
147
|
end
|
data/lib/rapns/embed.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Rapns
|
2
|
+
def self.embed(options = {})
|
3
|
+
Rapns.require_for_daemon
|
4
|
+
|
5
|
+
config = Rapns::ConfigurationWithoutDefaults.new
|
6
|
+
options.each { |k, v| config.send("#{k}=", v) }
|
7
|
+
config.embedded = true
|
8
|
+
Rapns.config.update(config)
|
9
|
+
Rapns::Daemon.start
|
10
|
+
|
11
|
+
Kernel.at_exit { shutdown }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.shutdown
|
15
|
+
return unless Rapns.config.embedded
|
16
|
+
Rapns::Daemon.shutdown
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.sync
|
20
|
+
return unless Rapns.config.embedded
|
21
|
+
Rapns::Daemon::AppRunner.sync
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.debug
|
25
|
+
return unless Rapns.config.embedded
|
26
|
+
Rapns::Daemon::AppRunner.debug
|
27
|
+
end
|
28
|
+
end
|
@@ -3,7 +3,8 @@ module Rapns
|
|
3
3
|
class Notification < Rapns::Notification
|
4
4
|
validates :registration_ids, :presence => true
|
5
5
|
validates_with Rapns::Gcm::ExpiryCollapseKeyMutualInclusionValidator
|
6
|
-
validates_with Rapns::Gcm::
|
6
|
+
validates_with Rapns::Gcm::PayloadDataSizeValidator
|
7
|
+
validates_with Rapns::Gcm::RegistrationIdsCountValidator
|
7
8
|
|
8
9
|
def registration_ids=(ids)
|
9
10
|
ids = [ids] if ids && !ids.is_a?(Array)
|
@@ -26,6 +27,10 @@ module Rapns
|
|
26
27
|
|
27
28
|
json
|
28
29
|
end
|
30
|
+
|
31
|
+
def payload_data_size
|
32
|
+
multi_json_dump(as_json['data']).bytesize
|
33
|
+
end
|
29
34
|
end
|
30
35
|
end
|
31
|
-
end
|
36
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rapns
|
2
|
+
module Gcm
|
3
|
+
class PayloadDataSizeValidator < ActiveModel::Validator
|
4
|
+
LIMIT = 4096
|
5
|
+
|
6
|
+
def validate(record)
|
7
|
+
if record.payload_data_size > LIMIT
|
8
|
+
record.errors[:base] << "GCM notification payload data cannot be larger than #{LIMIT} bytes."
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rapns
|
2
|
+
module Gcm
|
3
|
+
class RegistrationIdsCountValidator < ActiveModel::Validator
|
4
|
+
LIMIT = 1000
|
5
|
+
|
6
|
+
def validate(record)
|
7
|
+
if record.registration_ids && record.registration_ids.size > LIMIT
|
8
|
+
record.errors[:base] << "GCM notification num of registration_ids cannot be larger than #{LIMIT}."
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/rapns/push.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Rapns
|
2
|
+
def self.push(options = {})
|
3
|
+
Rapns.require_for_daemon
|
4
|
+
|
5
|
+
config = Rapns::ConfigurationWithoutDefaults.new
|
6
|
+
options.each { |k, v| config.send("#{k}=", v) }
|
7
|
+
config.push = true
|
8
|
+
Rapns.config.update(config)
|
9
|
+
Rapns::Daemon.start
|
10
|
+
Rapns::Daemon.shutdown(true)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Rapns
|
2
|
+
def self.reflect
|
3
|
+
yield reflections if block_given?
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.reflections
|
7
|
+
@reflections ||= Reflections.new
|
8
|
+
end
|
9
|
+
|
10
|
+
class Reflections
|
11
|
+
class NoSuchReflectionError < StandardError; end
|
12
|
+
|
13
|
+
REFLECTIONS = [
|
14
|
+
:apns_feedback, :notification_enqueued, :notification_delivered,
|
15
|
+
:notification_failed, :notification_will_retry, :apns_connection_lost,
|
16
|
+
:error
|
17
|
+
]
|
18
|
+
|
19
|
+
REFLECTIONS.each do |reflection|
|
20
|
+
class_eval(<<-RUBY, __FILE__, __LINE__)
|
21
|
+
def #{reflection}(*args, &blk)
|
22
|
+
raise "block required" unless block_given?
|
23
|
+
reflections[:#{reflection}] = blk
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
|
28
|
+
def __dispatch(reflection, *args)
|
29
|
+
unless REFLECTIONS.include?(reflection.to_sym)
|
30
|
+
raise NoSuchReflectionError, reflection
|
31
|
+
end
|
32
|
+
|
33
|
+
if reflections[reflection]
|
34
|
+
reflections[reflection].call(*args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def reflections
|
41
|
+
@reflections ||= {}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/rapns/version.rb
CHANGED
data/lib/rapns.rb
CHANGED
@@ -8,6 +8,9 @@ require 'rapns/multi_json_helper'
|
|
8
8
|
require 'rapns/notification'
|
9
9
|
require 'rapns/app'
|
10
10
|
require 'rapns/configuration'
|
11
|
+
require 'rapns/reflection'
|
12
|
+
require 'rapns/embed'
|
13
|
+
require 'rapns/push'
|
11
14
|
|
12
15
|
require 'rapns/apns/binary_notification_validator'
|
13
16
|
require 'rapns/apns/device_token_format_validator'
|
@@ -17,7 +20,14 @@ require 'rapns/apns/feedback'
|
|
17
20
|
require 'rapns/apns/app'
|
18
21
|
|
19
22
|
require 'rapns/gcm/expiry_collapse_key_mutual_inclusion_validator'
|
20
|
-
require 'rapns/gcm/
|
23
|
+
require 'rapns/gcm/payload_data_size_validator'
|
24
|
+
require 'rapns/gcm/registration_ids_count_validator'
|
21
25
|
require 'rapns/gcm/notification'
|
22
26
|
require 'rapns/gcm/app'
|
23
27
|
|
28
|
+
module Rapns
|
29
|
+
def self.require_for_daemon
|
30
|
+
require 'rapns/daemon'
|
31
|
+
require 'rapns/patches'
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
Bag Attributes
|
2
|
+
localKeyID: A4 1A DB 3E 3E 45 D9 C7 51 1E E6 DC 4E BC 29 19 E4 22 95 35
|
3
|
+
subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd
|
4
|
+
issuer=/C=UK/ST=Some-State/O=Internet Widgits Pty Ltd
|
5
|
+
-----BEGIN CERTIFICATE-----
|
6
|
+
MIIE/jCCAuYCAQEwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCVUsxEzARBgNV
|
7
|
+
BAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
|
8
|
+
ZDAeFw0xMjEyMjgxMzU4NTdaFw0xNDEyMjgxMzU4NTdaMEUxCzAJBgNVBAYTAkFV
|
9
|
+
MRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz
|
10
|
+
IFB0eSBMdGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDYCSfYP0P7
|
11
|
+
F+JbxUHYsl/9Plr2bFZGMBygqs5RWpw1X977p4FqcNtnRmGENQ/I5vE3KkAKCAlS
|
12
|
+
gmus7CjB9/FVzoJq65cT3wBkcqAAzPgIQuI0UjP+rLktV84eUup7Pt6f/Bmt/fBj
|
13
|
+
h3fvf80nBQcdK2ztu1xtTs7EsRgyudUDDEIUZw9ddK21RbCMYE0PFY0QapNVvevs
|
14
|
+
8lDVUJX60bZqVqzhvlTzmEaBF+E66J/DhHuhcWZV588hVKjHMNED9Aq4PxRy78vI
|
15
|
+
54v1buX2Y9C6dHfIzfu7zz0Zv5NAY1BZKpaglVGKArOrX+K6O7Bq+DwckTm1AbUf
|
16
|
+
pQqi10ghveOtVrm4GXJ4OYW8ohdYKMFjzwhgTTr/NQ+EVlcZ+8AOPVPPJk0HgPu/
|
17
|
+
eMNiLytvcSnB09OzIUAzcxOof2a1zfxn7aPzBTEC6kkoDJC/BDG8wxySUz0zRyyM
|
18
|
+
jUN2+J1mBJZX1h1sM/4ycAzsX1EYm2II5GGJaFngiV45Qv7wTC7W31kvih7FsdUX
|
19
|
+
rEYMkevB5AFPUtIvzevLUObLbPW4yWvU2CMcKLZdIaPTvtPMba91t1YOdufpPRDd
|
20
|
+
HTmT42h0aUWDSrcWDAuZPPPqEIQNjRVCudtHtobZDeaUfvbx05CvypyuNFaPonuQ
|
21
|
+
l5h0LpZWPetvfsrOlMxPm2kfVcxm8mWjzQIDAQABMA0GCSqGSIb3DQEBBQUAA4IC
|
22
|
+
AQBiwdPfLU0kjbU1hM6grUHduLqOPDqLT5gefGRrL5fDqb86G08+Wz+Tnn1YmH+7
|
23
|
+
rfiQhCrdi4zxRv6ZEaKZeEm/q1UMttuUdXuzd25BEEtav4R0POdR+H1q3trlS0ol
|
24
|
+
EcWlAbRgVaT2tTyKGAW54fH8vZPqS6IP+mXIzfaOFECPEgAO8BL8t7hBDpkL4ASO
|
25
|
+
HbU7ktYamsr6PAei3kXAnJ1thXyQqrhelwLrQJyM8RVOJYUgVbl6Fpdtgu7YQF5G
|
26
|
+
kxTAfshSmDCQkf+tbUEs44rZ6BZ2TWnbXjQGkRntHcMCP/1rjsPPdX3oeZ7P3jMQ
|
27
|
+
XER3drdm1mOiSdDUKbem9GzQ9Dx7WkwKNLYAZ+IWzVRACzGxnkxXyxOEFIgesDgg
|
28
|
+
RIhczN+eLIR8iwcUxEVKFmmsbEIve3Uh1/NE6xGudbfZDNfhyOhiNYIBnQqVkk8l
|
29
|
+
c3gw2UDR3lXTayiiXhK2l1etsyxtYncT3pgDsCe72RODrGKbASt3FzfBbalzN0GG
|
30
|
+
9tiPSNtGqCch9q4eHfViUh9s3+8n4bknAYcwzQ96+gMEn8PUVtDBv9F8Kxffn/Jt
|
31
|
+
XMWKX76nTVuAuCLkXxrKwc01lq8SeuvCH650xsv0LBvxj9h6vR34vHGrj0C3sH2E
|
32
|
+
VQpelKNv8IEwkSiQcwDtU8H0jaPJNqmYlkrxasSrSeg6PA==
|
33
|
+
-----END CERTIFICATE-----
|
34
|
+
Bag Attributes
|
35
|
+
localKeyID: A4 1A DB 3E 3E 45 D9 C7 51 1E E6 DC 4E BC 29 19 E4 22 95 35
|
36
|
+
Key Attributes: <No Attributes>
|
37
|
+
-----BEGIN RSA PRIVATE KEY-----
|
38
|
+
Proc-Type: 4,ENCRYPTED
|
39
|
+
DEK-Info: DES-EDE3-CBC,874F2C982467BF08
|
40
|
+
|
41
|
+
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
|
90
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,59 @@
|
|
1
|
+
Bag Attributes
|
2
|
+
friendlyName: test certificate
|
3
|
+
localKeyID: 00 93 8F E4 A3 C3 75 64 3D 7E EA 14 0B 0A EA DD 15 85 8A D5
|
4
|
+
subject=/CN=test certificate/O=Example/OU=Example/ST=QLD/C=AU/L=Example/emailAddress=user@example.com
|
5
|
+
issuer=/CN=test certificate/O=Example/OU=Example/ST=QLD/C=AU/L=Example/emailAddress=user@example.com
|
6
|
+
-----BEGIN CERTIFICATE-----
|
7
|
+
MIID5jCCAs6gAwIBAgIBATALBgkqhkiG9w0BAQswgY0xGTAXBgNVBAMMEHRlc3Qg
|
8
|
+
Y2VydGlmaWNhdGUxEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAsMB0V4YW1wbGUx
|
9
|
+
DDAKBgNVBAgMA1FMRDELMAkGA1UEBhMCQVUxEDAOBgNVBAcMB0V4YW1wbGUxHzAd
|
10
|
+
BgkqhkiG9w0BCQEWEHVzZXJAZXhhbXBsZS5jb20wHhcNMTIwOTA5MDMxODMyWhcN
|
11
|
+
MjIwOTA3MDMxODMyWjCBjTEZMBcGA1UEAwwQdGVzdCBjZXJ0aWZpY2F0ZTEQMA4G
|
12
|
+
A1UECgwHRXhhbXBsZTEQMA4GA1UECwwHRXhhbXBsZTEMMAoGA1UECAwDUUxEMQsw
|
13
|
+
CQYDVQQGEwJBVTEQMA4GA1UEBwwHRXhhbXBsZTEfMB0GCSqGSIb3DQEJARYQdXNl
|
14
|
+
ckBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKF+
|
15
|
+
UDsN1sLen8g+97PNTiWju9+wkSv+H5rQlvb6YFLPx11YvqpK8ms6kFU1OmWeLfmh
|
16
|
+
cpsT+bZtKupC7aGPoSG3RXzzf/YUMgs/ZSXA0idZHA6tkReAEzIX6jL5otfPWbaP
|
17
|
+
luCTUoVMeP4u9ywk628zlqh9IQHC1Agl0R1xGCpULDk8kn1gPyEisl38wI5aDbzy
|
18
|
+
6lYQGNUKOqt1xfVjtIFe/jyY/v0sxFjIJlRLcAFBuJx4sRV+PwRBkusOQtYwcwpI
|
19
|
+
loMxJj+GQe66ueATW81aC4iOU66DAFFEuGzwIwm3bOilimGGQbGb92F339RfmSOo
|
20
|
+
TPAvVhsakI3mzESb4lkCAwEAAaNRME8wDgYDVR0PAQH/BAQDAgeAMCAGA1UdJQEB
|
21
|
+
/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAbBgNVHREEFDASgRB1c2VyQGV4YW1w
|
22
|
+
bGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQA5UbNR+83ZdI2DiaB4dRmy0V5RDAqJ
|
23
|
+
k9+QskcTV4gBTjsOBS46Dw1tI6iTrfTyjYJdnyH0Y2Y2YVWBnvtON41UCZak+4ed
|
24
|
+
/IqyzU0dtfZ+frWa0RY4reyl80TwqnzyJfni0nDo4zGGvz70cxyaz2u1BWqwLjqb
|
25
|
+
dh8Dxvt+aHW2MQi0iGKh/HNbgwVanR4+ubNwziK9sR1Rnq9MkHWtwBw16SXQG6ao
|
26
|
+
SZKASWNaH8VL08Zz0E98cwd137UJkPsldCwJ8kHR5OzkcjPdXvnGD3d64yy2TC1Z
|
27
|
+
Gy1Aazt98wPcTYBytlhK8Rvzg9OoY9QmsdpmWxz1ZCXECJNqCa3IKsqO
|
28
|
+
-----END CERTIFICATE-----
|
29
|
+
Bag Attributes
|
30
|
+
friendlyName: test certificate
|
31
|
+
localKeyID: 00 93 8F E4 A3 C3 75 64 3D 7E EA 14 0B 0A EA DD 15 85 8A D5
|
32
|
+
Key Attributes: <No Attributes>
|
33
|
+
-----BEGIN RSA PRIVATE KEY-----
|
34
|
+
MIIEpQIBAAKCAQEAoX5QOw3Wwt6fyD73s81OJaO737CRK/4fmtCW9vpgUs/HXVi+
|
35
|
+
qkryazqQVTU6ZZ4t+aFymxP5tm0q6kLtoY+hIbdFfPN/9hQyCz9lJcDSJ1kcDq2R
|
36
|
+
F4ATMhfqMvmi189Zto+W4JNShUx4/i73LCTrbzOWqH0hAcLUCCXRHXEYKlQsOTyS
|
37
|
+
fWA/ISKyXfzAjloNvPLqVhAY1Qo6q3XF9WO0gV7+PJj+/SzEWMgmVEtwAUG4nHix
|
38
|
+
FX4/BEGS6w5C1jBzCkiWgzEmP4ZB7rq54BNbzVoLiI5TroMAUUS4bPAjCbds6KWK
|
39
|
+
YYZBsZv3YXff1F+ZI6hM8C9WGxqQjebMRJviWQIDAQABAoIBAQCTiLIDQUFSBdAz
|
40
|
+
QFNLD+S0vkCEuunlJuP4q1c/ir006l1YChsluBJ/o6D4NwiCjV+zDquEwVsALftm
|
41
|
+
yH4PewfZpXT2Ef508T5GyEO/mchj6iSXxDkpHvhqay6qIyWBwwxSnBtaTzy0Soi+
|
42
|
+
rmlhCtmLXbXld2sQEM1kJChGnWtWPtvSyrn+mapNPZviGRtgRNK+YsrAti1nUext
|
43
|
+
2syO5mTdHf1D8GR7I98OaX6odREuSocEV9PzfapWZx2GK5tvRiS1skiug5ciieTd
|
44
|
+
Am5/C+bb31h4drFslihLb5BRGO5SFQJvMJL2Sx1f19BCC4XikS01P4/zZbxQNq79
|
45
|
+
kxEQuDGBAoGBANP4pIYZ5xshCkx7cTYqmxzWLClGKE2S7Oa8N89mtOwfmqT9AFun
|
46
|
+
t9Us9Ukbi8BaKlKhGpQ1HlLf/KVcpyW0x2qLou6AyIWYH+/5VaR3graNgUnzpK9f
|
47
|
+
1F5HoaNHbhlAoebqhzhASFlJI2aqUdQjdOv73z+s9szJU4gpILNwGDFnAoGBAMMJ
|
48
|
+
j+vIxtG9J2jldyoXzpg5mbMXSj9u/wFLBVdjXWyOoiqVMMBto53RnoqAom7Ifr9D
|
49
|
+
49LxRAT1Q3l4vs/YnM3ziMsIg2vQK1EbrLsY9OnD/kvPaLXOlNIOdfLM8UeVWZMc
|
50
|
+
I4LPbbZrhv/7CC8RjbRhMoWWdGYPvxmvD6V4ZDY/AoGBALoI6OxA45Htx4okdNHj
|
51
|
+
RstiNNPsnQaoQn6nBhxiubraafEPkzbd1fukP4pwQJELEUX/2sHkdL6rkqLW1GPF
|
52
|
+
a5dZAiBsqpCFWNJWdBGqSfBJ9QSgbxLz+gDcwUH6OOi0zuNJRm/aCyVBiW5bYQHc
|
53
|
+
NIvAPMk31ksZDtTbs7WIVdNVAoGBALZ1+KWNxKqs+fSBT5UahpUUtfy8miJz9a7A
|
54
|
+
/3M8q0cGvSF3Rw+OwpW/aEGMi+l2OlU27ykFuyukRAac9m296RwnbF79TO2M5ylO
|
55
|
+
6a5zb5ROXlWP6RbE96b4DlIidssQJqegmHwlEC+rsrVBpOtb0aThlYEyOxzMOGyP
|
56
|
+
wOR9l8rDAoGADZ4TUHFM6VrvPlUZBkGbqiyXH9IM/y9JWk+22JQCEGnM6RFZemSs
|
57
|
+
jxWqQiPAdJtb3xKryJSCMtFPH9azedoCrSgaMflJ1QgoXgpiKZyoEXWraVUggh/0
|
58
|
+
CEavgZcTZ6SvMuayqJdGGB+zb1V8XwXMtCjApR/kTm47DjxO4DmpOPs=
|
59
|
+
-----END RSA PRIVATE KEY-----
|
data/spec/unit/apns/app_spec.rb
CHANGED
@@ -7,9 +7,23 @@ describe Rapns::App do
|
|
7
7
|
app.errors[:certificate].should == ['Certificate value must contain a certificate and a private key.']
|
8
8
|
end
|
9
9
|
|
10
|
-
it 'validates a
|
10
|
+
it 'validates a certificate without a password' do
|
11
11
|
app = Rapns::Apns::App.new :key => 'test', :environment => 'development', :certificate => TEST_CERT
|
12
12
|
app.valid?
|
13
13
|
app.errors[:certificate].should == []
|
14
14
|
end
|
15
|
+
|
16
|
+
it 'validates a certificate with a password' do
|
17
|
+
app = Rapns::Apns::App.new :key => 'test', :environment => 'development',
|
18
|
+
:certificate => TEST_CERT_WITH_PASSWORD, :password => 'fubar'
|
19
|
+
app.valid?
|
20
|
+
app.errors[:certificate].should == []
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'validates a certificate with an incorrect password' do
|
24
|
+
app = Rapns::Apns::App.new :key => 'test', :environment => 'development',
|
25
|
+
:certificate => TEST_CERT_WITH_PASSWORD, :password => 'incorrect'
|
26
|
+
app.valid?
|
27
|
+
app.errors[:certificate].should == ["Certificate value must contain a certificate and a private key."]
|
28
|
+
end
|
15
29
|
end
|
@@ -128,10 +128,25 @@ describe Rapns::Apns::Notification, 'content-available' do
|
|
128
128
|
notification.as_json['aps'].key?('content-available').should be_false
|
129
129
|
end
|
130
130
|
|
131
|
-
it 'does not include
|
131
|
+
it 'does not include content-available as a non-aps attribute' do
|
132
132
|
notification.content_available = true
|
133
133
|
notification.as_json.key?('content-available').should be_false
|
134
134
|
end
|
135
|
+
|
136
|
+
it 'does not overwrite existing attributes for the device' do
|
137
|
+
notification.data = {:hi => :mom}
|
138
|
+
notification.content_available = true
|
139
|
+
notification.as_json['aps']['content-available'].should == 1
|
140
|
+
notification.as_json['hi'].should == 'mom'
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'does not overwrite the content-available flag when setting attributes for the device' do
|
144
|
+
notification.content_available = true
|
145
|
+
notification.data = {:hi => :mom}
|
146
|
+
notification.as_json['aps']['content-available'].should == 1
|
147
|
+
notification.as_json['hi'].should == 'mom'
|
148
|
+
end
|
149
|
+
|
135
150
|
end
|
136
151
|
|
137
152
|
describe Rapns::Apns::Notification, "to_binary" do
|
@@ -15,7 +15,9 @@ describe Rapns::Configuration do
|
|
15
15
|
|
16
16
|
it 'configures a feedback callback' do
|
17
17
|
b = Proc.new {}
|
18
|
-
|
18
|
+
Rapns::Deprecation.silenced do
|
19
|
+
config.on_apns_feedback(&b)
|
20
|
+
end
|
19
21
|
config.apns_feedback_callback.should == b
|
20
22
|
end
|
21
23
|
|
@@ -35,4 +37,11 @@ describe Rapns::Configuration do
|
|
35
37
|
config.pid_file = '/tmp/rapns.pid'
|
36
38
|
config.pid_file.should == '/tmp/rapns.pid'
|
37
39
|
end
|
40
|
+
|
41
|
+
it 'does not allow foreground to be set to false if the platform is JRuby' do
|
42
|
+
config.foreground = true
|
43
|
+
stub_const('Rapns::Configuration::JRUBY_VERSION', '1.7.1')
|
44
|
+
config.foreground = false
|
45
|
+
config.foreground.should be_true
|
46
|
+
end
|
38
47
|
end
|
@@ -11,7 +11,8 @@ describe Rapns::Daemon::Apns::Connection do
|
|
11
11
|
let(:tcp_socket) { stub(:setsockopt => nil, :close => nil) }
|
12
12
|
let(:ssl_socket) { stub(:sync= => nil, :connect => nil, :close => nil, :write => nil, :flush => nil) }
|
13
13
|
let(:logger) { stub(:info => nil, :error => nil) }
|
14
|
-
let(:
|
14
|
+
let(:app) { stub(:name => 'Connection 0', :certificate => certificate, :password => password)}
|
15
|
+
let(:connection) { Rapns::Daemon::Apns::Connection.new(app, host, port) }
|
15
16
|
|
16
17
|
before do
|
17
18
|
OpenSSL::SSL::SSLContext.stub(:new => ssl_context)
|
@@ -121,6 +122,14 @@ describe Rapns::Daemon::Apns::Connection do
|
|
121
122
|
ssl_socket.stub(:write).and_raise(error_type)
|
122
123
|
end
|
123
124
|
|
125
|
+
it 'reflects the connection has been lost' do
|
126
|
+
connection.should_receive(:reflect).with(:apns_connection_lost, app, kind_of(error_type))
|
127
|
+
begin
|
128
|
+
connection.write(nil)
|
129
|
+
rescue Rapns::Daemon::Apns::ConnectionError
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
124
133
|
it "logs that the connection has been lost once only" do
|
125
134
|
logger.should_receive(:error).with("[Connection 0] Lost connection to gateway.push.apple.com:2195 (#{error_type.name}), reconnecting...").once
|
126
135
|
begin
|
@@ -231,4 +240,4 @@ describe Rapns::Daemon::Apns::Connection do
|
|
231
240
|
connection.write('blah')
|
232
241
|
end
|
233
242
|
end
|
234
|
-
end
|
243
|
+
end
|
@@ -23,7 +23,7 @@ describe Rapns::Daemon::Apns::DeliveryHandler do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
it "instantiates a new connection" do
|
26
|
-
Rapns::Daemon::Apns::Connection.should_receive(:new).with(app
|
26
|
+
Rapns::Daemon::Apns::Connection.should_receive(:new).with(app, host, port)
|
27
27
|
Rapns::Daemon::Apns::DeliveryHandler.new(app, host, port)
|
28
28
|
end
|
29
29
|
|
@@ -37,6 +37,11 @@ describe Rapns::Daemon::Apns::Delivery do
|
|
37
37
|
perform
|
38
38
|
end
|
39
39
|
|
40
|
+
it 'reflects the notification was delivered' do
|
41
|
+
delivery.should_receive(:reflect).with(:notification_delivered, notification)
|
42
|
+
perform
|
43
|
+
end
|
44
|
+
|
40
45
|
it "sets the time the notification was delivered" do
|
41
46
|
now = Time.now
|
42
47
|
Time.stub(:now).and_return(now)
|
@@ -83,6 +88,11 @@ describe Rapns::Daemon::Apns::Delivery do
|
|
83
88
|
perform
|
84
89
|
end
|
85
90
|
|
91
|
+
it 'reflects the notification delivery failed' do
|
92
|
+
delivery.should_receive(:reflect).with(:notification_failed, notification)
|
93
|
+
perform
|
94
|
+
end
|
95
|
+
|
86
96
|
it "sets the notification failed_at timestamp" do
|
87
97
|
now = Time.now
|
88
98
|
Time.stub(:now).and_return(now)
|
@@ -10,12 +10,13 @@ describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
|
10
10
|
let(:connection) { stub(:connect => nil, :read => nil, :close => nil) }
|
11
11
|
let(:logger) { stub(:error => nil, :info => nil) }
|
12
12
|
let(:receiver) { Rapns::Daemon::Apns::FeedbackReceiver.new(app, host, port, poll) }
|
13
|
+
let(:feedback) { stub }
|
13
14
|
|
14
15
|
before do
|
15
16
|
receiver.stub(:interruptible_sleep)
|
16
17
|
Rapns::Daemon.logger = logger
|
17
18
|
Rapns::Daemon::Apns::Connection.stub(:new => connection)
|
18
|
-
Rapns::Apns::Feedback.stub(:create!)
|
19
|
+
Rapns::Apns::Feedback.stub(:create! => feedback)
|
19
20
|
receiver.instance_variable_set("@stop", false)
|
20
21
|
end
|
21
22
|
|
@@ -31,7 +32,7 @@ describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
|
31
32
|
end
|
32
33
|
|
33
34
|
it 'instantiates a new connection' do
|
34
|
-
Rapns::Daemon::Apns::Connection.should_receive(:new).with(
|
35
|
+
Rapns::Daemon::Apns::Connection.should_receive(:new).with(app, host, port)
|
35
36
|
receiver.check_for_feedback
|
36
37
|
end
|
37
38
|
|
@@ -52,7 +53,7 @@ describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
|
52
53
|
|
53
54
|
it 'logs the feedback' do
|
54
55
|
stub_connection_read_with_tuple
|
55
|
-
Rapns::Daemon.logger.should_receive(:info).with("[
|
56
|
+
Rapns::Daemon.logger.should_receive(:info).with("[my_app] [FeedbackReceiver] Delivery failed at 2011-12-10 16:08:45 UTC for 834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17.")
|
56
57
|
receiver.check_for_feedback
|
57
58
|
end
|
58
59
|
|
@@ -90,11 +91,15 @@ describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
|
90
91
|
receiver.stop
|
91
92
|
end
|
92
93
|
|
94
|
+
it 'reflects feedback was received' do
|
95
|
+
stub_connection_read_with_tuple
|
96
|
+
receiver.should_receive(:reflect).with(:apns_feedback, feedback)
|
97
|
+
receiver.check_for_feedback
|
98
|
+
end
|
99
|
+
|
93
100
|
it 'calls the apns_feedback_callback when feedback is received and the callback is set' do
|
94
101
|
stub_connection_read_with_tuple
|
95
102
|
Rapns.config.apns_feedback_callback = Proc.new {}
|
96
|
-
feedback = Object.new
|
97
|
-
Rapns::Apns::Feedback.stub(:create! => feedback)
|
98
103
|
Rapns.config.apns_feedback_callback.should_receive(:call).with(feedback)
|
99
104
|
receiver.check_for_feedback
|
100
105
|
end
|
@@ -103,7 +108,9 @@ describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
|
103
108
|
error = StandardError.new('bork!')
|
104
109
|
stub_connection_read_with_tuple
|
105
110
|
callback = Proc.new { raise error }
|
106
|
-
Rapns.
|
111
|
+
Rapns::Deprecation.silenced do
|
112
|
+
Rapns.config.on_apns_feedback &callback
|
113
|
+
end
|
107
114
|
expect { receiver.check_for_feedback }.not_to raise_error
|
108
115
|
end
|
109
116
|
|
@@ -112,7 +119,9 @@ describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
|
112
119
|
stub_connection_read_with_tuple
|
113
120
|
callback = Proc.new { raise error }
|
114
121
|
Rapns::Daemon.logger.should_receive(:error).with(error)
|
115
|
-
Rapns.
|
122
|
+
Rapns::Deprecation.silenced do
|
123
|
+
Rapns.config.on_apns_feedback &callback
|
124
|
+
end
|
116
125
|
receiver.check_for_feedback
|
117
126
|
end
|
118
127
|
end
|
@@ -8,6 +8,14 @@ shared_examples_for 'an DeliveryHandler subclass' do
|
|
8
8
|
delivery_handler.send(:handle_next_notification)
|
9
9
|
end
|
10
10
|
|
11
|
+
it 'reflects an exception' do
|
12
|
+
Rapns::Daemon.stub(:logger => stub(:error => nil))
|
13
|
+
error = StandardError.new
|
14
|
+
delivery_handler.stub(:deliver).and_raise(error)
|
15
|
+
delivery_handler.should_receive(:reflect).with(:error, error)
|
16
|
+
delivery_handler.send(:handle_next_notification)
|
17
|
+
end
|
18
|
+
|
11
19
|
it "instructs the queue to wakeup the thread when told to stop" do
|
12
20
|
thread = stub(:join => nil)
|
13
21
|
Thread.stub(:new => thread)
|