ScriptV2: `callback`, file downloads and `logging` changes (#5514)
* Make logging in tracker script configurable * Improved callbacks * Improved fileDownloads * Export DEFAULT_FILE_TYPES * Cross-browser compatibility * Rename * Rename a test * chore: Bump tracker_script_version to 20
This commit is contained in:
parent
7da74b3031
commit
1f86ae55ef
|
|
@ -88,7 +88,7 @@ function wrapInstantlyEvaluatingFunction(baseCode) {
|
|||
|
||||
// Works around minification limitation of swc not allowing exports
|
||||
function addExports(code) {
|
||||
return `${code}\nexport { init, track }`
|
||||
return `${code}\nexport { init, track, DEFAULT_FILE_TYPES }`
|
||||
}
|
||||
|
||||
export function compileWebSnippet() {
|
||||
|
|
@ -129,7 +129,7 @@ function minify(code, globals, variant = {}) {
|
|||
}
|
||||
|
||||
if (variant.npm_package) {
|
||||
minifyOptions.mangle.reserved = ['init', 'track']
|
||||
minifyOptions.mangle.reserved = ['init', 'track', 'DEFAULT_FILE_TYPES']
|
||||
minifyOptions.mangle.toplevel = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## Unreleased
|
||||
|
||||
## [0.2.3] - 2025-06-17
|
||||
|
||||
- Rename `Form Submission` system event to `Form: Submission`.
|
||||
- Add `logging` option
|
||||
- Improve `callback` option
|
||||
- Improve `fileDownloads` typing, export `DEFAULT_FILE_TYPES`
|
||||
|
||||
## [0.2.2] - 2025-06-16
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ See also [plausible.d.ts](https://github.com/plausible/analytics/blob/master/tra
|
|||
| `fileDownloads` | Whether to track file downloads. | `false` |
|
||||
| `formSubmissions` | Whether to track form submissions. | `false` |
|
||||
| `captureOnLocalhost` | Whether to capture events on localhost. | `false` |
|
||||
| `logging` | Whether to log on ignored events. | `true` |
|
||||
| `customProperties` | Object or function that returns custom properties for a given event. | `{}` |
|
||||
| `transformRequest` | Function that allows transforming or ignoring requests | |
|
||||
|
||||
|
|
@ -102,6 +103,26 @@ track('Purchase', { revenue: { amount: 15.99, currency: 'USD' } })
|
|||
|
||||
More information can be found in [ecommerce revenue tracking docs](https://plausible.io/docs/ecommerce-revenue-tracking)
|
||||
|
||||
### Callbacks
|
||||
|
||||
When calling `track`, you can pass in a custom callback.
|
||||
|
||||
```javascript
|
||||
import { track } from 'macobo-test-tracker'
|
||||
|
||||
track('some-event', {
|
||||
callback: (result) => {
|
||||
if (result?.status) {
|
||||
console.debug("Request to plausible done. Status:", result.status)
|
||||
} else if (result?.error) {
|
||||
console.log("Error handling request:", result.error)
|
||||
} else {
|
||||
console.log("Request was ignored")
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Opt out and exclude yourself from the analytics
|
||||
|
||||
Since plausible-tracker is bundled with your application code, using an ad-blocker to exclude your visits isn't an option. Fortunately Plausible has an alternative for this scenario: plausible-tracker will not send events if `localStorage.plausible_ignore` is set to `"true"`.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export interface PlausibleConfig {
|
|||
outboundLinks?: boolean
|
||||
|
||||
// Whether to track file downloads. Defaults to false.
|
||||
fileDownloads?: boolean
|
||||
fileDownloads?: boolean | { fileExtensions: string[] }
|
||||
|
||||
// Whether to track form submissions. Defaults to false.
|
||||
formSubmissions?: boolean
|
||||
|
|
@ -31,6 +31,9 @@ export interface PlausibleConfig {
|
|||
// Whether to capture events on localhost. Defaults to false.
|
||||
captureOnLocalhost?: boolean
|
||||
|
||||
// Whether to log on ignored events. Defaults to true.
|
||||
logging?: boolean
|
||||
|
||||
// Custom properties to add to all events tracked.
|
||||
// If passed as a function, it will be called when `track` is called.
|
||||
customProperties?: CustomProperties | ((eventName: string) => CustomProperties)
|
||||
|
|
@ -59,7 +62,8 @@ export interface PlausibleEventOptions {
|
|||
// Called when request to `endpoint` completes or is ignored.
|
||||
// When request is ignored, the result will be undefined.
|
||||
// When request was delivered, the result will be an object with the response status code of the request.
|
||||
callback?: (result?: { status: number } | undefined) => void
|
||||
// When there was a network error, the result will be an object with the error object.
|
||||
callback?: (result?: { status: number } | { error: unknown } | undefined) => void
|
||||
|
||||
// Overrides the URL of the page that the event is being tracked on.
|
||||
// If not provided, `location.href` will be used.
|
||||
|
|
@ -91,3 +95,6 @@ export type PlausibleRequestPayload = {
|
|||
// Whether the event is interactive
|
||||
i?: boolean,
|
||||
} & Record<string, unknown>
|
||||
|
||||
// Default file types that are tracked when `fileDownloads` is enabled.
|
||||
export const DEFAULT_FILE_TYPES: string[]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"tracker_script_version": 19,
|
||||
"tracker_script_version": 20,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"deploy": "node compile.js",
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@ export function init(overrides) {
|
|||
Object.assign(config, overrides, {
|
||||
// Explicitly set domain before any overrides are applied as `plausible-web` does not support overriding it
|
||||
domain: config.domain,
|
||||
// autoCapturePageviews defaults to `true`
|
||||
autoCapturePageviews: overrides.autoCapturePageviews !== false
|
||||
// Configuration which defaults to `true`
|
||||
autoCapturePageviews: overrides.autoCapturePageviews !== false,
|
||||
logging: overrides.logging !== false
|
||||
})
|
||||
} else if (COMPILE_PLAUSIBLE_NPM) {
|
||||
if (config.isInitialized) {
|
||||
|
|
@ -41,13 +42,15 @@ export function init(overrides) {
|
|||
overrides.endpoint = 'https://plausible.io/api/event'
|
||||
}
|
||||
Object.assign(config, overrides, {
|
||||
autoCapturePageviews: overrides.autoCapturePageviews !== false
|
||||
autoCapturePageviews: overrides.autoCapturePageviews !== false,
|
||||
logging: overrides.logging !== false
|
||||
})
|
||||
config.isInitialized = true
|
||||
} else {
|
||||
// Legacy variant
|
||||
config.endpoint = scriptEl.getAttribute('data-api') || defaultEndpoint()
|
||||
config.domain = scriptEl.getAttribute('data-domain')
|
||||
config.logging = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
import { config, scriptEl, location, document } from './config'
|
||||
import { track } from './track'
|
||||
|
||||
export var DEFAULT_FILE_TYPES = ['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', 'dmg']
|
||||
|
||||
var MIDDLE_MOUSE_BUTTON = 1
|
||||
var PARENTS_TO_SEARCH_LIMIT = 3
|
||||
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', 'dmg']
|
||||
var fileTypesToTrack = defaultFileTypes
|
||||
var fileTypesToTrack = DEFAULT_FILE_TYPES
|
||||
|
||||
function getLinkEl(link) {
|
||||
while (link && (typeof link.tagName === 'undefined' || !isLink(link) || !link.href)) {
|
||||
|
|
@ -156,8 +157,8 @@ export function init() {
|
|||
|
||||
if (COMPILE_FILE_DOWNLOADS && (!COMPILE_CONFIG || config.fileDownloads)) {
|
||||
if (COMPILE_CONFIG) {
|
||||
if (Array.isArray(config.fileDownloads)) {
|
||||
fileTypesToTrack = config.fileDownloads
|
||||
if (typeof config.fileDownloads === 'object' && Array.isArray(config.fileDownloads.fileExtensions)) {
|
||||
fileTypesToTrack = config.fileDownloads.fileExtensions
|
||||
}
|
||||
} else {
|
||||
var fileTypesAttr = scriptEl.getAttribute('file-types')
|
||||
|
|
@ -167,7 +168,7 @@ export function init() {
|
|||
fileTypesToTrack = fileTypesAttr.split(",")
|
||||
}
|
||||
if (addFileTypesAttr) {
|
||||
fileTypesToTrack = addFileTypesAttr.split(",").concat(defaultFileTypes)
|
||||
fileTypesToTrack = addFileTypesAttr.split(",").concat(DEFAULT_FILE_TYPES)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,13 @@ export function sendRequest(endpoint, payload, options) {
|
|||
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState === 4) {
|
||||
if (request.status === 0) {
|
||||
options && options.callback && options.callback({ error: new Error('Network error') })
|
||||
} else {
|
||||
options && options.callback && options.callback({ status: request.status })
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (window.fetch) {
|
||||
fetch(endpoint, {
|
||||
|
|
@ -22,7 +26,9 @@ export function sendRequest(endpoint, payload, options) {
|
|||
body: JSON.stringify(payload)
|
||||
}).then(function (response) {
|
||||
options && options.callback && options.callback({ status: response.status })
|
||||
}).catch(function () { })
|
||||
}).catch(function (error) {
|
||||
options && options.callback && options.callback({ error })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { init as initEngagementTracking } from './engagement'
|
||||
import { init as initConfig, config } from './config'
|
||||
import { init as initCustomEvents } from './custom-events'
|
||||
import { init as initCustomEvents, DEFAULT_FILE_TYPES } from './custom-events'
|
||||
import { init as initAutocapture } from './autocapture'
|
||||
import { track } from './track'
|
||||
|
||||
function init(overrides) {
|
||||
initConfig(overrides || {})
|
||||
|
||||
if (COMPILE_PLAUSIBLE_WEB && window.plausible && window.plausible.l) {
|
||||
if (config.logging) {
|
||||
console.warn('Plausible analytics script was already initialized, skipping init')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
initConfig(overrides)
|
||||
initEngagementTracking()
|
||||
|
||||
if (!COMPILE_MANUAL || (COMPILE_CONFIG && config.autoCapturePageviews)) {
|
||||
|
|
@ -49,4 +52,4 @@ if (COMPILE_PLAUSIBLE_WEB) {
|
|||
}
|
||||
|
||||
// In npm module, we export the init and track functions
|
||||
// export { init, track }
|
||||
// export { init, track, DEFAULT_FILE_TYPES }
|
||||
|
|
|
|||
|
|
@ -128,7 +128,10 @@ export function track(eventName, options) {
|
|||
|
||||
|
||||
function onIgnoredEvent(eventName, options, reason) {
|
||||
if (reason) console.warn('Ignoring Event: ' + reason);
|
||||
if (reason && config.logging) {
|
||||
console.warn('Ignoring Event: ' + reason);
|
||||
}
|
||||
|
||||
options && options.callback && options.callback()
|
||||
|
||||
if (eventName === 'pageview') {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
import { LOCAL_SERVER_ADDR } from './support/server'
|
||||
|
||||
async function openPage(page, src, endpoint = `${LOCAL_SERVER_ADDR}/api/event`) {
|
||||
await page.goto(`/callbacks.html?src=${src}&endpoint=${endpoint}`)
|
||||
}
|
||||
|
||||
function trackWithCallback(page) {
|
||||
return page.evaluate(() => window.callPlausible())
|
||||
}
|
||||
|
||||
function testCallbacks(trackerScriptSrc) {
|
||||
test("on successful request", async ({ page }) => {
|
||||
await openPage(page, trackerScriptSrc)
|
||||
|
||||
const callbackResult = await trackWithCallback(page)
|
||||
expect(callbackResult).toEqual({ status: 202 })
|
||||
})
|
||||
|
||||
test('on ignored request', async ({ page }) => {
|
||||
const trackerScriptSrcwithoutLocal = trackerScriptSrc.replace('.local', '')
|
||||
await openPage(page, trackerScriptSrcwithoutLocal)
|
||||
|
||||
const callbackResult = await trackWithCallback(page)
|
||||
expect(callbackResult).toEqual(undefined)
|
||||
})
|
||||
|
||||
test('on 404', async ({ page }) => {
|
||||
await openPage(page, trackerScriptSrc, `${LOCAL_SERVER_ADDR}/api/404`)
|
||||
|
||||
const callbackResult = await trackWithCallback(page)
|
||||
expect(callbackResult).toEqual({ status: 404 })
|
||||
})
|
||||
|
||||
test('on network error', async ({ page }) => {
|
||||
await openPage(page, trackerScriptSrc, `h://bad.url////`)
|
||||
|
||||
const callbackResult = await trackWithCallback(page)
|
||||
expect(callbackResult.error).toBeInstanceOf(Error)
|
||||
})
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route(`${LOCAL_SERVER_ADDR}/api/event`, (route) => {
|
||||
return route.fulfill({ status: 202, contentType: 'text/plain', body: 'ok' })
|
||||
})
|
||||
await page.route(`${LOCAL_SERVER_ADDR}/api/404`, (route) => {
|
||||
return route.fulfill({ status: 404, contentType: 'text/plain', body: 'ok' })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe("callbacks behavior (with fetch)", () => {
|
||||
testCallbacks('/tracker/js/plausible.local.manual.js')
|
||||
})
|
||||
|
||||
test.describe("callbacks behavior (with xhr/compat)", () => {
|
||||
testCallbacks('/tracker/js/plausible.compat.local.manual.js')
|
||||
})
|
||||
|
|
@ -94,7 +94,7 @@ test.describe('file downloads', () => {
|
|||
})
|
||||
|
||||
test('tracks iso but not pdf files when config.fileDownloads includes "iso"', async ({ page }) => {
|
||||
await openPage(page, { fileDownloads: ['iso'] })
|
||||
await openPage(page, { fileDownloads: { fileExtensions: ['iso'] } })
|
||||
|
||||
await expectPlausibleInAction(page, {
|
||||
action: async () => {
|
||||
|
|
@ -111,7 +111,7 @@ test.describe('file downloads', () => {
|
|||
})
|
||||
|
||||
test('ignores malformed value but enables the feature', async ({ page }) => {
|
||||
await openPage(page, { fileDownloads: 'iso' })
|
||||
await openPage(page, { fileDownloads: { fileExtensions: 'iso' } })
|
||||
|
||||
await expectPlausibleInAction(page, {
|
||||
action: async () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>callbacks</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const src = params.get('src')
|
||||
const endpoint = params.get('endpoint')
|
||||
|
||||
const script = document.createElement('script')
|
||||
|
||||
script.src = src
|
||||
script.id = 'plausible'
|
||||
script.setAttribute('data-domain', 'test.com')
|
||||
script.setAttribute('data-api', endpoint)
|
||||
|
||||
var r = document.getElementsByTagName("script")[0]
|
||||
r.parentNode.insertBefore(script, r)
|
||||
|
||||
window.callPlausible = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
plausible('event', {
|
||||
callback: (result) => { resolve(result) }
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -5,9 +5,10 @@ import {
|
|||
mockRequest,
|
||||
e as expecting,
|
||||
isPageviewEvent,
|
||||
isEngagementEvent
|
||||
isEngagementEvent,
|
||||
delay
|
||||
} from './support/test-utils'
|
||||
import { test } from '@playwright/test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
// Wrapper around calling `plausible.init` in the page context for users of `testPlausibleConfiguration`
|
||||
export async function callInit(page, config, parent) {
|
||||
|
|
@ -49,13 +50,36 @@ export function testPlausibleConfiguration({ openPage, initPlausible, fixtureNam
|
|||
})
|
||||
})
|
||||
|
||||
test('does not trigger any events when `local` config is disabled', async ({ page }) => {
|
||||
test('logs, does not trigger any events when `local` config is disabled', async ({ page }) => {
|
||||
const consolePromise = page.waitForEvent('console')
|
||||
|
||||
await expectPlausibleInAction(page, {
|
||||
action: () => openPage(page, { captureOnLocalhost: false }),
|
||||
expectedRequests: [],
|
||||
refutedRequests: [{ n: 'pageview' }]
|
||||
})
|
||||
|
||||
const warning = await consolePromise
|
||||
expect(warning.type()).toBe("warning")
|
||||
expect(warning.text()).toEqual('Ignoring Event: localhost')
|
||||
})
|
||||
|
||||
test('does not log when `local` and `logging` config is disabled', async ({ page }) => {
|
||||
const consolePromise = page.waitForEvent('console')
|
||||
|
||||
await expectPlausibleInAction(page, {
|
||||
action: async () => {
|
||||
await openPage(page, {}, { skipPlausibleInit: true })
|
||||
await initPlausible(page, { captureOnLocalhost: false, logging: false })
|
||||
},
|
||||
expectedRequests: [],
|
||||
refutedRequests: [{ n: 'pageview' }]
|
||||
})
|
||||
|
||||
const result = await Promise.race([consolePromise, delay(1000)])
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
test('does not track pageview props without the feature being enabled', async ({ page }) => {
|
||||
await expectPlausibleInAction(page, {
|
||||
action: async () => {
|
||||
|
|
|
|||
|
|
@ -208,6 +208,6 @@ function checkEqual(a, b) {
|
|||
return a === b
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
export function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue