Created
February 17, 2026 13:06
-
-
Save julik/7db5a3737ba262c3a5b544e6ece0adc8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # frozen_string_literal: true | |
| module GenevaDrive | |
| module Admin | |
| # Serves static assets from the engine's public/ directory. | |
| # This keeps all engine URLs under the user-configured mount path, | |
| # avoiding any conflicts with the host application's URL namespace. | |
| class AssetsController < ActionController::Base | |
| # Static assets don't need CSRF protection and must allow | |
| # cross-origin requests (for ES module imports) | |
| skip_forgery_protection | |
| skip_before_action :verify_authenticity_token, raise: false | |
| # Define allowed extensions and their content types | |
| CONTENT_TYPES = { | |
| ".js" => "application/javascript", | |
| ".mjs" => "application/javascript", | |
| ".css" => "text/css" | |
| }.freeze | |
| # Extensions eligible for import-path rewriting | |
| JS_EXTENSIONS = %w[.js .mjs].to_set.freeze | |
| # Matches quoted strings that look like relative/absolute JS import paths. | |
| # Handles ./ ../ / but NOT protocol-relative // | |
| # $1 = quote char, $2 = path including extension | |
| JS_IMPORT_RE = /(["'])(\.{0,2}\/(?!\/)[^"']*\.(?:js|mjs|es))\1/ | |
| # Matches url() in CSS with relative/absolute paths. | |
| # Handles url(./path), url("./path"), url('../path'), url(/path) | |
| # but NOT url(data:...), url(https://...), url(//...) | |
| # $1 = opening (quote or empty), $2 = path, $3 = closing (quote or empty) | |
| CSS_URL_RE = /url\((\s*["']?)(\.{0,2}\/(?!\/)[^)"']*?)(\s*["']?\s*)\)/ | |
| def show | |
| # Reconstruct the path from the splat parameter | |
| path = params[:path] | |
| path = "#{path}.#{params[:format]}" if params[:format].present? | |
| # Validate extension | |
| extension = File.extname(path) | |
| content_type = CONTENT_TYPES[extension] | |
| return head(:not_found) unless content_type | |
| # Resolve the file path within the engine's public directory | |
| public_root = Engine.root.join("public") | |
| file_path = public_root.join(path).cleanpath | |
| # Security: ensure the resolved path is still within public/ | |
| return head(:not_found) unless file_path.to_s.start_with?(public_root.to_s + "/") | |
| # Check file exists | |
| return head(:not_found) unless file_path.exist? && file_path.file? | |
| # Etag is always computed over the unprocessed file | |
| raw_bytes = File.binread(file_path) | |
| fresh_when strong_etag: Digest::SHA1.digest(raw_bytes), public: true, cache_control: {max_age: 5.days, must_revalidate: true} | |
| # Rewrite relative asset references so the cachebuster cascades | |
| # through the entire dependency graph (JS imports, CSS url()s). | |
| # Always inject the version tag – either from the request URL or | |
| # computed from the engine version – so imports cascade even when | |
| # the entry-point script tag omits ?v=. | |
| tag = params[:v].presence || Engine.version_tag | |
| if JS_EXTENSIONS.include?(extension) | |
| send_data rewrite_js_imports(raw_bytes, tag), | |
| type: content_type, disposition: :inline | |
| elsif extension == ".css" | |
| send_data rewrite_css_urls(raw_bytes, tag), | |
| type: content_type, disposition: :inline | |
| else | |
| send_file file_path, type: content_type, disposition: :inline | |
| end | |
| end | |
| private | |
| def rewrite_js_imports(source, version_tag) | |
| source.gsub(JS_IMPORT_RE) do | |
| "#{$1}#{$2}?v=#{version_tag}#{$1}" | |
| end | |
| end | |
| def rewrite_css_urls(source, version_tag) | |
| source.gsub(CSS_URL_RE) do | |
| "url(#{$1}#{$2}?v=#{version_tag}#{$3})" | |
| end | |
| end | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment