rails-wayback 0.1.1 → 0.2.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +116 -7
  3. data/Rakefile +17 -1
  4. data/app/controllers/rails_wayback/assets_controller.rb +78 -0
  5. data/app/controllers/rails_wayback/pages_controller.rb +50 -9
  6. data/config/routes.rb +5 -0
  7. data/lib/generators/rails_wayback/install/install_generator.rb +5 -3
  8. data/lib/generators/rails_wayback/install/templates/initializer.rb.tt +18 -1
  9. data/lib/rails_wayback/asset_provenance.rb +41 -0
  10. data/lib/rails_wayback/bar_middleware.rb +235 -67
  11. data/lib/rails_wayback/bar_renderer.css +24 -3
  12. data/lib/rails_wayback/bar_renderer.html.erb +28 -7
  13. data/lib/rails_wayback/bar_renderer.js +346 -117
  14. data/lib/rails_wayback/bar_renderer.rb +69 -12
  15. data/lib/rails_wayback/bar_search.js +73 -0
  16. data/lib/rails_wayback/cache_inventory.rb +199 -0
  17. data/lib/rails_wayback/cli.rb +69 -2
  18. data/lib/rails_wayback/configuration.rb +13 -2
  19. data/lib/rails_wayback/controller_extensions.rb +71 -20
  20. data/lib/rails_wayback/doctor.rb +123 -0
  21. data/lib/rails_wayback/engine.rb +20 -19
  22. data/lib/rails_wayback/engine_mount.rb +15 -0
  23. data/lib/rails_wayback/git.rb +89 -8
  24. data/lib/rails_wayback/historical_asset_body.rb +41 -0
  25. data/lib/rails_wayback/historical_asset_helper.rb +68 -0
  26. data/lib/rails_wayback/historical_asset_resolver.rb +102 -0
  27. data/lib/rails_wayback/ref_policy.rb +64 -0
  28. data/lib/rails_wayback/render_context.rb +24 -0
  29. data/lib/rails_wayback/render_provenance.rb +84 -0
  30. data/lib/rails_wayback/render_tracker.rb +34 -0
  31. data/lib/rails_wayback/version.rb +1 -1
  32. data/lib/rails_wayback/view_source.rb +171 -24
  33. data/lib/rails_wayback.rb +42 -6
  34. data/lib/tasks/rails_wayback.rake +17 -0
  35. data/rails-wayback.gemspec +2 -5
  36. metadata +18 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d994f992691b844562d34eb5e06afaf69be7502d98c2107313a7fabcfe99209
4
- data.tar.gz: d8175d1cd12a74f6db23b6a58c4d26acd11a36825a98b6c49da50515c154d945
3
+ metadata.gz: '083ed3185650e0e8a199b639e18a5a2fe69fcd75d76871803e8bf74aea1afb31'
4
+ data.tar.gz: e7c79e78e2838c107d622d7a73fc793174b2678815e5239a6bf632ff39375fe8
5
5
  SHA512:
6
- metadata.gz: 3c90265c6459015b3c684fe0f3446f52bda36d1fae2e54cdbba0beb7748d912add4da80946b5d431c2e0f174975d9ceb1591ec24bb3163b5087bb72fc46f6994
7
- data.tar.gz: ba0e7095c8ebc4e2ea1d8034b9b3c9f49ed28a4b26608977c9cc9fc2a83c4594ec9e59476abb73b7c6856fec107ff85e8ae45b0a4b1818e0b6f8388f80ca204d
6
+ metadata.gz: 3a179537ef40d42489189aeddecdd2f885eb8ed2d1233b9379d95e322ab0e8c23c73a568c1d2e5303c7e64ffcd2d74bd1baf5df04060c7f650ce6cd23711064b
7
+ data.tar.gz: 40c9c0c81cbef0580603672c0114dcf63f0fc312a243bc64260ede1736d4d3cb61b9e080035122ff12088f66a0c67cc10be1bd365203e5f823614356aa0ca970
data/README.md CHANGED
@@ -50,11 +50,20 @@ Turn the UI on when you need it, off when you don't:
50
50
  bundle exec rails-wayback on # enable and inject the bar in every page
51
51
  bundle exec rails-wayback off # disable it
52
52
  bundle exec rails-wayback status # print current state
53
+ bundle exec rails-wayback cache # report ref cache usage and limits
54
+ bundle exec rails-wayback prune # apply configured LRU cache limits
53
55
  bundle exec rails-wayback clean # drop the tmp/rails_wayback ref cache
56
+ bundle exec rails-wayback doctor # check dependencies and app readiness
54
57
  ```
55
58
 
56
59
  Same commands as rake tasks: `rake wayback:on`, `wayback:off`,
57
- `wayback:status`, `wayback:clean`.
60
+ `wayback:status`, `wayback:cache`, `wayback:prune`, `wayback:clean`,
61
+ `wayback:doctor`.
62
+
63
+ Run `bin/rails wayback:doctor` from a Rails application to check Git and `tar`,
64
+ the repository root, cache write access, configured view and asset paths, and
65
+ the active Rails environment. Warnings do not fail the command; missing
66
+ dependencies or invalid runtime requirements return a non-zero exit status.
58
67
 
59
68
  Activation lasts for the current Rails server session. Every new server start
60
69
  begins with `rails-wayback` disabled, while `on` and `off` can still be used at
@@ -63,10 +72,16 @@ any time without restarting Rails.
63
72
  With the gem enabled, load any page of your app. A dark bar appears at the
64
73
  bottom with:
65
74
 
66
- - `Current branch` — dropdown of your local branches.
67
- - `Current commit` dropdown of the recent commits on that branch.
75
+ - `Current ref` — dropdown of trusted branches, remote-tracking branches, and
76
+ tags already stored in the local repository. Its search box filters the
77
+ loaded refs by name, label, or type.
78
+ - `Current commit` — dropdown of the recent commits on that ref. Its search
79
+ box filters the loaded commits by SHA, subject, author, or date.
68
80
  - `Travel` — reloads the current URL using views from that commit.
69
81
  - `Return to HEAD` — appears while traveling; goes back to live views.
82
+ It uses a server-side reset endpoint, which is also available directly at
83
+ `/rails-wayback/reset` if a historical template prevents the page from
84
+ rendering.
70
85
 
71
86
  There is no dedicated `/rails-wayback` page. The gem stays quiet on assets,
72
87
  ActionCable, and every URL under `/rails-wayback/*.json` (used by the bar to
@@ -83,13 +98,42 @@ fetch git data).
83
98
  3. The same before_action calls `prepend_view_path` with the sandbox, so
84
99
  Action View resolves templates from the ref before falling back to the
85
100
  current tree.
86
- 4. Your controller runs as usual, sets its `@` variables, and renders. The
101
+ 4. Standard Rails asset helpers resolve browser-ready files from the ref's
102
+ `public/` directory to immutable, SHA-specific engine URLs. Missing files
103
+ fall back to the current Rails asset resolver.
104
+ 5. Your controller runs as usual, sets its `@` variables, and renders. The
87
105
  visual layer (layouts, partials, ERB) comes from the past; the data and
88
106
  business logic come from the present.
89
107
 
90
108
  If a historical template references a helper or assign that no longer
91
109
  exists, you'll get the same `ActionView` error you'd get from any missing
92
- piece — the app itself never changes.
110
+ piece — the app itself never changes. Open `/rails-wayback/reset` to clear the
111
+ travel cookies if that error prevents the toolbar from rendering.
112
+
113
+ The toolbar tracks templates, partials, layouts, and collections. While
114
+ traveling it labels a response as historical, current fallback, or mixed so
115
+ you can tell when Rails filled a missing historical file from the current
116
+ view tree. It also reports historical public assets and current asset
117
+ fallbacks discovered through standard Rails helpers.
118
+
119
+ ### Historical public assets
120
+
121
+ While traveling, existing calls to `asset_path`, `image_tag`,
122
+ `stylesheet_link_tag`, `javascript_include_tag`, and the other standard Rails
123
+ asset helpers automatically look for browser-ready files in the selected
124
+ commit's `public/` directory. A historical file is served from a URL such as
125
+ `/rails-wayback/refs/<sha>/assets/images/logo.svg`; the SHA prevents browser
126
+ and proxy caches from mixing revisions. Asset responses reauthorize the ref,
127
+ reject unsafe paths and escaping symlinks, use immutable caching, and keep a
128
+ cache lease for the complete streamed response.
129
+
130
+ If a public file is absent, the normal current Rails resolver remains the
131
+ fallback and the toolbar reports it. Direct hard-coded URLs such as
132
+ `/assets/application.css` bypass Rails helpers and are not rewritten.
133
+ Historical Propshaft/Sprockets compilation, Sass or TypeScript processing,
134
+ import maps, and JavaScript/CSS bundlers are not reproduced yet; `app/assets`
135
+ continues to be extracted for comparison but is not served by this initial
136
+ static-file strategy.
93
137
 
94
138
  ## Configuration (optional)
95
139
 
@@ -97,14 +141,71 @@ piece — the app itself never changes.
97
141
 
98
142
  ```ruby
99
143
  RailsWayback.configure do |config|
144
+ # config.allowed_environments = %w[development test]
145
+ # config.trusted_ref_patterns = ["refs/heads/*"]
100
146
  # config.view_paths = %w[app/views]
101
147
  # config.asset_paths = %w[app/assets public]
102
148
  # config.max_commits = 50
149
+ # config.max_cached_refs = 25
150
+ # config.max_cache_bytes = 512 * 1024 * 1024
151
+ # config.max_response_bytes = 2 * 1024 * 1024
103
152
  end
104
153
  ```
105
154
 
106
- Everything above has a sensible default. Delete the initializer if you
107
- don't need to change anything.
155
+ Rails Wayback refuses to activate outside `development` and `test` by
156
+ default, even if its toggle file exists. Adding another environment is an
157
+ explicit opt-in; historical templates execute inside the host Rails process,
158
+ so only trusted refs should be used. Travel accepts only full commit SHAs and,
159
+ by default, the commit must be reachable from a local branch. Narrow
160
+ `trusted_ref_patterns` to branches such as `refs/heads/main` or
161
+ `refs/heads/release/*`, or explicitly add locally stored remote-tracking refs
162
+ and tags:
163
+
164
+ ```ruby
165
+ config.trusted_ref_patterns = [
166
+ "refs/heads/*",
167
+ "refs/remotes/origin/review/*",
168
+ "refs/tags/preview-*"
169
+ ]
170
+ ```
171
+
172
+ Matching refs appear in the toolbar and can be used for commit discovery.
173
+ Ref and commit search is performed in the browser over the data already loaded
174
+ by the toolbar; commit search is therefore limited to `max_commits` entries for
175
+ the selected ref and does not run additional Git commands or network requests.
176
+ Rails Wayback never runs `git fetch`: remote-tracking refs must already exist
177
+ locally, symbolic aliases such as `origin/HEAD` are omitted, and untrusted refs
178
+ cannot be queried through the commits endpoint. Detached commits remain blocked.
179
+
180
+ This policy reduces accidental execution but is not a sandbox. Historical ERB
181
+ and other executable template handlers run with the Rails server's filesystem,
182
+ database, credentials, environment, and network permissions. Historical
183
+ JavaScript also executes in the browser under the development application's
184
+ origin. The toolbar shows a permanent warning and asks for confirmation on the
185
+ first travel action in each browser session. The response-size limit applies only to
186
+ toolbar injection; streaming, encoded, file, partial, and larger responses are
187
+ returned untouched. Toolbar CSS and JavaScript are served from the mounted
188
+ engine, so CSP policies should allow same-origin styles, scripts, and fetches.
189
+ Rails-generated CSP nonces are copied to both asset tags automatically.
190
+ Everything above has a sensible default. Delete the initializer if you don't
191
+ need to change anything.
192
+
193
+ Cached refs use least-recently-used access metadata. After materialising a new
194
+ ref, Rails Wayback automatically removes the oldest refs until the configured
195
+ count and byte limits are met. A shared cache lease protects each ref for the
196
+ complete historical render, so automatic or manual `prune` and `clean`
197
+ operations wait for active previews before removing their files.
198
+ `rails-wayback cache` reports logical payload size and file count, while
199
+ `rails-wayback prune` applies the limits immediately. `rails-wayback clean`
200
+ removes all ref data and per-ref lock files. Set either cache limit to `nil` to
201
+ disable it.
202
+
203
+ ## Compatibility
204
+
205
+ Rails Wayback requires Ruby 3.3 or newer and Rails 8.0 or newer. CI runs the
206
+ complete matrix of Ruby 3.3, 3.4, and 4.0 against Rails 8.0 and 8.1.
207
+ Compatibility here means that the gem's suite passes on that combination; it
208
+ does not extend the upstream maintenance or security lifetime of Ruby or Rails.
108
209
 
109
210
  ## Development
110
211
 
@@ -113,6 +214,14 @@ bundle install
113
214
  bundle exec rspec
114
215
  ```
115
216
 
217
+ Rails dependency sets are managed with Appraisal. Regenerate their Gemfiles
218
+ after changing `Appraisals`, then run an individual Rails line with:
219
+
220
+ ```bash
221
+ bundle exec appraisal generate
222
+ bundle exec appraisal rails-8-0 rspec
223
+ ```
224
+
116
225
  ## Release
117
226
 
118
227
  Releases are cut by pushing an annotated `vX.Y.Z` git tag. GitHub Actions runs
data/Rakefile CHANGED
@@ -2,10 +2,26 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
 
5
+ default_tasks = []
6
+
5
7
  begin
6
8
  require "rspec/core/rake_task"
7
9
  RSpec::Core::RakeTask.new(:spec)
8
- task default: :spec
10
+ default_tasks << :spec
9
11
  rescue LoadError
10
12
  # RSpec is only available in development
11
13
  end
14
+
15
+ begin
16
+ require "rubocop/rake_task"
17
+ RuboCop::RakeTask.new(:rubocop) do |task|
18
+ task.options = ["--parallel"]
19
+ end
20
+ desc "Run RuboCop"
21
+ task lint: :rubocop
22
+ default_tasks << :rubocop
23
+ rescue LoadError
24
+ # RuboCop is only available in development
25
+ end
26
+
27
+ task default: default_tasks
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "rack/mime"
5
+ require "rails_wayback/bar_renderer"
6
+
7
+ module RailsWayback
8
+ # Serves the toolbar's bundled assets without depending on the host
9
+ # application's asset pipeline.
10
+ class AssetsController < ApplicationController
11
+ CACHE_CONTROL = "public, max-age=31536000, immutable"
12
+
13
+ def stylesheet
14
+ render_asset(BarRenderer::STYLES, "text/css")
15
+ end
16
+
17
+ def javascript
18
+ render_asset(BarRenderer::SCRIPT, "application/javascript")
19
+ end
20
+
21
+ def historical
22
+ selection = RailsWayback::RefPolicy.new.authorize(params[:sha])
23
+ return render_rejection(selection) if selection.rejected?
24
+
25
+ source = RailsWayback::ViewSource.new
26
+ resolution = resolve_asset(source, selection.sha, params[:path])
27
+ return head :not_found unless resolution
28
+
29
+ set_historical_headers(selection.sha, resolution)
30
+ return head :ok if request.head?
31
+
32
+ self.response_body = RailsWayback::HistoricalAssetBody.new(
33
+ sha: selection.sha,
34
+ public_path: resolution.public_path,
35
+ view_source: source
36
+ )
37
+ rescue RailsWayback::Git::GitError, RailsWayback::MaterializationError => e
38
+ render json: { error: e.message }, status: :service_unavailable
39
+ end
40
+
41
+ private
42
+
43
+ def resolve_asset(source, sha, path)
44
+ resolution = nil
45
+ source.with_materialization(sha) do |root|
46
+ resolution = RailsWayback::HistoricalAssetResolver.new.resolve_public_path(
47
+ root: root,
48
+ public_path: path
49
+ )
50
+ end
51
+ resolution
52
+ end
53
+
54
+ def set_historical_headers(sha, resolution)
55
+ content_type = Rack::Mime.mime_type(File.extname(resolution.public_path), "application/octet-stream")
56
+ response.content_type = content_type
57
+ response.set_header("Content-Length", resolution.size_bytes.to_s)
58
+ response.set_header("Cache-Control", CACHE_CONTROL)
59
+ response.set_header("X-Content-Type-Options", "nosniff")
60
+ response.set_header("ETag", %("#{Digest::SHA256.hexdigest("#{sha}:#{resolution.public_path}")}"))
61
+ end
62
+
63
+ def render_rejection(selection)
64
+ status = case selection.reason
65
+ when :invalid_format then :bad_request
66
+ when :unknown_ref then :not_found
67
+ else :forbidden
68
+ end
69
+ render json: { error: selection.message }, status: status
70
+ end
71
+
72
+ def render_asset(content, content_type)
73
+ response.set_header("Cache-Control", CACHE_CONTROL)
74
+ response.set_header("X-Content-Type-Options", "nosniff")
75
+ render plain: content, content_type: content_type
76
+ end
77
+ end
78
+ end
@@ -1,28 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
4
+
3
5
  module RailsWayback
4
6
  # JSON endpoints consumed by the bar's JavaScript to populate the
5
7
  # branch and commit selectors. All rendering of the actual UI lives
6
8
  # in the middleware; this controller only exposes git metadata.
7
9
  class PagesController < ApplicationController
10
+ WAYBACK_COOKIES = %w[rails_wayback_ref rails_wayback_branch].freeze
11
+ Result = Struct.new(:value, :error)
12
+ private_constant :Result
13
+
8
14
  def branches
9
15
  current_branch = safe_call { git.current_branch }
10
16
  current_commit = safe_call { git.current_commit }
11
- branches = safe_call { git.branches }
17
+ references = safe_call { git.references }
18
+ list = references.value || []
12
19
 
13
20
  render json: {
14
21
  current_branch: current_branch.value,
15
22
  current_commit: current_commit.value,
16
- branches: branches.value || [],
17
- errors: [branches.error].compact
23
+ branches: list.select { |reference| reference.kind == :branch }.map(&:name),
24
+ refs: list.map { |reference| reference_json(reference) },
25
+ errors: [references.error].compact
18
26
  }
19
27
  end
20
28
 
21
29
  def commits
22
- branch = params[:branch].to_s.sub(/\.json\z/, "")
23
- result = safe_call { git.commits(branch) }
30
+ selector = params[:branch].to_s.sub(/\.json\z/, "")
31
+ result = safe_call do
32
+ reference = git.reference(selector)
33
+ [reference, git.commits(reference.full_name)]
34
+ end
35
+ reference, commits = result.value
24
36
 
25
- list = (result.value || []).map do |commit|
37
+ list = (commits || []).map do |commit|
26
38
  {
27
39
  sha: commit.sha,
28
40
  short_sha: commit.short_sha,
@@ -33,15 +45,22 @@ module RailsWayback
33
45
  end
34
46
 
35
47
  render json: {
36
- branch: branch,
48
+ branch: reference&.name || selector,
49
+ ref: reference && reference_json(reference),
37
50
  commits: list,
38
51
  error: result.error
39
52
  }
40
53
  end
41
54
 
42
- private
55
+ # Emergency escape hatch that does not render any host application view.
56
+ # It remains usable when a historical template crashes before the injected
57
+ # toolbar can render its normal Return to HEAD control.
58
+ def reset
59
+ WAYBACK_COOKIES.each { |name| response.delete_cookie(name, path: "/") }
60
+ redirect_to safe_return_to, allow_other_host: false
61
+ end
43
62
 
44
- Result = Struct.new(:value, :error)
63
+ private
45
64
 
46
65
  def safe_call
47
66
  Result.new(yield, nil)
@@ -49,5 +68,27 @@ module RailsWayback
49
68
  Rails.logger.warn("[rails-wayback] #{e.class}: #{e.message}") if defined?(Rails)
50
69
  Result.new(nil, "#{e.class}: #{e.message}")
51
70
  end
71
+
72
+ def reference_json(reference)
73
+ {
74
+ full_name: reference.full_name,
75
+ name: reference.name,
76
+ type: reference.kind,
77
+ label: reference.label
78
+ }
79
+ end
80
+
81
+ def safe_return_to
82
+ target = params[:return_to].to_s
83
+ return "/" if target.empty? || target.include?("\\")
84
+
85
+ uri = URI.parse(target)
86
+ return "/" unless target.start_with?("/") && !target.start_with?("//")
87
+ return "/" if uri.scheme || uri.host
88
+
89
+ target
90
+ rescue URI::InvalidURIError
91
+ "/"
92
+ end
52
93
  end
53
94
  end
data/config/routes.rb CHANGED
@@ -1,10 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RailsWayback::Engine.routes.draw do
4
+ get "assets/bar.css", to: "assets#stylesheet", format: false, as: :bar_stylesheet
5
+ get "assets/bar.js", to: "assets#javascript", format: false, as: :bar_javascript
6
+ get "refs/:sha/assets/*path", to: "assets#historical", format: false, as: :historical_asset
7
+
4
8
  # `format: false` keeps Rails from parsing a trailing `.json` as an
5
9
  # implicit format, and the glob `*branch` allows branch names that
6
10
  # contain slashes (e.g. "feature/foo"). The controller trims a
7
11
  # trailing `.json` defensively in case a client still appends it.
8
12
  get "branches", to: "pages#branches", format: false, as: :branches
9
13
  get "commits/*branch", to: "pages#commits", format: false, as: :commits
14
+ get "reset", to: "pages#reset", format: false, as: :reset
10
15
  end
@@ -17,8 +17,9 @@ module RailsWayback
17
17
  ROUTE
18
18
 
19
19
  routes_file = "config/routes.rb"
20
+ routes_path = File.join(destination_root, routes_file)
20
21
 
21
- if File.file?(routes_file) && File.read(routes_file).include?("RailsWayback::Engine")
22
+ if File.file?(routes_path) && File.read(routes_path).include?("RailsWayback::Engine")
22
23
  say_status :skip, "RailsWayback::Engine mount already present in #{routes_file}"
23
24
  else
24
25
  route route_snippet.strip
@@ -27,10 +28,11 @@ module RailsWayback
27
28
 
28
29
  def update_gitignore
29
30
  gitignore = ".gitignore"
31
+ gitignore_path = File.join(destination_root, gitignore)
30
32
  entry = "/tmp/rails_wayback/\n"
31
- if File.file?(gitignore) && !File.read(gitignore).include?("tmp/rails_wayback")
33
+ if File.file?(gitignore_path) && !File.read(gitignore_path).include?("tmp/rails_wayback")
32
34
  append_to_file(gitignore, entry)
33
- elsif !File.file?(gitignore)
35
+ elsif !File.file?(gitignore_path)
34
36
  create_file(gitignore, entry)
35
37
  end
36
38
  end
@@ -8,11 +8,28 @@
8
8
  # so you can pick a branch/commit and travel to that ref's views for
9
9
  # the current URL. Delete this file if the defaults below work for you.
10
10
  RailsWayback.configure do |config|
11
+ # Rails Wayback is unavailable outside these environments, even if its
12
+ # toggle file exists. Add another environment only as an explicit opt-in.
13
+ # config.allowed_environments = %w[development test]
14
+ # Commits must be reachable from one of these Git ref patterns.
15
+ # Add refs/remotes/... or refs/tags/... patterns to show locally stored
16
+ # remote-tracking branches or tags. Rails Wayback never fetches from Git.
17
+ # config.trusted_ref_patterns = ["refs/heads/*"]
18
+
11
19
  # Directories (relative to Rails.root) whose contents are pulled from
12
- # the target git ref and used to render the historical preview.
20
+ # the target git ref. Browser-ready files under historical public/ are
21
+ # discovered through standard Rails helpers; pipeline builds are not run.
13
22
  # config.view_paths = %w[app/views]
14
23
  # config.asset_paths = %w[app/assets public]
15
24
 
16
25
  # How many recent commits per branch are shown in the bar dropdown.
17
26
  # config.max_commits = 50
27
+ # Both LRU limits are applied automatically after a new ref is cached.
28
+ # Set either value to nil to disable that limit.
29
+ # config.max_cached_refs = 25
30
+ # config.max_cache_bytes = 512 * 1024 * 1024
31
+
32
+ # Maximum buffered HTML response size that may receive the toolbar.
33
+ # Streaming, encoded, file, and larger responses are always left untouched.
34
+ # config.max_response_bytes = 2 * 1024 * 1024
18
35
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWayback
4
+ module AssetProvenance
5
+ EVENT_PREFIX = "rails_wayback.asset."
6
+ ORIGINS = %i[historical current].freeze
7
+
8
+ module_function
9
+
10
+ def record(origin:, identifier:)
11
+ return unless ORIGINS.include?(origin.to_sym)
12
+
13
+ RailsWayback::RenderContext.current&.record(
14
+ event: "#{EVENT_PREFIX}#{origin}",
15
+ identifier: identifier
16
+ )
17
+ end
18
+
19
+ def paths_by_origin(entries)
20
+ result = { historical: [], current: [] }
21
+ Array(entries).each do |entry|
22
+ event = entry_value(entry, :event)
23
+ next unless event.start_with?(EVENT_PREFIX)
24
+
25
+ origin = event.delete_prefix(EVENT_PREFIX).to_sym
26
+ next unless result.key?(origin)
27
+
28
+ identifier = entry_value(entry, :identifier)
29
+ result[origin] << identifier unless identifier.empty?
30
+ end
31
+ result.transform_values(&:uniq)
32
+ end
33
+
34
+ def entry_value(entry, key)
35
+ return "" unless entry.is_a?(Hash)
36
+
37
+ (entry[key] || entry[key.to_s]).to_s
38
+ end
39
+ private_class_method :entry_value
40
+ end
41
+ end