From bb36bae15c8ef665bd260d373d7cd11b561e7afe Mon Sep 17 00:00:00 2001
From: Uku Taht <Uku.taht@gmail.com>
Date: Thu, 11 Jun 2020 09:30:00 +0300
Subject: [PATCH] Inject base url to tracker at runtime (#78)

* Inject base url to tracker at runtime

* Cache tracker script for 1 hour
---
 config/config.exs                             |  3 +-
 config/test.exs                               |  7 +++++
 lib/plausible/application.ex                  |  1 -
 .../controllers/tracker_controller.ex         | 29 +++++++++++++++++++
 lib/plausible_web/router.ex                   |  4 +++
 priv/tracker/js/analytics.js                  |  1 +
 priv/tracker/js/p.js                          |  1 +
 priv/tracker/js/plausible.js                  |  1 +
 tracker/compile.js                            | 14 +++------
 tracker/src/p.js                              |  2 +-
 tracker/src/plausible.js                      |  2 +-
 11 files changed, 51 insertions(+), 14 deletions(-)
 create mode 100644 lib/plausible_web/controllers/tracker_controller.ex
 create mode 100644 priv/tracker/js/analytics.js
 create mode 100644 priv/tracker/js/p.js
 create mode 100644 priv/tracker/js/plausible.js

diff --git a/config/config.exs b/config/config.exs
index ff01ab0f..16460450 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -8,9 +8,10 @@ config :plausible,
   ecto_repos: [Plausible.Repo],
   environment: System.get_env("ENVIRONMENT", "dev")
 
+
 config :plausible, :clickhouse,
   hostname: System.get_env("CLICKHOUSE_DATABASE_HOST", "localhost"),
-  database: System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_test"),
+  database: System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_dev"),
   username: System.get_env("CLICKHOUSE_DATABASE_USER"),
   password: System.get_env("CLICKHOUSE_DATABASE_PASSWORD"),
   pool_size: 10
diff --git a/config/test.exs b/config/test.exs
index f2a42bff..998a05f3 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -22,6 +22,13 @@ config :plausible,
          ),
        pool: Ecto.Adapters.SQL.Sandbox
 
+config :plausible, :clickhouse,
+  hostname: System.get_env("CLICKHOUSE_DATABASE_HOST", "localhost"),
+  database: System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_test"),
+  username: System.get_env("CLICKHOUSE_DATABASE_USER"),
+  password: System.get_env("CLICKHOUSE_DATABASE_PASSWORD"),
+  pool_size: 10
+
 config :plausible, Plausible.Mailer, adapter: Bamboo.TestAdapter
 
 config :plausible, Oban, crontab: false, queues: false
diff --git a/lib/plausible/application.ex b/lib/plausible/application.ex
index bae4a975..0535fe51 100644
--- a/lib/plausible/application.ex
+++ b/lib/plausible/application.ex
@@ -5,7 +5,6 @@ defmodule Plausible.Application do
 
   def start(_type, _args) do
     clickhouse_config = Application.get_env(:plausible, :clickhouse)
-
     children = [
       Plausible.Repo,
       PlausibleWeb.Endpoint,
diff --git a/lib/plausible_web/controllers/tracker_controller.ex b/lib/plausible_web/controllers/tracker_controller.ex
new file mode 100644
index 00000000..6e597bc9
--- /dev/null
+++ b/lib/plausible_web/controllers/tracker_controller.ex
@@ -0,0 +1,29 @@
+defmodule PlausibleWeb.TrackerController do
+  use PlausibleWeb, :controller
+  require EEx
+  EEx.function_from_file(:defp, :render_plausible, Application.app_dir(:plausible, "priv/tracker/js/plausible.js"), [:base_url])
+  EEx.function_from_file(:defp, :render_p, Application.app_dir(:plausible, "priv/tracker/js/p.js"), [:base_url])
+  @max_age 3600 # 1 hour
+
+  def plausible(conn, _params) do
+    conn
+    |> put_resp_header("cache-control", "max-age=#{@max_age},public")
+    |> send_resp(200, render_plausible(base_url()))
+  end
+
+  def analytics(conn, _params) do
+    conn
+    |> put_resp_header("cache-control", "max-age=#{@max_age},public")
+    |> send_resp(200, render_plausible(base_url()))
+  end
+
+  def p(conn, _params) do
+    conn
+    |> put_resp_header("cache-control", "max-age=#{@max_age},public")
+    |> send_resp(200, render_p(base_url()))
+  end
+
+  defp base_url() do
+    PlausibleWeb.Endpoint.clean_url()
+  end
+end
diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex
index 9e5af384..39484299 100644
--- a/lib/plausible_web/router.ex
+++ b/lib/plausible_web/router.ex
@@ -33,6 +33,10 @@ defmodule PlausibleWeb.Router do
     forward "/sent-emails", Bamboo.SentEmailViewerPlug
   end
 
+  get "/js/plausible.js", PlausibleWeb.TrackerController, :plausible
+  get "/js/analytics.js", PlausibleWeb.TrackerController, :plausible
+  get "/js/p.js", PlausibleWeb.TrackerController, :p
+
   scope "/api/stats", PlausibleWeb.Api do
     pipe_through :stats_api
 
diff --git a/priv/tracker/js/analytics.js b/priv/tracker/js/analytics.js
new file mode 100644
index 00000000..341f8f67
--- /dev/null
+++ b/priv/tracker/js/analytics.js
@@ -0,0 +1 @@
+!function(o,s){"use strict";var i=o.location,l=o.document,e=l.querySelector('[src*="'+s+'"]'),c={domain:e&&e.getAttribute("data-domain")||i.hostname};function u(e){console.warn("[Plausible] Ignore event: "+e)}function t(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(i.hostname)||"file:"===i.protocol)return u("running locally");if("prerender"===l.visibilityState)return u("prerendering");var n,a={};a.name=e,a.url=i.protocol+"//"+i.hostname+i.pathname+i.search,a.domain=c.domain,a.referrer=l.referrer||null,a.source=(n=i.search.match(/[?&](ref|source|utm_source)=([^?&]+)/))?n[2]:null,a.user_agent=o.navigator.userAgent,a.screen_width=o.innerWidth;var r=new XMLHttpRequest;r.open("POST",s+"/api/event",!0),r.setRequestHeader("Content-Type","text/plain"),r.send(JSON.stringify(a)),r.onreadystatechange=function(){4==r.readyState&&t&&t.callback&&t.callback()}}function n(){t("pageview")}try{var a,r=o.history;r.pushState&&(a=r.pushState,r.pushState=function(){a.apply(this,arguments),n()},o.addEventListener("popstate",n));var p=o.plausible&&o.plausible.q||[];o.plausible=t;for(var h=0;h<p.length;h++)t.apply(this,p[h]);n()}catch(e){(new Image).src=s+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
\ No newline at end of file
diff --git a/priv/tracker/js/p.js b/priv/tracker/js/p.js
new file mode 100644
index 00000000..c859795f
--- /dev/null
+++ b/priv/tracker/js/p.js
@@ -0,0 +1 @@
+!function(r,a){"use strict";function o(e){console.warn("[Plausible] Ignoring event because "+e)}function c(){var e=r.location.search.match(/[?&](ref|source|utm_source)=([^?&]+)/);return e?e[2]:null}function s(){var e,t,n=JSON.parse((e="plausible_user",(t=document.cookie.match(new RegExp("(?:^|; )"+e.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g,"\\$1")+"=([^;]*)")))?decodeURIComponent(t[1]):null));return n?{initial_referrer:n.initial_referrer&&decodeURIComponent(n.initial_referrer),initial_source:n.initial_source&&decodeURIComponent(n.initial_source)}:(n={initial_referrer:r.document.referrer||null,initial_source:c()},function(e,t){var n=new Date;n.setTime(n.getTime()+94608e6);var i="; expires="+n.toUTCString();document.cookie=e+"="+(t||"")+i+"; samesite=strict; path=/"}("plausible_user",JSON.stringify({initial_referrer:n.initial_referrer&&encodeURIComponent(n.initial_referrer),initial_source:n.initial_source&&encodeURIComponent(n.initial_source)})),n)}function t(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(r.location.hostname))return o("website is running locally");if("file:"===r.location.protocol)return o("website is running locally");if("prerender"===r.document.visibilityState)return o("document is prerendering");var n=l.trackAcquisition?s():{};n.name=e,n.url=r.location.protocol+"//"+r.location.hostname+r.location.pathname+r.location.search,n.domain=l.domain,n.referrer=r.document.referrer||null,n.source=c(),n.user_agent=r.navigator.userAgent,n.screen_width=r.innerWidth;var i=new XMLHttpRequest;i.open("POST",a+"/api/event",!0),i.setRequestHeader("Content-Type","text/plain"),i.send(JSON.stringify(n)),i.onreadystatechange=function(){i.readyState==XMLHttpRequest.DONE&&t&&t.callback&&t.callback()}}function n(e){t("pageview",e)}try{var l={domain:r.location.hostname},i={page:n,trigger:t,trackPushState:function(){var e,t=r.history;t.pushState&&(e=t.pushState,t.pushState=function(){e.apply(this,arguments),n()}),r.addEventListener("popstate",n)},configure:function(e,t){l[e]=t}},e=r.plausible.q||[];r.plausible=function(){var e=[].slice.call(arguments),t=e.shift();i[t].apply(this,e)};for(var u=0;u<e.length;u++)r.plausible.apply(this,e[u])}catch(e){(new Image).src=a+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
\ No newline at end of file
diff --git a/priv/tracker/js/plausible.js b/priv/tracker/js/plausible.js
new file mode 100644
index 00000000..341f8f67
--- /dev/null
+++ b/priv/tracker/js/plausible.js
@@ -0,0 +1 @@
+!function(o,s){"use strict";var i=o.location,l=o.document,e=l.querySelector('[src*="'+s+'"]'),c={domain:e&&e.getAttribute("data-domain")||i.hostname};function u(e){console.warn("[Plausible] Ignore event: "+e)}function t(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(i.hostname)||"file:"===i.protocol)return u("running locally");if("prerender"===l.visibilityState)return u("prerendering");var n,a={};a.name=e,a.url=i.protocol+"//"+i.hostname+i.pathname+i.search,a.domain=c.domain,a.referrer=l.referrer||null,a.source=(n=i.search.match(/[?&](ref|source|utm_source)=([^?&]+)/))?n[2]:null,a.user_agent=o.navigator.userAgent,a.screen_width=o.innerWidth;var r=new XMLHttpRequest;r.open("POST",s+"/api/event",!0),r.setRequestHeader("Content-Type","text/plain"),r.send(JSON.stringify(a)),r.onreadystatechange=function(){4==r.readyState&&t&&t.callback&&t.callback()}}function n(){t("pageview")}try{var a,r=o.history;r.pushState&&(a=r.pushState,r.pushState=function(){a.apply(this,arguments),n()},o.addEventListener("popstate",n));var p=o.plausible&&o.plausible.q||[];o.plausible=t;for(var h=0;h<p.length;h++)t.apply(this,p[h]);n()}catch(e){(new Image).src=s+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
\ No newline at end of file
diff --git a/tracker/compile.js b/tracker/compile.js
index b3182c10..9ec48562 100644
--- a/tracker/compile.js
+++ b/tracker/compile.js
@@ -2,22 +2,16 @@ const uglify = require("uglify-js");
 const fs = require('fs')
 const path = require('path')
 
-const scheme = process.env.SCHEME || "https"
-const host = process.env.HOST || "plausible.io"
-const baseUrl = scheme + "://" + host
-
 function relPath(segment) {
   return path.join(__dirname, segment)
 }
 
 function compilefile(input, output) {
-  const code = fs.readFileSync(input)
-    .toString()
-    .replace('BASE_URL', "'" + baseUrl + "'")
+  const code = fs.readFileSync(input).toString()
   const result = uglify.minify(code)
   fs.writeFileSync(output, result.code)
 }
 
-compilefile(relPath('src/plausible.js'), relPath('../priv/static/js/plausible.js'))
-compilefile(relPath('src/p.js'), relPath('../priv/static/js/p.js'))
-fs.copyFileSync(relPath('../priv/static/js/plausible.js'), relPath('../priv/static/js/analytics.js'))
+compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.js'))
+compilefile(relPath('src/p.js'), relPath('../priv/tracker/js/p.js'))
+fs.copyFileSync(relPath('../priv/tracker/js/plausible.js'), relPath('../priv/tracker/js/analytics.js'))
diff --git a/tracker/src/p.js b/tracker/src/p.js
index 45990473..57c296fb 100644
--- a/tracker/src/p.js
+++ b/tracker/src/p.js
@@ -124,4 +124,4 @@
   } catch (e) {
     new Image().src = plausibleHost + '/api/error?message=' +  encodeURIComponent(e.message);
   }
-})(window, "https://plausible.io");
+})(window, '<%= base_url %>');
diff --git a/tracker/src/plausible.js b/tracker/src/plausible.js
index bdeded53..1c63fc21 100644
--- a/tracker/src/plausible.js
+++ b/tracker/src/plausible.js
@@ -72,4 +72,4 @@
   } catch (e) {
     new Image().src = plausibleHost + '/api/error?message=' +  encodeURIComponent(e.message);
   }
-})(window, BASE_URL);
+})(window, '<%= base_url %>');
-- 
GitLab