mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Merge branch 'master' into dev
# Conflicts: # .github/workflows/ci.yml # src/lang/es-ES.json # src/lang/sl-SI.json # src/lib/constants.ts # src/lib/detect.ts # src/queries/sql/reports/getRevenue.ts
This commit is contained in:
commit
04c06443a8
9 changed files with 259 additions and 370 deletions
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
|
@ -19,18 +19,14 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'pnpm'
|
||||
|
||||
env:
|
||||
DATABASE_TYPE: ${{ matrix.db-type }}
|
||||
- run: npm install --global pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
- run: pnpm build
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,6 +4,7 @@
|
|||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
.pnpm-store
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -43,7 +43,7 @@ A detailed getting started guide can be found at [umami.is/docs](https://umami.i
|
|||
```bash
|
||||
git clone https://github.com/umami-software/umami.git
|
||||
cd umami
|
||||
npm install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Configure Umami
|
||||
|
|
@ -64,7 +64,7 @@ mysql://username:mypassword@localhost:3306/mydb
|
|||
### Build the Application
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
_The build step will create tables in your database if you are installing for the first time. It will also create a login user with username **admin** and password **umami**._
|
||||
|
|
@ -72,7 +72,7 @@ _The build step will create tables in your database if you are installing for th
|
|||
### Start the Application
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
_By default, this will launch the application on `http://localhost:3000`. You will need to either [proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) requests from your web server or change the [port](https://nextjs.org/docs/api-reference/cli#production) to serve the application directly._
|
||||
|
|
@ -107,8 +107,8 @@ To get the latest features, simply do a pull, install any new dependencies, and
|
|||
|
||||
```bash
|
||||
git pull
|
||||
npm install
|
||||
npm run build
|
||||
pnpm install
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
To update the Docker image, simply pull the new images and rebuild:
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
"label.last-days": "Últimos {x} días",
|
||||
"label.last-hours": "Últimas {x} horas",
|
||||
"label.last-months": "Últimos {x} meses",
|
||||
"label.last-seen": "Última vez visto",
|
||||
"label.last-seen": "Visto por última vez",
|
||||
"label.leave": "Abandonar",
|
||||
"label.leave-team": "Abandonar equipo",
|
||||
"label.less-than": "Menor que",
|
||||
|
|
@ -210,8 +210,9 @@
|
|||
"label.reset-website": "Reiniciar analíticas",
|
||||
"label.retention": "Retención",
|
||||
"label.retention-description": "Medir la frecuencia con la que los usuarios vuelven a tu sitio web.",
|
||||
"label.revenue": "Ingresos",
|
||||
"label.revenue-description": "Consulte sus ingresos a lo largo del tiempo.",
|
||||
"label.revenue": "Ganancias",
|
||||
"label.revenue-description": "Analice sus ganancias a lo largo del tiempo.",
|
||||
"label.revenue-property": "Propiedad de ganancias",
|
||||
"label.role": "Rol",
|
||||
"label.run-query": "Ejecutar consulta",
|
||||
"label.save": "Guardar",
|
||||
|
|
@ -223,7 +224,6 @@
|
|||
"label.select-role": "Seleccionar rol",
|
||||
"label.select-website": "Seleccionar sitio web",
|
||||
"label.session": "Sesión",
|
||||
"label.session-data": "Datos de sesión",
|
||||
"label.sessions": "Sesiones",
|
||||
"label.settings": "Ajustes",
|
||||
"label.share": "Compartir",
|
||||
|
|
@ -259,18 +259,19 @@
|
|||
"label.total": "Total",
|
||||
"label.total-records": "Total de registros",
|
||||
"label.tracking-code": "Código de rastreo",
|
||||
"label.transactions": "Transactions",
|
||||
"label.transactions": "Transacciones",
|
||||
"label.transfer": "Transferir",
|
||||
"label.transfer-website": "Transferir sitio web",
|
||||
"label.true": "Verdadero",
|
||||
"label.type": "Tipo",
|
||||
"label.unique": "Único",
|
||||
"label.unique-visitors": "Visitantes únicos",
|
||||
"label.uniqueCustomers": "Unique Customers",
|
||||
"label.uniqueCustomers": "Clientes únicos",
|
||||
"label.unknown": "Desconocida",
|
||||
"label.untitled": "Sin título",
|
||||
"label.update": "Actualizar",
|
||||
"label.user": "Usuario",
|
||||
"label.user-property": "Propiedad de usuario",
|
||||
"label.username": "Nombre de usuario",
|
||||
"label.users": "Usuarios",
|
||||
"label.utm": "UTM",
|
||||
|
|
|
|||
|
|
@ -32,27 +32,21 @@
|
|||
"label.cities": "Mesta",
|
||||
"label.city": "Mesto",
|
||||
"label.clear-all": "Počisti vse",
|
||||
"label.cohort": "Kohorta",
|
||||
"label.compare": "Primerjaj",
|
||||
"label.compare-dates": "Primerjaj datume",
|
||||
"label.confirm": "Potrdi",
|
||||
"label.confirm-password": "Potrdi geslo",
|
||||
"label.contains": "Vsebuje",
|
||||
"label.content": "Vsebina",
|
||||
"label.continue": "Nadaljuj",
|
||||
"label.conversion": "Konverzija",
|
||||
"label.conversion-rate": "Stopnja konverzije",
|
||||
"label.conversion-step": "Korak konverzije",
|
||||
"label.count": "Števec",
|
||||
"label.count": "Število",
|
||||
"label.countries": "Države",
|
||||
"label.country": "Država",
|
||||
"label.create": "Create",
|
||||
"label.create": "Ustvari",
|
||||
"label.create-report": "Ustvari poročilo",
|
||||
"label.create-team": "Ustvari ekipo",
|
||||
"label.create-user": "Ustvari uporabnika",
|
||||
"label.created": "Ustvarjeno",
|
||||
"label.created-by": "Ustvaril",
|
||||
"label.currency": "Valuta",
|
||||
"label.current": "Trenutno",
|
||||
"label.current-password": "Trenutno geslo",
|
||||
"label.custom-range": "Obdobje po meri",
|
||||
|
|
@ -83,16 +77,14 @@
|
|||
"label.edit": "Uredi",
|
||||
"label.edit-dashboard": "Uredi nadzorno ploščo",
|
||||
"label.edit-member": "Uredi člana",
|
||||
"label.email": "Email",
|
||||
"label.enable-share-url": "Uredi povezavo za deljenje",
|
||||
"label.enable-share-url": "Omogoči povezavo za deljenje",
|
||||
"label.end-step": "Končni korak",
|
||||
"label.entry": "Vhodni URL",
|
||||
"label.entry": "Vstopni URL",
|
||||
"label.event": "Dogodek",
|
||||
"label.event-data": "Podatki dogodka",
|
||||
"label.event-name": "Ime dogodka",
|
||||
"label.events": "Dogodki",
|
||||
"label.exists": "Obstaja",
|
||||
"label.exit": "Exit URL",
|
||||
"label.exit": "Izhodni URL",
|
||||
"label.false": "Napačno",
|
||||
"label.field": "Polje",
|
||||
"label.fields": "Polja",
|
||||
|
|
@ -100,22 +92,18 @@
|
|||
"label.filter-combined": "Skupaj",
|
||||
"label.filter-raw": "Neobdelano",
|
||||
"label.filters": "Filtri",
|
||||
"label.first-click": "Prvi klik",
|
||||
"label.first-seen": "First seen",
|
||||
"label.first-seen": "Prvič viden",
|
||||
"label.funnel": "Prodajni lijak",
|
||||
"label.funnel-description": "Understand the conversion and drop-off rate of users.",
|
||||
"label.funnels": "Lijaki",
|
||||
"label.funnel-description": "Razumite stopnjo konverzije in osipa uporabnikov.",
|
||||
"label.goal": "Cilj",
|
||||
"label.goals": "Cilji",
|
||||
"label.goals-description": "Spremljajte svoje cilje za oglede strani in dogodke.",
|
||||
"label.greater-than": "Večje od",
|
||||
"label.greater-than-equals": "Večje ali enako kot",
|
||||
"label.grouped": "Združeno",
|
||||
"label.hostname": "Ime gostitelja",
|
||||
"label.includes": "Vključuje",
|
||||
"label.insight": "Vpogled",
|
||||
"label.host": "Gostitelj",
|
||||
"label.hosts": "Gostitelji",
|
||||
"label.insights": "Vpogled",
|
||||
"label.insights-description": "Dive deeper into your data by using segments and filters.",
|
||||
"label.insights-description": "Poglobite se v podatke z uporabo segmentov in filtrov.",
|
||||
"label.is": "Je",
|
||||
"label.is-false": "Je napačno",
|
||||
"label.is-not": "Ni",
|
||||
|
|
@ -124,9 +112,8 @@
|
|||
"label.is-true": "Je res",
|
||||
"label.join": "Pridruži se",
|
||||
"label.join-team": "Pridruži se ekipi",
|
||||
"label.journey": "Potovanje",
|
||||
"label.journey": "Uporabniška pot",
|
||||
"label.journey-description": "Razumite, kako uporabniki krmarijo po vašem spletnem mestu.",
|
||||
"label.journeys": "Potovanja",
|
||||
"label.language": "Jezik",
|
||||
"label.languages": "Jeziki",
|
||||
"label.laptop": "Prenosni računalnik",
|
||||
|
|
@ -134,7 +121,7 @@
|
|||
"label.last-days": "Zadnjih {x} dni",
|
||||
"label.last-hours": "Zadnjih {x} ur",
|
||||
"label.last-months": "Zadnjih {x} mesecev",
|
||||
"label.last-seen": "Zadnjič videno",
|
||||
"label.last-seen": "Nazadnje viden",
|
||||
"label.leave": "Zapusti",
|
||||
"label.leave-team": "Zapusti ekipo",
|
||||
"label.less-than": "Manjše kot",
|
||||
|
|
@ -142,11 +129,9 @@
|
|||
"label.links": "Povezave",
|
||||
"label.login": "Prijava",
|
||||
"label.logout": "Odjava",
|
||||
"label.manage": "Manage",
|
||||
"label.manager": "Manager",
|
||||
"label.manage": "Upravljaj",
|
||||
"label.manager": "Upravitelj",
|
||||
"label.max": "Največ",
|
||||
"label.maximize": "Razširi",
|
||||
"label.medium": "Srednje",
|
||||
"label.member": "Član",
|
||||
"label.members": "Člani",
|
||||
"label.min": "Najmanj",
|
||||
|
|
@ -182,7 +167,6 @@
|
|||
"label.password": "Geslo",
|
||||
"label.path": "Pot",
|
||||
"label.paths": "Poti",
|
||||
"label.pixels": "Pikslov",
|
||||
"label.powered-by": "Poganja {name}",
|
||||
"label.previous": "Prejšnji",
|
||||
"label.previous-period": "Prejšnje obdobje",
|
||||
|
|
@ -203,48 +187,44 @@
|
|||
"label.regions": "Regije",
|
||||
"label.remaining": "Preostalo",
|
||||
"label.remove": "Odstrani",
|
||||
"label.remove-member": "Remove member",
|
||||
"label.remove-member": "Odstrani člana",
|
||||
"label.reports": "Poročila",
|
||||
"label.required": "Zahtevano",
|
||||
"label.reset": "Ponastavi",
|
||||
"label.reset-website": "Ponastavi statistiko",
|
||||
"label.retention": "Ohranjanje uporabnikov",
|
||||
"label.retention-description": "Measure your website stickiness by tracking how often users return.",
|
||||
"label.revenue": "Prihodek",
|
||||
"label.revenue-description": "Oglejte si svoj prihodek skozi čas.",
|
||||
"label.retention-description": "Merite uporabnikovo zadržanost s sledenjem, kako pogosto se vračajo.",
|
||||
"label.revenue": "Prihodki",
|
||||
"label.revenue-description": "Preglejte svoje prihodke skozi čas.",
|
||||
"label.revenue-property": "Lastnost prihodkov",
|
||||
"label.role": "Vloga",
|
||||
"label.run-query": "Izvedi poizvedbo",
|
||||
"label.save": "Shrani",
|
||||
"label.screens": "Zasloni",
|
||||
"label.search": "Search",
|
||||
"label.select": "Select",
|
||||
"label.search": "Išči",
|
||||
"label.select": "Izberi",
|
||||
"label.select-date": "Izberi datum",
|
||||
"label.select-filter": "Izberi filter",
|
||||
"label.select-role": "Select role",
|
||||
"label.select-role": "Izberi vlogo",
|
||||
"label.select-website": "Izberi spletno mesto",
|
||||
"label.session": "Seja",
|
||||
"label.session-data": "Podatki seje",
|
||||
"label.sessions": "Seje",
|
||||
"label.settings": "Nastavitve",
|
||||
"label.share": "Deli",
|
||||
"label.share-url": "Deli povezavo",
|
||||
"label.single-day": "En dan",
|
||||
"label.sms": "SMS",
|
||||
"label.sources": "Viri",
|
||||
"label.start-step": "Start Step",
|
||||
"label.steps": "Steps",
|
||||
"label.start-step": "Začetni korak",
|
||||
"label.steps": "Koraki",
|
||||
"label.sum": "Seštevek",
|
||||
"label.tablet": "Tablični računalnik",
|
||||
"label.tag": "Oznaka",
|
||||
"label.tags": "Oznake",
|
||||
"label.team": "Ekipa",
|
||||
"label.team-id": "ID ekipe",
|
||||
"label.team-manager": "Vodja ekipe",
|
||||
"label.team-manager": "Upravitelj ekipe",
|
||||
"label.team-member": "Član ekipe",
|
||||
"label.team-name": "Ime ekipe",
|
||||
"label.team-owner": "Lastnik ekipe",
|
||||
"label.team-settings": "Nastavitve ekipe",
|
||||
"label.team-view-only": "Team view only",
|
||||
"label.team-view-only": "Ekipa samo za ogled",
|
||||
"label.team-websites": "Spletna mesta ekipe",
|
||||
"label.teams": "Ekipe",
|
||||
"label.terms": "Pogoji",
|
||||
|
|
@ -286,18 +266,17 @@
|
|||
"label.visits": "Visits",
|
||||
"label.website": "Spletno mesto",
|
||||
"label.website-id": "ID spletnega mesta",
|
||||
"label.websites": "Spletnih mest",
|
||||
"label.websites": "Spletna mesta",
|
||||
"label.window": "Okno",
|
||||
"label.yesterday": "Včeraj",
|
||||
"message.action-confirmation": "Type {confirmation} in the box below to confirm.",
|
||||
"message.action-confirmation": "Za potrditev v spodnje polje vnesite {confirmation}.",
|
||||
"message.active-users": "{x} trenutni {x, plural, one {obiskovalec} other {obiskovalcev}}",
|
||||
"message.bad-request": "Bad request",
|
||||
"message.collected-data": "Collected data",
|
||||
"message.collected-data": "Zbrani podatki",
|
||||
"message.confirm-delete": "Ste prepričani, da želite izbrisati {target}?",
|
||||
"message.confirm-leave": "Ste prepričani, da želite zapustiti {target}?",
|
||||
"message.confirm-remove": "Are you sure you want to remove {target}?",
|
||||
"message.confirm-remove": "Ali ste prepričani, da želite odstraniti {target}?",
|
||||
"message.confirm-reset": "Ste prepričani, da želite ponastaviti statistiko {target}?",
|
||||
"message.delete-team-warning": "Deleting a team will also delete all team websites.",
|
||||
"message.delete-team-warning": "Brisanje ekipe bo izbrisalo tudi vsa spletna mesta ekipe.",
|
||||
"message.delete-website-warning": "Izbrisani bodo tudi vsi pripadajoči podatki.",
|
||||
"message.error": "Nekaj je šlo narobe.",
|
||||
"message.event-log": "{event} na {url}",
|
||||
|
|
@ -327,12 +306,12 @@
|
|||
"message.team-not-found": "Ekipa ni bila najdena.",
|
||||
"message.team-websites-info": "Spletne strani si lahko ogleda vsak član ekipe.",
|
||||
"message.tracking-code": "Koda za sledenje",
|
||||
"message.transfer-team-website-to-user": "Transfer this website to your account?",
|
||||
"message.transfer-user-website-to-team": "Select the team to transfer this website to.",
|
||||
"message.transfer-website": "Transfer website ownership to your account or another team.",
|
||||
"message.triggered-event": "Triggered event",
|
||||
"message.unauthorized": "Unauthorized",
|
||||
"message.transfer-team-website-to-user": "Želite prenesti to spletno mesto v svoj račun?",
|
||||
"message.transfer-user-website-to-team": "Izberite ekipo, na katero želite prenesti to spletno mesto.",
|
||||
"message.transfer-website": "Prenesite lastništvo spletnega mesta na svoj račun ali drugo ekipo.",
|
||||
"message.triggered-event": "Sprožen dogodek",
|
||||
"message.user-deleted": "Uporabnik je izbrisan.",
|
||||
"message.viewed-page": "Viewed page",
|
||||
"message.visitor-log": "Obiskovalec iz {country} uporablja {browser} na {os} {device}"
|
||||
"message.viewed-page": "Ogledana stran",
|
||||
"message.visitor-log": "Obiskovalec iz {country} uporablja {browser} na {os} {device}",
|
||||
"message.visitors-dropped-off": "Osip obiskovalcev"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import * as detect from '../detect';
|
||||
|
||||
const IP = '127.0.0.1';
|
||||
const BAD_IP = '127.127.127.127';
|
||||
|
||||
test('getIpAddress: Custom header', () => {
|
||||
process.env.CLIENT_IP_HEADER = 'x-custom-ip-header';
|
||||
|
|
@ -16,6 +17,12 @@ test('getIpAddress: Standard header', () => {
|
|||
expect(detect.getIpAddress(new Headers({ 'x-forwarded-for': IP }))).toEqual(IP);
|
||||
});
|
||||
|
||||
test('getIpAddress: CloudFlare header is lower priority than standard header', () => {
|
||||
expect(
|
||||
detect.getIpAddress(new Headers({ 'cf-connecting-ip': BAD_IP, 'x-forwarded-for': IP })),
|
||||
).toEqual(IP);
|
||||
});
|
||||
|
||||
test('getIpAddress: No header', () => {
|
||||
expect(detect.getIpAddress(new Headers())).toEqual(null);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -126,12 +126,12 @@ export const DATA_TYPES = {
|
|||
|
||||
export const ROLES = {
|
||||
admin: 'admin',
|
||||
user: 'user',
|
||||
viewOnly: 'view-only',
|
||||
teamOwner: 'team-owner',
|
||||
teamManager: 'team-manager',
|
||||
teamMember: 'team-member',
|
||||
teamOwner: 'team-owner',
|
||||
teamViewOnly: 'team-view-only',
|
||||
user: 'user',
|
||||
viewOnly: 'view-only',
|
||||
} as const;
|
||||
|
||||
export const PERMISSIONS = {
|
||||
|
|
@ -223,7 +223,7 @@ export const URL_LENGTH = 500;
|
|||
export const PAGE_TITLE_LENGTH = 500;
|
||||
export const EVENT_NAME_LENGTH = 50;
|
||||
|
||||
export const UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
|
||||
export const UTM_PARAMS = ['utm_campaign', 'utm_content', 'utm_medium', 'utm_source', 'utm_term'];
|
||||
|
||||
export const DESKTOP_OS = [
|
||||
'BeOS',
|
||||
|
|
@ -261,8 +261,8 @@ export const OS_NAMES = {
|
|||
export const BROWSERS = {
|
||||
android: 'Android',
|
||||
aol: 'AOL',
|
||||
beaker: 'Beaker',
|
||||
bb10: 'BlackBerry 10',
|
||||
beaker: 'Beaker',
|
||||
chrome: 'Chrome',
|
||||
'chromium-webview': 'Chrome (webview)',
|
||||
crios: 'Chrome (iOS)',
|
||||
|
|
@ -284,15 +284,17 @@ export const BROWSERS = {
|
|||
phantomjs: 'PhantomJS',
|
||||
safari: 'Safari',
|
||||
samsung: 'Samsung',
|
||||
silk: 'Silk',
|
||||
searchbot: 'Searchbot',
|
||||
silk: 'Silk',
|
||||
yandexbrowser: 'Yandex',
|
||||
} as const;
|
||||
|
||||
// The order here is important and influences how IPs are detected by lib/detect.ts
|
||||
// Please do not change the order unless you know exactly what you're doing - read https://developers.cloudflare.com/fundamentals/reference/http-headers/
|
||||
export const IP_ADDRESS_HEADERS = [
|
||||
'cf-connecting-ip',
|
||||
'x-client-ip',
|
||||
'x-forwarded-for',
|
||||
'cf-connecting-ip', // This should be *after* x-forwarded-for, so that x-forwarded-for is respected if present
|
||||
'do-connecting-ip',
|
||||
'fastly-client-ip',
|
||||
'true-client-ip',
|
||||
|
|
@ -301,354 +303,246 @@ export const IP_ADDRESS_HEADERS = [
|
|||
'x-forwarded',
|
||||
'forwarded',
|
||||
'x-appengine-user-ip',
|
||||
'x-nf-client-connection-ip',
|
||||
'x-real-ip',
|
||||
];
|
||||
|
||||
export const SOCIAL_DOMAINS = [
|
||||
'bsky.app',
|
||||
'facebook.com',
|
||||
'fb.com',
|
||||
'instagram.com',
|
||||
'ig.com',
|
||||
'twitter.com',
|
||||
't.co',
|
||||
'x.com',
|
||||
'instagram.com',
|
||||
'linkedin.',
|
||||
'tiktok.',
|
||||
'reddit.',
|
||||
'threads.net',
|
||||
'bsky.app',
|
||||
'news.ycombinator.com',
|
||||
'snapchat.',
|
||||
'pinterest.',
|
||||
'reddit.',
|
||||
'snapchat.',
|
||||
't.co',
|
||||
'threads.net',
|
||||
'tiktok.',
|
||||
'twitter.com',
|
||||
'x.com',
|
||||
];
|
||||
|
||||
export const SEARCH_DOMAINS = [
|
||||
'google.',
|
||||
'baidu.com',
|
||||
'bing.com',
|
||||
'msn.com',
|
||||
'chatgpt.com',
|
||||
'duckduckgo.com',
|
||||
'ecosia.org',
|
||||
'google.',
|
||||
'msn.com',
|
||||
'perplexity.ai',
|
||||
'search.brave.com',
|
||||
'yandex.',
|
||||
'baidu.com',
|
||||
'ecosia.org',
|
||||
'chatgpt.com',
|
||||
'perplexity.ai',
|
||||
];
|
||||
|
||||
export const SHOPPING_DOMAINS = [
|
||||
'amazon.',
|
||||
'ebay.com',
|
||||
'walmart.com',
|
||||
'alibab.com',
|
||||
'alibaba.com',
|
||||
'aliexpress.com',
|
||||
'etsy.com',
|
||||
'amazon.',
|
||||
'bestbuy.com',
|
||||
'target.com',
|
||||
'ebay.com',
|
||||
'etsy.com',
|
||||
'newegg.com',
|
||||
'target.com',
|
||||
'walmart.com',
|
||||
];
|
||||
|
||||
export const EMAIL_DOMAINS = [
|
||||
'gmail.',
|
||||
'hotmail.',
|
||||
'mail.yahoo.',
|
||||
'outlook.',
|
||||
'hotmail.',
|
||||
'protonmail.',
|
||||
'proton.me',
|
||||
'protonmail.',
|
||||
];
|
||||
|
||||
export const VIDEO_DOMAINS = ['youtube.', 'twitch.'];
|
||||
export const VIDEO_DOMAINS = ['twitch.', 'youtube.'];
|
||||
|
||||
export const PAID_AD_PARAMS = [
|
||||
'utm_source=google',
|
||||
'gclid=',
|
||||
'fbclid=',
|
||||
'msclkid=',
|
||||
'dclid=',
|
||||
'twclid=',
|
||||
'li_fat_id=',
|
||||
'epik=',
|
||||
'ttclid=',
|
||||
'scid=',
|
||||
'aid=',
|
||||
'pc_id=',
|
||||
'ad_id=',
|
||||
'rdt_cid=',
|
||||
'aid=',
|
||||
'dclid=',
|
||||
'epik=',
|
||||
'fbclid=',
|
||||
'gclid=',
|
||||
'li_fat_id=',
|
||||
'msclkid=',
|
||||
'ob_click_id=',
|
||||
'pc_id=',
|
||||
'rdt_cid=',
|
||||
'scid=',
|
||||
'ttclid=',
|
||||
'twclid=',
|
||||
'utm_medium=cpc',
|
||||
'utm_medium=paid',
|
||||
'utm_medium=paid_social',
|
||||
'utm_source=google',
|
||||
];
|
||||
|
||||
export const GROUPED_DOMAINS = [
|
||||
{ name: 'Google', domain: 'google.com', match: 'google.' },
|
||||
{ name: 'Facebook', domain: 'facebook.com', match: 'facebook.' },
|
||||
{ name: 'Reddit', domain: 'reddit.com', match: 'reddit.' },
|
||||
{ name: 'LinkedIn', domain: 'linkedin.com', match: 'linkedin.' },
|
||||
{ name: 'GitHub', domain: 'github.com', match: 'github.' },
|
||||
{ name: 'Hacker News', domain: 'news.ycombinator.com', match: 'news.ycombinator.com' },
|
||||
{ name: 'Baidu', domain: 'baidu.com', match: 'baidu.' },
|
||||
{ name: 'Bing', domain: 'bing.com', match: 'bing.' },
|
||||
{ name: 'Brave', domain: 'brave.com', match: 'brave.' },
|
||||
{ name: 'DuckDuckGo', domain: 'duckduckgo.com', match: 'duckduckgo.' },
|
||||
{ name: 'Twitter', domain: 'twitter.com', match: ['twitter.', 't.co', 'x.com'] },
|
||||
{ name: 'Instagram', domain: 'instagram.com', match: ['instagram.', 'ig.com'] },
|
||||
{ name: 'Snapchat', domain: 'snapchat.com', match: 'snapchat.' },
|
||||
{ name: 'Pinterest', domain: 'pinterest.com', match: 'pinterest.' },
|
||||
{ name: 'ChatGPT', domain: 'chatgpt.com', match: 'chatgpt.' },
|
||||
{ name: 'DuckDuckGo', domain: 'duckduckgo.com', match: 'duckduckgo.' },
|
||||
{ name: 'Facebook', domain: 'facebook.com', match: 'facebook.' },
|
||||
{ name: 'GitHub', domain: 'github.com', match: 'github.' },
|
||||
{ name: 'Google', domain: 'google.com', match: 'google.' },
|
||||
{ name: 'Hacker News', domain: 'news.ycombinator.com', match: 'news.ycombinator.com' },
|
||||
{ name: 'Instagram', domain: 'instagram.com', match: ['instagram.', 'ig.com'] },
|
||||
{ name: 'LinkedIn', domain: 'linkedin.com', match: 'linkedin.' },
|
||||
{ name: 'Pinterest', domain: 'pinterest.com', match: 'pinterest.' },
|
||||
{ name: 'Reddit', domain: 'reddit.com', match: 'reddit.' },
|
||||
{ name: 'Snapchat', domain: 'snapchat.com', match: 'snapchat.' },
|
||||
{ name: 'Twitter', domain: 'twitter.com', match: ['twitter.', 't.co', 'x.com'] },
|
||||
{ name: 'Yahoo', domain: 'yahoo.com', match: 'yahoo.' },
|
||||
{ name: 'Yandex', domain: 'yandex.ru', match: 'yandex.' },
|
||||
{ name: 'Baidu', domain: 'baidu.com', match: 'baidu.' },
|
||||
];
|
||||
|
||||
export const MAP_FILE = '/datamaps.world.json';
|
||||
|
||||
export const ISO_COUNTRIES = {
|
||||
AFG: 'AF',
|
||||
ALA: 'AX',
|
||||
ALB: 'AL',
|
||||
DZA: 'DZ',
|
||||
ASM: 'AS',
|
||||
AND: 'AD',
|
||||
AGO: 'AO',
|
||||
AIA: 'AI',
|
||||
ATA: 'AQ',
|
||||
ATG: 'AG',
|
||||
ARG: 'AR',
|
||||
ARM: 'AM',
|
||||
ABW: 'AW',
|
||||
AUS: 'AU',
|
||||
AUT: 'AT',
|
||||
AZE: 'AZ',
|
||||
BHS: 'BS',
|
||||
BHR: 'BH',
|
||||
BGD: 'BD',
|
||||
BRB: 'BB',
|
||||
BLR: 'BY',
|
||||
BEL: 'BE',
|
||||
BLZ: 'BZ',
|
||||
BEN: 'BJ',
|
||||
BMU: 'BM',
|
||||
BTN: 'BT',
|
||||
BOL: 'BO',
|
||||
BIH: 'BA',
|
||||
BWA: 'BW',
|
||||
BVT: 'BV',
|
||||
BRA: 'BR',
|
||||
VGB: 'VG',
|
||||
IOT: 'IO',
|
||||
BRN: 'BN',
|
||||
BGR: 'BG',
|
||||
BFA: 'BF',
|
||||
BDI: 'BI',
|
||||
KHM: 'KH',
|
||||
CMR: 'CM',
|
||||
CAN: 'CA',
|
||||
CPV: 'CV',
|
||||
CYM: 'KY',
|
||||
CAF: 'CF',
|
||||
TCD: 'TD',
|
||||
CHL: 'CL',
|
||||
CHN: 'CN',
|
||||
HKG: 'HK',
|
||||
MAC: 'MO',
|
||||
CXR: 'CX',
|
||||
CCK: 'CC',
|
||||
COL: 'CO',
|
||||
COM: 'KM',
|
||||
COG: 'CG',
|
||||
COD: 'CD',
|
||||
COK: 'CK',
|
||||
CRI: 'CR',
|
||||
CIV: 'CI',
|
||||
HRV: 'HR',
|
||||
CUB: 'CU',
|
||||
CYP: 'CY',
|
||||
CZE: 'CZ',
|
||||
DNK: 'DK',
|
||||
DJI: 'DJ',
|
||||
DMA: 'DM',
|
||||
DOM: 'DO',
|
||||
ECU: 'EC',
|
||||
EGY: 'EG',
|
||||
SLV: 'SV',
|
||||
GNQ: 'GQ',
|
||||
ERI: 'ER',
|
||||
EST: 'EE',
|
||||
ETH: 'ET',
|
||||
FLK: 'FK',
|
||||
FRO: 'FO',
|
||||
FJI: 'FJ',
|
||||
FIN: 'FI',
|
||||
FRA: 'FR',
|
||||
GUF: 'GF',
|
||||
PYF: 'PF',
|
||||
ATF: 'TF',
|
||||
GAB: 'GA',
|
||||
GMB: 'GM',
|
||||
GEO: 'GE',
|
||||
DEU: 'DE',
|
||||
GHA: 'GH',
|
||||
GIB: 'GI',
|
||||
GRC: 'GR',
|
||||
GRL: 'GL',
|
||||
GRD: 'GD',
|
||||
GLP: 'GP',
|
||||
GUM: 'GU',
|
||||
GTM: 'GT',
|
||||
GGY: 'GG',
|
||||
GIN: 'GN',
|
||||
GNB: 'GW',
|
||||
GUY: 'GY',
|
||||
HTI: 'HT',
|
||||
HMD: 'HM',
|
||||
VAT: 'VA',
|
||||
HND: 'HN',
|
||||
HUN: 'HU',
|
||||
ISL: 'IS',
|
||||
IND: 'IN',
|
||||
IDN: 'ID',
|
||||
IRN: 'IR',
|
||||
IRQ: 'IQ',
|
||||
IRL: 'IE',
|
||||
IMN: 'IM',
|
||||
ISR: 'IL',
|
||||
ITA: 'IT',
|
||||
ANT: 'AN',
|
||||
ARE: 'AE',
|
||||
BLM: 'BL',
|
||||
CHE: 'CH',
|
||||
ESH: 'EH',
|
||||
ESP: 'ES',
|
||||
FSM: 'FM',
|
||||
GBR: 'GB',
|
||||
JAM: 'JM',
|
||||
JPN: 'JP',
|
||||
JEY: 'JE',
|
||||
JOR: 'JO',
|
||||
JPN: 'JP',
|
||||
KAZ: 'KZ',
|
||||
KEN: 'KE',
|
||||
KGZ: 'KG',
|
||||
KIR: 'KI',
|
||||
PRK: 'KP',
|
||||
KNA: 'KN',
|
||||
KOR: 'KR',
|
||||
KWT: 'KW',
|
||||
KGZ: 'KG',
|
||||
LAO: 'LA',
|
||||
LVA: 'LV',
|
||||
LBN: 'LB',
|
||||
LSO: 'LS',
|
||||
LBR: 'LR',
|
||||
LBY: 'LY',
|
||||
LCA: 'LC',
|
||||
LIE: 'LI',
|
||||
LKA: 'LK',
|
||||
LSO: 'LS',
|
||||
LTU: 'LT',
|
||||
LUX: 'LU',
|
||||
MKD: 'MK',
|
||||
LVA: 'LV',
|
||||
MAF: 'MF',
|
||||
MAR: 'MA',
|
||||
MCO: 'MC',
|
||||
MDA: 'MD',
|
||||
MDG: 'MG',
|
||||
MWI: 'MW',
|
||||
MYS: 'MY',
|
||||
MDV: 'MV',
|
||||
MEX: 'MX',
|
||||
MHL: 'MH',
|
||||
MKD: 'MK',
|
||||
MLI: 'ML',
|
||||
MLT: 'MT',
|
||||
MHL: 'MH',
|
||||
MTQ: 'MQ',
|
||||
MRT: 'MR',
|
||||
MUS: 'MU',
|
||||
MYT: 'YT',
|
||||
MEX: 'MX',
|
||||
FSM: 'FM',
|
||||
MDA: 'MD',
|
||||
MCO: 'MC',
|
||||
MNG: 'MN',
|
||||
MNE: 'ME',
|
||||
MSR: 'MS',
|
||||
MAR: 'MA',
|
||||
MOZ: 'MZ',
|
||||
MMR: 'MM',
|
||||
NAM: 'NA',
|
||||
NRU: 'NR',
|
||||
NPL: 'NP',
|
||||
NLD: 'NL',
|
||||
ANT: 'AN',
|
||||
NCL: 'NC',
|
||||
NZL: 'NZ',
|
||||
NIC: 'NI',
|
||||
NER: 'NE',
|
||||
NGA: 'NG',
|
||||
NIU: 'NU',
|
||||
NFK: 'NF',
|
||||
MNE: 'ME',
|
||||
MNG: 'MN',
|
||||
MNP: 'MP',
|
||||
MOZ: 'MZ',
|
||||
MRT: 'MR',
|
||||
MSR: 'MS',
|
||||
MTQ: 'MQ',
|
||||
MUS: 'MU',
|
||||
MWI: 'MW',
|
||||
MYS: 'MY',
|
||||
MYT: 'YT',
|
||||
NAM: 'NA',
|
||||
NCL: 'NC',
|
||||
NER: 'NE',
|
||||
NFK: 'NF',
|
||||
NGA: 'NG',
|
||||
NIC: 'NI',
|
||||
NIU: 'NU',
|
||||
NLD: 'NL',
|
||||
NOR: 'NO',
|
||||
NPL: 'NP',
|
||||
NRU: 'NR',
|
||||
NZL: 'NZ',
|
||||
OMN: 'OM',
|
||||
PAK: 'PK',
|
||||
PLW: 'PW',
|
||||
PSE: 'PS',
|
||||
PAN: 'PA',
|
||||
PNG: 'PG',
|
||||
PRY: 'PY',
|
||||
PCN: 'PN',
|
||||
PER: 'PE',
|
||||
PHL: 'PH',
|
||||
PCN: 'PN',
|
||||
PLW: 'PW',
|
||||
PNG: 'PG',
|
||||
POL: 'PL',
|
||||
PRT: 'PT',
|
||||
PRI: 'PR',
|
||||
PRK: 'KP',
|
||||
PRT: 'PT',
|
||||
PRY: 'PY',
|
||||
PSE: 'PS',
|
||||
QAT: 'QA',
|
||||
REU: 'RE',
|
||||
ROU: 'RO',
|
||||
RUS: 'RU',
|
||||
RWA: 'RW',
|
||||
BLM: 'BL',
|
||||
SHN: 'SH',
|
||||
KNA: 'KN',
|
||||
LCA: 'LC',
|
||||
MAF: 'MF',
|
||||
SPM: 'PM',
|
||||
VCT: 'VC',
|
||||
WSM: 'WS',
|
||||
SMR: 'SM',
|
||||
STP: 'ST',
|
||||
SAU: 'SA',
|
||||
SDN: 'SD',
|
||||
SEN: 'SN',
|
||||
SRB: 'RS',
|
||||
SYC: 'SC',
|
||||
SLE: 'SL',
|
||||
SGP: 'SG',
|
||||
SGS: 'GS',
|
||||
SHN: 'SH',
|
||||
SJM: 'SJ',
|
||||
SLB: 'SB',
|
||||
SLE: 'SL',
|
||||
SMR: 'SM',
|
||||
SOM: 'SO',
|
||||
SPM: 'PM',
|
||||
SRB: 'RS',
|
||||
SSD: 'SS',
|
||||
STP: 'ST',
|
||||
SUR: 'SR',
|
||||
SVK: 'SK',
|
||||
SVN: 'SI',
|
||||
SLB: 'SB',
|
||||
SOM: 'SO',
|
||||
ZAF: 'ZA',
|
||||
SGS: 'GS',
|
||||
SSD: 'SS',
|
||||
ESP: 'ES',
|
||||
LKA: 'LK',
|
||||
SDN: 'SD',
|
||||
SUR: 'SR',
|
||||
SJM: 'SJ',
|
||||
SWZ: 'SZ',
|
||||
SWE: 'SE',
|
||||
CHE: 'CH',
|
||||
SWZ: 'SZ',
|
||||
SYC: 'SC',
|
||||
SYR: 'SY',
|
||||
TWN: 'TW',
|
||||
TJK: 'TJ',
|
||||
TZA: 'TZ',
|
||||
THA: 'TH',
|
||||
TLS: 'TL',
|
||||
TCA: 'TC',
|
||||
TGO: 'TG',
|
||||
THA: 'TH',
|
||||
TJK: 'TJ',
|
||||
TKL: 'TK',
|
||||
TKM: 'TM',
|
||||
TLS: 'TL',
|
||||
TON: 'TO',
|
||||
TTO: 'TT',
|
||||
TUN: 'TN',
|
||||
TUR: 'TR',
|
||||
TKM: 'TM',
|
||||
TCA: 'TC',
|
||||
TUV: 'TV',
|
||||
TWN: 'TW',
|
||||
TZA: 'TZ',
|
||||
UGA: 'UG',
|
||||
UKR: 'UA',
|
||||
ARE: 'AE',
|
||||
GBR: 'GB',
|
||||
USA: 'US',
|
||||
UMI: 'UM',
|
||||
URY: 'UY',
|
||||
USA: 'US',
|
||||
UZB: 'UZ',
|
||||
VUT: 'VU',
|
||||
VCT: 'VC',
|
||||
VEN: 'VE',
|
||||
VNM: 'VN',
|
||||
VIR: 'VI',
|
||||
VNM: 'VN',
|
||||
VUT: 'VU',
|
||||
WLF: 'WF',
|
||||
ESH: 'EH',
|
||||
WSM: 'WS',
|
||||
XKX: 'XK',
|
||||
YEM: 'YE',
|
||||
ZAF: 'ZA',
|
||||
ZMB: 'ZM',
|
||||
ZWE: 'ZW',
|
||||
XKX: 'XK',
|
||||
};
|
||||
|
||||
export const CURRENCIES = [
|
||||
|
|
|
|||
|
|
@ -15,6 +15,27 @@ import { safeDecodeURIComponent } from '@/lib/url';
|
|||
|
||||
const MAXMIND = 'maxmind';
|
||||
|
||||
const PROVIDER_HEADERS = [
|
||||
// Cloudflare headers
|
||||
{
|
||||
countryHeader: 'cf-ipcountry',
|
||||
regionHeader: 'cf-region-code',
|
||||
cityHeader: 'cf-ipcity',
|
||||
},
|
||||
// Vercel headers
|
||||
{
|
||||
countryHeader: 'x-vercel-ip-country',
|
||||
regionHeader: 'x-vercel-ip-country-region',
|
||||
cityHeader: 'x-vercel-ip-city',
|
||||
},
|
||||
// CloudFront headers
|
||||
{
|
||||
countryHeader: 'cloudfront-viewer-country',
|
||||
regionHeader: 'cloudfront-viewer-country-region',
|
||||
cityHeader: 'cloudfront-viewer-city',
|
||||
},
|
||||
];
|
||||
|
||||
export function getIpAddress(headers: Headers) {
|
||||
const customHeader = process.env.CLIENT_IP_HEADER;
|
||||
|
||||
|
|
@ -94,11 +115,12 @@ export async function getLocation(ip: string = '', headers: Headers, hasPayloadI
|
|||
}
|
||||
|
||||
if (!hasPayloadIP && !process.env.SKIP_LOCATION_HEADERS) {
|
||||
// Cloudflare headers
|
||||
if (headers.get('cf-ipcountry')) {
|
||||
const country = decodeHeader(headers.get('cf-ipcountry'));
|
||||
const region = decodeHeader(headers.get('cf-region-code'));
|
||||
const city = decodeHeader(headers.get('cf-ipcity'));
|
||||
for (const provider of PROVIDER_HEADERS) {
|
||||
const countryHeader = headers.get(provider.countryHeader);
|
||||
if (countryHeader) {
|
||||
const country = decodeHeader(countryHeader);
|
||||
const region = decodeHeader(headers.get(provider.regionHeader));
|
||||
const city = decodeHeader(headers.get(provider.cityHeader));
|
||||
|
||||
return {
|
||||
country,
|
||||
|
|
@ -106,18 +128,6 @@ export async function getLocation(ip: string = '', headers: Headers, hasPayloadI
|
|||
city,
|
||||
};
|
||||
}
|
||||
|
||||
// Vercel headers
|
||||
if (headers.get('x-vercel-ip-country')) {
|
||||
const country = decodeHeader(headers.get('x-vercel-ip-country'));
|
||||
const region = decodeHeader(headers.get('x-vercel-ip-country-region'));
|
||||
const city = decodeHeader(headers.get('x-vercel-ip-city'));
|
||||
|
||||
return {
|
||||
country,
|
||||
region: getRegionCode(country, region),
|
||||
city,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ async function clickhouseQuery(
|
|||
and website_revenue.currency = {currency:String}
|
||||
${filterQuery}
|
||||
group by website_event.country
|
||||
order by value desc
|
||||
`,
|
||||
queryParams,
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue