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 +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +16 -0
- data/app/helpers/dispatch/widget_helper.rb +5 -0
- data/lib/dispatch/rails/asset_middleware.rb +65 -0
- data/lib/dispatch/rails/configuration.rb +9 -1
- data/lib/dispatch/rails/engine.rb +8 -0
- data/lib/dispatch/rails/reporting_endpoint_middleware.rb +4 -1
- data/lib/dispatch/rails/response_annotator.rb +6 -3
- data/lib/dispatch/rails/version.rb +1 -1
- data/lib/dispatch-rails.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a712af989dbaa59fcd32135400b74a6ddb41058f291d60dce865516e117a50c6
|
|
4
|
+
data.tar.gz: 1e1d88267bcffb7177556c6a0dd92c7e9e191356288e647881a1ef04410bbeb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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["
|
|
106
|
+
headers["content-length"] = bytesize.to_s
|
|
104
107
|
end
|
|
105
108
|
end
|
|
106
109
|
end
|
data/lib/dispatch-rails.rb
CHANGED
|
@@ -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.
|
|
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
|