mailgun-tracking 2.0.0 → 3.0.0
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/lib/generators/mailgun/tracking/templates/mailgun_tracking.rb.erb +1 -1
- data/lib/mailgun-tracking.rb +3 -0
- data/lib/mailgun/tracking.rb +65 -33
- data/lib/mailgun/tracking/auth.rb +52 -0
- data/lib/mailgun/tracking/configuration.rb +5 -9
- data/lib/mailgun/tracking/fanout.rb +18 -0
- data/lib/mailgun/tracking/middleware.rb +10 -47
- data/lib/mailgun/tracking/railtie.rb +2 -2
- data/lib/mailgun/tracking/version.rb +8 -7
- data/spec/dummy/logs/test.log +77 -0
- data/spec/dummy/rails/logs/test.log +19 -11
- data/spec/mailgun/tracking/auth_spec.rb +31 -0
- data/spec/mailgun/tracking/middleware_spec.rb +43 -51
- data/spec/mailgun/tracking_spec.rb +11 -4
- data/spec/support/shared_examples/integration/acts_as_rack.rb +6 -4
- metadata +18 -53
- data/lib/mailgun/tracking/exceptions.rb +0 -11
- data/lib/mailgun/tracking/listener.rb +0 -55
- data/lib/mailgun/tracking/notifier.rb +0 -61
- data/lib/mailgun/tracking/payload.rb +0 -96
- data/lib/mailgun/tracking/request.rb +0 -47
- data/lib/mailgun/tracking/signature.rb +0 -48
- data/lib/mailgun/tracking/subscriber.rb +0 -26
- data/lib/mailgun/tracking/subscriber/all_messages.rb +0 -37
- data/lib/mailgun/tracking/subscriber/evented.rb +0 -40
- data/lib/mailgun/tracking/util.rb +0 -67
- data/spec/mailgun/tracking/listener_spec.rb +0 -48
- data/spec/mailgun/tracking/notifier_spec.rb +0 -66
- data/spec/mailgun/tracking/payload_spec.rb +0 -73
- data/spec/mailgun/tracking/request_spec.rb +0 -63
- data/spec/mailgun/tracking/signature_spec.rb +0 -65
- data/spec/mailgun/tracking/subscriber/all_messages_spec.rb +0 -13
- data/spec/mailgun/tracking/subscriber/evented_spec.rb +0 -14
- data/spec/mailgun/tracking/subscriber_spec.rb +0 -15
- data/spec/mailgun/tracking/util_spec.rb +0 -155
@@ -1,61 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mailgun
|
4
|
-
module Tracking
|
5
|
-
# Wraps the {Listener} which gives a friendlier way to subscribe or broadcast.
|
6
|
-
class Notifier
|
7
|
-
# Initializes a new Notifier object.
|
8
|
-
#
|
9
|
-
# @param listener [Mailgun::Tracking::Listener] Event listener.
|
10
|
-
#
|
11
|
-
# @return [Mailgun::Tracking::Notifier]
|
12
|
-
def initialize(listener = Listener.new)
|
13
|
-
@listener = listener
|
14
|
-
end
|
15
|
-
|
16
|
-
# Returns true if there is at least one subscriber.
|
17
|
-
#
|
18
|
-
# @return [Boolean]
|
19
|
-
def empty?
|
20
|
-
listener.subscribers.empty?
|
21
|
-
end
|
22
|
-
|
23
|
-
# Adds subscriber for the specified event.
|
24
|
-
#
|
25
|
-
# @param event [Symbol, String] The name of event.
|
26
|
-
# @param callable [Proc, Class] The listener of event.
|
27
|
-
# The callable objects should respond to call.
|
28
|
-
#
|
29
|
-
# @return [NilClass]
|
30
|
-
def subscribe(event, callable = Proc.new)
|
31
|
-
listener.add_subscriber(event, callable)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Adds a subscriber for all events.
|
35
|
-
#
|
36
|
-
# @param callable [Proc, Class] The listener of event.
|
37
|
-
# The callable objects should respond to call.
|
38
|
-
#
|
39
|
-
# @return [NilClass]
|
40
|
-
def all(callable = Proc.new)
|
41
|
-
listener.add_subscriber(nil, callable)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Broadcasts parameters to event subscribers.
|
45
|
-
#
|
46
|
-
# @param event [String] The name of event.
|
47
|
-
# @param payload [Hash] The response parameters.
|
48
|
-
#
|
49
|
-
# @return [NilClass]
|
50
|
-
def broadcast(event, payload)
|
51
|
-
Signature.verify!(payload)
|
52
|
-
listener.broadcast(event, payload)
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
# @return [Mailgun::Tracking::Listener]
|
58
|
-
attr_reader :listener
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'set'
|
4
|
-
|
5
|
-
module Mailgun
|
6
|
-
module Tracking
|
7
|
-
# Payload object.
|
8
|
-
class Payload
|
9
|
-
TRY_TO_HASH = lambda do |value|
|
10
|
-
return if value.nil?
|
11
|
-
|
12
|
-
value.respond_to?(:to_hash) ? value.to_hash : value
|
13
|
-
end.freeze
|
14
|
-
|
15
|
-
# Initializes a new Payload object.
|
16
|
-
#
|
17
|
-
# @param values [Hash]
|
18
|
-
#
|
19
|
-
# @return [Mailgun::Tracking::Payload]
|
20
|
-
def initialize(values = {})
|
21
|
-
@values = Util.normalize(values)
|
22
|
-
|
23
|
-
@values.each do |key, value|
|
24
|
-
define_instance_methods([key], values) unless __metaclass__.method_defined?(key)
|
25
|
-
@values[key] = Util.convert_to_payload_object(value)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns a value of the payload with the given key.
|
30
|
-
#
|
31
|
-
# @param key [String]
|
32
|
-
#
|
33
|
-
# @return a value of the payload.
|
34
|
-
def [](key)
|
35
|
-
@values[key]
|
36
|
-
end
|
37
|
-
|
38
|
-
# @return [Boolean]
|
39
|
-
def ==(other)
|
40
|
-
return false unless other.is_a?(Payload)
|
41
|
-
|
42
|
-
values == other.values
|
43
|
-
end
|
44
|
-
|
45
|
-
alias eql? ==
|
46
|
-
|
47
|
-
# @return [Integer] The object's hash value (for equality checking)
|
48
|
-
def hash
|
49
|
-
values.hash
|
50
|
-
end
|
51
|
-
|
52
|
-
# @return [Hash] Recursively convert payload objects to the hash.
|
53
|
-
def to_hash
|
54
|
-
@values.each_with_object({}) do |(key, value), memo|
|
55
|
-
memo[key] =
|
56
|
-
case value
|
57
|
-
when Array
|
58
|
-
value.map(&TRY_TO_HASH)
|
59
|
-
else
|
60
|
-
TRY_TO_HASH.call(value)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# @return [String] The string representation of the payload.
|
66
|
-
def to_s
|
67
|
-
JSON.pretty_generate(to_hash)
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
# @return [Class]
|
73
|
-
def __metaclass__
|
74
|
-
class << self
|
75
|
-
self
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# @return [void]
|
80
|
-
def define_instance_methods(keys, values)
|
81
|
-
__metaclass__.instance_eval do
|
82
|
-
keys.each do |key|
|
83
|
-
define_method(key) { @values[key] }
|
84
|
-
next unless [FalseClass, TrueClass].include?(values[key].class)
|
85
|
-
|
86
|
-
define_method(:"#{key}?") { @values[key] }
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
protected
|
92
|
-
|
93
|
-
attr_reader :values
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rack/request'
|
4
|
-
|
5
|
-
module Mailgun
|
6
|
-
module Tracking
|
7
|
-
# Provides a convenient interface to a Rack environment.
|
8
|
-
class Request < ::Rack::Request
|
9
|
-
# Checks if the request is respond to the specified URL.
|
10
|
-
#
|
11
|
-
# @return [Boolean]
|
12
|
-
def mailgun_tracking?
|
13
|
-
return false unless post?
|
14
|
-
return false unless media_type == APPLICATION_JSON
|
15
|
-
|
16
|
-
path == Configuration.instance.endpoint
|
17
|
-
end
|
18
|
-
|
19
|
-
# @return [Mailgun::Tracking::Payload]
|
20
|
-
def payload
|
21
|
-
@payload ||= Payload.new(params)
|
22
|
-
end
|
23
|
-
|
24
|
-
# A Mailgun::Tracking::Request::JSON is used to parsing JSON requests.
|
25
|
-
module JSON
|
26
|
-
APPLICATION_JSON = 'application/json'
|
27
|
-
FORM_HASH = 'rack.request.form_hash'
|
28
|
-
FORM_INPUT = 'rack.request.form_input'
|
29
|
-
|
30
|
-
# Returns the data received in the request body.
|
31
|
-
#
|
32
|
-
# @return [Hash]
|
33
|
-
def POST
|
34
|
-
return super unless media_type == APPLICATION_JSON
|
35
|
-
|
36
|
-
body = self.body.read
|
37
|
-
self.body.rewind
|
38
|
-
env.update(FORM_HASH => ::JSON.parse(body), FORM_INPUT => body)
|
39
|
-
|
40
|
-
env[FORM_HASH]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
include JSON
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'openssl'
|
4
|
-
|
5
|
-
module Mailgun
|
6
|
-
module Tracking
|
7
|
-
# A Mailgun::Tracking::Signature object is used to verify the signature.
|
8
|
-
class Signature
|
9
|
-
# Verify the signature of the response parameters.
|
10
|
-
#
|
11
|
-
# @param payload [Mailgun::Tracking::Payload]
|
12
|
-
# @raise [InvalidSignature] Error raised when signature is invalid.
|
13
|
-
#
|
14
|
-
# @return [Boolean]
|
15
|
-
# Always returns true.
|
16
|
-
def self.verify!(payload)
|
17
|
-
signature = new(payload)
|
18
|
-
raise InvalidSignature unless signature.valid?
|
19
|
-
|
20
|
-
true
|
21
|
-
end
|
22
|
-
|
23
|
-
# Initializes a new Signature object.
|
24
|
-
#
|
25
|
-
# @param payload [Mailgun::Tracking::Payload]
|
26
|
-
#
|
27
|
-
# @return [Mailgun::Tracking::Signature]
|
28
|
-
def initialize(payload)
|
29
|
-
@payload = payload
|
30
|
-
end
|
31
|
-
|
32
|
-
# @return [Boolean]
|
33
|
-
def valid?
|
34
|
-
@payload.signature.signature == \
|
35
|
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, Configuration.instance.api_key, data)
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
# Joins the timestamp and the response token.
|
41
|
-
#
|
42
|
-
# @return [String]
|
43
|
-
def data
|
44
|
-
[@payload.signature.timestamp, @payload.signature.token].join
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'subscriber/all_messages'
|
4
|
-
require_relative 'subscriber/evented'
|
5
|
-
|
6
|
-
module Mailgun
|
7
|
-
module Tracking
|
8
|
-
# Namespace for classes that wraps subscribers.
|
9
|
-
module Subscriber
|
10
|
-
# Determines the type of subscription.
|
11
|
-
#
|
12
|
-
# @param name [Symbol, String] The name of event.
|
13
|
-
# @param callable [Proc, Class] The callable object.
|
14
|
-
# The callable objects should respond to call.
|
15
|
-
#
|
16
|
-
# @return [Mailgun::Tracking::Subscriber::Evented, Mailgun::Tracking::Subscriber::AllMessages]
|
17
|
-
def self.for(name, callable)
|
18
|
-
if name
|
19
|
-
Evented.new(name, callable)
|
20
|
-
else
|
21
|
-
AllMessages.new(callable)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mailgun
|
4
|
-
module Tracking
|
5
|
-
module Subscriber
|
6
|
-
# Wraps the subscriber for events.
|
7
|
-
class AllMessages
|
8
|
-
# Initializes a new AllMessages object.
|
9
|
-
#
|
10
|
-
# @param callable [Proc, Class] The callable object.
|
11
|
-
# The callable objects should respond to call.
|
12
|
-
#
|
13
|
-
# @return [Mailgun::Tracking::Subscriber::AllMessages]
|
14
|
-
def initialize(callable)
|
15
|
-
@callable = callable
|
16
|
-
end
|
17
|
-
|
18
|
-
# Invokes the callable object.
|
19
|
-
#
|
20
|
-
# @param payload [Hash] The response parameters.
|
21
|
-
#
|
22
|
-
# @return [void]
|
23
|
-
def call(payload)
|
24
|
-
@callable.call(payload)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Checks if a callable object is a subscribed to the specified event.
|
28
|
-
#
|
29
|
-
# @return [Boolean]
|
30
|
-
# Always returns true.
|
31
|
-
def subscribed_to?(*)
|
32
|
-
true
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mailgun
|
4
|
-
module Tracking
|
5
|
-
module Subscriber
|
6
|
-
# Wraps the evented subscriber.
|
7
|
-
class Evented
|
8
|
-
# Initializes a new Evented object.
|
9
|
-
#
|
10
|
-
# @param name [Symbol, String] The name of event.
|
11
|
-
# @param callable [Proc, Class] The callable object.
|
12
|
-
# The callable objects should respond to call.
|
13
|
-
#
|
14
|
-
# @return [Mailgun::Tracking::Subscriber::Evented]
|
15
|
-
def initialize(name, callable)
|
16
|
-
@name = name.to_sym
|
17
|
-
@callable = callable
|
18
|
-
end
|
19
|
-
|
20
|
-
# Invokes the callable object.
|
21
|
-
#
|
22
|
-
# @param payload [Hash] The response parameters.
|
23
|
-
#
|
24
|
-
# @return [void]
|
25
|
-
def call(payload)
|
26
|
-
@callable.call(payload)
|
27
|
-
end
|
28
|
-
|
29
|
-
# Checks if a callable object is a subscribed to the specified event.
|
30
|
-
#
|
31
|
-
# @param name [String] The name of event.
|
32
|
-
#
|
33
|
-
# @return [Boolean]
|
34
|
-
def subscribed_to?(name)
|
35
|
-
@name == name.to_sym
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mailgun
|
4
|
-
module Tracking
|
5
|
-
# Utility methods.
|
6
|
-
module Util
|
7
|
-
class << self
|
8
|
-
# Converts a hash of fields or an array of hashes into a {Payload}.
|
9
|
-
#
|
10
|
-
# @param data [Hash, Array] Hash of fields and values to be converted into a Payload.
|
11
|
-
#
|
12
|
-
# @return [Mailgun::Tracking::Payload, Array(Mailgun::Tracking::Payload)]
|
13
|
-
def convert_to_payload_object(data)
|
14
|
-
case data
|
15
|
-
when Array
|
16
|
-
data.map { |item| convert_to_payload_object(item) }
|
17
|
-
when Hash
|
18
|
-
Payload.new(data)
|
19
|
-
else
|
20
|
-
data
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class << self
|
26
|
-
# Returns a new hash with all keys converted to symbols in downcase.
|
27
|
-
#
|
28
|
-
# @param options [Hash]
|
29
|
-
#
|
30
|
-
# @return [Hash]
|
31
|
-
def normalize(options = {})
|
32
|
-
object = normalize_keys(options.dup)
|
33
|
-
object
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def normalize_keys(options)
|
39
|
-
return from_h(options) if options.is_a?(Hash)
|
40
|
-
return from_ary(options) if options.is_a?(Array)
|
41
|
-
|
42
|
-
options
|
43
|
-
end
|
44
|
-
|
45
|
-
def from_h(options)
|
46
|
-
Hash[options.map do |key, value|
|
47
|
-
[underscore(key), normalize_keys(value)]
|
48
|
-
end]
|
49
|
-
end
|
50
|
-
|
51
|
-
def from_ary(options)
|
52
|
-
options.map(&method(:normalize_keys))
|
53
|
-
end
|
54
|
-
|
55
|
-
def underscore(key)
|
56
|
-
return key unless key.respond_to?(:to_sym)
|
57
|
-
|
58
|
-
key.to_s
|
59
|
-
.dup
|
60
|
-
.tr('-', '_')
|
61
|
-
.downcase
|
62
|
-
.to_sym
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Mailgun::Tracking::Listener do
|
4
|
-
subject(:listener) { described_class.new }
|
5
|
-
|
6
|
-
let(:callable) { proc {} }
|
7
|
-
let(:subscriber) { instance_double(Mailgun::Tracking::Subscriber::Evented) }
|
8
|
-
|
9
|
-
describe '#add_subscriber' do
|
10
|
-
before { allow(Mailgun::Tracking::Subscriber).to receive(:for).with(/delivered/, callable) { subscriber } }
|
11
|
-
|
12
|
-
it 'adds subscriber' do
|
13
|
-
expect { listener.add_subscriber(:delivered, callable) }.to change(listener, :subscribers)
|
14
|
-
.from([])
|
15
|
-
.to([subscriber])
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'adds multiple subscribers with the same name' do
|
19
|
-
expect do
|
20
|
-
listener.add_subscriber(:delivered, callable)
|
21
|
-
listener.add_subscriber('delivered', callable)
|
22
|
-
end.to change(listener, :subscribers).from([]).to([subscriber, subscriber])
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
describe '#broadcast' do
|
27
|
-
let(:payload) { fixture('delivered.json') }
|
28
|
-
|
29
|
-
before do
|
30
|
-
allow(subscriber).to receive(:call)
|
31
|
-
allow(subscriber).to receive(:subscribed_to?).with(/delivered/).and_return(true)
|
32
|
-
allow(Mailgun::Tracking::Subscriber).to receive(:for).with(:delivered, callable) { subscriber }
|
33
|
-
|
34
|
-
listener.add_subscriber(:delivered, callable)
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'executes subscriber' do
|
38
|
-
listener.broadcast(:delivered, payload)
|
39
|
-
expect(subscriber).to have_received(:call).with(payload)
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'executes multiple subscribers' do
|
43
|
-
listener.add_subscriber(:delivered, callable)
|
44
|
-
listener.broadcast('delivered', payload)
|
45
|
-
expect(subscriber).to have_received(:call).with(payload).twice
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|