From b14b3127bb0b4d47fa6dc1ee9e43b25a3fbf3bce Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Mon, 25 Apr 2022 21:31:14 +0200 Subject: [PATCH] add performancetracking --- .gitignore | 2 +- README.md | 9 +++--- example/index.html | 13 ++++++++ example/main.ts | 17 ++++++++++ package.json | 3 +- src/aliases.ts | 1 + src/performancetracking.ts | 66 ++++++++++++++++++++++++++++++++++++++ src/tracker.ts | 60 +++++++++++++++++++++++----------- 8 files changed, 146 insertions(+), 25 deletions(-) create mode 100644 example/index.html create mode 100644 example/main.ts diff --git a/.gitignore b/.gitignore index c3d9ba6..52a95cb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,6 @@ out.min.js *.map node_modules dist -example/ +example/* !example/index.html !example/main.ts diff --git a/README.md b/README.md index 1f82cba..7e82711 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,6 @@ That said, if you are interested in trying it out, I'd be interested in hearing - much smaller (1KB gzipped with only the core code) - easier to bundle in applications - - ## Features - tracking page views @@ -32,17 +30,17 @@ That said, if you are interested in trying it out, I'd be interested in hearing - Campaign tracking - Search Tracking - Goal Tracking -- manual Outlink Tracking +- Outlink Tracking - manual ping - manual DoNotTrack detection +- sendBeacon ## missing (yet) - non utf-8 pages +- performance tracking - setCustomUrl - setReferrerUrl -- automatic outlink tracking -- event queue (every tracking request is sent immediatly when it is tracked)[^1] - HeartBeatTimer (automatic regular ping) ## missing (???) @@ -51,6 +49,7 @@ That said, if you are interested in trying it out, I'd be interested in hearing - Content Tracking - download tracking - setDomains +- event queue (every tracking request is sent immediatly when it is tracked)[^1] ## missing (intentionally) diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..0175dc0 --- /dev/null +++ b/example/index.html @@ -0,0 +1,13 @@ + + + + + Title + + + +TEST + + + + diff --git a/example/main.ts b/example/main.ts new file mode 100644 index 0000000..f4e5d46 --- /dev/null +++ b/example/main.ts @@ -0,0 +1,17 @@ +import {MatomoLiteTracker} from "../src/tracker"; +import {BrowserFeatures, detectBrowserPlugins} from "../src/browserfeatures"; +import {defaultDownloadFileExtensions, enableLinkTracking} from "../src/linktracking"; +import {PerformanceMetric} from "../src/performancetracking"; + +const matomo = new MatomoLiteTracker("https://dev.matomo", 1) +matomo.performanceMetric = new PerformanceMetric() +console.log(matomo.performanceMetric) +const features = new BrowserFeatures() +features.addBrowserPlugins(detectBrowserPlugins()) +matomo.browserFeatures = features +matomo.customDimensions = { + 2: "something" +} + +enableLinkTracking(matomo, defaultDownloadFileExtensions) +matomo.trackPageview() diff --git a/package.json b/package.json index 1d4bbf1..fde9e16 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "module": "dist/tracker.js", "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "tsc && node --loader ts-node/esm build.ts", + "serve": "node --loader ts-node/esm build.ts serve" }, "devDependencies": { "esbuild": "^0.14.38", diff --git a/src/aliases.ts b/src/aliases.ts index 647560d..55c437a 100644 --- a/src/aliases.ts +++ b/src/aliases.ts @@ -3,3 +3,4 @@ export const navigatorAlias = navigator export const screenAlias = screen export const windowAlias = window export const locationAlias = windowAlias.location +export const performanceAlias = windowAlias.performance diff --git a/src/performancetracking.ts b/src/performancetracking.ts index e69de29..afd495a 100644 --- a/src/performancetracking.ts +++ b/src/performancetracking.ts @@ -0,0 +1,66 @@ +import {performanceAlias} from "./aliases"; + +// strongly based on appendAvailablePerformanceMetrics() in piwik.js + +function diff(end: number, start: number): number | undefined { + const value = Math.round(end - start) + if (value > 0) { + return value + } + return +} + +export class PerformanceMetric { + pf_net?: number | undefined + pf_srv?: number | undefined + pf_tfr?: number | undefined + pf_dm1?: number | undefined + pf_dm2?: number | undefined + pf_onl?: number | undefined + + constructor() { + let performanceData: PerformanceNavigationTiming | PerformanceTiming | undefined + performanceData = (typeof performanceAlias.timing === 'object') && performanceAlias.timing ? performanceAlias.timing : undefined; + if (typeof performanceData === "undefined") { + if ((typeof performanceAlias.getEntriesByType === 'function') && performanceAlias.getEntriesByType('navigation')) { + performanceData = performanceAlias.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; + } + } + + if (!performanceData) { + return; + } + console.log(performanceData) + if (performanceData.connectEnd && performanceData.fetchStart) { + this.pf_net = diff(performanceData.connectEnd, performanceData.fetchStart) + console.info(performanceData.connectEnd - performanceData.fetchStart) + } + if (performanceData.responseStart && performanceData.requestStart) { + this.pf_srv = diff(performanceData.responseStart, performanceData.requestStart) + } + if (performanceData.responseStart && performanceData.responseEnd) { + this.pf_tfr = diff(performanceData.responseStart, performanceData.responseEnd) + } + if (performanceData.responseStart && performanceData.responseEnd) { + this.pf_tfr = diff(performanceData.responseStart, performanceData.responseEnd) + } + // @ts-ignore + if (performanceData.domLoading !== "undefined") { + // @ts-ignore + if (performanceData.domInteractive && performanceData.domLoading) { + // @ts-ignore + this.pf_dm1 = diff(performanceData.domInteractive, performanceData.domLoading) + } + } else { + if (performanceData.domInteractive && performanceData.responseEnd) { + this.pf_dm1 = diff(performanceData.domInteractive, performanceData.responseEnd) + } + } + if (performanceData.domComplete && performanceData.domInteractive) { + this.pf_dm2 = diff(performanceData.domComplete, performanceData.domInteractive) + } + if (performanceData.loadEventEnd && performanceData.loadEventStart) { + this.pf_onl = diff(performanceData.loadEventEnd, performanceData.loadEventStart) + } + } +} diff --git a/src/tracker.ts b/src/tracker.ts index a5c8aa6..0b0f36c 100644 --- a/src/tracker.ts +++ b/src/tracker.ts @@ -1,8 +1,9 @@ -import {documentAlias} from "./aliases"; +import {documentAlias, navigatorAlias} from "./aliases"; import {boolToIntBool, getCurrentURL} from "./url"; import type {CustomDimensionName, CustomDimensions, IntBoolean, LinkType} from "./types"; import type {BrowserFeatures} from "./browserfeatures"; import {generateUniqueId} from "./util"; +import type {PerformanceMetric} from "./performancetracking"; // the query paramters the Matomo API expects to recieve @@ -66,6 +67,13 @@ interface Request { interface PageViewRequest extends Request { // Title action_name: string + pf_net?: number + pf_srv?: number + pf_tfr?: number + pf_dm1?: number + pf_dm2?: number + pf_onl?: number + } interface EventRequest extends Request { @@ -106,6 +114,8 @@ export class MatomoLiteTracker { siteID: number matomoURL: string phpFileName: string = "matomo.php" + useSendBeacon: boolean = false + performanceMetric?: PerformanceMetric userID?: string browserFeatures?: BrowserFeatures customDimensions?: CustomDimensions @@ -163,6 +173,16 @@ export class MatomoLiteTracker { this.pageViewID = generateUniqueId() const parameters = this.getRequest() as PageViewRequest parameters.action_name = customTitle ? customTitle : documentAlias.title + const performanceMetric = this.performanceMetric + if (performanceMetric) { + console.log(performanceMetric) + Object.entries(performanceMetric).forEach(([key, value]) => { + if (typeof value !== "undefined") { + parameters[key] = value + } + }) + + } this.sendRequest(parameters) } @@ -203,7 +223,7 @@ export class MatomoLiteTracker { trackLink(sourceUrl: string, linkType: LinkType) { const parameters = this.getRequest() as OutlinkRequest parameters[linkType] = sourceUrl - this.sendRequest(parameters) + this.sendRequest(parameters, true) } ping() { @@ -212,28 +232,32 @@ export class MatomoLiteTracker { this.sendRequest(parameters) } - sendRequest(parameters: Request) { + sendRequest(parameters: Request, forceBeacon: boolean = false) { console.log(parameters) const requestMethod = this.requestMethod const params = new URLSearchParams(parameters) let url = this.matomoURL + (this.matomoURL.endsWith("/") ? "" : "/") + this.phpFileName - if (requestMethod === "GET") { - url += "?" + params.toString() - } - const options: RequestInit = { - method: requestMethod, - mode: "no-cors", - cache: "no-cache", - credentials: "omit", - } - if (requestMethod === "POST") { - options.body = params - } console.log(url) - fetch(url, options).then(value => { - console.info(value) - }) + if (this.useSendBeacon || forceBeacon) { + navigatorAlias.sendBeacon(url, params) + } else { + if (requestMethod === "GET") { + url += "?" + params.toString() + } + const options: RequestInit = { + method: requestMethod, + mode: "no-cors", + cache: "no-cache", + credentials: "omit", + } + if (requestMethod === "POST") { + options.body = params + } + fetch(url, options).then(value => { + console.info(value) + }) + } } }