dispatch-rails 0.10.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 +13 -0
- data/lib/dispatch/rails/asset_middleware.rb +65 -0
- 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 +2 -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
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to `dispatch-rails` are documented here. The format is based
|
|
|
4
4
|
on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
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
|
+
|
|
7
20
|
## [0.10.0] - 2026-06-18
|
|
8
21
|
|
|
9
22
|
### Added
|
|
@@ -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
|
|
@@ -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.10.
|
|
4
|
+
version: 0.10.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dispatch Team
|
|
@@ -47,6 +47,7 @@ files:
|
|
|
47
47
|
- app/views/dispatch/_error_tracker.html.erb
|
|
48
48
|
- app/views/dispatch/_widget.html.erb
|
|
49
49
|
- lib/dispatch-rails.rb
|
|
50
|
+
- lib/dispatch/rails/asset_middleware.rb
|
|
50
51
|
- lib/dispatch/rails/configuration.rb
|
|
51
52
|
- lib/dispatch/rails/engine.rb
|
|
52
53
|
- lib/dispatch/rails/error_subscriber.rb
|