resque-aps 0.9.9 → 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.
- data/lib/resque_aps/application.rb +142 -131
- data/lib/resque_aps/feedback.rb +66 -66
- data/lib/resque_aps/notification.rb +178 -172
- data/lib/resque_aps/version.rb +1 -1
- data/lib/resque_aps.rb +135 -115
- metadata +25 -46
|
@@ -3,166 +3,177 @@ require 'openssl'
|
|
|
3
3
|
module Resque
|
|
4
4
|
module Plugins
|
|
5
5
|
module Aps
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
class Application
|
|
7
|
+
include Resque::Plugins::Aps::Helper
|
|
8
|
+
extend Resque::Plugins::Aps::Helper
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
attr_accessor :name, :cert_file, :cert_passwd
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
@queue = "apple_push_service"
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
def inspect
|
|
15
|
+
"#<#{self.class.name} #{name.inspect}, #{cert_passwd.inspect}, #{cert_file.inspect}>"
|
|
16
|
+
end
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
def self.perform(*args)
|
|
19
|
+
count = 0
|
|
20
|
+
start = Time.now
|
|
21
|
+
app_name = args[0]
|
|
22
|
+
Resque.aps_application(app_name).socket do |socket, app|
|
|
23
|
+
while true
|
|
24
|
+
n = Resque.dequeue_aps(app_name)
|
|
25
|
+
if n.nil?
|
|
26
|
+
if app.aps_nil_notification_retry? count, start
|
|
27
|
+
next
|
|
28
|
+
else
|
|
29
|
+
break
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
app.before_aps_write n
|
|
34
|
+
begin
|
|
35
|
+
n.batch_id = count + 1
|
|
36
|
+
n.expiry = (Time.now.utc + 1.hour).to_i
|
|
37
|
+
socket.write(n.formatted)
|
|
38
|
+
app.after_aps_write n
|
|
39
|
+
count += 1
|
|
40
|
+
unless (resp = socket.read).blank?
|
|
41
|
+
logger.error "Failure response: #{resp.inspect}" if logger
|
|
42
|
+
break
|
|
43
|
+
end
|
|
44
|
+
rescue
|
|
45
|
+
logger.error Application.application_exception($!, app_name) if logger
|
|
46
|
+
app.failed_aps_write n, $!
|
|
47
|
+
logger.error "Sent #{count} notifications before failure." if logger
|
|
48
|
+
redis.rpush(aps_application_queue_key(app_name), encode(n.to_hash))
|
|
49
|
+
break
|
|
50
|
+
end
|
|
30
51
|
end
|
|
31
52
|
end
|
|
53
|
+
logger.info("Sent #{count} #{app_name} notifications in batch over #{Time.now - start} sec.") if logger
|
|
54
|
+
ensure
|
|
55
|
+
Resque.dequeue_aps_application(app_name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# Create the TCP and SSL sockets for sending the notification
|
|
60
|
+
#
|
|
61
|
+
def self.create_sockets(cert, passphrase, host, port)
|
|
62
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
63
|
+
ctx.key = OpenSSL::PKey::RSA.new(cert, passphrase)
|
|
64
|
+
ctx.cert = OpenSSL::X509::Certificate.new(cert)
|
|
65
|
+
|
|
66
|
+
s = TCPSocket.new(host, port)
|
|
67
|
+
ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
|
|
68
|
+
ssl.sync = true
|
|
69
|
+
|
|
70
|
+
return s, ssl
|
|
71
|
+
end
|
|
32
72
|
|
|
33
|
-
|
|
73
|
+
#
|
|
74
|
+
# Close the sockets
|
|
75
|
+
#
|
|
76
|
+
def self.close_sockets(socket, ssl_socket)
|
|
34
77
|
begin
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
78
|
+
if ssl_socket
|
|
79
|
+
ssl_socket.close
|
|
80
|
+
end
|
|
38
81
|
rescue
|
|
39
|
-
logger.error
|
|
40
|
-
app.failed_aps_write n, $!
|
|
82
|
+
Resque.logger.error("#{$!}: #{$!.backtrace.join("\n")}") if Resque.logger
|
|
41
83
|
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
logger.info("Sent #{count} #{app_name} notifications in batch over #{Time.now - start} sec.") if logger
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
# Create the TCP and SSL sockets for sending the notification
|
|
49
|
-
#
|
|
50
|
-
def self.create_sockets(cert, passphrase, host, port)
|
|
51
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
|
52
|
-
ctx.key = OpenSSL::PKey::RSA.new(cert, passphrase)
|
|
53
|
-
ctx.cert = OpenSSL::X509::Certificate.new(cert)
|
|
54
|
-
|
|
55
|
-
s = TCPSocket.new(host, port)
|
|
56
|
-
ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
|
|
57
|
-
ssl.sync = true
|
|
58
|
-
|
|
59
|
-
return s, ssl
|
|
60
|
-
end
|
|
61
84
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
begin
|
|
86
|
+
if socket
|
|
87
|
+
socket.close
|
|
88
|
+
end
|
|
89
|
+
rescue
|
|
90
|
+
Resque.logger.error("#{$!}: #{$!.backtrace.join("\n")}") if Resque.logger
|
|
91
|
+
end
|
|
69
92
|
end
|
|
70
|
-
rescue
|
|
71
|
-
Resque.logger.error("#{$!}: #{$!.backtrace.join("\n")}") if Resque.logger
|
|
72
|
-
end
|
|
73
93
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
94
|
+
def self.application_exception(exception, name)
|
|
95
|
+
exc = Exception.new("#{exception} (#{name})")
|
|
96
|
+
exc.set_backtrace(exception.backtrace)
|
|
97
|
+
return exc
|
|
77
98
|
end
|
|
78
|
-
rescue
|
|
79
|
-
Resque.logger.error("#{$!}: #{$!.backtrace.join("\n")}") if Resque.logger
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def self.application_exception(exception, name)
|
|
84
|
-
exc = Exception.new("#{exception} (#{name})")
|
|
85
|
-
exc.set_backtrace(exception.backtrace)
|
|
86
|
-
return exc
|
|
87
|
-
end
|
|
88
99
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
def initialize(attributes)
|
|
101
|
+
attributes.each do |k, v|
|
|
102
|
+
respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
def socket(cert = nil, certp = nil, host = nil, port = nil, &block)
|
|
107
|
+
logger.debug("resque-aps: ssl_socket(#{name})") if logger
|
|
108
|
+
exc = nil
|
|
109
|
+
|
|
110
|
+
begin
|
|
111
|
+
socket, ssl_socket = Application.create_sockets(cert || File.read(cert_file),
|
|
112
|
+
certp || cert_passwd,
|
|
113
|
+
host || Resque.aps_gateway_host,
|
|
114
|
+
port || Resque.aps_gateway_port)
|
|
115
|
+
rescue
|
|
116
|
+
raise Application.application_exception($!, name)
|
|
117
|
+
end
|
|
107
118
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
begin
|
|
120
|
+
ssl_socket.connect
|
|
121
|
+
yield ssl_socket, self if block_given?
|
|
122
|
+
rescue
|
|
123
|
+
exc = Application.application_exception($!, name)
|
|
124
|
+
if $!.message =~ /^SSL_connect .* certificate (expired|revoked)/
|
|
125
|
+
notify_aps_admin exc
|
|
126
|
+
end
|
|
127
|
+
raise exc
|
|
128
|
+
ensure
|
|
129
|
+
Application.close_sockets(socket, ssl_socket)
|
|
130
|
+
end
|
|
120
131
|
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
exc
|
|
133
|
+
end
|
|
123
134
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
def to_hash
|
|
136
|
+
{'name' => name, 'cert_file' => cert_file, 'cert_passwd' => cert_passwd}
|
|
137
|
+
end
|
|
127
138
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
def to_json
|
|
140
|
+
to_hash.to_json
|
|
141
|
+
end
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
def before_aps_write(notification)
|
|
144
|
+
logger.debug("ResqueAps[before_write]: #{notification}") if logger
|
|
145
|
+
end
|
|
135
146
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
def after_aps_write(notification)
|
|
148
|
+
logger.debug("ResqueAps[after_write]: #{notification}") if logger
|
|
149
|
+
end
|
|
139
150
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
def failed_aps_write(notification, exception)
|
|
152
|
+
logger.error("ResqueAps[write_failed]: #{exception} (#{notification}): #{exception.backtrace.join("\n")}") if logger
|
|
153
|
+
end
|
|
143
154
|
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
def notify_aps_admin(exception)
|
|
156
|
+
end
|
|
146
157
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
def aps_nil_notification_retry?(sent_count, start_time)
|
|
159
|
+
false
|
|
160
|
+
end
|
|
150
161
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
def before_aps_read
|
|
163
|
+
logger.debug("ResqueAps[before_read]:") if logger
|
|
164
|
+
end
|
|
154
165
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
def after_aps_read(feedback)
|
|
167
|
+
logger.debug("ResqueAps[after_read]: #{feedback.to_s}") if logger
|
|
168
|
+
end
|
|
158
169
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
170
|
+
def aps_read_error(exception)
|
|
171
|
+
logger.error("ResqueAps[read_error]: #{exception} (#{name}): #{exception.backtrace.join("\n")}") if logger
|
|
172
|
+
end
|
|
162
173
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
def aps_read_failed
|
|
175
|
+
logger.error("ResqueAps[read_failed]: Bad data on the socket (#{name})") if logger
|
|
176
|
+
end
|
|
166
177
|
|
|
167
178
|
end
|
|
168
179
|
end
|
data/lib/resque_aps/feedback.rb
CHANGED
|
@@ -3,90 +3,90 @@ require 'timeout'
|
|
|
3
3
|
module Resque
|
|
4
4
|
module Plugins
|
|
5
5
|
module Aps
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
class Feedback
|
|
7
|
+
include Resque::Plugins::Aps::Helper
|
|
8
|
+
extend Resque::Plugins::Aps::Helper
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
@queue = "apple_push_service"
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
attr_accessor :application_name, :device_token, :received_at
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
def initialize(attributes)
|
|
15
|
+
attributes.each do |k, v|
|
|
16
|
+
respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
def inspect
|
|
21
|
+
"#<#{self.class.name} #{application_name.inspect}, #{device_token.inspect}, #{received_at.inspect}>"
|
|
22
|
+
end
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
def to_s
|
|
25
|
+
"#{application_name} #{received_at} #{device_token}"
|
|
26
|
+
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
def to_hash
|
|
29
|
+
{:application_name => application_name, :device_token => device_token, :received_at => received_at}
|
|
30
|
+
end
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
def self.read_feedback(ssl_socket, application_name, product_id)
|
|
33
|
+
data_str = ssl_socket.read(4)
|
|
34
|
+
return nil unless data_str
|
|
35
|
+
data_ary = data_str.unpack('N')
|
|
36
|
+
return nil unless data_ary && data_ary[0]
|
|
37
|
+
time = Time.at(data_ary[0])
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
data_str = ssl_socket.read(2)
|
|
40
|
+
return nil unless data_str
|
|
41
|
+
data_ary = data_str.unpack('n')
|
|
42
|
+
return nil unless data_ary && data_ary[0]
|
|
43
|
+
tl = data_ary[0]
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
data_str = ssl_socket.read(tl)
|
|
46
|
+
return nil unless data_str
|
|
47
|
+
data_ary = data_str.unpack('H*')
|
|
48
|
+
return nil unless data_ary && data_ary[0]
|
|
49
|
+
token = data_ary[0]
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
feedback = Feedback.new({:received_at => time, :device_token => token, :application_name => application_name})
|
|
52
|
+
return feedback
|
|
53
|
+
end
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
#
|
|
56
|
+
# Perform a Feedback check on the APN server, for the given app key (which must be the first argument)
|
|
57
|
+
#
|
|
58
|
+
def self.perform(*args)
|
|
59
|
+
app_name = args[0]
|
|
60
|
+
start = Time.now
|
|
61
|
+
count = 0
|
|
62
|
+
appl = Resque.aps_application(app_name)
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
return unless appl
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
66
|
+
appl.socket(nil, nil, Resque.aps_feedback_host, Resque.aps_feedback_port) do |socket, app|
|
|
67
|
+
begin
|
|
68
|
+
logger.debug("Feedback: Reading feedbacks for #{app_name}.") if logger
|
|
69
|
+
timeout(5) do
|
|
70
|
+
until socket.eof?
|
|
71
|
+
app.before_aps_read
|
|
72
|
+
feedback = read_feedback(socket, app_name, product_id)
|
|
73
|
+
if feedback
|
|
74
|
+
count += 1
|
|
75
|
+
app.after_aps_read(feedback)
|
|
76
|
+
else
|
|
77
|
+
app.aps_read_failed
|
|
78
|
+
end
|
|
79
|
+
end
|
|
78
80
|
end
|
|
81
|
+
rescue
|
|
82
|
+
logger.error Application.application_exception($!, app_name) if logger
|
|
83
|
+
app.aps_read_error(Application.application_exception($!, app_name))
|
|
79
84
|
end
|
|
80
85
|
end
|
|
81
|
-
|
|
82
|
-
logger.error Application.application_exception($!, app_name) if logger
|
|
83
|
-
app.aps_read_error(Application.application_exception($!, app_name))
|
|
86
|
+
logger.info("Read #{count} #{app_name} feedbacks over #{Time.now - start} sec.") if logger
|
|
84
87
|
end
|
|
88
|
+
|
|
85
89
|
end
|
|
86
|
-
logger.info("Read #{count} #{app_name} feedbacks over #{Time.now - start} sec.") if logger
|
|
87
90
|
end
|
|
88
|
-
|
|
89
|
-
end
|
|
90
91
|
end
|
|
91
|
-
end
|
|
92
92
|
end
|
|
@@ -1,197 +1,203 @@
|
|
|
1
1
|
module Resque
|
|
2
2
|
module Plugins
|
|
3
3
|
module Aps
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
class Notification
|
|
5
|
+
include Resque::Plugins::Aps::Helper
|
|
6
|
+
extend Resque::Plugins::Aps::Helper
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
attr_accessor :application_name, :device_token, :payload, :batch_id, :expiry
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
def initialize(attributes)
|
|
11
|
+
attributes.each do |k, v|
|
|
12
|
+
respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(Resque::Plugins::Aps::UnknownAttributeError, "unknown attribute: #{k}")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
def inspect
|
|
17
|
+
"#<#{self.class.name} #{application_name.inspect}, #{device_token.inspect}, #{payload.inspect}>"
|
|
18
|
+
end
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
def to_s
|
|
21
|
+
"#{device_token.inspect}, #{payload.inspect}"
|
|
22
|
+
end
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
def to_hash
|
|
25
|
+
{:application_name => application_name, :device_token => device_token, :payload => payload}
|
|
26
|
+
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
# SSL Configuration
|
|
29
|
+
# open Keychain Access, and export the "Apple Development Push" certificate associated with your app in p12 format
|
|
30
|
+
# Convert the certificate to PEM using openssl:
|
|
31
|
+
# openssl pkcs12 -in mycert.p12 -out client-cert.pem -nodes -clcerts
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
# To use with cross application push
|
|
34
|
+
# Notification.new(:user => user, :current_product => product, :cross_app => { :capabilities => 'cross_app', :payload => '{aps.....}' })
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
36
|
+
#
|
|
37
|
+
# https://developer.apple.com/iphone/prerelease/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1
|
|
38
|
+
#
|
|
39
|
+
# Table 2-1 Keys and values of the aps dictionary
|
|
40
|
+
# alert | string or dictionary
|
|
41
|
+
# => If this property is included, iPhone OS displays a standard alert. You may specify a string as the value of alert or a dictionary as its value.
|
|
42
|
+
# => If you specify a string, it becomes the message text of an alert with two buttons: Close and View. If the user taps View, the application is launched.
|
|
43
|
+
# => Alternatively, you can specify a dictionary as the value of alert. See Table 2-2 for descriptions of the keys of this dictionary.
|
|
44
|
+
#
|
|
45
|
+
# badge | number
|
|
46
|
+
# => The number to display as the badge of the application icon. If this property is absent, any badge number currently shown is removed.
|
|
47
|
+
#
|
|
48
|
+
# sound | string
|
|
49
|
+
# => The name of a sound file in the application bundle. The sound in this file is played as an alert.
|
|
50
|
+
# => If the sound file doesn’t exist or default is specified as the value, the default alert sound is played.
|
|
51
|
+
# => The audio must be in one of the audio data formats that are compatible with system sounds; see “Preparing Custom Alert Sounds” for details.
|
|
52
|
+
#
|
|
53
|
+
# Table 2-2 Child properties of the alert property
|
|
54
|
+
# body | string
|
|
55
|
+
# => The text of the alert message.
|
|
56
|
+
#
|
|
57
|
+
# action-loc-key | string or null
|
|
58
|
+
# => If a string is specified, displays an alert with two buttons, whose behavior is described in Table 2-1.
|
|
59
|
+
# => However, iPhone OS uses the string as a key to get a localized string in the current localization to use for the right button’s title instead of “View”.
|
|
60
|
+
# => If the value is null, the system displays an alert with a single OK button that simply dismisses the alert when tapped.
|
|
61
|
+
#
|
|
62
|
+
# loc-key | string
|
|
63
|
+
# => A key to an alert-message string in a Localizable.strings file for the current localization (which is set by the user’s language preference).
|
|
64
|
+
# => The key string can be formatted with %@ and %n$@ specifiers to take the variables specified in loc-args. See “Localized Formatted Strings” for more information.
|
|
65
|
+
#
|
|
66
|
+
# loc-args | array of strings
|
|
67
|
+
# => Variable string values to appear in place of the format specifiers in loc-key. See “Localized Formatted Strings” for more information.
|
|
68
|
+
#
|
|
69
|
+
#
|
|
70
|
+
# Example Result:
|
|
71
|
+
# {
|
|
72
|
+
# "aps" : {
|
|
73
|
+
# "alert" : "You got your emails.",
|
|
74
|
+
# "badge" : 9,
|
|
75
|
+
# "sound" : "bingbong.aiff"
|
|
76
|
+
# },
|
|
77
|
+
# "acme1" : "bar",
|
|
78
|
+
# "acme2" : 42
|
|
79
|
+
#}
|
|
80
|
+
# Or
|
|
81
|
+
# {
|
|
82
|
+
# "aps" : {
|
|
83
|
+
# "alert" : {
|
|
84
|
+
# "action-loc-key" : "PLAY",
|
|
85
|
+
# "loc-key" : "SERVER.ERROR"
|
|
86
|
+
# "loc-args" : ["bob", "sierra"]
|
|
87
|
+
# },
|
|
88
|
+
# "badge" : 9,
|
|
89
|
+
# "sound" : "bingbong.aiff"
|
|
90
|
+
# },
|
|
91
|
+
# "acme1" : "bar",
|
|
92
|
+
# "acme2" : 42
|
|
93
|
+
#}
|
|
94
|
+
def self.to_payload(alert = nil, badge = nil, sound = nil, app_data = nil)
|
|
95
|
+
result = ActiveSupport::OrderedHash.new
|
|
96
|
+
result['aps'] = ActiveSupport::OrderedHash.new
|
|
97
|
+
result['aps']['alert'] = self.format_alert(alert) unless alert.blank?
|
|
98
|
+
result['aps']['badge'] = badge.to_i unless badge.blank?
|
|
99
|
+
result['aps']['sound'] = sound unless sound.blank?
|
|
100
|
+
result.merge!(app_data) unless app_data.blank?
|
|
101
|
+
self.to_json(result)
|
|
102
|
+
end
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
104
|
+
#
|
|
105
|
+
# Create an ordered hash of the data in the given alert hash
|
|
106
|
+
#
|
|
107
|
+
def self.format_alert(alert)
|
|
108
|
+
if alert.is_a? Hash
|
|
109
|
+
result = ActiveSupport::OrderedHash.new
|
|
110
|
+
result['action-loc-key'] = alert['action-loc-key'] unless alert['action-loc-key'].blank?
|
|
111
|
+
result['loc-key'] = alert['loc-key'] unless alert['loc-key'].blank?
|
|
112
|
+
unless alert['loc-args'].blank?
|
|
113
|
+
if alert['loc-args'].is_a? Hash
|
|
114
|
+
result['loc-args'] = Array.new(alert['loc-args'].size)
|
|
115
|
+
alert['loc-args'].map do |key,value|
|
|
116
|
+
result['loc-args'][key.to_i] = value
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
result['loc-args'] = alert['loc-args']
|
|
120
|
+
end
|
|
117
121
|
end
|
|
122
|
+
return result
|
|
118
123
|
else
|
|
119
|
-
|
|
124
|
+
return alert
|
|
120
125
|
end
|
|
121
126
|
end
|
|
122
|
-
return result
|
|
123
|
-
else
|
|
124
|
-
return alert
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
#
|
|
129
|
+
# Generate a JSON string from the given Hash/Array which does not screw up the ordering of the Hash
|
|
130
|
+
#
|
|
131
|
+
def self.to_json(hash)
|
|
132
|
+
if hash.is_a? Hash
|
|
133
|
+
hash_keys = hash.keys
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
result = '{'
|
|
136
|
+
result << hash_keys.map do |key|
|
|
137
|
+
if hash[key].is_a?(Hash) || hash[key].is_a?(Array)
|
|
138
|
+
"#{key.to_s.to_json}:#{to_json(hash[key])}"
|
|
139
|
+
else
|
|
140
|
+
"#{key.to_s.to_json}:#{hash[key].to_json}"
|
|
141
|
+
end
|
|
142
|
+
end * ','
|
|
143
|
+
result << '}'
|
|
144
|
+
elsif hash.is_a? Array
|
|
145
|
+
result = '['
|
|
146
|
+
result << hash.map do |value|
|
|
147
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
|
148
|
+
"#{to_json(value)}"
|
|
149
|
+
else
|
|
150
|
+
value.to_json
|
|
151
|
+
end
|
|
152
|
+
end * ','
|
|
153
|
+
result << ']'
|
|
141
154
|
end
|
|
142
|
-
end
|
|
143
|
-
result << '}'
|
|
144
|
-
elsif hash.is_a? Array
|
|
145
|
-
result = '['
|
|
146
|
-
result << hash.map do |value|
|
|
147
|
-
if value.is_a?(Hash) || value.is_a?(Array)
|
|
148
|
-
"#{to_json(value)}"
|
|
149
|
-
else
|
|
150
|
-
value.to_json
|
|
151
|
-
end
|
|
152
|
-
end * ','
|
|
153
|
-
result << ']'
|
|
154
|
-
end
|
|
155
|
-
end
|
|
155
|
+
end
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
#
|
|
158
|
+
# The message formatted for sending in binary
|
|
159
|
+
#
|
|
160
|
+
def formatted
|
|
161
|
+
Resque::Plugins::Aps::Notification.format_message_for_sending(self.device_token, self.payload, self.batch_id, self.expiry)
|
|
162
|
+
end
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
#
|
|
165
|
+
# A HEX dump of the formatted message so that you can debug the binary data
|
|
166
|
+
#
|
|
167
|
+
def to_hex
|
|
168
|
+
formatted.unpack('H*')
|
|
169
|
+
end
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
#
|
|
172
|
+
# HEX version of the device token
|
|
173
|
+
#
|
|
174
|
+
def self.device_token_hex(device_token)
|
|
175
|
+
#self.device_token
|
|
176
|
+
[device_token.gsub(' ', '')].pack('H*')
|
|
177
|
+
end
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
179
|
+
#
|
|
180
|
+
# Combine the device token and JSON into a binary package to send.
|
|
181
|
+
#
|
|
182
|
+
def self.format_message_for_sending(device_token, json, batch_id = nil, expiry = nil)
|
|
183
|
+
token_hex = self.device_token_hex(device_token)
|
|
184
|
+
tl = [token_hex.length].pack('n')
|
|
185
|
+
# puts("token length [#{tl.unpack('H*')}]")
|
|
186
|
+
# puts("device token [#{token_hex.unpack('H*')}]")
|
|
187
|
+
# logger.debug "Formatting #{json} for #{self.device_token}"
|
|
188
|
+
jl = [json.length].pack('n')
|
|
189
|
+
# puts("json length [#{jl.unpack('H*')}]")
|
|
190
|
+
# puts("json [#{json}]")
|
|
191
|
+
unless batch_id
|
|
192
|
+
"\0#{tl}#{token_hex}#{jl}#{json}"
|
|
193
|
+
# "\0\0 #{token_hex}\0#{json.length.chr}#{json}"
|
|
194
|
+
else
|
|
195
|
+
bid = [batch_id].pack('N')
|
|
196
|
+
exp = [expiry].pack('N')
|
|
197
|
+
"\1#{bid}#{exp}#{tl}#{token_hex}#{jl}#{json}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
195
202
|
end
|
|
196
|
-
end
|
|
197
203
|
end
|
data/lib/resque_aps/version.rb
CHANGED
data/lib/resque_aps.rb
CHANGED
|
@@ -14,126 +14,146 @@ module Resque
|
|
|
14
14
|
module Plugins
|
|
15
15
|
module Aps
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
17
|
+
def logger=(logger)
|
|
18
|
+
@logger = logger
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def logger
|
|
22
|
+
unless @logger
|
|
23
|
+
@logger = Logger.new(STDOUT)
|
|
24
|
+
@logger.level = Logger::WARN
|
|
25
|
+
end
|
|
26
|
+
@logger
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def aps_gateway_host=(host)
|
|
30
|
+
@aps_gateway_host = host
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def aps_gateway_host
|
|
34
|
+
@aps_gateway_host ||= "gateway.sandbox.push.apple.com"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def aps_gateway_port=(port)
|
|
38
|
+
@aps_gateway_port = port
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def aps_gateway_port
|
|
42
|
+
@aps_gateway_port ||= 2195
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def aps_feedback_host=(host)
|
|
46
|
+
@aps_feedback_host = host
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def aps_feedback_host
|
|
50
|
+
@aps_feedback_host ||= "feedback.sandbox.push.apple.com"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def aps_feedback_port=(port)
|
|
54
|
+
@aps_feedback_port = port
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def aps_feedback_port
|
|
58
|
+
@aps_feedback_port ||= 2196
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def aps_queue_size_upper=(size)
|
|
62
|
+
@aps_queue_size_upper = size
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def aps_queue_size_upper
|
|
66
|
+
@aps_queue_size_upper ||= 1000
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def aps_application_job_limit=(size)
|
|
70
|
+
@aps_queue_size_upper = size
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def aps_application_job_limit
|
|
74
|
+
@aps_application_job_limit ||= 5
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def aps_applications_queued_count(application_name)
|
|
78
|
+
redis.get(aps_application_queued_key(application_name)) || 0
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def enqueue_aps_application(application_name, override = false)
|
|
82
|
+
count_apps = aps_applications_queued_count(application_name)
|
|
83
|
+
count_not = aps_notification_count_for_application(application_name)
|
|
84
|
+
if override || count_apps <= 0 || (count_apps < aps_application_job_limit && (count_not > aps_queue_size_upper && count_not % (aps_queue_size_upper / 10) == 0))
|
|
85
|
+
enqueue(Resque::Plugins::Aps::Application, application_name)
|
|
86
|
+
redis.incr(aps_application_queued_key(application_name))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def dequeue_aps_application(application_name)
|
|
91
|
+
redis.decr(aps_application_queued_key(application_name)) if aps_applications_queued_count(application_name) > 0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def enqueue_aps(application_name, notification)
|
|
95
|
+
redis.rpush(aps_application_queue_key(application_name), encode(notification.to_hash))
|
|
96
|
+
enqueue_aps_application(application_name)
|
|
97
|
+
true
|
|
98
|
+
end
|
|
83
99
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
def dequeue_aps(application_name)
|
|
101
|
+
h = decode(redis.lpop(aps_application_queue_key(application_name)))
|
|
102
|
+
return Resque::Plugins::Aps::Notification.new(h) if h
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the number of queued notifications for a given application
|
|
107
|
+
def aps_notification_count_for_application(application_name)
|
|
108
|
+
redis.llen(aps_application_queue_key(application_name)).to_i
|
|
109
|
+
end
|
|
94
110
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
# Returns an array of queued notifications for the given application
|
|
112
|
+
def aps_notifications_for_application(application_name, start = 0, count = 1)
|
|
113
|
+
r = redis.lrange(aps_application_queue_key(application_name), start, count)
|
|
114
|
+
if r
|
|
115
|
+
r.map { |h| Resque::Plugins::Aps::Notification.new(decode(h)) }
|
|
116
|
+
else
|
|
117
|
+
[]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
104
120
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
def create_aps_application(name, cert_file, cert_passwd = nil)
|
|
122
|
+
redis.set(aps_application_key(name), encode({'name' => name, 'cert_file' => cert_file, 'cert_passwd' => cert_passwd}))
|
|
123
|
+
redis.sadd(:aps_applications, name)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def aps_application(name)
|
|
127
|
+
h = decode(redis.get(aps_application_key(name)))
|
|
128
|
+
return Resque::Plugins::Aps::Application.new(h) if h
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Returns an array of applications based on start and count
|
|
133
|
+
def aps_application_names(start = 0, count = 1)
|
|
134
|
+
a = redis.smembers(:aps_applications)
|
|
135
|
+
return a if count == 0
|
|
136
|
+
ret = a[start..(start + count)]
|
|
137
|
+
return [] unless ret
|
|
138
|
+
ret
|
|
139
|
+
end
|
|
124
140
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
141
|
+
# Returns the number of application queues
|
|
142
|
+
def aps_applications_count
|
|
143
|
+
redis.smembers(:aps_applications).size
|
|
144
|
+
end
|
|
129
145
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
def aps_application_key(application_name)
|
|
147
|
+
"aps:application:#{application_name}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def aps_application_queued_key(application_name)
|
|
151
|
+
"#{aps_application_key(application_name)}:queued"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def aps_application_queue_key(application_name)
|
|
155
|
+
"#{aps_application_key(application_name)}:queue"
|
|
156
|
+
end
|
|
137
157
|
end
|
|
138
158
|
end
|
|
139
159
|
end
|
metadata
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resque-aps
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
5
|
-
segments:
|
|
6
|
-
- 0
|
|
7
|
-
- 9
|
|
8
|
-
- 9
|
|
9
|
-
version: 0.9.9
|
|
4
|
+
version: 0.9.10
|
|
10
5
|
platform: ruby
|
|
11
6
|
authors:
|
|
12
7
|
- Ashley Martens
|
|
@@ -14,73 +9,59 @@ autorequire:
|
|
|
14
9
|
bindir: bin
|
|
15
10
|
cert_chain: []
|
|
16
11
|
|
|
17
|
-
date: 2010-08-
|
|
12
|
+
date: 2010-08-31 00:00:00 -07:00
|
|
18
13
|
default_executable:
|
|
19
14
|
dependencies:
|
|
20
15
|
- !ruby/object:Gem::Dependency
|
|
21
16
|
name: redis
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
type: :runtime
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
20
|
requirements:
|
|
25
21
|
- - ">="
|
|
26
22
|
- !ruby/object:Gem::Version
|
|
27
|
-
segments:
|
|
28
|
-
- 1
|
|
29
|
-
- 0
|
|
30
|
-
- 7
|
|
31
23
|
version: 1.0.7
|
|
32
|
-
|
|
33
|
-
version_requirements: *id001
|
|
24
|
+
version:
|
|
34
25
|
- !ruby/object:Gem::Dependency
|
|
35
26
|
name: resque
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
type: :runtime
|
|
28
|
+
version_requirement:
|
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
30
|
requirements:
|
|
39
31
|
- - ">="
|
|
40
32
|
- !ruby/object:Gem::Version
|
|
41
|
-
segments:
|
|
42
|
-
- 1
|
|
43
|
-
- 5
|
|
44
|
-
- 0
|
|
45
33
|
version: 1.5.0
|
|
46
|
-
|
|
47
|
-
version_requirements: *id002
|
|
34
|
+
version:
|
|
48
35
|
- !ruby/object:Gem::Dependency
|
|
49
36
|
name: jeweler
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
type: :development
|
|
38
|
+
version_requirement:
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
40
|
requirements:
|
|
53
41
|
- - ">="
|
|
54
42
|
- !ruby/object:Gem::Version
|
|
55
|
-
segments:
|
|
56
|
-
- 0
|
|
57
43
|
version: "0"
|
|
58
|
-
|
|
59
|
-
version_requirements: *id003
|
|
44
|
+
version:
|
|
60
45
|
- !ruby/object:Gem::Dependency
|
|
61
46
|
name: mocha
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
type: :development
|
|
48
|
+
version_requirement:
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
50
|
requirements:
|
|
65
51
|
- - ">="
|
|
66
52
|
- !ruby/object:Gem::Version
|
|
67
|
-
segments:
|
|
68
|
-
- 0
|
|
69
53
|
version: "0"
|
|
70
|
-
|
|
71
|
-
version_requirements: *id004
|
|
54
|
+
version:
|
|
72
55
|
- !ruby/object:Gem::Dependency
|
|
73
56
|
name: rack-test
|
|
74
|
-
|
|
75
|
-
|
|
57
|
+
type: :development
|
|
58
|
+
version_requirement:
|
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
76
60
|
requirements:
|
|
77
61
|
- - ">="
|
|
78
62
|
- !ruby/object:Gem::Version
|
|
79
|
-
segments:
|
|
80
|
-
- 0
|
|
81
63
|
version: "0"
|
|
82
|
-
|
|
83
|
-
version_requirements: *id005
|
|
64
|
+
version:
|
|
84
65
|
description: |-
|
|
85
66
|
Queuing system for Apple's Push Service on top of Resque.
|
|
86
67
|
Adds methods enqueue_aps to queue a notification message.
|
|
@@ -132,20 +113,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
132
113
|
requirements:
|
|
133
114
|
- - ">="
|
|
134
115
|
- !ruby/object:Gem::Version
|
|
135
|
-
segments:
|
|
136
|
-
- 0
|
|
137
116
|
version: "0"
|
|
117
|
+
version:
|
|
138
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
119
|
requirements:
|
|
140
120
|
- - ">="
|
|
141
121
|
- !ruby/object:Gem::Version
|
|
142
|
-
segments:
|
|
143
|
-
- 0
|
|
144
122
|
version: "0"
|
|
123
|
+
version:
|
|
145
124
|
requirements: []
|
|
146
125
|
|
|
147
126
|
rubyforge_project:
|
|
148
|
-
rubygems_version: 1.3.
|
|
127
|
+
rubygems_version: 1.3.5
|
|
149
128
|
signing_key:
|
|
150
129
|
specification_version: 3
|
|
151
130
|
summary: Queuing system for Apple's Push Service on top of Resque
|