dispatch-rails 0.9.0 → 0.10.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 797a73d62a39b0ab3640364d8ef71186b61f3233da2788f7733b72c85e77cc75
4
- data.tar.gz: 235fa20b827e25a6f0537e20d3cdd51221c0bebbed18013046901c9bdabf51ed
3
+ metadata.gz: a712af989dbaa59fcd32135400b74a6ddb41058f291d60dce865516e117a50c6
4
+ data.tar.gz: 1e1d88267bcffb7177556c6a0dd92c7e9e191356288e647881a1ef04410bbeb3
5
5
  SHA512:
6
- metadata.gz: f3cbf75d09473580ec5f66ffaaf7d7623631c1a453bbe9b67b86e091e4fbcb56cd7da1ea141080b6375b714802915c00c4fd9f64d7c649bb2de7e6d6b46d1c0b
7
- data.tar.gz: f24f36ca5d2769053faa65cb5b77da6e09a28aea5b4e3166bcbb64e2473729cd9b54df4d6936572fe3ecb41937cb5e43e32f0b9de24f2c27b1879532da0232de
6
+ metadata.gz: 891dee4bfc449f10ba4e4478ebd3fdd695d023f1853179563f63ccbbfa0a67c5431151576b403db02520c6132a0bb0ea6c93d64fe4999bde11006d820c39af26
7
+ data.tar.gz: f5be0e31f9ecbfce518e7533fc6a483c7bc12bcb5a624aae24a8617666232bf2d7a10f1787367313c7b741bf5ffe88dc07fff8e05bc0805e70012a50c65ef517
data/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ All notable changes to `dispatch-rails` are documented here. The format is based
4
+ on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.10.1] - 2026-06-18
8
+
9
+ ### Fixed
10
+ - The browser tracker/widget loaders import a fixed `/dispatch/error_tracker.js`
11
+ and `/dispatch/widget.js`, but nothing served those exact paths — the asset
12
+ pipeline only exposes the files under `/assets/...` (digested under Propshaft),
13
+ so the dynamic `import()` 404'd in every environment and the tracker/widget
14
+ silently never loaded. A new `AssetMiddleware` now serves both files at the
15
+ imported paths with a JS content type, requiring no host mount, route, or
16
+ precompile entry. (The config island stays a non-executable
17
+ `<script type="application/json">` and the loaders keep their nonce-aware
18
+ `javascript_tag`, so no CSP nonce is needed for either.)
19
+
20
+ ## [0.10.0] - 2026-06-18
21
+
22
+ ### Added
23
+ - `config.require_authenticated_user` (default `false`): when enabled,
24
+ `dispatch_widget_tag` renders nothing on requests where the `user` lambda
25
+ resolves to `nil` or a blank hash. Lets a host app keep the feedback widget
26
+ off public/unauthenticated pages (home page, EULA, privacy policy) so only
27
+ signed-in users can submit reports. Off by default to preserve existing
28
+ behavior — the stock `user` resolver returns `nil`, so defaulting it on would
29
+ hide the widget for apps that haven't wired up identity.
30
+
31
+ ## [0.9.0]
32
+
33
+ ### Added
34
+ - Capture SolidQueue infrastructure failures (pruned/orphaned jobs, thread
35
+ crashes, recurring-enqueue misses) that never flow through `Rails.error` or
36
+ ActiveJob.
37
+
38
+ ## [0.8.1]
39
+
40
+ ### Fixed
41
+ - `ReportingEndpointMiddleware` no longer swallows host-app errors when the
42
+ browser-reports path is not owned by the gem.
43
+
44
+ ## [0.8.0]
45
+
46
+ ### Added
47
+ - Browser CSP & Reporting-API capture (`capture_csp_violations`,
48
+ `capture_browser_reports`).
49
+
50
+ ## [0.7.0]
51
+
52
+ ### Added
53
+ - Process-lifecycle capture (crash-at-exit, rake failures, shutdown flush) for
54
+ Honeybadger parity.
data/README.md CHANGED
@@ -246,6 +246,22 @@ end
246
246
  <% end %>
247
247
  ```
248
248
 
249
+ ### Hiding the widget on public pages
250
+
251
+ By default the widget renders for everyone, including anonymous visitors. If you
252
+ only want signed-in users to be able to submit reports — keeping the button off
253
+ public surfaces like the home page, EULA, or privacy policy — opt in:
254
+
255
+ ```ruby
256
+ c.require_authenticated_user = true
257
+ ```
258
+
259
+ With this on, `dispatch_widget_tag` returns `nil` (renders nothing) on any
260
+ request where your `user` lambda resolves to `nil` or a blank hash, so make sure
261
+ that lambda is wired up to your auth. It's off by default because the stock
262
+ `user` resolver returns `nil`, and defaulting it on would hide the widget for
263
+ apps that haven't configured identity.
264
+
249
265
  ### Per-page severity / labels
250
266
 
251
267
  ```erb
@@ -13,6 +13,11 @@ module Dispatch
13
13
  return nil unless config.configured?
14
14
 
15
15
  user = config.user.respond_to?(:call) ? config.user.call(self) : nil
16
+ # Hide the widget on unauthenticated pages when the host opts in — keeps
17
+ # the report button off public surfaces (home page, EULA, …) where there
18
+ # is no signed-in user to attribute a report to.
19
+ return nil if config.require_authenticated_user && user.blank?
20
+
16
21
  base_metadata = config.metadata.respond_to?(:call) ? config.metadata.call(self) : {}
17
22
 
18
23
  page_metadata = base_metadata.to_h.merge(extra_metadata)
@@ -0,0 +1,65 @@
1
+ module Dispatch
2
+ module Rails
3
+ # Serves the gem's browser JS (error_tracker.js, widget.js) at the stable,
4
+ # non-digested /dispatch/<name>.js paths the view partials import from.
5
+ #
6
+ # The engine also registers the JS directory in config.assets.paths, but the
7
+ # partials deliberately import a fixed URL (so the inline loader needs no
8
+ # asset-pipeline lookup) — and the asset pipeline never exposes that exact URL:
9
+ # Propshaft serves the files under /assets/dispatch/<name>-<digest>.js, Sprockets
10
+ # under /assets/dispatch/<name>.js, and neither at /dispatch/<name>.js. With
11
+ # nothing serving that path the import 404s in every environment and the
12
+ # tracker/widget silently never load. This middleware closes that gap with no
13
+ # host mount, route, or precompile entry required.
14
+ #
15
+ # Registered outermost of the gem's middlewares (see Engine), so a matching GET
16
+ # short-circuits before the capture/heartbeat layers and never counts as traffic.
17
+ # It owns ONLY the two exact paths it knows; every other request passes straight
18
+ # through.
19
+ class AssetMiddleware
20
+ ASSET_DIR = File.expand_path("../../../app/assets/javascripts/dispatch", __dir__).freeze
21
+
22
+ # Exact request path => filename. A fixed allowlist (no path joining from the
23
+ # request) so a crafted PATH_INFO can never traverse out of ASSET_DIR.
24
+ ASSETS = {
25
+ "/dispatch/error_tracker.js" => "error_tracker.js",
26
+ "/dispatch/widget.js" => "widget.js"
27
+ }.freeze
28
+
29
+ def initialize(app)
30
+ @app = app
31
+ end
32
+
33
+ def call(env)
34
+ method = env["REQUEST_METHOD"]
35
+ filename = (method == "GET" || method == "HEAD") && ASSETS[env["PATH_INFO"]]
36
+ return @app.call(env) unless filename
37
+
38
+ serve(File.join(ASSET_DIR, filename), head: method == "HEAD")
39
+ end
40
+
41
+ private
42
+
43
+ def serve(path, head:)
44
+ body = File.binread(path)
45
+ # Lowercase header names: Rack 3 requires it, and middleware above us
46
+ # (Rack::ETag/ConditionalGet) only honors lowercase keys — a capitalized
47
+ # "Cache-Control" gets silently overridden with Rack's private default.
48
+ #
49
+ # Non-digested URL, so don't cache forever — a gem upgrade changes the bytes
50
+ # at the same path. An hour balances revalidation against churn; the host's
51
+ # Rack::ETag/ConditionalGet (outside this middleware) adds a content ETag and
52
+ # turns repeat fetches into 304s for free.
53
+ headers = {
54
+ "content-type" => "text/javascript; charset=utf-8",
55
+ "cache-control" => "public, max-age=3600",
56
+ "content-length" => body.bytesize.to_s
57
+ }
58
+ [200, headers, head ? [] : [body]]
59
+ rescue SystemCallError => e
60
+ warn "[dispatch-rails] failed to serve #{path}: #{e.class}: #{e.message}"
61
+ [404, { "content-type" => "text/plain" }, ["Not found"]]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -10,7 +10,8 @@ module Dispatch
10
10
  MODES = %i[widget errors_only].freeze
11
11
 
12
12
  # Bug-report widget
13
- attr_accessor :api_key, :endpoint, :user, :metadata, :capture_console, :capture_clicks, :button_position
13
+ attr_accessor :api_key, :endpoint, :user, :metadata, :capture_console, :capture_clicks, :button_position,
14
+ :require_authenticated_user
14
15
  # Mode + API-only context
15
16
  attr_accessor :mode, :context, :send_default_params
16
17
  # Exception tracking
@@ -38,6 +39,13 @@ module Dispatch
38
39
  @capture_console = false
39
40
  @capture_clicks = true # track the last few clicks as a "user path" for context
40
41
  @button_position = "bottom-right"
42
+ # Opt-in: hide the widget on requests with no resolved user (the `user`
43
+ # lambda returned nil/blank). Lets a host app keep the widget off
44
+ # public/unauthenticated pages — a home page, EULA, privacy policy —
45
+ # where anyone could otherwise submit a report. Off by default because
46
+ # the stock `user` resolver returns nil, so defaulting it on would hide
47
+ # the widget for every app that hasn't wired up identity.
48
+ @require_authenticated_user = false
41
49
 
42
50
  # Mode + API-only context
43
51
  @mode = :widget
@@ -23,6 +23,14 @@ module Dispatch
23
23
  if defined?(::Importmap) && app.respond_to?(:importmap)
24
24
  # Host app can pin "dispatch-widget" / "dispatch-error-tracker" in its importmap.
25
25
  end
26
+
27
+ # Serve error_tracker.js / widget.js at the /dispatch/<name>.js paths the
28
+ # view partials import from. Added first (so it's the OUTERMOST of the gem's
29
+ # middlewares — config.middleware.use appends, and this initializer runs
30
+ # before the capture/heartbeat ones below), letting a matching GET
31
+ # short-circuit before those layers. The asset pipeline only ever exposes
32
+ # these under /assets/..., so without this the import 404s everywhere.
33
+ app.config.middleware.use Dispatch::Rails::AssetMiddleware
26
34
  end
27
35
 
28
36
  # Server-side exception capture. The middleware is added last (innermost),
@@ -57,7 +57,10 @@ module Dispatch
57
57
  # A FRESH Rack triple per call — downstream middleware mutates the array and
58
58
  # headers, so a shared/frozen response would raise or corrupt across requests.
59
59
  def no_content
60
- [204, { "Content-Type" => "text/plain" }, []]
60
+ # Lowercase header name for Rack 3 conformance (it requires lowercase
61
+ # response header field names; capitalized keys can be dropped by
62
+ # downstream middleware).
63
+ [204, { "content-type" => "text/plain" }, []]
61
64
  end
62
65
 
63
66
  def handles?(config, env)
@@ -16,8 +16,11 @@ module Dispatch
16
16
  # never changes the status code, never swallows a propagating exception, and
17
17
  # passes through untouched anything it cannot safely parse.
18
18
  class ResponseAnnotator
19
- HEADER_REQUEST_ID = "X-Dispatch-Request-Id"
20
- HEADER_REPORT_URL = "X-Dispatch-Report-Url"
19
+ # Lowercase header names for Rack 3 conformance. HTTP header names are
20
+ # case-insensitive to clients, so emitting them lowercase changes nothing
21
+ # the caller sees, while satisfying Rack 3's lowercase-only requirement.
22
+ HEADER_REQUEST_ID = "x-dispatch-request-id"
23
+ HEADER_REPORT_URL = "x-dispatch-report-url"
21
24
  BODY_REQUEST_ID = "dispatch_request_id"
22
25
  BODY_REPORT_URL = "dispatch_report_url"
23
26
 
@@ -100,7 +103,7 @@ module Dispatch
100
103
  def replace_content_length!(headers, bytesize)
101
104
  headers.delete("Content-Length")
102
105
  headers.delete("content-length")
103
- headers["Content-Length"] = bytesize.to_s
106
+ headers["content-length"] = bytesize.to_s
104
107
  end
105
108
  end
106
109
  end
@@ -1,5 +1,5 @@
1
1
  module Dispatch
2
2
  module Rails
3
- VERSION = "0.9.0".freeze
3
+ VERSION = "0.10.1".freeze
4
4
  end
5
5
  end
@@ -4,6 +4,7 @@ require "dispatch/rails/event_builder"
4
4
  require "dispatch/rails/transport"
5
5
  require "dispatch/rails/reporter"
6
6
  require "dispatch/rails/middleware"
7
+ require "dispatch/rails/asset_middleware"
7
8
  require "dispatch/rails/reporting_endpoint_middleware"
8
9
  require "dispatch/rails/response_annotator"
9
10
  require "dispatch/rails/heartbeat_aggregator"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dispatch-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dispatch Team
@@ -39,6 +39,7 @@ executables: []
39
39
  extensions: []
40
40
  extra_rdoc_files: []
41
41
  files:
42
+ - CHANGELOG.md
42
43
  - README.md
43
44
  - app/assets/javascripts/dispatch/error_tracker.js
44
45
  - app/assets/javascripts/dispatch/widget.js
@@ -46,6 +47,7 @@ files:
46
47
  - app/views/dispatch/_error_tracker.html.erb
47
48
  - app/views/dispatch/_widget.html.erb
48
49
  - lib/dispatch-rails.rb
50
+ - lib/dispatch/rails/asset_middleware.rb
49
51
  - lib/dispatch/rails/configuration.rb
50
52
  - lib/dispatch/rails/engine.rb
51
53
  - lib/dispatch/rails/error_subscriber.rb