-
-
Save khromov/8f7cfbf2e3255be4a8c4a3f63083ea75 to your computer and use it in GitHub Desktop.
Server side analytics using Umami + SvelteKit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Run on the server side | |
trackRequest( | |
'event', | |
{ | |
url: '/', | |
event_name: 'user_created' | |
} | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// src/hooks.server.ts | |
import { trackRequest } from '$lib/server/umami'; | |
const track: Handle = async ({ event, resolve }) => { | |
const userAgent = event.request.headers.get ? event.request.headers.get('user-agent') : null; | |
// Add user agent to event locals | |
event.locals.userAgent = userAgent; | |
const response = await resolve(event); | |
// Some of these are spammy like web manifest requests that we don't want to track | |
const untrackablePaths = [ | |
'/manifest.webmanifest', | |
]; | |
const shouldTrack = | |
!untrackablePaths.some((path) => event.url.pathname.startsWith(path)) && | |
event.request.method !== 'OPTIONS'; | |
if (shouldTrack) { | |
// This promise just goes out into the ether | |
trackRequest( | |
'pageview', | |
{ | |
url: event.url.pathname | |
}, | |
userAgent | |
); | |
} | |
return response; | |
}; | |
export const handle: Handle = sequence(track); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { env } from '$env/dynamic/private'; | |
/** | |
* Docs: https://umami.is/docs/sending-stats | |
*/ | |
type PayloadType = 'event'; | |
interface Payload { | |
language?: string; // Eg: en-US | |
referrer?: string; // Full url or "" if empty | |
screen?: string; // Eg. 414x986 | |
url: string; // Without domain. Eg. /settings | |
event_name?: string; | |
title?: string; // Eg. Settings | |
data?: Record<string, any> | |
name?: string // Event name | |
// website: string // UUID for the site | |
// hostname: string, // Eg. localhost | |
} | |
const UMAMI_HOSTNAME = env?.UMAMI_HOSTNAME || 'localhost'; | |
const UMAMI_SERVER = env?.UMAMI_SERVER; | |
const UMAMI_SITE = env?.UMAMI_SITE; | |
/** | |
* This is needed to prevent bot detection check that prevents the request from being tracked in case we don't pass a user agent | |
* You can also set DISABLE_BOT_CHECK=true in your umami environment to disable the bot check entirely: | |
* https://github.com/umami-software/umami/blob/7a3443cd06772f3cde37bdbb0bf38eabf4515561/pages/api/collect.js#L13 | |
*/ | |
const DEFAULT_USER_AGENT = | |
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'; | |
/** | |
* Tracks the request in Umami | |
* Screen and language is not possible to get from the client without JavaScript | |
*/ | |
export const trackRequest = async ( | |
type: PayloadType = 'event', | |
payload: Payload, | |
userAgent: string | null = null | |
) => { | |
const { language = 'en-US', screen = '1024x1024', url, referrer = '', event_name } = payload; | |
if (!url) { | |
console.warn('💢 Error: Tracking payload sent without url', url); | |
return null; | |
} | |
console.log(`🏹 Tracking ${type} for url: ${url} ${event_name ? `with event name ${event_name}` : ''}`); | |
const finalPayload = { | |
website: UMAMI_SITE, | |
hostname: UMAMI_HOSTNAME, | |
language, | |
screen, | |
url, | |
title: undefined, // New since Umami 2.0 | |
referrer, | |
name: event_name ?? undefined // New format since Umami 2.0 | |
}; | |
const response = await sendRequest( | |
{ | |
type, | |
payload: finalPayload | |
}, | |
userAgent | |
); | |
return response; | |
}; | |
export const sendRequest = async (data: object, userAgent: string | null) => { | |
const controller = new AbortController(); | |
const signal = controller.signal; | |
const timeoutPromise = setTimeout(() => { | |
console.error('💣 Request timed out, aborting!'); | |
if (controller) { | |
controller.abort('The request timed out.'); | |
} | |
}, 5000); | |
let response; | |
try { | |
response = await fetch(`${UMAMI_SERVER}/api/send`, { | |
signal, | |
method: 'POST', | |
body: JSON.stringify(data), | |
headers: { | |
'content-type': 'application/json', // We need a proper user agent to bypass the bot check: https://github.com/umami-software/umami/blob/7a3443cd06772f3cde37bdbb0bf38eabf4515561/pages/api/collect.js#LL13C68-L13C68 | |
'user-agent': userAgent ?? DEFAULT_USER_AGENT | |
// This is set by umami to keep track of the user, it's a JWT token that contains the sessionUuid and websiteUuid | |
// 'x-umami-cache': 'NOT-IMPLEMENTED', | |
} | |
}); | |
if(!response.ok) { | |
console.error('💣 Umami request failed', response.status, response.statusText); | |
} | |
// Clean up the timeout after we received the data | |
clearTimeout(timeoutPromise); | |
const responseData = await response.text(); | |
return { | |
status: response?.status, | |
ok: response.ok ? true : false, | |
data: responseData | |
}; | |
} catch (e) { | |
console.log('Error when fetching data', e); | |
// Clear timeout as there was an error | |
clearTimeout(timeoutPromise); | |
return { | |
status: response?.status, | |
ok: false, | |
message: `${e}` | |
}; | |
} | |
}; |
@buhodev Hi! It's just a normal console.log()
statement, I updated the gist to remove it!
Thanks man, its exactly what i was looking for!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this! Is the
log
function from$lib/log
inumami.ts
something special or just aconsole.log
?