mercurius 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/lib/mercurius/apns/connection.rb +37 -0
- data/lib/mercurius/apns/notification.rb +30 -30
- data/lib/mercurius/apns/pem.rb +22 -0
- data/lib/mercurius/apns/service.rb +62 -0
- data/lib/mercurius/apns.rb +10 -0
- data/lib/mercurius/errors/pem_not_configured_error.rb +7 -0
- data/lib/mercurius/errors/pem_not_found_error.rb +7 -0
- data/lib/mercurius/errors/too_many_retries_error.rb +7 -0
- data/lib/mercurius/gcm/connection.rb +24 -0
- data/lib/mercurius/gcm/notification.rb +16 -40
- data/lib/mercurius/gcm/response.rb +35 -0
- data/lib/mercurius/gcm/result.rb +27 -0
- data/lib/mercurius/gcm/service.rb +36 -0
- data/lib/mercurius/gcm.rb +9 -0
- data/lib/mercurius/version.rb +1 -1
- data/lib/mercurius.rb +25 -3
- data/mercurius.gemspec +5 -1
- data/spec/lib/apns_service_spec.rb +70 -0
- data/spec/lib/gcm_service_spec.rb +72 -0
- data/spec/lib/notification_spec.rb +20 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/apns.pem +61 -0
- data/spec/support/fake_socket.rb +30 -0
- metadata +83 -10
- data/lib/mercurius/android.rb +0 -2
- data/lib/mercurius/apns/core.rb +0 -128
- data/lib/mercurius/apple.rb +0 -2
- data/lib/mercurius/gcm/core.rb +0 -113
- data/spec/lib/mercurius_spec.rb +0 -148
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bba600c5ea9ccfe58f1be1c8a0190f76ed156876
|
4
|
+
data.tar.gz: 878a4fbc8539723e11c8b489a6335fa1e0839e88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a80bcb308c866c6d4b98beb7ea1ec19e7407c306f6ec6fa322dd40388973473324363dbf6d26432ff8aeea0f57fb492086c4b6e87d2ed50be1e8ba0982b09502
|
7
|
+
data.tar.gz: f63ae742964e49c26bb46ada8088dc77cdb94539663ecef72ffa63fcb0232a1945caed3ce11eedb63b516db770ff85de4d63ff865fb8a8995fa14d1f4dc2161d
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.1.5
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module APNS
|
2
|
+
class Connection
|
3
|
+
attr_reader :host, :port, :ssl
|
4
|
+
|
5
|
+
def initialize(host, port, pem)
|
6
|
+
@socket = TCPSocket.new host, port
|
7
|
+
@ssl = OpenSSL::SSL::SSLSocket.new @socket, ssl_context_for_pem(pem)
|
8
|
+
end
|
9
|
+
|
10
|
+
def open
|
11
|
+
@ssl.connect
|
12
|
+
end
|
13
|
+
|
14
|
+
def close
|
15
|
+
@ssl.close
|
16
|
+
@socket.close
|
17
|
+
end
|
18
|
+
|
19
|
+
def closed?
|
20
|
+
@ssl.closed? && @socket.closed?
|
21
|
+
end
|
22
|
+
|
23
|
+
def write(data)
|
24
|
+
@ssl.write data
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def ssl_context_for_pem(pem)
|
30
|
+
context = OpenSSL::SSL::SSLContext.new
|
31
|
+
context.cert = OpenSSL::X509::Certificate.new(pem.data)
|
32
|
+
context.key = OpenSSL::PKey::RSA.new(pem.data, pem.password)
|
33
|
+
context
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -1,46 +1,46 @@
|
|
1
1
|
module APNS
|
2
2
|
class Notification
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
self.alert = message
|
14
|
-
else
|
15
|
-
raise "Notification needs to have either a Hash or String"
|
16
|
-
end
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
MAX_PAYLOAD_BYTES = 2048
|
6
|
+
|
7
|
+
attr_accessor :alert, :badge, :sound, :other
|
8
|
+
attr_reader :attributes
|
9
|
+
|
10
|
+
def initialize(attributes = {})
|
11
|
+
@attributes = attributes
|
12
|
+
super
|
17
13
|
end
|
18
14
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
def payload
|
16
|
+
{
|
17
|
+
alert: alert,
|
18
|
+
badge: badge,
|
19
|
+
sound: sound,
|
20
|
+
other: other
|
21
|
+
}.compact
|
23
22
|
end
|
24
23
|
|
25
|
-
def
|
24
|
+
def pack(device_token)
|
25
|
+
[0, 0, 32, package_device_token(device_token), 0, packaged_message.bytesize, packaged_message].pack("ccca*cca*")
|
26
|
+
end
|
27
|
+
|
28
|
+
def package_device_token(device_token)
|
26
29
|
[device_token.gsub(/[\s|<|>]/,'')].pack('H*')
|
27
30
|
end
|
28
31
|
|
29
32
|
def packaged_message
|
30
|
-
aps
|
31
|
-
|
32
|
-
|
33
|
-
aps['aps']['sound'] = self.sound if self.sound
|
34
|
-
aps.merge!(self.other) if self.other
|
35
|
-
aps.to_json.gsub(/\\u([\da-fA-F]{4})/) {|m| [$1].pack("H*").unpack("n*").pack("U*")}
|
33
|
+
{ aps: payload }.to_json.gsub(/\\u([\da-fA-F]{4})/) do |m|
|
34
|
+
[$1].pack("H*").unpack("n*").pack("U*")
|
35
|
+
end
|
36
36
|
end
|
37
37
|
|
38
38
|
def ==(that)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
attributes == that.attributes
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid?
|
43
|
+
packaged_message.bytesize <= MAX_PAYLOAD_BYTES
|
44
44
|
end
|
45
45
|
|
46
46
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module APNS
|
2
|
+
class Pem
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
attr_accessor :path, :data, :password
|
6
|
+
|
7
|
+
def data
|
8
|
+
@_data ||= (@data || read_file_at_path || raise(PemNotConfiguredError.new))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def read_file_at_path
|
14
|
+
if File.exist? path
|
15
|
+
File.read path
|
16
|
+
else
|
17
|
+
raise PemNotFoundError.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module APNS
|
2
|
+
class Service
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
MAX_NUMBER_OF_RETRIES = 3
|
6
|
+
|
7
|
+
attr_accessor :host, :port, :pem
|
8
|
+
attr_reader :connection, :attempts
|
9
|
+
|
10
|
+
def initialize(*)
|
11
|
+
super
|
12
|
+
@host ||= APNS.host
|
13
|
+
@port ||= APNS.port
|
14
|
+
@pem ||= APNS.pem
|
15
|
+
@connection = APNS::Connection.new(@host, @port, @pem)
|
16
|
+
@attempts = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def persist(&block)
|
20
|
+
@_persistent = true
|
21
|
+
yield
|
22
|
+
@_persistent = false
|
23
|
+
connection.close
|
24
|
+
end
|
25
|
+
|
26
|
+
def deliver(notification, *device_tokens)
|
27
|
+
device_tokens = Array(device_tokens).flatten
|
28
|
+
with_connection do |connection|
|
29
|
+
device_tokens.each do |device_token|
|
30
|
+
connection.write notification.pack(device_token)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def persistent?
|
38
|
+
@_persistent
|
39
|
+
end
|
40
|
+
|
41
|
+
def with_connection(&block)
|
42
|
+
@attempts = 1
|
43
|
+
|
44
|
+
begin
|
45
|
+
connection.open if connection.closed?
|
46
|
+
yield connection
|
47
|
+
rescue StandardError, Errno::EPIPE => e
|
48
|
+
raise TooManyRetriesError.new if too_many_retries?
|
49
|
+
connection.close
|
50
|
+
@attempts += 1
|
51
|
+
retry
|
52
|
+
end
|
53
|
+
|
54
|
+
connection.close unless persistent?
|
55
|
+
end
|
56
|
+
|
57
|
+
def too_many_retries?
|
58
|
+
@attempts >= MAX_NUMBER_OF_RETRIES
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module GCM
|
2
|
+
class Connection
|
3
|
+
|
4
|
+
attr_accessor :host, :key
|
5
|
+
|
6
|
+
def initialize(host, key)
|
7
|
+
@host = host
|
8
|
+
@key = key
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(json)
|
12
|
+
client.post '/gcm/send', json
|
13
|
+
end
|
14
|
+
|
15
|
+
def client
|
16
|
+
@_client ||= Faraday.new(host) do |http|
|
17
|
+
http.headers['Authorization'] = "key=#{key}"
|
18
|
+
http.request :json
|
19
|
+
http.adapter :net_http
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -1,54 +1,30 @@
|
|
1
1
|
module GCM
|
2
2
|
class Notification
|
3
|
-
|
3
|
+
include ActiveModel::Model
|
4
4
|
|
5
|
-
|
6
|
-
self.device_tokens = tokens
|
7
|
-
self.data = data
|
5
|
+
attr_accessor :data, :collapse_key, :time_to_live, :delay_while_idle
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
@delay_while_idle = options[:delay_while_idle]
|
12
|
-
@identity = options[:identity]
|
13
|
-
end
|
14
|
-
|
15
|
-
def device_tokens=(tokens)
|
16
|
-
if tokens.is_a?(Array)
|
17
|
-
@device_tokens = tokens
|
18
|
-
elsif tokens.is_a?(String)
|
19
|
-
@device_tokens = [tokens]
|
20
|
-
else
|
21
|
-
raise "device_tokens needs to be either an Array or a String"
|
22
|
-
end
|
23
|
-
end
|
7
|
+
# validate delay_while_idle is true/false
|
8
|
+
# validate ttl is integer in seconds
|
24
9
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
else
|
29
|
-
raise "data parameter must be the type of Hash"
|
30
|
-
end
|
10
|
+
def initialize(attributes = {})
|
11
|
+
@attributes = attributes
|
12
|
+
super data: @attributes.except(:collapse_key, :time_to_live, :delay_while_idle)
|
31
13
|
end
|
32
14
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
15
|
+
def to_h
|
16
|
+
hash = {
|
17
|
+
data: data,
|
18
|
+
collapse_key: collapse_key,
|
19
|
+
time_to_live: time_to_live,
|
20
|
+
delay_while_idle: delay_while_idle
|
21
|
+
}
|
36
22
|
|
37
|
-
|
38
|
-
if time_to_live.is_a?(Integer)
|
39
|
-
@time_to_live = time_to_live
|
40
|
-
else
|
41
|
-
raise %q{"time_to_live" must be seconds as an integer value, like "100"}
|
42
|
-
end
|
23
|
+
hash.reject { |k, v| v.nil? }
|
43
24
|
end
|
44
25
|
|
45
26
|
def ==(that)
|
46
|
-
|
47
|
-
data == that.data &&
|
48
|
-
collapse_key == that.collapse_key &&
|
49
|
-
time_to_live == that.time_to_live &&
|
50
|
-
delay_while_idle == that.delay_while_idle &&
|
51
|
-
identity == that.identity
|
27
|
+
attributes == that.attributes
|
52
28
|
end
|
53
29
|
|
54
30
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module GCM
|
2
|
+
class Response
|
3
|
+
attr_reader :response, :device_tokens
|
4
|
+
|
5
|
+
MESSAGES = {
|
6
|
+
200 => 'Success',
|
7
|
+
400 => 'The request could not be parsed as JSON or it contained invalid fields',
|
8
|
+
401 => 'There was an error authenticating the sender account',
|
9
|
+
500 => 'There was an internal error in the GCM server',
|
10
|
+
503 => 'GCM server is temporarily unavailable',
|
11
|
+
default: 'Unknown error'
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(response, device_tokens)
|
15
|
+
@response = response
|
16
|
+
@device_tokens = device_tokens
|
17
|
+
end
|
18
|
+
|
19
|
+
def status
|
20
|
+
@response.status
|
21
|
+
end
|
22
|
+
|
23
|
+
def message
|
24
|
+
MESSAGES.fetch(status, MESSAGES[:default])
|
25
|
+
end
|
26
|
+
|
27
|
+
def success?
|
28
|
+
@response.success?
|
29
|
+
end
|
30
|
+
|
31
|
+
def failed?
|
32
|
+
!success?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module GCM
|
2
|
+
class Result
|
3
|
+
attr_reader :responses
|
4
|
+
|
5
|
+
def initialize(notification)
|
6
|
+
@notification = notification
|
7
|
+
@responses = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def success?
|
11
|
+
failed_responses.empty?
|
12
|
+
end
|
13
|
+
|
14
|
+
def process_response(response, device_tokens)
|
15
|
+
self.responses << GCM::Response.new(response, device_tokens)
|
16
|
+
end
|
17
|
+
|
18
|
+
def failed_responses
|
19
|
+
@_failed_responses ||= responses.select(&:failed?)
|
20
|
+
end
|
21
|
+
|
22
|
+
def failed_device_tokens
|
23
|
+
@_failed_device_tokens ||= failed_responses.flat_map { |response| response.device_tokens }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module GCM
|
2
|
+
class Service
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
MAX_NUMBER_OF_RETRIES = 3
|
6
|
+
MAX_DEVICES_AT_ONCE = 999
|
7
|
+
|
8
|
+
attr_accessor :host, :key
|
9
|
+
attr_reader :connection, :attempts
|
10
|
+
|
11
|
+
def initialize(*)
|
12
|
+
super
|
13
|
+
@host ||= GCM.host
|
14
|
+
@key ||= GCM.key
|
15
|
+
@connection = GCM::Connection.new(@host, @key)
|
16
|
+
@attempts = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def deliver(notification, *device_tokens)
|
20
|
+
result = GCM::Result.new(notification)
|
21
|
+
device_tokens = Array(device_tokens).flatten
|
22
|
+
device_tokens.each_slice(MAX_DEVICES_AT_ONCE) do |tokens|
|
23
|
+
payload = notification.to_h.merge registration_ids: tokens
|
24
|
+
result.process_response connection.write(payload), tokens
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def too_many_retries?
|
32
|
+
@attempts >= MAX_NUMBER_OF_RETRIES
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
data/lib/mercurius/version.rb
CHANGED
data/lib/mercurius.rb
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/core_ext/hash/compact'
|
3
|
+
require 'active_support/core_ext/hash/except'
|
4
|
+
require 'faraday'
|
5
|
+
require 'faraday_middleware'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
require 'mercurius/version'
|
9
|
+
|
10
|
+
require 'mercurius/errors/pem_not_configured_error'
|
11
|
+
require 'mercurius/errors/pem_not_found_error'
|
12
|
+
require 'mercurius/errors/too_many_retries_error'
|
13
|
+
|
14
|
+
require 'mercurius/apns'
|
15
|
+
require 'mercurius/apns/pem'
|
16
|
+
require 'mercurius/apns/connection'
|
17
|
+
require 'mercurius/apns/notification'
|
18
|
+
require 'mercurius/apns/service'
|
19
|
+
|
20
|
+
require 'mercurius/gcm'
|
21
|
+
require 'mercurius/gcm/connection'
|
22
|
+
require 'mercurius/gcm/notification'
|
23
|
+
require 'mercurius/gcm/service'
|
24
|
+
require 'mercurius/gcm/response'
|
25
|
+
require 'mercurius/gcm/result'
|
data/mercurius.gemspec
CHANGED
@@ -22,9 +22,13 @@ Gem::Specification.new do |s|
|
|
22
22
|
|
23
23
|
s.require_paths = ["lib"]
|
24
24
|
|
25
|
-
s.add_dependency 'httparty'
|
26
25
|
s.add_dependency 'json'
|
26
|
+
s.add_dependency 'faraday'
|
27
|
+
s.add_dependency 'faraday_middleware'
|
28
|
+
s.add_dependency 'activemodel', '>= 4.0.0'
|
29
|
+
s.add_dependency 'activesupport', '>= 4.1.0'
|
27
30
|
|
28
31
|
s.add_development_dependency 'rake'
|
29
32
|
s.add_development_dependency 'rspec'
|
33
|
+
s.add_development_dependency 'webmock'
|
30
34
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
describe APNS::Service do
|
2
|
+
let(:service) { APNS::Service.new }
|
3
|
+
|
4
|
+
before do
|
5
|
+
# setup APNS
|
6
|
+
bundle = File.read File.expand_path(File.join(File.dirname(__FILE__), '..', 'support', 'apns.pem'))
|
7
|
+
APNS.host = 'gateway.sandbox.push.apple.com'
|
8
|
+
APNS.port = 2195
|
9
|
+
APNS.pem = APNS::Pem.new(data: bundle, password: 'test123')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should default to the APNS module configs' do
|
13
|
+
expect(service.host).to eq 'gateway.sandbox.push.apple.com'
|
14
|
+
expect(service.port).to eq 2195
|
15
|
+
expect(service.pem.password).to eq 'test123'
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#send' do
|
19
|
+
let(:ssl) { FakeSocket.new }
|
20
|
+
let(:socket) { FakeSocket.new }
|
21
|
+
|
22
|
+
before do
|
23
|
+
expect(OpenSSL::SSL::SSLSocket).to receive(:new) { ssl }
|
24
|
+
expect(TCPSocket).to receive(:new) { socket }
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'sends a single message' do
|
28
|
+
message = APNS::Notification.new(alert: 'Hey')
|
29
|
+
service.deliver message, 'token123'
|
30
|
+
expect(ssl.wrote[0]).to include ({ alert: 'Hey' }).to_json
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'sends to multiple tokens via splat' do
|
34
|
+
message = APNS::Notification.new(alert: 'Hey')
|
35
|
+
service.deliver message, 'token123', 'token456'
|
36
|
+
expect(ssl.wrote.size).to eq 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'sends to multiple token via array' do
|
40
|
+
message = APNS::Notification.new(alert: 'Hey1')
|
41
|
+
service.deliver message, ['token123', 'token456']
|
42
|
+
expect(ssl.wrote.size).to eq 2
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'persist' do
|
46
|
+
it 'with persist, it keeps the SSL connection open until all messages are sent' do
|
47
|
+
expect(service.connection).to receive(:close).once
|
48
|
+
service.persist do
|
49
|
+
service.deliver APNS::Notification.new(alert: 'Hey1'), 'token123'
|
50
|
+
service.deliver APNS::Notification.new(alert: 'Hey2'), 'token123'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'without persist, closes the connection on each message' do
|
55
|
+
expect(service.connection).to receive(:close).twice
|
56
|
+
service.deliver APNS::Notification.new(alert: 'Hey1'), 'token123'
|
57
|
+
service.deliver APNS::Notification.new(alert: 'Hey2'), 'token123'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'retries' do
|
62
|
+
it 'tries 3 times before giving up' do
|
63
|
+
allow(service.connection).to receive(:open) { raise StandardError }
|
64
|
+
expect { service.deliver APNS::Notification.new(alert: 'Hey1'), 'token123' }.to raise_exception(TooManyRetriesError)
|
65
|
+
expect(service.attempts).to eq 3
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
describe GCM::Service do
|
2
|
+
let(:service) { GCM::Service.new }
|
3
|
+
let(:message) { GCM::Notification.new(alert: 'Hey') }
|
4
|
+
|
5
|
+
it 'should default to the GCM module configs' do
|
6
|
+
expect(service.host).to eq 'https://android.googleapis.com/'
|
7
|
+
expect(service.key).to be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#send' do
|
11
|
+
before { stub_request :post, %r[android.googleapis.com/gcm/send] }
|
12
|
+
|
13
|
+
it 'sends a single message' do
|
14
|
+
service.deliver message, 'token123'
|
15
|
+
expect(WebMock).to have_requested(:post, %r[android.googleapis.com/gcm/send]).
|
16
|
+
with(body: { data: { alert: 'Hey' }, registration_ids: ['token123'] })
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'sends to multiple tokens via splat' do
|
20
|
+
service.deliver message, 'token123', 'token456'
|
21
|
+
expect(WebMock).to have_requested(:post, %r[android.googleapis.com/gcm/send]).
|
22
|
+
with(body: { data: { alert: 'Hey' }, registration_ids: ['token123', 'token456'] })
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'sends to multiple tokens via array' do
|
26
|
+
service.deliver message, ['token123', 'token456']
|
27
|
+
expect(WebMock).to have_requested(:post, %r[android.googleapis.com/gcm/send]).
|
28
|
+
with(body: { data: { alert: 'Hey' }, registration_ids: ['token123', 'token456'] })
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'only sends 999 tokens at a time' do
|
32
|
+
tokens = (1..1000).to_a.map { |i| "token#{i}" }
|
33
|
+
service.deliver message, tokens
|
34
|
+
expect(WebMock).to have_requested(:post, %r[android.googleapis.com/gcm/send]).
|
35
|
+
with(body: { data: { alert: 'Hey' }, registration_ids: tokens.take(999) })
|
36
|
+
expect(WebMock).to have_requested(:post, %r[android.googleapis.com/gcm/send]).
|
37
|
+
with(body: { data: { alert: 'Hey' }, registration_ids: [tokens.last] })
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'response' do
|
42
|
+
context 'success' do
|
43
|
+
before { stub_request :post, %r[android.googleapis.com/gcm/send] }
|
44
|
+
|
45
|
+
it 'processes a 200 response' do
|
46
|
+
result = service.deliver message, 'token123'
|
47
|
+
expect(result.responses[0].status).to eq 200
|
48
|
+
expect(result.responses[0].message).to eq GCM::Response::MESSAGES[200]
|
49
|
+
expect(result.failed_device_tokens).to eq []
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'failure' do
|
54
|
+
before { stub_request(:post, %r[android.googleapis.com/gcm/send]).to_return(status: 400) }
|
55
|
+
|
56
|
+
it 'processes a 400 response' do
|
57
|
+
result = service.deliver message, 'token123'
|
58
|
+
expect(result.responses[0].status).to eq 400
|
59
|
+
expect(result.responses[0].message).to eq GCM::Response::MESSAGES[400]
|
60
|
+
expect(result.failed_device_tokens).to eq ['token123']
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'adds all failed messages to the failed device tokens array' do
|
64
|
+
tokens = (1..1000).to_a.map { |i| "token#{i}" }
|
65
|
+
result = service.deliver message, tokens
|
66
|
+
expect(result.failed_device_tokens).to include 'token1'
|
67
|
+
expect(result.failed_device_tokens).to include 'token1000'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|