diff --git a/config/config.exs b/config/config.exs index ff01ab0f135985f8e102a108f6c86d0da37d53fa..16460450e900bc385fff25137994ab4bd150db36 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 f2a42bffc6e162c7a95d1b49d1a67773a9dfbff6..998a05f3442a0c2b72b1ae2cea644e426c0df509 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 bae4a975146030229d26e0b910ee78602ed8f4ad..0535fe513547d42ef38a91f003db6b2edf8e6319 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 0000000000000000000000000000000000000000..6e597bc9e71897b3e79f1d87410fd261a3beb623 --- /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 9e5af38465e43af7c0fe65d4d1d11f485d7a0480..39484299810c919386b7b1e7f3e987cc7a18b1a7 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 0000000000000000000000000000000000000000..341f8f679afec81a282cd6838a134e4f8f589380 --- /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 0000000000000000000000000000000000000000..c859795f2b39efc2579c56b150597d6c4c899a1f --- /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 0000000000000000000000000000000000000000..341f8f679afec81a282cd6838a134e4f8f589380 --- /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 b3182c101e5ae09a2d486f7db63e3a4e3d802e1f..9ec485626e306ab74aef5eca4462899c3eaf3849 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 459904734df8bcb8a41e1ba78233badd4b693025..57c296fb6c3df21b30202ca4367991ca13b5774b 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 bdeded53daff3e8dc7fe49ec863436c754d8fbe3..1c63fc21dcaaa861f7a7be465ee8facf39994049 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 %>');