mailgun-tracking 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/mailgun/tracking/install_generator.rb +19 -0
- data/lib/generators/mailgun/tracking/templates/mailgun_tracking.rb.erb +15 -0
- data/lib/mailgun/tracking.rb +34 -2
- data/lib/mailgun/tracking/exceptions.rb +3 -0
- data/lib/mailgun/tracking/listener.rb +28 -0
- data/lib/mailgun/tracking/middleware.rb +21 -1
- data/lib/mailgun/tracking/notifier.rb +26 -0
- data/lib/mailgun/tracking/railtie.rb +1 -0
- data/lib/mailgun/tracking/signature.rb +18 -0
- data/lib/mailgun/tracking/subscriber.rb +8 -0
- data/lib/mailgun/tracking/subscriber/all_messages.rb +16 -0
- data/lib/mailgun/tracking/subscriber/evented.rb +18 -0
- data/lib/mailgun/tracking/version.rb +1 -1
- data/spec/mailgun/tracking/signature_spec.rb +2 -2
- metadata +13 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f2b53add355033fc2a5b6cc4f93dc1818fdd4f5
|
4
|
+
data.tar.gz: 2abd9e0afdc9621f48584160547756b721ca0e10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d6781261987073ad9cee2f8bf7b516892798a8b08dd5aebfe392204fcb417e2033ac8481a4f4c17a9114c7ecaf052b1b4e4f314bb9165ebedad7752f017dcd2
|
7
|
+
data.tar.gz: cff2a9d86aec1d5bb1b23e39074ae3eafb8e96cdf1907b8ec63b7055119c269f2321764393622206921d8ea82849435633cf8485d2e5d92369b69d18865a6751
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Mailgun
|
2
|
+
module Tracking
|
3
|
+
# Creates the Mailgun Tracking initializer file for Rails apps.
|
4
|
+
#
|
5
|
+
# @example Invokation from terminal
|
6
|
+
# rails generate mailgun:tracking:install API_KEY ENDPOINT
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
source_root File.expand_path('../templates', __FILE__)
|
9
|
+
|
10
|
+
argument :api_key, required: false
|
11
|
+
argument :endpoint, required: false
|
12
|
+
|
13
|
+
# Copies the initialization file.
|
14
|
+
def copy_initializer_file
|
15
|
+
template('mailgun_tracking.rb.erb', 'config/initializers/mailgun_tracking.rb')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Mailgun::Tracking.configure do |config|
|
2
|
+
# Mailgun API public key.
|
3
|
+
<% if api_key -%>
|
4
|
+
config.api_key = <%= api_key %>
|
5
|
+
<% else -%>
|
6
|
+
config.api_key = ENV['MAILGUN_API_KEY']
|
7
|
+
<% end -%>
|
8
|
+
|
9
|
+
# Mailgun Webhook API endpoint. Default is '/mailgun'.
|
10
|
+
<% if endpoint -%>
|
11
|
+
config.endpoint = <%= endpoint %>
|
12
|
+
<% else -%>
|
13
|
+
# config.endpoint = '/mailgun-tracking'
|
14
|
+
<% end -%>
|
15
|
+
end
|
data/lib/mailgun/tracking.rb
CHANGED
@@ -7,14 +7,30 @@ require 'mailgun/tracking/subscriber'
|
|
7
7
|
require 'mailgun/tracking/version'
|
8
8
|
require 'mailgun/tracking/railtie' if defined?(Rails)
|
9
9
|
|
10
|
+
# Module for interacting with the Mailgun.
|
10
11
|
module Mailgun
|
12
|
+
# Namespace for classes and modules that handle Mailgun Webhooks.
|
11
13
|
module Tracking
|
12
14
|
DEFAULT_ENDPOINT = '/mailgun'.freeze
|
13
15
|
|
14
16
|
class << self
|
15
|
-
|
16
|
-
|
17
|
+
# Mailgun API public key.
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
attr_accessor :api_key
|
17
21
|
|
22
|
+
# Mailgun Webhook API endpoint
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
attr_accessor :endpoint
|
26
|
+
|
27
|
+
# Default way to setup Mailgun Tracking.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# Mailgun::Tracking.configure do |config|
|
31
|
+
# config.api_key = ENV['MAILGUN_API_KEY']
|
32
|
+
# config.endpoint = '/mailgun-tracking'
|
33
|
+
# end
|
18
34
|
def configure
|
19
35
|
yield(self)
|
20
36
|
end
|
@@ -24,6 +40,22 @@ module Mailgun
|
|
24
40
|
|
25
41
|
module_function
|
26
42
|
|
43
|
+
# A Notifier instance.
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# Mailgun::Tracking.configure do |config|
|
47
|
+
# config.notifier.subscribe :delivered do |payload|
|
48
|
+
# # Do something with the incoming data.
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# config.notifier.subscribe :bounced, Bounced.new
|
52
|
+
#
|
53
|
+
# config.notifier.all do |payload|
|
54
|
+
# # Handle all event types.
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# @return [Mailgun::Tracking::Notifier]
|
27
59
|
def notifier
|
28
60
|
@notifier ||= Notifier.new
|
29
61
|
end
|
@@ -1,22 +1,50 @@
|
|
1
1
|
module Mailgun
|
2
2
|
module Tracking
|
3
|
+
# Represents a mechanism for event listeners to subscribe to events and for event broadcasts.
|
3
4
|
class Listener
|
5
|
+
# List of subscribers.
|
6
|
+
#
|
7
|
+
# @return [Array<Mailgun::Tracking::Subscriber::AllMessages, Mailgun::Tracking::Subscriber::Evented>]
|
4
8
|
attr_reader :subscribers
|
5
9
|
|
10
|
+
# Initializes a new Listener object.
|
11
|
+
#
|
12
|
+
# @return [Mailgun::Tracking::Listener]
|
6
13
|
def initialize
|
7
14
|
@subscribers = []
|
8
15
|
end
|
9
16
|
|
17
|
+
# Adds a subscriber to the list of subscribers for the specified event.
|
18
|
+
#
|
19
|
+
# @param event [Symbol, String] The name of event.
|
20
|
+
# @param callable [Proc, Class] The listener of event.
|
21
|
+
# The callable objects should respond to call.
|
22
|
+
#
|
23
|
+
# @return [Array<Mailgun::Tracking::Subscriber::AllMessages, Mailgun::Tracking::Subscriber::Evented>]
|
24
|
+
# The list of subscribers.
|
10
25
|
def add_subscriber(event, callable)
|
11
26
|
@subscribers << Subscriber.for(event, callable)
|
12
27
|
end
|
13
28
|
|
29
|
+
# Broadcasts an event to the subscribers.
|
30
|
+
#
|
31
|
+
# @param event [String] The name of event.
|
32
|
+
# @param payload [Hash] The response parameters.
|
33
|
+
#
|
34
|
+
# @return [Array<Mailgun::Tracking::Subscriber::AllMessages, Mailgun::Tracking::Subscriber::Evented>]
|
35
|
+
# The list of subscribers.
|
14
36
|
def broadcast(event, payload)
|
15
37
|
subscribers_for(event).each { |subscriber| subscriber.call(payload) }
|
16
38
|
end
|
17
39
|
|
18
40
|
private
|
19
41
|
|
42
|
+
# Selects the subscribers for the specified event.
|
43
|
+
#
|
44
|
+
# @param event [String] The name of event.
|
45
|
+
#
|
46
|
+
# @return [Array<Mailgun::Tracking::Subscriber::AllMessages, Mailgun::Tracking::Subscriber::Evented>]
|
47
|
+
# The list of subscribers.
|
20
48
|
def subscribers_for(event)
|
21
49
|
@subscribers.select { |subscriber| subscriber.subscribed_to?(event) }
|
22
50
|
end
|
@@ -1,16 +1,25 @@
|
|
1
1
|
module Mailgun
|
2
2
|
module Tracking
|
3
|
+
# Rack-based middleware to handle event notifications.
|
3
4
|
class Middleware
|
5
|
+
# Initializes a new Middleware object.
|
6
|
+
#
|
7
|
+
# @param app the next Rack middleware in the stack.
|
4
8
|
def initialize(app)
|
5
9
|
@app = app
|
6
10
|
end
|
7
11
|
|
12
|
+
# Responds to Rack requests.
|
13
|
+
#
|
14
|
+
# @param env [Hash] Environment hash.
|
15
|
+
#
|
16
|
+
# @return [Array(Numeric,Hash,Array)] The Rack-style response.
|
8
17
|
def call(env)
|
9
18
|
@env = env
|
10
19
|
|
11
20
|
if mailgun_tracking_request?
|
12
21
|
Mailgun::Tracking.notifier.broadcast(request.params.fetch('event'), request.params)
|
13
|
-
|
22
|
+
null_response
|
14
23
|
else
|
15
24
|
@app.call(env)
|
16
25
|
end
|
@@ -18,15 +27,26 @@ module Mailgun
|
|
18
27
|
|
19
28
|
private
|
20
29
|
|
30
|
+
# Interface to a Rack environment.
|
31
|
+
#
|
32
|
+
# @return [Rack::Request]
|
21
33
|
def request
|
22
34
|
::Rack::Request.new(@env)
|
23
35
|
end
|
24
36
|
|
37
|
+
# Checks if the path of the request is equal to the endpoint.
|
38
|
+
#
|
39
|
+
# @return [Boolean]
|
25
40
|
def mailgun_tracking_request?
|
26
41
|
return false unless request.post?
|
27
42
|
return false unless request.path == Mailgun::Tracking.endpoint
|
28
43
|
true
|
29
44
|
end
|
45
|
+
|
46
|
+
# @return [Array(Numeric,Hash,Array)] The Rack-style response.
|
47
|
+
def null_response
|
48
|
+
[200, {}, []]
|
49
|
+
end
|
30
50
|
end
|
31
51
|
end
|
32
52
|
end
|
@@ -1,18 +1,43 @@
|
|
1
1
|
module Mailgun
|
2
2
|
module Tracking
|
3
|
+
# Wraps the {Listener} which gives a friendlier way to subscribe or broadcast.
|
3
4
|
class Notifier
|
5
|
+
# Initializes a new Notifier object.
|
6
|
+
#
|
7
|
+
# @param listener [Mailgun::Tracking::Listener] Event listener.
|
8
|
+
#
|
9
|
+
# @return [Mailgun::Tracking::Notifier]
|
4
10
|
def initialize(listener = Listener.new)
|
5
11
|
@listener ||= listener
|
6
12
|
end
|
7
13
|
|
14
|
+
# Adds subscriber for the specified event.
|
15
|
+
#
|
16
|
+
# @param event [Symbol, String] The name of event.
|
17
|
+
# @param callable [Proc, Class] The listener of event.
|
18
|
+
# The callable objects should respond to call.
|
19
|
+
#
|
20
|
+
# @return [NilClass]
|
8
21
|
def subscribe(event, callable = Proc.new)
|
9
22
|
listener.add_subscriber(event, callable)
|
10
23
|
end
|
11
24
|
|
25
|
+
# Adds a subscriber for all events.
|
26
|
+
#
|
27
|
+
# @param callable [Proc, Class] The listener of event.
|
28
|
+
# The callable objects should respond to call.
|
29
|
+
#
|
30
|
+
# @return [NilClass]
|
12
31
|
def all(callable = Proc.new)
|
13
32
|
listener.add_subscriber(nil, callable)
|
14
33
|
end
|
15
34
|
|
35
|
+
# Broadcasts parameters to event subscribers.
|
36
|
+
#
|
37
|
+
# @param event [String] The name of event.
|
38
|
+
# @param payload [Hash] The response parameters.
|
39
|
+
#
|
40
|
+
# @return [NilClass]
|
16
41
|
def broadcast(event, payload)
|
17
42
|
Signature.verify!(payload)
|
18
43
|
listener.broadcast(event, payload)
|
@@ -20,6 +45,7 @@ module Mailgun
|
|
20
45
|
|
21
46
|
private
|
22
47
|
|
48
|
+
# @return [Mailgun::Tracking::Listener]
|
23
49
|
attr_reader :listener
|
24
50
|
end
|
25
51
|
end
|
@@ -2,29 +2,47 @@ require 'openssl'
|
|
2
2
|
|
3
3
|
module Mailgun
|
4
4
|
module Tracking
|
5
|
+
# A Mailgun::Tracking::Signature object is used to verify the signature.
|
5
6
|
class Signature
|
7
|
+
# Verify the signature of the response parameters.
|
8
|
+
#
|
9
|
+
# @param payload [Hash]
|
10
|
+
# @raise [InvalidSignature] Error raised when signature is invalid.
|
11
|
+
#
|
12
|
+
# @return [Boolean]
|
13
|
+
# Always returns true.
|
6
14
|
def self.verify!(payload)
|
7
15
|
signature = new(payload)
|
8
16
|
raise InvalidSignature unless signature.valid?
|
9
17
|
true
|
10
18
|
end
|
11
19
|
|
20
|
+
# Initializes a new Signature object.
|
21
|
+
#
|
22
|
+
# @param payload [Hash]
|
23
|
+
#
|
24
|
+
# @return [Mailgun::Tracking::Signature]
|
12
25
|
def initialize(payload)
|
13
26
|
@token = payload.fetch('token')
|
14
27
|
@timestamp = payload.fetch('timestamp')
|
15
28
|
@signature = payload.fetch('signature')
|
16
29
|
end
|
17
30
|
|
31
|
+
# @return [Boolean]
|
18
32
|
def valid?
|
19
33
|
@signature == OpenSSL::HMAC.hexdigest(digest, Mailgun::Tracking.api_key, data)
|
20
34
|
end
|
21
35
|
|
22
36
|
private
|
23
37
|
|
38
|
+
# @return [OpenSSL::Digest::SHA256]
|
24
39
|
def digest
|
25
40
|
OpenSSL::Digest::SHA256.new
|
26
41
|
end
|
27
42
|
|
43
|
+
# Joins the timestamp and the response token.
|
44
|
+
#
|
45
|
+
# @return [String]
|
28
46
|
def data
|
29
47
|
[@timestamp, @token].join
|
30
48
|
end
|
@@ -3,7 +3,15 @@ require 'mailgun/tracking/subscriber/evented'
|
|
3
3
|
|
4
4
|
module Mailgun
|
5
5
|
module Tracking
|
6
|
+
# Namespace for classes that wraps subscribers.
|
6
7
|
module Subscriber
|
8
|
+
# Determines the type of subscription.
|
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, Mailgun::Tracking::Subscriber::AllMessages]
|
7
15
|
def self.for(name, callable)
|
8
16
|
if name
|
9
17
|
Evented.new(name, callable)
|
@@ -1,15 +1,31 @@
|
|
1
1
|
module Mailgun
|
2
2
|
module Tracking
|
3
3
|
module Subscriber
|
4
|
+
# Wraps the subscriber for events.
|
4
5
|
class AllMessages
|
6
|
+
# Initializes a new AllMessages object.
|
7
|
+
#
|
8
|
+
# @param callable [Proc, Class] The callable object.
|
9
|
+
# The callable objects should respond to call.
|
10
|
+
#
|
11
|
+
# @return [Mailgun::Tracking::Subscriber::AllMessages]
|
5
12
|
def initialize(callable)
|
6
13
|
@callable = callable
|
7
14
|
end
|
8
15
|
|
16
|
+
# Invokes the callable object.
|
17
|
+
#
|
18
|
+
# @param payload [Hash] The response parameters.
|
19
|
+
#
|
20
|
+
# @return [void]
|
9
21
|
def call(payload)
|
10
22
|
@callable.call(payload)
|
11
23
|
end
|
12
24
|
|
25
|
+
# Checks if a callable object is a subscribed to the specified event.
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
# Always returns true.
|
13
29
|
def subscribed_to?(*)
|
14
30
|
true
|
15
31
|
end
|
@@ -1,16 +1,34 @@
|
|
1
1
|
module Mailgun
|
2
2
|
module Tracking
|
3
3
|
module Subscriber
|
4
|
+
# Wraps the evented subscriber.
|
4
5
|
class Evented
|
6
|
+
# Initializes a new Evented object.
|
7
|
+
#
|
8
|
+
# @param name [Symbol, String] The name of event.
|
9
|
+
# @param callable [Proc, Class] The callable object.
|
10
|
+
# The callable objects should respond to call.
|
11
|
+
#
|
12
|
+
# @return [Mailgun::Tracking::Subscriber::Evented]
|
5
13
|
def initialize(name, callable)
|
6
14
|
@name = name.to_sym
|
7
15
|
@callable = callable
|
8
16
|
end
|
9
17
|
|
18
|
+
# Invokes the callable object.
|
19
|
+
#
|
20
|
+
# @param payload [Hash] The response parameters.
|
21
|
+
#
|
22
|
+
# @return [void]
|
10
23
|
def call(payload)
|
11
24
|
@callable.call(payload)
|
12
25
|
end
|
13
26
|
|
27
|
+
# Checks if a callable object is a subscribed to the specified event.
|
28
|
+
#
|
29
|
+
# @param name [String] The name of event.
|
30
|
+
#
|
31
|
+
# @return [Boolean]
|
14
32
|
def subscribed_to?(name)
|
15
33
|
@name == name.to_sym
|
16
34
|
end
|
@@ -11,7 +11,7 @@ RSpec.describe Mailgun::Tracking::Signature do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
context 'when the signature comparison is unsuccessful' do
|
14
|
-
before { payload
|
14
|
+
before { payload['timestamp'] = '' }
|
15
15
|
|
16
16
|
it { expect { described_class.verify!(payload) }.to raise_error(Mailgun::Tracking::InvalidSignature) }
|
17
17
|
end
|
@@ -23,7 +23,7 @@ RSpec.describe Mailgun::Tracking::Signature do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
context 'when the signature comparison is unsuccessful' do
|
26
|
-
before { payload
|
26
|
+
before { payload['timestamp'] = '' }
|
27
27
|
|
28
28
|
it { is_expected.not_to be_valid }
|
29
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mailgun-tracking
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Artem Chubchenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-07-
|
11
|
+
date: 2017-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Integration with Mailgun Webhooks
|
14
14
|
email:
|
@@ -17,6 +17,8 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- lib/generators/mailgun/tracking/install_generator.rb
|
21
|
+
- lib/generators/mailgun/tracking/templates/mailgun_tracking.rb.erb
|
20
22
|
- lib/mailgun/tracking.rb
|
21
23
|
- lib/mailgun/tracking/exceptions.rb
|
22
24
|
- lib/mailgun/tracking/listener.rb
|
@@ -66,16 +68,16 @@ signing_key:
|
|
66
68
|
specification_version: 4
|
67
69
|
summary: Integration with Mailgun Webhooks
|
68
70
|
test_files:
|
69
|
-
- spec/fixtures/delivered.json
|
70
|
-
- spec/spec_helper.rb
|
71
|
-
- spec/mailgun/tracking_spec.rb
|
72
|
-
- spec/mailgun/tracking/listener_spec.rb
|
73
|
-
- spec/mailgun/tracking/middleware_spec.rb
|
74
|
-
- spec/mailgun/tracking/notifier_spec.rb
|
75
|
-
- spec/mailgun/tracking/subscriber_spec.rb
|
76
71
|
- spec/mailgun/tracking/signature_spec.rb
|
77
|
-
- spec/mailgun/tracking/subscriber/evented_spec.rb
|
78
72
|
- spec/mailgun/tracking/subscriber/all_messages_spec.rb
|
79
|
-
- spec/
|
73
|
+
- spec/mailgun/tracking/subscriber/evented_spec.rb
|
74
|
+
- spec/mailgun/tracking/subscriber_spec.rb
|
75
|
+
- spec/mailgun/tracking/listener_spec.rb
|
76
|
+
- spec/mailgun/tracking/notifier_spec.rb
|
77
|
+
- spec/mailgun/tracking/middleware_spec.rb
|
78
|
+
- spec/mailgun/tracking_spec.rb
|
80
79
|
- spec/support/rack_helpers.rb
|
80
|
+
- spec/support/fixture.rb
|
81
81
|
- spec/support/shared_examples/subscriber.rb
|
82
|
+
- spec/fixtures/delivered.json
|
83
|
+
- spec/spec_helper.rb
|