postmortem 0.2.0 → 0.2.6

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.
@@ -6,7 +6,7 @@ require 'postmortem/adapters/mail'
6
6
  require 'postmortem/adapters/pony'
7
7
 
8
8
  module Postmortem
9
- # Adapters for various email senders (e.g. ActionMailer).
9
+ # Adapters for various email senders (e.g. Mail, Pony).
10
10
  module Adapters
11
11
  end
12
12
  end
@@ -7,53 +7,18 @@ module Postmortem
7
7
  private
8
8
 
9
9
  def adapted
10
- {
11
- from: mail.from,
12
- reply_to: mail.reply_to,
13
- to: mail.to,
14
- cc: mail.cc,
15
- bcc: normalized_bcc,
16
- subject: mail.subject,
17
- text_body: text_part,
18
- html_body: html_part
19
- }
20
- end
21
-
22
- def text_part
23
- return nil unless text?
24
- return mail.body.decoded unless mail.text_part
25
-
26
- mail.text_part.decoded
27
- end
28
-
29
- def html_part
30
- return nil unless html?
31
- return mail.body.decoded unless mail.html_part
32
-
33
- mail.html_part.decoded
34
- end
35
-
36
- def mail
37
- @mail ||= ::Mail.new(@data[:mail])
10
+ %i[from reply_to to cc subject message_id]
11
+ .map { |field| [field, mail.public_send(field)] }
12
+ .to_h
13
+ .merge({ text_body: text_part, html_body: html_part, bcc: normalized_bcc })
38
14
  end
39
15
 
40
16
  def normalized_bcc
41
17
  ::Mail.new(to: @data[:bcc]).to
42
18
  end
43
19
 
44
- def text?
45
- return true unless mail.has_content_type?
46
- return true if mail.content_type.include?('text/plain')
47
- return true if mail.multipart? && mail.text_part
48
-
49
- false
50
- end
51
-
52
- def html?
53
- return true if mail.has_content_type? && mail.content_type.include?('text/html')
54
- return true if mail.multipart? && mail.html_part
55
-
56
- false
20
+ def mail
21
+ @mail ||= ::Mail.new(@data[:mail])
57
22
  end
58
23
  end
59
24
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Postmortem
4
4
  module Adapters
5
- FIELDS = %i[from reply_to to cc bcc subject text_body html_body].freeze
5
+ FIELDS = %i[from reply_to to cc bcc subject text_body html_body message_id].freeze
6
6
 
7
7
  # Base interface implementation for all Postmortem adapters.
8
8
  class Base
@@ -16,7 +16,11 @@ module Postmortem
16
16
  end
17
17
 
18
18
  def serializable
19
- FIELDS.map { |field| [camelize(field.to_s), public_send(field)] }.to_h
19
+ (%i[id] + FIELDS).map { |field| [camelize(field.to_s), public_send(field)] }.to_h
20
+ end
21
+
22
+ def id
23
+ @id ||= SecureRandom.uuid
20
24
  end
21
25
 
22
26
  FIELDS.each do |method_name|
@@ -25,6 +29,14 @@ module Postmortem
25
29
  end
26
30
  end
27
31
 
32
+ def html_body
33
+ @adapted[:html_body].to_s
34
+ end
35
+
36
+ def text_body
37
+ @adapted[:text_body].to_s
38
+ end
39
+
28
40
  private
29
41
 
30
42
  def adapted
@@ -38,6 +50,35 @@ module Postmortem
38
50
  .map { |substring, index| index.zero? ? substring : substring.capitalize }
39
51
  .join
40
52
  end
53
+
54
+ def text_part
55
+ return nil unless text?
56
+ return mail.body.decoded unless mail.text_part
57
+
58
+ mail.text_part.decoded
59
+ end
60
+
61
+ def html_part
62
+ return nil unless html?
63
+ return mail.body.decoded unless mail.html_part
64
+
65
+ mail.html_part.decoded
66
+ end
67
+
68
+ def text?
69
+ return true unless mail.has_content_type?
70
+ return true if mail.content_type.include?('text/plain')
71
+ return true if mail.multipart? && mail.text_part
72
+
73
+ false
74
+ end
75
+
76
+ def html?
77
+ return true if mail.has_content_type? && mail.content_type.include?('text/html')
78
+ return true if mail.multipart? && mail.html_part
79
+
80
+ false
81
+ end
41
82
  end
42
83
  end
43
84
  end
@@ -7,10 +7,10 @@ module Postmortem
7
7
  private
8
8
 
9
9
  def adapted
10
- %i[from reply_to to cc bcc subject]
11
- .map { |field| [field, @data.public_send(field)] }
10
+ %i[from reply_to to cc bcc subject message_id]
11
+ .map { |field| [field, mail.public_send(field)] }
12
12
  .to_h
13
- .merge({ text_body: @data.text_part, html_body: @data.html_part })
13
+ .merge({ text_body: text_part, html_body: html_part })
14
14
  end
15
15
 
16
16
  def mail
@@ -8,14 +8,11 @@ module Postmortem
8
8
 
9
9
  def adapted
10
10
  {
11
- from: mail.from,
12
- reply_to: mail.reply_to,
13
- to: mail.to,
14
- cc: mail.cc,
15
- bcc: mail.bcc,
11
+ from: mail.from, reply_to: mail.reply_to, to: mail.to, cc: mail.cc, bcc: mail.bcc,
16
12
  subject: mail.subject,
17
13
  text_body: @data[:body],
18
- html_body: @data[:html_body]
14
+ html_body: @data[:html_body],
15
+ message_id: mail.message_id # We use a synthetic Mail instance so this is a bit useless.
19
16
  }
20
17
  end
21
18
 
@@ -3,17 +3,9 @@
3
3
  module Postmortem
4
4
  # Provides interface for configuring Postmortem and implements sensible defaults.
5
5
  class Configuration
6
- attr_writer :colorize, :timestamp, :mail_skip_delivery, :token
6
+ attr_writer :colorize, :mail_skip_delivery
7
7
  attr_accessor :log_path
8
8
 
9
- def timestamp
10
- defined?(@timestamp) ? @timestamp : true
11
- end
12
-
13
- def token
14
- defined?(@token) ? @token : true
15
- end
16
-
17
9
  def colorize
18
10
  defined?(@colorize) ? @colorize : true
19
11
  end
@@ -7,33 +7,27 @@ module Postmortem
7
7
 
8
8
  def initialize(mail)
9
9
  @mail = mail
10
- @path = Postmortem.config.preview_directory.join(filename)
11
- @index_path = Postmortem.config.preview_directory.join('index.html')
10
+ @path = Postmortem.config.preview_directory.join('index.html')
11
+ @index_path = Postmortem.config.preview_directory.join('postmortem_index.html')
12
+ @identity_path = Postmortem.config.preview_directory.join('postmortem_identity.html')
12
13
  end
13
14
 
14
15
  def record
15
16
  path.parent.mkpath
16
17
  content = Layout.new(@mail).content
17
18
  path.write(content)
18
- index_path.write(Index.new(index_path, path, timestamp, @mail).content)
19
+ index_path.write(index.content)
20
+ @identity_path.write(identity.content)
19
21
  end
20
22
 
21
23
  private
22
24
 
23
- def filename
24
- format = '%Y-%m-%d_%H-%M-%S'
25
- timestamp_chunk = Postmortem.config.timestamp ? "#{timestamp.strftime(format)}__" : nil
26
- token_chunk = Postmortem.config.token ? "#{token}__" : nil
27
-
28
- "#{timestamp_chunk}#{token_chunk}#{safe_subject}.html"
29
- end
30
-
31
- def timestamp
32
- @timestamp ||= Time.now
25
+ def index
26
+ @index ||= Index.new(index_path, path, @mail)
33
27
  end
34
28
 
35
- def token
36
- SecureRandom.hex(4)
29
+ def identity
30
+ @identity ||= Identity.new
37
31
  end
38
32
 
39
33
  def subject
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postmortem
4
+ # Provides an HTML document that announces a unique ID to a parent page via JS message events.
5
+ class Identity
6
+ def content
7
+ ERB.new(File.read(path), nil, '-').result(binding)
8
+ end
9
+
10
+ private
11
+
12
+ def uuid
13
+ @uuid ||= SecureRandom.uuid
14
+ end
15
+
16
+ def path
17
+ File.expand_path(File.join(__dir__, '..', '..', 'layout', 'postmortem_identity.html.erb'))
18
+ end
19
+ end
20
+ end
@@ -3,10 +3,9 @@
3
3
  module Postmortem
4
4
  # Generates and parses an index of previously-sent emails.
5
5
  class Index
6
- def initialize(index_path, mail_path, timestamp, mail)
6
+ def initialize(index_path, mail_path, mail)
7
7
  @index_path = index_path
8
8
  @mail_path = mail_path
9
- @timestamp = timestamp.iso8601
10
9
  @mail = mail
11
10
  end
12
11
 
@@ -15,23 +14,36 @@ module Postmortem
15
14
  ERB.new(File.read(template_path), nil, '-').result(binding)
16
15
  end
17
16
 
17
+ def size
18
+ encoded_index.size
19
+ end
20
+
18
21
  private
19
22
 
23
+ def uuid
24
+ @uuid ||= SecureRandom.uuid
25
+ end
26
+
27
+ def timestamp
28
+ Time.now.iso8601
29
+ end
30
+
20
31
  def encoded_index
21
32
  return [encoded_mail] unless @index_path.file?
22
33
 
23
- [encoded_mail] + lines[index(:start)..index(:end)]
34
+ @encoded_index ||= [encoded_mail] + lines[index(:start)..index(:end)]
24
35
  end
25
36
 
26
37
  def encoded_mail
27
- Base64.urlsafe_encode64(mail_data.to_json)
38
+ Base64.encode64(mail_data.to_json).split("\n").join
28
39
  end
29
40
 
30
41
  def mail_data
31
42
  {
32
43
  subject: @mail.subject || '(no subject)',
33
- timestamp: @timestamp,
44
+ timestamp: timestamp,
34
45
  path: @mail_path,
46
+ id: @mail.id,
35
47
  content: @mail.serializable
36
48
  }
37
49
  end
@@ -50,7 +62,7 @@ module Postmortem
50
62
  end
51
63
 
52
64
  def template_path
53
- File.expand_path(File.join(__dir__, '..', '..', 'layout', 'index.html.erb'))
65
+ File.expand_path(File.join(__dir__, '..', '..', 'layout', 'postmortem_index.html.erb'))
54
66
  end
55
67
  end
56
68
  end
@@ -25,10 +25,22 @@ module Postmortem
25
25
  default_layout_directory.join('layout.js').read
26
26
  end
27
27
 
28
+ def css_dependencies
29
+ default_layout_directory.join('dependencies.css').read
30
+ end
31
+
32
+ def javascript_dependencies
33
+ default_layout_directory.join('dependencies.js').read
34
+ end
35
+
28
36
  def headers_template
29
37
  default_layout_directory.join('headers_template.html').read
30
38
  end
31
39
 
40
+ def favicon_b64
41
+ default_layout_directory.join('favicon.b64').read
42
+ end
43
+
32
44
  private
33
45
 
34
46
  def default_layout_directory
@@ -38,7 +50,7 @@ module Postmortem
38
50
  def with_inlined_images(body)
39
51
  parsed = Nokogiri::HTML.parse(body)
40
52
  parsed.css('img').each do |img|
41
- uri = URI(img['src'])
53
+ uri = try_uri(img['src'])
42
54
  next unless local_file?(uri)
43
55
 
44
56
  path = located_image(uri)
@@ -48,6 +60,7 @@ module Postmortem
48
60
  end
49
61
 
50
62
  def local_file?(uri)
63
+ return false if uri.nil?
51
64
  return true if uri.host.nil?
52
65
  return true if /^www\.example\.[a-z]+$/.match(uri.host)
53
66
  return true if %w[127.0.0.1 localhost].include?(uri.host)
@@ -55,6 +68,12 @@ module Postmortem
55
68
  false
56
69
  end
57
70
 
71
+ def try_uri(uri)
72
+ URI(uri)
73
+ rescue URI::InvalidURIError
74
+ nil
75
+ end
76
+
58
77
  def located_image(uri)
59
78
  path = uri.path.partition('/').last
60
79
  common_locations.each do |location|
@@ -1,5 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ActiveSupport::Notifications.subscribe 'deliver.action_mailer' do |*args|
4
+ delivery_method = Rails.try(:application)
5
+ &.try(:config)
6
+ &.try(:action_mailer)
7
+ &.try(:delivery_method)
8
+ next if delivery_method.nil?
9
+ next if %i[sendmail smtp].include?(delivery_method&.to_sym) # Delegate to Mail plugin.
10
+
4
11
  Postmortem.record_delivery(Postmortem::Adapters::ActionMailer.new(args.extract_options!))
5
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Postmortem
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.6'
5
5
  end
data/postmortem.gemspec CHANGED
@@ -12,14 +12,14 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Preview HTML emails in your browser during development'
13
13
  spec.homepage = 'https://github.com/bobf/postmortem'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = spec.homepage
19
19
  spec.metadata['changelog_uri'] = 'https://github.com/bobf/postmortem/blob/master/README.md'
20
20
 
21
21
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|doc)/}) }
23
23
  end
24
24
  spec.bindir = 'bin'
25
25
  spec.executables = []
@@ -28,10 +28,13 @@ Gem::Specification.new do |spec|
28
28
  spec.add_runtime_dependency 'mail', '~> 2.7'
29
29
 
30
30
  spec.add_development_dependency 'actionmailer', '~> 6.0'
31
+ spec.add_development_dependency 'devpack', '~> 0.3.2'
32
+ spec.add_development_dependency 'faker', '~> 2.17'
31
33
  spec.add_development_dependency 'pony', '~> 1.13'
32
34
  spec.add_development_dependency 'rspec', '~> 3.9'
33
35
  spec.add_development_dependency 'rspec-its', '~> 1.3'
34
- spec.add_development_dependency 'rubocop', '~> 0.88.0'
36
+ spec.add_development_dependency 'rubocop', '~> 1.10'
37
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.2'
35
38
  spec.add_development_dependency 'strong_versions', '~> 0.4.5'
36
39
  spec.add_development_dependency 'timecop', '~> 0.9.1'
37
40
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postmortem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-10 00:00:00.000000000 Z
11
+ date: 2021-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mail
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: devpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: faker
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.17'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.17'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: pony
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +114,28 @@ dependencies:
86
114
  requirements:
87
115
  - - "~>"
88
116
  - !ruby/object:Gem::Version
89
- version: 0.88.0
117
+ version: '1.10'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.10'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop-rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.2'
90
132
  type: :development
91
133
  prerelease: false
92
134
  version_requirements: !ruby/object:Gem::Requirement
93
135
  requirements:
94
136
  - - "~>"
95
137
  - !ruby/object:Gem::Version
96
- version: 0.88.0
138
+ version: '2.2'
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: strong_versions
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -140,12 +182,15 @@ files:
140
182
  - Rakefile
141
183
  - bin/console
142
184
  - bin/setup
143
- - doc/screenshot.png
144
185
  - layout/default.html.erb
186
+ - layout/dependencies.css
187
+ - layout/dependencies.js
188
+ - layout/favicon.b64
145
189
  - layout/headers_template.html
146
- - layout/index.html.erb
147
190
  - layout/layout.css
148
191
  - layout/layout.js
192
+ - layout/postmortem_identity.html.erb
193
+ - layout/postmortem_index.html.erb
149
194
  - lib/postmortem.rb
150
195
  - lib/postmortem/adapters.rb
151
196
  - lib/postmortem/adapters/action_mailer.rb
@@ -154,6 +199,7 @@ files:
154
199
  - lib/postmortem/adapters/pony.rb
155
200
  - lib/postmortem/configuration.rb
156
201
  - lib/postmortem/delivery.rb
202
+ - lib/postmortem/identity.rb
157
203
  - lib/postmortem/index.rb
158
204
  - lib/postmortem/layout.rb
159
205
  - lib/postmortem/plugins/action_mailer.rb
@@ -176,7 +222,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
222
  requirements:
177
223
  - - ">="
178
224
  - !ruby/object:Gem::Version
179
- version: 2.3.0
225
+ version: 2.5.0
180
226
  required_rubygems_version: !ruby/object:Gem::Requirement
181
227
  requirements:
182
228
  - - ">="
@@ -184,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
230
  version: '0'
185
231
  requirements: []
186
232
  rubyforge_project:
187
- rubygems_version: 2.7.6
233
+ rubygems_version: 2.7.6.2
188
234
  signing_key:
189
235
  specification_version: 4
190
236
  summary: Development HTML Email Inspection Tool