mailgun-tracking 0.1.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 +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
|