| defmodule PlausibleWeb.TrackerPlug do |
| @moduledoc """ |
| Plug to serve the Plausible tracker script. |
| """ |
|
|
| import Plug.Conn |
| use Agent |
| use Plausible |
|
|
| base_variants = [ |
| "hash", |
| "outbound-links", |
| "exclusions", |
| "compat", |
| "local", |
| "manual", |
| "file-downloads", |
| "pageview-props", |
| "tagged-events", |
| "revenue", |
| "pageleave" |
| ] |
|
|
| |
| legacy_variants = |
| 1..Enum.count(base_variants) |
| |> Enum.map(fn x -> |
| Combination.combine(base_variants, x) |
| |> Enum.map(fn y -> Enum.sort(y) |> Enum.join(".") end) |
| end) |
| |> List.flatten() |
|
|
| @base_legacy_filenames ["plausible", "script", "analytics"] |
| @files_available ["plausible.js", "p.js"] ++ |
| Enum.map(legacy_variants, fn v -> "plausible.#{v}.js" end) |
|
|
| def init(opts) do |
| Keyword.merge(opts, files_available: MapSet.new(@files_available)) |
| end |
|
|
| def call(conn, files_available: files_available) do |
| case conn.request_path do |
| "/js/pa-" <> path -> |
| if String.ends_with?(path, ".js") do |
| id = String.replace_trailing(path, ".js", "") |
| request_tracker_script("pa-" <> id, conn) |
| else |
| conn |
| end |
|
|
| "/js/p.js" -> |
| legacy_request_file("p.js", files_available, conn) |
|
|
| "/js/" <> requested_filename -> |
| sorted_script_variant(requested_filename) |> legacy_request_file(files_available, conn) |
|
|
| _ -> |
| conn |
| end |
| end |
|
|
| def telemetry_event(name), do: [:plausible, :tracker_script, :request, name] |
|
|
| defp request_tracker_script(id, conn) do |
| case PlausibleWeb.Tracker.get_plausible_main_script(id) do |
| script_tag when is_binary(script_tag) -> |
| :telemetry.execute( |
| telemetry_event(:v2), |
| %{}, |
| %{status: 200} |
| ) |
|
|
| conn |
| |> put_resp_header("content-type", "application/javascript") |
| |> put_resp_header("x-content-type-options", "nosniff") |
| |> put_resp_header("cross-origin-resource-policy", "cross-origin") |
| |> put_resp_header("access-control-allow-origin", "*") |
| |> put_resp_header("cache-control", "public, max-age=60, no-transform") |
| |
| |
| |> put_resp_header("cdn-tag", "tracker_script::#{id}") |
| |> send_resp(200, script_tag) |
| |> halt() |
|
|
| nil -> |
| :telemetry.execute( |
| telemetry_event(:v2), |
| %{}, |
| %{status: 404} |
| ) |
|
|
| conn |
| |> send_resp(404, "Not found") |
| |> halt() |
| end |
| end |
|
|
| defp legacy_request_file(filename, files_available, conn) do |
| if filename && MapSet.member?(files_available, filename) do |
| location = Application.app_dir(:plausible, "priv/tracker/js/" <> filename) |
|
|
| :telemetry.execute( |
| telemetry_event(:legacy), |
| %{}, |
| %{status: 200} |
| ) |
|
|
| conn |
| |> put_resp_header("content-type", "application/javascript") |
| |> put_resp_header("x-content-type-options", "nosniff") |
| |> put_resp_header("cross-origin-resource-policy", "cross-origin") |
| |> put_resp_header("access-control-allow-origin", "*") |
| |> put_resp_header("cache-control", "public, max-age=86400, must-revalidate") |
| |> send_file(200, location) |
| |> halt() |
| else |
| conn |
| end |
| end |
|
|
| |
| @ignore_variants ["js", "pageleave"] |
|
|
| defp sorted_script_variant(requested_filename) do |
| case String.split(requested_filename, ".") do |
| [base_filename | rest] when base_filename in @base_legacy_filenames -> |
| sorted_variants = |
| rest |
| |> Enum.reject(&(&1 in @ignore_variants)) |
| |> Enum.sort() |
|
|
| Enum.join(["plausible"] ++ sorted_variants ++ ["js"], ".") |
|
|
| _ -> |
| nil |
| end |
| end |
| end |
|
|