|
| 1 | +/* eslint-disable func-style */ |
| 2 | +import type { App } from "vue"; |
| 3 | +import { createGtm } from "@gtm-support/vue-gtm"; |
| 4 | + |
| 5 | +import { |
| 6 | + ANALYTIC_EVENT_CATEGORIES, |
| 7 | + type AnalyticEventNames, |
| 8 | + type AnalyticEvents, |
| 9 | + type AnalyticsConfig, |
| 10 | + type TrackFn, |
| 11 | +} from "./types"; |
| 12 | + |
| 13 | +// Following types are taken from: |
| 14 | +// https://github.com/SocialGouv/matomo-next/blob/master/src/types.ts |
| 15 | + |
| 16 | +type HeatmapConfig = { |
| 17 | + /** |
| 18 | + * Enable/disable keystroke capture (default: false) |
| 19 | + * Since v3.2.0, keystrokes are disabled by default |
| 20 | + */ |
| 21 | + captureKeystrokes?: boolean; |
| 22 | + |
| 23 | + /** |
| 24 | + * Enable/disable recording of mouse and touch movements (default: true) |
| 25 | + * Set to false to disable the "Move Heatmap" feature |
| 26 | + */ |
| 27 | + recordMovements?: boolean; |
| 28 | + |
| 29 | + /** |
| 30 | + * Maximum capture time in seconds (default: 600 = 10 minutes) |
| 31 | + * Set to less than 29 minutes to avoid creating new visits |
| 32 | + */ |
| 33 | + maxCaptureTime?: number; |
| 34 | + |
| 35 | + /** |
| 36 | + * Disable automatic detection of new page views (default: false) |
| 37 | + * Set to true if you track "virtual" page views for events/downloads |
| 38 | + */ |
| 39 | + disableAutoDetectNewPageView?: boolean; |
| 40 | + |
| 41 | + /** |
| 42 | + * Custom trigger function to control when recording happens |
| 43 | + * Return true to record, false to skip |
| 44 | + * @param config - Configuration object with heatmap/session ID |
| 45 | + */ |
| 46 | + trigger?: (config: { id?: number }) => boolean; |
| 47 | + |
| 48 | + /** |
| 49 | + * Manually add heatmap/session configuration |
| 50 | + * Use this to manually configure specific heatmaps or sessions |
| 51 | + */ |
| 52 | + addConfig?: { |
| 53 | + heatmap?: { id: number }; |
| 54 | + sessionRecording?: { id: number }; |
| 55 | + }; |
| 56 | +}; |
| 57 | + |
| 58 | +/** |
| 59 | + * Custom Dimensions object that can be passed as the last argument of many tracking calls |
| 60 | + * (action-scoped dimensions). |
| 61 | + * |
| 62 | + * Examples: |
| 63 | + * - `["trackEvent", "Video", "Play", "Intro", 42, { dimension1: "Premium" }]` |
| 64 | + * - `["trackSiteSearch", "keyword", "category", 12, { dimension4: "Test" }]` |
| 65 | + * - `["trackPageView", "My title", { dimension7: "Value" }]` |
| 66 | + * |
| 67 | + * Note: keys are expected to be `"dimension1"`, `"dimension2"`, etc. |
| 68 | + * We intentionally keep this type compatible with older TS/ESLint parsers. |
| 69 | + */ |
| 70 | +type Dimensions = { |
| 71 | + dimension1?: string; |
| 72 | + dimension2?: string; |
| 73 | + dimension3?: string; |
| 74 | + dimension4?: string; |
| 75 | + dimension5?: string; |
| 76 | + dimension6?: string; |
| 77 | + dimension7?: string; |
| 78 | + dimension8?: string; |
| 79 | + dimension9?: string; |
| 80 | + dimension10?: string; |
| 81 | +}; |
| 82 | + |
| 83 | +/** |
| 84 | + * A single value inside a Matomo command pushed to the queue. |
| 85 | + * Kept as a separate exported type for consumers that want to model custom commands. |
| 86 | + */ |
| 87 | +export type PushArg = |
| 88 | + | string |
| 89 | + | number |
| 90 | + | boolean |
| 91 | + | null |
| 92 | + | undefined |
| 93 | + | Record<string, unknown> |
| 94 | + | readonly unknown[] |
| 95 | + | ((...args: any[]) => unknown); |
| 96 | + |
| 97 | +/** |
| 98 | + * Strict Matomo `trackEvent` typing. |
| 99 | + * |
| 100 | + * Log an event with an event category (Videos, Music, Games...), an event action |
| 101 | + * (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...), and an optional event name |
| 102 | + * |
| 103 | + * Notes: |
| 104 | + * - `name` and `value` are optional |
| 105 | + * - `value` (when present) must be numeric |
| 106 | + * - `value` requires `name` to be provided (no "hole" argument) |
| 107 | + */ |
| 108 | +type MatomoTrackEventCommand = |
| 109 | + | readonly ["trackEvent", string, string] |
| 110 | + | readonly ["trackEvent", string, string, Dimensions] |
| 111 | + | readonly ["trackEvent", string, string, string] |
| 112 | + | readonly ["trackEvent", string, string, string, Dimensions] |
| 113 | + | readonly ["trackEvent", string, string, string, number] |
| 114 | + | readonly ["trackEvent", string, string, string, number, Dimensions]; |
| 115 | + |
| 116 | +/** |
| 117 | + * Core commands used by this library (and/or documented in docs). |
| 118 | + * This list is intentionally limited: any unknown command is still allowed via `MatomoCustomCommand`. |
| 119 | + */ |
| 120 | +type MatomoCoreCommand = |
| 121 | + // Page view |
| 122 | + | readonly ["trackPageView"] |
| 123 | + | readonly ["trackPageView", string] |
| 124 | + | readonly ["trackPageView", Dimensions] |
| 125 | + | readonly ["trackPageView", string, Dimensions] |
| 126 | + // Standard setup / configuration |
| 127 | + | readonly ["enableLinkTracking"] |
| 128 | + | readonly ["disableCookies"] |
| 129 | + | readonly ["setTrackerUrl", string] |
| 130 | + | readonly ["setSiteId", string] |
| 131 | + | readonly ["setReferrerUrl", string] |
| 132 | + | readonly ["setCustomUrl", string] |
| 133 | + | readonly ["deleteCustomVariables", string] |
| 134 | + | readonly ["setDocumentTitle", string] |
| 135 | + // Site search (Matomo supports an optional dimensions object as last param) |
| 136 | + | readonly ["trackSiteSearch", string] |
| 137 | + | readonly ["trackSiteSearch", string, Dimensions] |
| 138 | + | readonly ["trackSiteSearch", string, string] |
| 139 | + | readonly ["trackSiteSearch", string, string, Dimensions] |
| 140 | + | readonly ["trackSiteSearch", string, string, number] |
| 141 | + | readonly ["trackSiteSearch", string, string, number, Dimensions] |
| 142 | + // Heartbeat |
| 143 | + | readonly ["enableHeartBeatTimer"] |
| 144 | + | readonly ["enableHeartBeatTimer", number] |
| 145 | + // Custom dimensions (global, persisted until changed) |
| 146 | + | readonly ["setCustomDimension", number, string] |
| 147 | + // Goals (Matomo supports an optional dimensions object as last param) |
| 148 | + | readonly ["trackGoal", number] |
| 149 | + | readonly ["trackGoal", number, Dimensions] |
| 150 | + | readonly ["trackGoal", number, number] |
| 151 | + | readonly ["trackGoal", number, number, Dimensions] |
| 152 | + // Links (Matomo supports an optional dimensions object as last param) |
| 153 | + | readonly ["trackLink", string, string] |
| 154 | + | readonly ["trackLink", string, string, Dimensions] |
| 155 | + // User |
| 156 | + | readonly ["setUserId", string]; |
| 157 | + |
| 158 | +/** |
| 159 | + * Heatmap & Session Recording plugin commands used by this library. |
| 160 | + */ |
| 161 | +type HeatmapSessionRecordingCommand = |
| 162 | + | readonly ["HeatmapSessionRecording::enableDebugMode"] |
| 163 | + | readonly ["HeatmapSessionRecording::disableCaptureKeystrokes"] |
| 164 | + | readonly ["HeatmapSessionRecording::disableRecordMovements"] |
| 165 | + | readonly ["HeatmapSessionRecording::setMaxCaptureTime", number] |
| 166 | + | readonly ["HeatmapSessionRecording::disableAutoDetectNewPageView"] |
| 167 | + | readonly [ |
| 168 | + "HeatmapSessionRecording::setTrigger", |
| 169 | + NonNullable<HeatmapConfig["trigger"]>, |
| 170 | + ] |
| 171 | + | readonly [ |
| 172 | + "HeatmapSessionRecording::addConfig", |
| 173 | + NonNullable<HeatmapConfig["addConfig"]>, |
| 174 | + ] |
| 175 | + | readonly ["HeatmapSessionRecording::enable"]; |
| 176 | + |
| 177 | +type MatomoKnownCommand = |
| 178 | + | MatomoTrackEventCommand |
| 179 | + | MatomoCoreCommand |
| 180 | + | HeatmapSessionRecordingCommand; |
| 181 | + |
| 182 | +/** |
| 183 | + * Matomo also supports queueing functions executed once the tracker is ready. |
| 184 | + */ |
| 185 | +type MatomoCallbackCommand = readonly [(...args: any[]) => unknown]; |
| 186 | + |
| 187 | +export type PushArgs = MatomoKnownCommand | MatomoCallbackCommand; |
| 188 | + |
| 189 | +function push(args: PushArgs): void { |
| 190 | + if (!(window as any)._paq) { |
| 191 | + (window as any)._paq = []; |
| 192 | + } |
| 193 | + |
| 194 | + (window as any)._paq.push(args); |
| 195 | +} |
| 196 | + |
| 197 | +type Mapper = { |
| 198 | + [K in AnalyticEventNames]: (payload: AnalyticEvents[K]) => void; |
| 199 | +}; |
| 200 | + |
| 201 | +const mapper: Mapper = { |
| 202 | + "node.added": (payload) => { |
| 203 | + push(["trackEvent", ANALYTIC_EVENT_CATEGORIES.Authoring, payload.via, ""]); |
| 204 | + }, |
| 205 | + "node.executed": (payload) => { |
| 206 | + push(["trackEvent", ANALYTIC_EVENT_CATEGORIES.Execution, payload.via, ""]); |
| 207 | + }, |
| 208 | +}; |
| 209 | + |
| 210 | +const track: TrackFn = (eventType, payload) => { |
| 211 | + mapper[eventType](payload); |
| 212 | +}; |
| 213 | + |
| 214 | +const init = (app: App, config: AnalyticsConfig) => { |
| 215 | + // TODO: investigate |
| 216 | + /** |
| 217 | + * We use Matomo, but indirectly. Currently we fetch the analytics scripts |
| 218 | + * via google-tag-manager (gtm). However, the actual tracking tool used under the hood |
| 219 | + * is Matomo. We would need to check if the gtm integration can be used directly, but for this |
| 220 | + * impl, we just use gtm for init and then further tracking is made via the Matomo |
| 221 | + * global functions |
| 222 | + */ |
| 223 | + |
| 224 | + const plugin = createGtm({ id: config.trackingAPIKey, defer: true }); |
| 225 | + app.use(plugin); |
| 226 | + |
| 227 | + // eslint-disable-next-line no-magic-numbers |
| 228 | + push(["enableHeartBeatTimer", 30]); |
| 229 | +}; |
| 230 | + |
| 231 | +export const matomoAdapter = { init, track }; |
0 commit comments