mailgun-tracking 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/mailgun/tracking.rb +45 -0
- data/lib/mailgun/tracking/exceptions.rb +6 -0
- data/lib/mailgun/tracking/listener.rb +19 -0
- data/lib/mailgun/tracking/rack.rb +32 -0
- data/lib/mailgun/tracking/signature.rb +33 -0
- data/lib/mailgun/tracking/version.rb +5 -0
- data/spec/fixtures/delivered.json +8 -0
- data/spec/mailgun/tracking/listener_spec.rb +42 -0
- data/spec/mailgun/tracking/rack_spec.rb +44 -0
- data/spec/mailgun/tracking/signature_spec.rb +31 -0
- data/spec/mailgun/tracking_spec.rb +49 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/fixture.rb +17 -0
- data/spec/support/rack_helpers.rb +5 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9dca3bb2ecebb3b77835850e80c913df78b24c03
|
4
|
+
data.tar.gz: 3b468bcfc59e17b6bbad6d92dbc5f7b57d7d2be9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eb8b9b8227712aea6e298118aa98a39d9b82590b611056aa56f914072183b55c98cf53e2f14ceec8215247a690bed332f0a2fbd2eda670cf462c13b75098fd6e
|
7
|
+
data.tar.gz: 10652711a22a8d7c8fcee8404cc3dc4524e189cf0ea208211d7e43429bda35d831fb62a58349364d47d8afa8d97ce6c5506a012cb780cd1b0eeddd6fff9fb666
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'mailgun/tracking/exceptions'
|
2
|
+
require 'mailgun/tracking/listener'
|
3
|
+
require 'mailgun/tracking/rack'
|
4
|
+
require 'mailgun/tracking/signature'
|
5
|
+
require 'mailgun/tracking/version'
|
6
|
+
|
7
|
+
module Mailgun
|
8
|
+
module Tracking
|
9
|
+
DEFAULT_ENDPOINT = '/mailgun'.freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :api_key,
|
13
|
+
:endpoint
|
14
|
+
|
15
|
+
def configure
|
16
|
+
yield(self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.endpoint = DEFAULT_ENDPOINT
|
21
|
+
|
22
|
+
module_function
|
23
|
+
|
24
|
+
def subscribe(event, &block)
|
25
|
+
listener.add_subscriber(event, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def broadcast(event, payload)
|
29
|
+
Signature.verify!(payload)
|
30
|
+
listener.broadcast(event, payload)
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_cached_variables
|
34
|
+
@listener = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
module_function
|
40
|
+
|
41
|
+
def listener
|
42
|
+
@listener ||= Listener.new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Mailgun
|
2
|
+
module Tracking
|
3
|
+
class Listener
|
4
|
+
attr_reader :subscribers
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@subscribers = Hash.new { |h, k| h[k] = [] }
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_subscriber(event, &block)
|
11
|
+
@subscribers[event.to_sym] << block
|
12
|
+
end
|
13
|
+
|
14
|
+
def broadcast(event, payload)
|
15
|
+
@subscribers[event.to_sym].each { |subscriber| subscriber.call(payload) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Mailgun
|
2
|
+
module Tracking
|
3
|
+
class Rack
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
@env = env
|
10
|
+
|
11
|
+
if mailgun_tracking_request?
|
12
|
+
Mailgun::Tracking.broadcast(request.params.fetch('event'), request.params)
|
13
|
+
[200, {}, []]
|
14
|
+
else
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def request
|
22
|
+
::Rack::Request.new(@env)
|
23
|
+
end
|
24
|
+
|
25
|
+
def mailgun_tracking_request?
|
26
|
+
return false unless request.post?
|
27
|
+
return false unless request.path == Mailgun::Tracking.endpoint
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Mailgun
|
4
|
+
module Tracking
|
5
|
+
class Signature
|
6
|
+
def self.verify!(payload)
|
7
|
+
signature = new(payload)
|
8
|
+
raise InvalidSignature unless signature.valid?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(payload)
|
13
|
+
@token = payload.fetch('token')
|
14
|
+
@timestamp = payload.fetch('timestamp')
|
15
|
+
@signature = payload.fetch('signature')
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
@signature == OpenSSL::HMAC.hexdigest(digest, Mailgun::Tracking.api_key, data)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def digest
|
25
|
+
OpenSSL::Digest::SHA256.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def data
|
29
|
+
[@timestamp, @token].join
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
{
|
2
|
+
"domain": "mail.example.com",
|
3
|
+
"Message-Id": "<20160329071939.35138.9413.6915422C@mail.example.com>",
|
4
|
+
"event": "delivered",
|
5
|
+
"timestamp": "1499697910",
|
6
|
+
"token": "b5751a49a024483da8d41c3684f98b8f",
|
7
|
+
"signature": "374e0b1a3deeb57318c783d43ff71093fbf26406a452761dab91bf346a93b49e"
|
8
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Mailgun::Tracking::Listener do
|
4
|
+
subject(:listener) { described_class.new }
|
5
|
+
|
6
|
+
let(:block) { proc {} }
|
7
|
+
|
8
|
+
describe '#add_subscriber' do
|
9
|
+
it 'adds subscriber' do
|
10
|
+
expect { listener.add_subscriber(:delivered, &block) }.to change(listener, :subscribers)
|
11
|
+
.from({})
|
12
|
+
.to(delivered: [block])
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'adds multiple subscribers with the same name' do
|
16
|
+
expect do
|
17
|
+
listener.add_subscriber(:delivered, &block)
|
18
|
+
listener.add_subscriber('delivered', &block)
|
19
|
+
end.to change(listener, :subscribers).from({}).to(delivered: [block, block])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#broadcast' do
|
24
|
+
let(:payload) { fixture('delivered.json') }
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow(block).to receive(:call)
|
28
|
+
listener.add_subscriber(:delivered, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'executes block' do
|
32
|
+
listener.broadcast(:delivered, payload)
|
33
|
+
expect(block).to have_received(:call).with(payload)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'executes multiple blocks' do
|
37
|
+
listener.add_subscriber(:delivered, &block)
|
38
|
+
listener.broadcast('delivered', payload)
|
39
|
+
expect(block).to have_received(:call).with(payload).twice
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Mailgun::Tracking::Rack do
|
4
|
+
subject(:rack) { described_class.new(app) }
|
5
|
+
|
6
|
+
describe '#call' do
|
7
|
+
let(:app) { proc { [200, {}, []] } }
|
8
|
+
let(:payload) { fixture('delivered.json') }
|
9
|
+
let(:code) { rack.call(env)[0] }
|
10
|
+
|
11
|
+
before { allow(Mailgun::Tracking).to receive(:broadcast) }
|
12
|
+
|
13
|
+
context 'when a request to a endpoint with a POST method' do
|
14
|
+
let(:env) { env_for('http://localhost:3000/mailgun', method: :post, params: payload) }
|
15
|
+
|
16
|
+
it { expect(code).to eq(200) }
|
17
|
+
it do
|
18
|
+
code
|
19
|
+
expect(Mailgun::Tracking).to have_received(:broadcast).with('delivered', payload)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when a request to a endpoint with a POST method' do
|
24
|
+
let(:env) { env_for('http://localhost:3000/mailgun', method: :get, params: payload) }
|
25
|
+
|
26
|
+
it { expect(code).to eq(200) }
|
27
|
+
it do
|
28
|
+
code
|
29
|
+
expect(Mailgun::Tracking).not_to have_received(:broadcast)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when the request is not to the endpoint' do
|
34
|
+
let(:env) { env_for('http://localhost:3000') }
|
35
|
+
let(:code) { rack.call(env)[0] }
|
36
|
+
|
37
|
+
it { expect(code).to eq(200) }
|
38
|
+
it do
|
39
|
+
code
|
40
|
+
expect(Mailgun::Tracking).not_to have_received(:broadcast)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Mailgun::Tracking::Signature do
|
4
|
+
subject(:signature) { described_class.new(payload) }
|
5
|
+
|
6
|
+
let(:payload) { fixture('delivered.json') }
|
7
|
+
|
8
|
+
describe '.verify!' do
|
9
|
+
context 'when the signature comparison is successful' do
|
10
|
+
it { expect(described_class.verify!(payload)).to be true }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when the signature comparison is unsuccessful' do
|
14
|
+
before { payload.merge!('timestamp' => '') }
|
15
|
+
|
16
|
+
it { expect { described_class.verify!(payload) }.to raise_error(Mailgun::Tracking::InvalidSignature) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#valid?' do
|
21
|
+
context 'when the signature comparison is successful' do
|
22
|
+
it { is_expected.to be_valid }
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when the signature comparison is unsuccessful' do
|
26
|
+
before { payload.merge!('timestamp' => '') }
|
27
|
+
|
28
|
+
it { is_expected.not_to be_valid }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Mailgun::Tracking do
|
4
|
+
describe '.configure' do
|
5
|
+
it 'setup block yields self' do
|
6
|
+
described_class.configure do |config|
|
7
|
+
expect(described_class).to eq(config)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.subscribe' do
|
13
|
+
let(:listener) { instance_double(Mailgun::Tracking::Listener) }
|
14
|
+
|
15
|
+
before do
|
16
|
+
described_class.clear_cached_variables
|
17
|
+
allow(listener).to receive(:add_subscriber)
|
18
|
+
allow(Mailgun::Tracking::Listener).to receive(:new) { listener }
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'subscribes on event' do
|
22
|
+
described_class.subscribe(:delivered) {}
|
23
|
+
|
24
|
+
expect(listener).to have_received(:add_subscriber)
|
25
|
+
.with(:delivered)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '.broadcast' do
|
30
|
+
let(:payload) { fixture('delivered.json') }
|
31
|
+
let(:listener) { instance_double(Mailgun::Tracking::Listener) }
|
32
|
+
|
33
|
+
before do
|
34
|
+
described_class.clear_cached_variables
|
35
|
+
allow(Mailgun::Tracking::Signature).to receive(:verify!)
|
36
|
+
allow(listener).to receive(:broadcast)
|
37
|
+
allow(Mailgun::Tracking::Listener).to receive(:new) { listener }
|
38
|
+
described_class.broadcast(:delivered, payload)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'verify signature' do
|
42
|
+
expect(Mailgun::Tracking::Signature).to have_received(:verify!).with(payload)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'broadcasts an event' do
|
46
|
+
expect(listener).to have_received(:broadcast).with(:delivered, payload)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'simplecov'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'mailgun/tracking'
|
5
|
+
|
6
|
+
require_relative 'support/fixture'
|
7
|
+
require_relative 'support/rack_helpers'
|
8
|
+
|
9
|
+
SimpleCov.start
|
10
|
+
|
11
|
+
Mailgun::Tracking.configure do |config|
|
12
|
+
config.api_key = 'key-qblubkqnkdn4lfes5oscf57ryllaia42'
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.include(RackHelpers)
|
17
|
+
|
18
|
+
config.expect_with :rspec do |c|
|
19
|
+
c.syntax = :expect
|
20
|
+
end
|
21
|
+
|
22
|
+
config.order = :random
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class FixtureFinder
|
4
|
+
FIXTURE_PATH = File.expand_path('../fixtures', __dir__)
|
5
|
+
|
6
|
+
def initialize(file)
|
7
|
+
@file = file
|
8
|
+
end
|
9
|
+
|
10
|
+
def find
|
11
|
+
File.read(FIXTURE_PATH + '/' + @file)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def fixture(file)
|
16
|
+
JSON.parse(FixtureFinder.new(file).find)
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mailgun-tracking
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Artem Chubchenko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-14 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Integration for Mailgun Webhooks
|
14
|
+
email:
|
15
|
+
- artem.chubchenko@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/mailgun/tracking.rb
|
21
|
+
- lib/mailgun/tracking/exceptions.rb
|
22
|
+
- lib/mailgun/tracking/listener.rb
|
23
|
+
- lib/mailgun/tracking/rack.rb
|
24
|
+
- lib/mailgun/tracking/signature.rb
|
25
|
+
- lib/mailgun/tracking/version.rb
|
26
|
+
- spec/fixtures/delivered.json
|
27
|
+
- spec/mailgun/tracking/listener_spec.rb
|
28
|
+
- spec/mailgun/tracking/rack_spec.rb
|
29
|
+
- spec/mailgun/tracking/signature_spec.rb
|
30
|
+
- spec/mailgun/tracking_spec.rb
|
31
|
+
- spec/spec_helper.rb
|
32
|
+
- spec/support/fixture.rb
|
33
|
+
- spec/support/rack_helpers.rb
|
34
|
+
homepage: https://github.com/chubchenko/mailgun-tracking
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.0.0
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 2.5.1
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: Integration for Mailgun Webhooks
|
58
|
+
test_files:
|
59
|
+
- spec/fixtures/delivered.json
|
60
|
+
- spec/spec_helper.rb
|
61
|
+
- spec/mailgun/tracking_spec.rb
|
62
|
+
- spec/mailgun/tracking/listener_spec.rb
|
63
|
+
- spec/mailgun/tracking/rack_spec.rb
|
64
|
+
- spec/mailgun/tracking/signature_spec.rb
|
65
|
+
- spec/support/fixture.rb
|
66
|
+
- spec/support/rack_helpers.rb
|