defmodule Outerfaces.Odd.Plugs.OddCDNRoflHTMLPlug do @moduledoc """ Provides HTML transformation functions for rewriting [OUTERFACES_ODD_CDN] and [OUTERFACES_ODD_SPA] tokens in HTML src/href attributes. This module always emits rev-pinned URLs in the canonical format: - CDN: `/__rev//cdn/` - SPA: `/__rev//spa/` ## Examples # Unified proxy mode (cdn_origin = ""): html = "" transform_html_cdn_tokens(html, conn, "") #=> "" # Direct CDN mode (cdn_origin = "http://localhost:60032"): html = "" transform_html_cdn_tokens(html, conn, "http://localhost:60032") #=> "" # SPA tokens (always relative): html = "" transform_html_cdn_tokens(html, conn, "") #=> "" """ alias Outerfaces.Rev # Regex patterns for matching tokens in HTML attributes # Matches: src="[OUTERFACES_ODD_CDN]/path" or href='[OUTERFACES_ODD_CDN]/path' @odd_cdn_attr_regex ~r/((?:src|href)=["'])([^"']*)\[OUTERFACES_(?:ODD|LOCAL)_CDN\]\/([^"']*)(["'])/i @odd_spa_attr_regex ~r/((?:src|href)=["'])([^"']*)\[OUTERFACES_ODD_SPA\]\/([^"']*)(["'])/i # Regex for injecting rev meta tag (finds or tag) @head_close_regex ~r{}i @body_open_regex ~r{]*>}i @doc """ Transforms HTML content by rewriting ODD_CDN and ODD_SPA tokens in src/href attributes and injecting a rev meta tag. ## Parameters - `html_content` - The HTML content to transform - `conn` - The Plug.Conn struct (used to get rev) - `cdn_origin` - Origin for CDN assets: - Unified proxy mode: "" (empty string, emits relative URLs) - Direct CDN mode: "http://localhost:60032" (full origin) ## Returns Transformed HTML string with: - All [OUTERFACES_ODD_CDN] and [OUTERFACES_ODD_SPA] tokens replaced - Rev meta tag injected in """ @spec transform_html_cdn_tokens(String.t(), Plug.Conn.t(), String.t()) :: String.t() def transform_html_cdn_tokens(html_content, conn, cdn_origin \\ "") do # Short-circuit if no tokens present (performance optimization) if String.contains?(html_content, "[OUTERFACES_") do html_content |> replace_odd_cdn_tokens(conn, cdn_origin) |> replace_odd_spa_tokens(conn) |> inject_rev_meta_tag(conn) else html_content end end # Private Functions @spec replace_odd_cdn_tokens(String.t(), Plug.Conn.t(), String.t()) :: String.t() defp replace_odd_cdn_tokens(html_content, conn, cdn_origin) do rev = get_rev(conn) # Emit canonical rev-pinned URLs: /__rev//cdn/ Regex.replace(@odd_cdn_attr_regex, html_content, fn _match, attr_start, prefix, path, quote -> "#{attr_start}#{prefix}#{cdn_origin}/__rev/#{rev}/cdn/#{path}#{quote}" end) end @spec replace_odd_spa_tokens(String.t(), Plug.Conn.t()) :: String.t() defp replace_odd_spa_tokens(html_content, conn) do # ODD_SPA is always rev-pinned rev = get_rev(conn) Regex.replace(@odd_spa_attr_regex, html_content, fn _match, attr_start, prefix, path, quote -> "#{attr_start}#{prefix}/__rev/#{rev}/spa/#{path}#{quote}" end) end @spec inject_rev_meta_tag(String.t(), Plug.Conn.t()) :: String.t() defp inject_rev_meta_tag(html_content, conn) do rev = get_rev(conn) meta_tag = " \n" cond do Regex.match?(@head_close_regex, html_content) -> # Inject before Regex.replace(@head_close_regex, html_content, "#{meta_tag}", global: false) Regex.match?(@body_open_regex, html_content) -> # No found, inject after Regex.replace( @body_open_regex, html_content, fn match -> "#{match}\n#{meta_tag}" end, global: false ) true -> # No or found, prepend to content meta_tag <> html_content end end @spec get_rev(Plug.Conn.t()) :: String.t() defp get_rev(conn) do Map.get(conn.assigns, :outerfaces_rev) || Rev.current_rev() end end