diff --git a/CHANGELOG.md b/CHANGELOG.md index 0359bcd86..63ac137d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## Unreleased ### Added +- A `file-downloads` script extension for automatically tracking file downloads as custom events - Integration with [Matomo's referrer spam list](https://github.com/matomo-org/referrer-spam-list/blob/master/spammers.txt) to block known spammers - API route `PUT /api/v1/sites/goals` with form params `site_id`, `event_name` and/or `page_path`, and `goal_type` with supported types `event` and `page` - API route `DELETE /api/v1/sites/goals/:goal_id` with form params `site_id` diff --git a/lib/plausible_web/plugs/tracker.ex b/lib/plausible_web/plugs/tracker.ex index 71d603274..e64e631d6 100644 --- a/lib/plausible_web/plugs/tracker.ex +++ b/lib/plausible_web/plugs/tracker.ex @@ -2,7 +2,16 @@ defmodule PlausibleWeb.Tracker do import Plug.Conn use Agent - base_variants = ["hash", "outbound-links", "exclusions", "compat", "local", "manual"] + base_variants = [ + "hash", + "outbound-links", + "exclusions", + "compat", + "local", + "manual", + "file-downloads" + ] + base_filenames = ["plausible", "script"] # Generates Power Set of all variants diff --git a/priv/tracker/js/plausible.compat.exclusions.file-downloads.hash.js b/priv/tracker/js/plausible.compat.exclusions.file-downloads.hash.js new file mode 100644 index 000000000..ddd3e12ca --- /dev/null +++ b/priv/tracker/js/plausible.compat.exclusions.file-downloads.hash.js @@ -0,0 +1 @@ +!function(){"use strict";var e,t,i,o=window.location,r=window.document,l=r.getElementById("plausible"),p=l.getAttribute("data-api")||(e=l.src.split("/"),t=e[0],i=e[2],t+"//"+i+"/api/event"),s=l&&l.getAttribute("data-exclude").split(",");function c(e){console.warn("Ignoring Event: "+e)}function a(e,t){if(/^localhost$|^127(\.[0-9]+){0,2}\.[0-9]+$|^\[::1?\]$/.test(o.hostname)||"file:"===o.protocol)return c("localhost");if(!(window._phantom||window.__nightmare||window.navigator.webdriver||window.Cypress)){try{if("true"==window.localStorage.plausible_ignore)return c("localStorage flag")}catch(e){}if(s)for(var i=0;i a.length > 0).map(a => a.sort()); compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.js')) diff --git a/tracker/src/plausible.js b/tracker/src/plausible.js index 207f2e1a8..916d0db7b 100644 --- a/tracker/src/plausible.js +++ b/tracker/src/plausible.js @@ -43,10 +43,12 @@ } {{#if exclusions}} - if (excludedPaths) - for (var i = 0; i < excludedPaths.length; i++) + if (excludedPaths) { + for (var i = 0; i < excludedPaths.length; i++) { if (eventName == "pageview" && location.pathname.match(new RegExp('^' + excludedPaths[i].trim().replace(/\*\*/g, '.*').replace(/([^\.])\*/g, '$1[^\\s\/]*') + '\/?$'))) return warn('exclusion rule'); + } + } {{/if}} var payload = {} @@ -92,8 +94,9 @@ } if (link && link.href && link.host && link.host !== location.host) { - if (middle || click) - plausible('Outbound Link: Click', {props: {url: link.href}}) + if (middle || click) { + plausible('Outbound Link: Click', {props: {url: link.href}}) + } // Delay navigation so that Plausible is notified of the click if(!link.target || link.target.match(/^_(self|parent|top)$/i)) { @@ -117,6 +120,50 @@ registerOutboundLinkEvents() {{/if}} + {{#if file_downloads}} + var defaultFileTypes = ['pdf', 'xlsx', 'docx', 'txt', 'rtf', 'csv', 'exe', 'key', 'pps', 'ppt', 'pptx', '7z', 'pkg', 'rar', 'gz', 'zip', 'avi', 'mov', 'mp4', 'mpeg', 'wmv', 'midi', 'mp3', 'wav', 'wma'] + var fileTypesAttr = scriptEl.getAttribute('file-types') + var fileTypesToTrack = (fileTypesAttr && fileTypesAttr.split(",")) || defaultFileTypes; + + function handleDownload(event) { + + var link = event.target; + var middle = event.type == "auxclick" && event.which == 2; + var click = event.type == "click"; + + while(link && (typeof link.tagName == 'undefined' || link.tagName.toLowerCase() != 'a' || !link.href)) { + link = link.parentNode + } + + if (link && link.href && isDownloadToTrack(link.href)) { + + if (middle || click) { + plausible('File Download', {props: {url: link.href}}) + } + + // Delay navigation so that Plausible is notified of the click + if(!link.target || link.target.match(/^_(self|parent|top)$/i)) { + if (!(event.ctrlKey || event.metaKey || event.shiftKey) && click) { + setTimeout(function() { + location.href = link.href; + }, 150); + event.preventDefault(); + } + } + } + } + + function isDownloadToTrack(url) { + var fileType = url.split(".").pop(); + return fileTypesToTrack.some(function(fileTypeToTrack) { + return fileTypeToTrack == fileType + }) + } + + document.addEventListener('click', handleDownload); + document.addEventListener('auxclick', handleDownload); + {{/if}} + var queue = (window.plausible && window.plausible.q) || [] window.plausible = trigger for (var i = 0; i < queue.length; i++) {