mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 06:37:18 +01:00
Merge branch 'dev' into jajaja
# Conflicts: # package.json # pnpm-lock.yaml
This commit is contained in:
commit
94b4b66a3d
122 changed files with 41347 additions and 2747 deletions
|
|
@ -187,26 +187,19 @@ export async function POST(request: Request) {
|
|||
websiteId,
|
||||
sessionId,
|
||||
visitId,
|
||||
createdAt,
|
||||
|
||||
// Page
|
||||
pageTitle: safeDecodeURIComponent(title),
|
||||
hostname: hostname || urlDomain,
|
||||
urlPath: safeDecodeURI(urlPath),
|
||||
urlQuery,
|
||||
utmSource,
|
||||
utmMedium,
|
||||
utmCampaign,
|
||||
utmContent,
|
||||
utmTerm,
|
||||
referrerPath: safeDecodeURI(referrerPath),
|
||||
referrerQuery,
|
||||
referrerDomain,
|
||||
pageTitle: safeDecodeURIComponent(title),
|
||||
gclid,
|
||||
fbclid,
|
||||
msclkid,
|
||||
ttclid,
|
||||
lifatid,
|
||||
twclid,
|
||||
eventName: name,
|
||||
eventData: data,
|
||||
hostname: hostname || urlDomain,
|
||||
|
||||
// Session
|
||||
distinctId: id,
|
||||
browser,
|
||||
os,
|
||||
device,
|
||||
|
|
@ -215,24 +208,39 @@ export async function POST(request: Request) {
|
|||
country,
|
||||
region,
|
||||
city,
|
||||
|
||||
// Events
|
||||
eventName: name,
|
||||
eventData: data,
|
||||
tag,
|
||||
distinctId: id,
|
||||
createdAt,
|
||||
|
||||
// UTM
|
||||
utmSource,
|
||||
utmMedium,
|
||||
utmCampaign,
|
||||
utmContent,
|
||||
utmTerm,
|
||||
|
||||
// Click IDs
|
||||
gclid,
|
||||
fbclid,
|
||||
msclkid,
|
||||
ttclid,
|
||||
lifatid,
|
||||
twclid,
|
||||
});
|
||||
}
|
||||
|
||||
if (type === COLLECTION_TYPE.identify) {
|
||||
if (!data) {
|
||||
return badRequest('Data required.');
|
||||
if (data) {
|
||||
await saveSessionData({
|
||||
websiteId,
|
||||
sessionId,
|
||||
sessionData: data,
|
||||
distinctId: id,
|
||||
createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
await saveSessionData({
|
||||
websiteId,
|
||||
sessionId,
|
||||
sessionData: data,
|
||||
distinctId: id,
|
||||
createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
const token = createToken({ websiteId, sessionId, visitId, iat }, secret());
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function renderDateLabels(unit: string, locale: string) {
|
|||
|
||||
switch (unit) {
|
||||
case 'minute':
|
||||
return formatDate(d, 'p', locale).split(' ')[0];
|
||||
return formatDate(d, 'h:mm', locale);
|
||||
case 'hour':
|
||||
return formatDate(d, 'p', locale);
|
||||
case 'day':
|
||||
|
|
|
|||
|
|
@ -6,30 +6,23 @@ import prisma from '@/lib/prisma';
|
|||
import { uuid } from '@/lib/crypto';
|
||||
import { saveEventData } from './saveEventData';
|
||||
|
||||
export async function saveEvent(args: {
|
||||
export interface SaveEventArgs {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
visitId: string;
|
||||
createdAt?: Date;
|
||||
|
||||
// Page
|
||||
pageTitle?: string;
|
||||
hostname?: string;
|
||||
urlPath: string;
|
||||
urlQuery?: string;
|
||||
utmSource?: string;
|
||||
utmMedium?: string;
|
||||
utmCampaign?: string;
|
||||
utmContent?: string;
|
||||
utmTerm?: string;
|
||||
referrerPath?: string;
|
||||
referrerQuery?: string;
|
||||
referrerDomain?: string;
|
||||
pageTitle?: string;
|
||||
gclid?: string;
|
||||
fbclid?: string;
|
||||
msclkid?: string;
|
||||
ttclid?: string;
|
||||
lifatid?: string;
|
||||
twclid?: string;
|
||||
eventName?: string;
|
||||
eventData?: any;
|
||||
hostname?: string;
|
||||
|
||||
// Session
|
||||
distinctId?: string;
|
||||
browser?: string;
|
||||
os?: string;
|
||||
device?: string;
|
||||
|
|
@ -38,73 +31,65 @@ export async function saveEvent(args: {
|
|||
country?: string;
|
||||
region?: string;
|
||||
city?: string;
|
||||
tag?: string;
|
||||
distinctId?: string;
|
||||
createdAt?: Date;
|
||||
}) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(data: {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
visitId: string;
|
||||
urlPath: string;
|
||||
urlQuery?: string;
|
||||
// Events
|
||||
eventName?: string;
|
||||
eventData?: any;
|
||||
tag?: string;
|
||||
|
||||
// UTM
|
||||
utmSource?: string;
|
||||
utmMedium?: string;
|
||||
utmCampaign?: string;
|
||||
utmContent?: string;
|
||||
utmTerm?: string;
|
||||
referrerPath?: string;
|
||||
referrerQuery?: string;
|
||||
referrerDomain?: string;
|
||||
|
||||
// Click IDs
|
||||
gclid?: string;
|
||||
fbclid?: string;
|
||||
msclkid?: string;
|
||||
ttclid?: string;
|
||||
lifatid?: string;
|
||||
twclid?: string;
|
||||
pageTitle?: string;
|
||||
eventName?: string;
|
||||
eventData?: any;
|
||||
tag?: string;
|
||||
hostname?: string;
|
||||
createdAt?: Date;
|
||||
}) {
|
||||
const {
|
||||
websiteId,
|
||||
sessionId,
|
||||
visitId,
|
||||
urlPath,
|
||||
urlQuery,
|
||||
utmSource,
|
||||
utmMedium,
|
||||
utmCampaign,
|
||||
utmContent,
|
||||
utmTerm,
|
||||
referrerPath,
|
||||
referrerQuery,
|
||||
referrerDomain,
|
||||
eventName,
|
||||
eventData,
|
||||
pageTitle,
|
||||
gclid,
|
||||
fbclid,
|
||||
msclkid,
|
||||
ttclid,
|
||||
lifatid,
|
||||
twclid,
|
||||
tag,
|
||||
hostname,
|
||||
createdAt,
|
||||
} = data;
|
||||
}
|
||||
|
||||
export async function saveEvent(args: SaveEventArgs) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(args),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery({
|
||||
websiteId,
|
||||
sessionId,
|
||||
visitId,
|
||||
createdAt,
|
||||
pageTitle,
|
||||
tag,
|
||||
hostname,
|
||||
urlPath,
|
||||
urlQuery,
|
||||
referrerPath,
|
||||
referrerQuery,
|
||||
referrerDomain,
|
||||
eventName,
|
||||
eventData,
|
||||
utmSource,
|
||||
utmMedium,
|
||||
utmCampaign,
|
||||
utmContent,
|
||||
utmTerm,
|
||||
gclid,
|
||||
fbclid,
|
||||
msclkid,
|
||||
ttclid,
|
||||
lifatid,
|
||||
twclid,
|
||||
}: SaveEventArgs) {
|
||||
const websiteEventId = uuid();
|
||||
|
||||
const websiteEvent = prisma.client.websiteEvent.create({
|
||||
await prisma.client.websiteEvent.create({
|
||||
data: {
|
||||
id: websiteEventId,
|
||||
websiteId,
|
||||
|
|
@ -146,83 +131,49 @@ async function relationalQuery(data: {
|
|||
createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
return websiteEvent;
|
||||
}
|
||||
|
||||
async function clickhouseQuery(data: {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
visitId: string;
|
||||
urlPath: string;
|
||||
urlQuery?: string;
|
||||
utmSource?: string;
|
||||
utmMedium?: string;
|
||||
utmCampaign?: string;
|
||||
utmContent?: string;
|
||||
utmTerm?: string;
|
||||
referrerPath?: string;
|
||||
referrerQuery?: string;
|
||||
referrerDomain?: string;
|
||||
pageTitle?: string;
|
||||
gclid?: string;
|
||||
fbclid?: string;
|
||||
msclkid?: string;
|
||||
ttclid?: string;
|
||||
lifatid?: string;
|
||||
twclid?: string;
|
||||
eventName?: string;
|
||||
eventData?: any;
|
||||
hostname?: string;
|
||||
browser?: string;
|
||||
os?: string;
|
||||
device?: string;
|
||||
screen?: string;
|
||||
language?: string;
|
||||
country?: string;
|
||||
region?: string;
|
||||
city?: string;
|
||||
tag?: string;
|
||||
distinctId?: string;
|
||||
createdAt?: Date;
|
||||
}) {
|
||||
const {
|
||||
websiteId,
|
||||
sessionId,
|
||||
visitId,
|
||||
urlPath,
|
||||
urlQuery,
|
||||
utmSource,
|
||||
utmMedium,
|
||||
utmCampaign,
|
||||
utmContent,
|
||||
utmTerm,
|
||||
referrerPath,
|
||||
referrerQuery,
|
||||
referrerDomain,
|
||||
gclid,
|
||||
fbclid,
|
||||
msclkid,
|
||||
ttclid,
|
||||
lifatid,
|
||||
twclid,
|
||||
pageTitle,
|
||||
eventName,
|
||||
eventData,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
tag,
|
||||
distinctId,
|
||||
createdAt,
|
||||
...args
|
||||
} = data;
|
||||
async function clickhouseQuery({
|
||||
websiteId,
|
||||
sessionId,
|
||||
visitId,
|
||||
distinctId,
|
||||
createdAt,
|
||||
pageTitle,
|
||||
browser,
|
||||
os,
|
||||
device,
|
||||
screen,
|
||||
language,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
tag,
|
||||
hostname,
|
||||
urlPath,
|
||||
urlQuery,
|
||||
referrerPath,
|
||||
referrerQuery,
|
||||
referrerDomain,
|
||||
eventName,
|
||||
eventData,
|
||||
utmSource,
|
||||
utmMedium,
|
||||
utmCampaign,
|
||||
utmContent,
|
||||
utmTerm,
|
||||
gclid,
|
||||
fbclid,
|
||||
msclkid,
|
||||
ttclid,
|
||||
lifatid,
|
||||
twclid,
|
||||
}: SaveEventArgs) {
|
||||
const { insert, getUTCString } = clickhouse;
|
||||
const { sendMessage } = kafka;
|
||||
const eventId = uuid();
|
||||
|
||||
const message = {
|
||||
...args,
|
||||
website_id: websiteId,
|
||||
session_id: sessionId,
|
||||
visit_id: visitId,
|
||||
|
|
@ -252,6 +203,12 @@ async function clickhouseQuery(data: {
|
|||
tag: tag,
|
||||
distinct_id: distinctId,
|
||||
created_at: getUTCString(createdAt),
|
||||
browser,
|
||||
os,
|
||||
device,
|
||||
screen,
|
||||
language,
|
||||
hostname,
|
||||
};
|
||||
|
||||
if (kafka.enabled) {
|
||||
|
|
@ -271,6 +228,4 @@ async function clickhouseQuery(data: {
|
|||
createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { Prisma } from '@prisma/client';
|
||||
import { DATA_TYPE } from '@/lib/constants';
|
||||
import { uuid } from '@/lib/crypto';
|
||||
import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
||||
|
|
@ -8,7 +7,7 @@ import kafka from '@/lib/kafka';
|
|||
import prisma from '@/lib/prisma';
|
||||
import { DynamicData } from '@/lib/types';
|
||||
|
||||
export async function saveEventData(data: {
|
||||
export interface SaveEventDataArgs {
|
||||
websiteId: string;
|
||||
eventId: string;
|
||||
sessionId?: string;
|
||||
|
|
@ -16,19 +15,16 @@ export async function saveEventData(data: {
|
|||
eventName?: string;
|
||||
eventData: DynamicData;
|
||||
createdAt?: Date;
|
||||
}) {
|
||||
}
|
||||
|
||||
export async function saveEventData(data: SaveEventDataArgs) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(data),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(data),
|
||||
});
|
||||
}
|
||||
|
||||
async function relationalQuery(data: {
|
||||
websiteId: string;
|
||||
eventId: string;
|
||||
eventData: DynamicData;
|
||||
createdAt?: Date;
|
||||
}): Promise<Prisma.BatchPayload> {
|
||||
async function relationalQuery(data: SaveEventDataArgs) {
|
||||
const { websiteId, eventId, eventData, createdAt } = data;
|
||||
|
||||
const jsonKeys = flattenJSON(eventData);
|
||||
|
|
@ -46,20 +42,12 @@ async function relationalQuery(data: {
|
|||
createdAt,
|
||||
}));
|
||||
|
||||
return prisma.client.eventData.createMany({
|
||||
await prisma.client.eventData.createMany({
|
||||
data: flattenedData,
|
||||
});
|
||||
}
|
||||
|
||||
async function clickhouseQuery(data: {
|
||||
websiteId: string;
|
||||
eventId: string;
|
||||
sessionId?: string;
|
||||
urlPath?: string;
|
||||
eventName?: string;
|
||||
eventData: DynamicData;
|
||||
createdAt?: Date;
|
||||
}) {
|
||||
async function clickhouseQuery(data: SaveEventDataArgs) {
|
||||
const { websiteId, sessionId, eventId, urlPath, eventName, eventData, createdAt } = data;
|
||||
|
||||
const { insert, getUTCString } = clickhouse;
|
||||
|
|
@ -88,6 +76,4 @@ async function clickhouseQuery(data: {
|
|||
} else {
|
||||
await insert('event_data', messages);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
`
|
||||
select
|
||||
referrer_domain as domain,
|
||||
referrer_query as query,
|
||||
url_query as query,
|
||||
count(distinct session_id) as visitors
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
|
|
@ -41,7 +41,7 @@ async function clickhouseQuery(
|
|||
const sql = `
|
||||
select
|
||||
referrer_domain as domain,
|
||||
referrer_query as query,
|
||||
url_query as query,
|
||||
uniq(session_id) as visitors
|
||||
from website_event
|
||||
where website_id = {websiteId:UUID}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
|
|||
where website_event.website_id = {{websiteId::uuid}}
|
||||
${filterQuery}
|
||||
${dateQuery}
|
||||
order by website_event.created_at asc
|
||||
order by website_event.created_at desc
|
||||
limit 100
|
||||
`,
|
||||
params,
|
||||
|
|
@ -59,7 +59,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promis
|
|||
where website_id = {websiteId:UUID}
|
||||
${filterQuery}
|
||||
${dateQuery}
|
||||
order by createdAt asc
|
||||
order by createdAt desc
|
||||
limit 100
|
||||
`,
|
||||
{ ...filters, ...params },
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export async function getRealtimeData(
|
|||
|
||||
const uniques = new Set();
|
||||
|
||||
const { countries, urls, referrers, events } = activity.reduce(
|
||||
const { countries, urls, referrers, events } = activity.reverse().reduce(
|
||||
(
|
||||
obj: { countries: any; urls: any; referrers: any; events: any },
|
||||
event: {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ async function relationalQuery(
|
|||
sum(coalesce(cast(number_value as decimal(10,2)), cast(string_value as decimal(10,2)))) value
|
||||
from event_data ed
|
||||
join website_event we
|
||||
on we.event_id = ed.website_event_Id
|
||||
on we.event_id = ed.website_event_id
|
||||
and we.website_id = ed.website_id
|
||||
join (select website_event_id
|
||||
from event_data
|
||||
|
|
@ -395,7 +395,7 @@ async function clickhouseQuery(
|
|||
fbclid != '', 'Facebook / Meta',
|
||||
msclkid != '', 'Microsoft Ads',
|
||||
ttclid != '', 'TikTok Ads',
|
||||
li_fat_id != '', ' LinkedIn Ads',
|
||||
li_fat_id != '', 'LinkedIn Ads',
|
||||
twclid != '', 'Twitter Ads (X)','') name,
|
||||
${currency ? 'sum(e.value)' : 'uniqExact(we.session_id)'} value
|
||||
from model m
|
||||
|
|
|
|||
|
|
@ -7,28 +7,29 @@ import { CLICKHOUSE, PRISMA, runQuery } from '@/lib/db';
|
|||
import kafka from '@/lib/kafka';
|
||||
import clickhouse from '@/lib/clickhouse';
|
||||
|
||||
export async function saveSessionData(data: {
|
||||
export interface SaveSessionDataArgs {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
sessionData: DynamicData;
|
||||
distinctId?: string;
|
||||
createdAt?: Date;
|
||||
}) {
|
||||
}
|
||||
|
||||
export async function saveSessionData(data: SaveSessionDataArgs) {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(data),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(data),
|
||||
});
|
||||
}
|
||||
|
||||
export async function relationalQuery(data: {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
sessionData: DynamicData;
|
||||
distinctId?: string;
|
||||
createdAt?: Date;
|
||||
}) {
|
||||
export async function relationalQuery({
|
||||
websiteId,
|
||||
sessionId,
|
||||
sessionData,
|
||||
distinctId,
|
||||
createdAt,
|
||||
}: SaveSessionDataArgs) {
|
||||
const { client } = prisma;
|
||||
const { websiteId, sessionId, sessionData, distinctId, createdAt } = data;
|
||||
|
||||
const jsonKeys = flattenJSON(sessionData);
|
||||
|
||||
|
|
@ -75,19 +76,15 @@ export async function relationalQuery(data: {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
return flattenedData;
|
||||
}
|
||||
|
||||
async function clickhouseQuery(data: {
|
||||
websiteId: string;
|
||||
sessionId: string;
|
||||
sessionData: DynamicData;
|
||||
distinctId?: string;
|
||||
createdAt?: Date;
|
||||
}) {
|
||||
const { websiteId, sessionId, sessionData, distinctId, createdAt } = data;
|
||||
|
||||
async function clickhouseQuery({
|
||||
websiteId,
|
||||
sessionId,
|
||||
sessionData,
|
||||
distinctId,
|
||||
createdAt,
|
||||
}: SaveSessionDataArgs) {
|
||||
const { insert, getUTCString } = clickhouse;
|
||||
const { sendMessage } = kafka;
|
||||
|
||||
|
|
@ -112,6 +109,4 @@ async function clickhouseQuery(data: {
|
|||
} else {
|
||||
await insert('session_data', messages);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,19 +8,20 @@
|
|||
top,
|
||||
doNotTrack,
|
||||
} = window;
|
||||
const { hostname, href, origin } = location;
|
||||
const { currentScript, referrer } = document;
|
||||
const localStorage = href.startsWith('data:') ? undefined : window.localStorage;
|
||||
|
||||
if (!currentScript) return;
|
||||
|
||||
const { hostname, href, origin } = location;
|
||||
const localStorage = href.startsWith('data:') ? undefined : window.localStorage;
|
||||
|
||||
const _data = 'data-';
|
||||
const _false = 'false';
|
||||
const _true = 'true';
|
||||
const attr = currentScript.getAttribute.bind(currentScript);
|
||||
const website = attr(_data + 'website-id');
|
||||
const hostUrl = attr(_data + 'host-url');
|
||||
const tag = attr(_data + 'tag');
|
||||
const beforeSend = attr(_data + 'before-send');
|
||||
const tag = attr(_data + 'tag') || undefined;
|
||||
const autoTrack = attr(_data + 'auto-track') !== _false;
|
||||
const dnt = attr(_data + 'do-not-track') === _true;
|
||||
const excludeSearch = attr(_data + 'exclude-search') === _true;
|
||||
|
|
@ -41,11 +42,11 @@
|
|||
website,
|
||||
screen,
|
||||
language,
|
||||
title,
|
||||
title: document.title,
|
||||
hostname,
|
||||
url: currentUrl,
|
||||
referrer: currentRef,
|
||||
tag: tag ? tag : undefined,
|
||||
tag,
|
||||
id: identity ? identity : undefined,
|
||||
});
|
||||
|
||||
|
|
@ -56,20 +57,14 @@
|
|||
|
||||
/* Event handlers */
|
||||
|
||||
const handlePush = (state, title, url) => {
|
||||
const handlePush = (_state, _title, url) => {
|
||||
if (!url) return;
|
||||
|
||||
currentRef = currentUrl;
|
||||
currentUrl = new URL(url, location.href);
|
||||
|
||||
if (excludeSearch) {
|
||||
currentUrl.search = '';
|
||||
}
|
||||
|
||||
if (excludeHash) {
|
||||
currentUrl.hash = '';
|
||||
}
|
||||
|
||||
if (excludeSearch) currentUrl.search = '';
|
||||
if (excludeHash) currentUrl.hash = '';
|
||||
currentUrl = currentUrl.toString();
|
||||
|
||||
if (currentUrl !== currentRef) {
|
||||
|
|
@ -80,10 +75,8 @@
|
|||
const handlePathChanges = () => {
|
||||
const hook = (_this, method, callback) => {
|
||||
const orig = _this[method];
|
||||
|
||||
return (...args) => {
|
||||
callback.apply(null, args);
|
||||
|
||||
return orig.apply(_this, args);
|
||||
};
|
||||
};
|
||||
|
|
@ -92,96 +85,47 @@
|
|||
history.replaceState = hook(history, 'replaceState', handlePush);
|
||||
};
|
||||
|
||||
const handleTitleChanges = () => {
|
||||
const observer = new MutationObserver(([entry]) => {
|
||||
title = entry && entry.target ? entry.target.text : undefined;
|
||||
});
|
||||
|
||||
const node = document.querySelector('head > title');
|
||||
|
||||
if (node) {
|
||||
observer.observe(node, {
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleClicks = () => {
|
||||
document.addEventListener(
|
||||
'click',
|
||||
async e => {
|
||||
const isSpecialTag = tagName => ['BUTTON', 'A'].includes(tagName);
|
||||
const trackElement = async el => {
|
||||
const eventName = el.getAttribute(eventNameAttribute);
|
||||
if (eventName) {
|
||||
const eventData = {};
|
||||
|
||||
const trackElement = async el => {
|
||||
const attr = el.getAttribute.bind(el);
|
||||
const eventName = attr(eventNameAttribute);
|
||||
el.getAttributeNames().forEach(name => {
|
||||
const match = name.match(eventRegex);
|
||||
if (match) eventData[match[1]] = el.getAttribute(name);
|
||||
});
|
||||
|
||||
if (eventName) {
|
||||
const eventData = {};
|
||||
return track(eventName, eventData);
|
||||
}
|
||||
};
|
||||
const onClick = async e => {
|
||||
const el = e.target;
|
||||
const parentElement = el.closest('a,button');
|
||||
if (!parentElement) return trackElement(el);
|
||||
|
||||
el.getAttributeNames().forEach(name => {
|
||||
const match = name.match(eventRegex);
|
||||
const { href, target } = parentElement;
|
||||
if (!parentElement.getAttribute(eventNameAttribute)) return;
|
||||
|
||||
if (match) {
|
||||
eventData[match[1]] = attr(name);
|
||||
}
|
||||
});
|
||||
|
||||
return track(eventName, eventData);
|
||||
if (parentElement.tagName === 'BUTTON') {
|
||||
return trackElement(parentElement);
|
||||
}
|
||||
if (parentElement.tagName === 'A' && href) {
|
||||
const external =
|
||||
target === '_blank' ||
|
||||
e.ctrlKey ||
|
||||
e.shiftKey ||
|
||||
e.metaKey ||
|
||||
(e.button && e.button === 1);
|
||||
if (!external) e.preventDefault();
|
||||
return trackElement(parentElement).then(() => {
|
||||
if (!external) {
|
||||
(target === '_top' ? top.location : location).href = href;
|
||||
}
|
||||
};
|
||||
|
||||
const findParentTag = (rootElem, maxSearchDepth) => {
|
||||
let currentElement = rootElem;
|
||||
for (let i = 0; i < maxSearchDepth; i++) {
|
||||
if (isSpecialTag(currentElement.tagName)) {
|
||||
return currentElement;
|
||||
}
|
||||
currentElement = currentElement.parentElement;
|
||||
if (!currentElement) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const el = e.target;
|
||||
const parentElement = isSpecialTag(el.tagName) ? el : findParentTag(el, 10);
|
||||
|
||||
if (parentElement) {
|
||||
const { href, target } = parentElement;
|
||||
const eventName = parentElement.getAttribute(eventNameAttribute);
|
||||
|
||||
if (eventName) {
|
||||
if (parentElement.tagName === 'A') {
|
||||
const external =
|
||||
target === '_blank' ||
|
||||
e.ctrlKey ||
|
||||
e.shiftKey ||
|
||||
e.metaKey ||
|
||||
(e.button && e.button === 1);
|
||||
|
||||
if (eventName && href) {
|
||||
if (!external) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return trackElement(parentElement).then(() => {
|
||||
if (!external) {
|
||||
(target === '_top' ? top.location : location).href = href;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (parentElement.tagName === 'BUTTON') {
|
||||
return trackElement(parentElement);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return trackElement(el);
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', onClick, true);
|
||||
};
|
||||
|
||||
/* Tracking functions */
|
||||
|
|
@ -196,56 +140,49 @@
|
|||
const send = async (payload, type = 'event') => {
|
||||
if (trackingDisabled()) return;
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
const callback = window[beforeSend];
|
||||
|
||||
if (typeof cache !== 'undefined') {
|
||||
headers['x-umami-cache'] = cache;
|
||||
if (typeof callback === 'function') {
|
||||
payload = callback(type, payload);
|
||||
}
|
||||
|
||||
if (!payload) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ type, payload }),
|
||||
headers,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(typeof cache !== 'undefined' && { 'x-umami-cache': cache }),
|
||||
},
|
||||
credentials: 'omit',
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data) {
|
||||
disabled = !!data.disabled;
|
||||
cache = data.cache;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
/* no-op */
|
||||
}
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
track();
|
||||
handlePathChanges();
|
||||
handleTitleChanges();
|
||||
handleClicks();
|
||||
initialized = true;
|
||||
}
|
||||
};
|
||||
|
||||
const track = (name, data) => {
|
||||
if (typeof name === 'string') {
|
||||
return send({
|
||||
...getPayload(),
|
||||
name,
|
||||
data,
|
||||
});
|
||||
} else if (typeof name === 'object') {
|
||||
return send({ ...name });
|
||||
} else if (typeof name === 'function') {
|
||||
return send(name(getPayload()));
|
||||
}
|
||||
if (typeof name === 'string') return send({ ...getPayload(), name, data });
|
||||
if (typeof name === 'object') return send({ ...name });
|
||||
if (typeof name === 'function') return send(name(getPayload()));
|
||||
return send(getPayload());
|
||||
};
|
||||
|
||||
|
|
@ -275,10 +212,9 @@
|
|||
|
||||
let currentUrl = href;
|
||||
let currentRef = referrer.startsWith(origin) ? '' : referrer;
|
||||
let title = document.title;
|
||||
let cache;
|
||||
let initialized;
|
||||
let initialized = false;
|
||||
let disabled = false;
|
||||
let cache;
|
||||
let identity;
|
||||
|
||||
if (autoTrack && !trackingDisabled()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue