mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-24 18:26:20 +01:00
Merge branch 'dev' into patch-1
This commit is contained in:
commit
e7a6787046
@ -10,7 +10,7 @@ import { dateFormat } from 'lib/date';
|
||||
import Calendar from 'assets/calendar-alt.svg';
|
||||
import Icon from './Icon';
|
||||
|
||||
const filterOptions = [
|
||||
export const filterOptions = [
|
||||
{ label: <FormattedMessage id="label.today" defaultMessage="Today" />, value: '1day' },
|
||||
{
|
||||
label: (
|
||||
@ -59,7 +59,7 @@ const filterOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
function DateFilter({ value, startDate, endDate, onChange, className }) {
|
||||
function DateFilter({ value, startDate, endDate, onChange, className, options }) {
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
const displayValue =
|
||||
value === 'custom' ? (
|
||||
@ -86,7 +86,7 @@ function DateFilter({ value, startDate, endDate, onChange, className }) {
|
||||
<DropDown
|
||||
className={className}
|
||||
value={displayValue}
|
||||
options={filterOptions}
|
||||
options={options || filterOptions}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{showPicker && (
|
||||
|
@ -13,6 +13,8 @@ import Icon from 'components/common/Icon';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import styles from './LoginForm.module.css';
|
||||
import usePost from 'hooks/usePost';
|
||||
import { setItem } from 'lib/web';
|
||||
import { AUTH_TOKEN } from '../../lib/constants';
|
||||
|
||||
const validate = ({ username, password }) => {
|
||||
const errors = {};
|
||||
@ -39,6 +41,8 @@ export default function LoginForm() {
|
||||
});
|
||||
|
||||
if (ok) {
|
||||
setItem(AUTH_TOKEN, data.token);
|
||||
|
||||
return router.push('/');
|
||||
} else {
|
||||
setMessage(
|
||||
|
@ -7,15 +7,15 @@ import { TOKEN_HEADER } from 'lib/constants';
|
||||
import useShareToken from 'hooks/useShareToken';
|
||||
import styles from './ActiveUsers.module.css';
|
||||
|
||||
export default function ActiveUsers({ websiteId, className }) {
|
||||
export default function ActiveUsers({ websiteId, className, value, interval = 60000 }) {
|
||||
const shareToken = useShareToken();
|
||||
const { data } = useFetch(`/api/website/${websiteId}/active`, {
|
||||
interval: 60000,
|
||||
const { data } = useFetch(!value && `/api/website/${websiteId}/active`, {
|
||||
interval,
|
||||
headers: { [TOKEN_HEADER]: shareToken?.token },
|
||||
});
|
||||
const count = useMemo(() => {
|
||||
return data?.[0]?.x || 0;
|
||||
}, [data]);
|
||||
return value || data?.[0]?.x || 0;
|
||||
}, [data, value]);
|
||||
|
||||
if (count === 0) {
|
||||
return null;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PageHeader from '../layout/PageHeader';
|
||||
import DropDown from '../common/DropDown';
|
||||
import { differenceInMinutes } from 'date-fns';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
import ActiveUsers from './ActiveUsers';
|
||||
import MetricCard from './MetricCard';
|
||||
import styles from './RealtimeHeader.module.css';
|
||||
@ -19,6 +20,12 @@ export default function RealtimeHeader({ websites, data, websiteId, onSelect })
|
||||
|
||||
const { pageviews, sessions, events, countries } = data;
|
||||
|
||||
const count = useMemo(() => {
|
||||
return sessions.filter(
|
||||
({ created_at }) => differenceInMinutes(new Date(), new Date(created_at)) <= 5,
|
||||
).length;
|
||||
}, [sessions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
@ -26,7 +33,7 @@ export default function RealtimeHeader({ websites, data, websiteId, onSelect })
|
||||
<FormattedMessage id="label.realtime" defaultMessage="Realtime" />
|
||||
</div>
|
||||
<div>
|
||||
<ActiveUsers className={styles.active} websiteId={websiteId} />
|
||||
<ActiveUsers className={styles.active} value={count} />
|
||||
</div>
|
||||
<DropDown value={websiteId} options={options} onChange={onSelect} />
|
||||
</PageHeader>
|
||||
|
@ -2,3 +2,9 @@
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 576px) {
|
||||
.active {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export default function WebsiteChart({
|
||||
};
|
||||
}
|
||||
return { pageviews: [], sessions: [] };
|
||||
}, [data]);
|
||||
}, [data, startDate, endDate, unit]);
|
||||
|
||||
function handleCloseFilter(param) {
|
||||
router.push(resolve({ [param]: undefined }));
|
||||
@ -77,8 +77,10 @@ export default function WebsiteChart({
|
||||
if (ok) {
|
||||
setDateRange({ value, ...getDateRangeValues(new Date(data.created_at), Date.now()) });
|
||||
}
|
||||
} else {
|
||||
} else if (typeof value === 'string') {
|
||||
setDateRange(getDateRange(value, locale));
|
||||
} else {
|
||||
setDateRange(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import DateFilter from 'components/common/DateFilter';
|
||||
import DateFilter, { filterOptions } from 'components/common/DateFilter';
|
||||
import Button from 'components/common/Button';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import { DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||
@ -12,6 +12,7 @@ export default function DateRangeSetting() {
|
||||
const { locale } = useLocale();
|
||||
const [dateRange, setDateRange] = useDateRange();
|
||||
const { startDate, endDate, value } = dateRange;
|
||||
const options = filterOptions.filter(e => e.value !== 'all');
|
||||
|
||||
function handleReset() {
|
||||
setDateRange(getDateRange(DEFAULT_DATE_RANGE, locale));
|
||||
@ -19,7 +20,13 @@ export default function DateRangeSetting() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DateFilter value={value} startDate={startDate} endDate={endDate} onChange={setDateRange} />
|
||||
<DateFilter
|
||||
options={options}
|
||||
value={value}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onChange={setDateRange}
|
||||
/>
|
||||
<Button className={styles.button} size="small" onClick={handleReset}>
|
||||
<FormattedMessage id="label.reset" defaultMessage="Reset" />
|
||||
</Button>
|
||||
|
@ -7,6 +7,8 @@ import Icon from 'components/common/Icon';
|
||||
import User from 'assets/user.svg';
|
||||
import Chevron from 'assets/chevron-down.svg';
|
||||
import styles from './UserButton.module.css';
|
||||
import { removeItem } from 'lib/web';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
|
||||
export default function UserButton() {
|
||||
const user = useSelector(state => state.user);
|
||||
@ -30,7 +32,8 @@ export default function UserButton() {
|
||||
|
||||
function handleSelect(value) {
|
||||
if (value === 'logout') {
|
||||
router.push('/logout');
|
||||
removeItem(AUTH_TOKEN);
|
||||
router.push('/login');
|
||||
} else if (value === 'profile') {
|
||||
router.push('/settings/profile');
|
||||
}
|
||||
|
@ -11,13 +11,14 @@ export default function useFetch(url, options = {}, update = []) {
|
||||
const [loading, setLoadiing] = useState(false);
|
||||
const [count, setCount] = useState(0);
|
||||
const { basePath } = useRouter();
|
||||
const { params = {}, disabled, headers, delay = 0, interval, onDataLoad } = options;
|
||||
const { params = {}, headers = {}, disabled, delay = 0, interval, onDataLoad } = options;
|
||||
|
||||
async function loadData(params) {
|
||||
try {
|
||||
setLoadiing(true);
|
||||
setError(null);
|
||||
const time = performance.now();
|
||||
|
||||
const { data, status, ok } = await get(`${basePath}${url}`, params, headers);
|
||||
|
||||
dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() }));
|
||||
|
@ -97,7 +97,7 @@
|
||||
"metrics.filter.combined": "Kombiniert",
|
||||
"metrics.filter.domain-only": "Nur diese Domain",
|
||||
"metrics.filter.raw": "Rohdaten",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.languages": "Sprachen",
|
||||
"metrics.operating-systems": "Betriebssysteme",
|
||||
"metrics.page-views": "Seitenaufrufe",
|
||||
"metrics.pages": "Seiten",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"label.administrator": "Administrador",
|
||||
"label.all": "Todos",
|
||||
"label.all-events": "Todos los eventos",
|
||||
"label.all-time": "All time",
|
||||
"label.all-time": "Todos los tiempos",
|
||||
"label.all-websites": "Todos los sitios",
|
||||
"label.back": "Atrás",
|
||||
"label.cancel": "Cancelar",
|
||||
@ -36,16 +36,16 @@
|
||||
"label.more": "Más",
|
||||
"label.name": "Nombre",
|
||||
"label.new-password": "Nueva contraseña",
|
||||
"label.owner": "Owner",
|
||||
"label.owner": "Propietario",
|
||||
"label.password": "Contraseña",
|
||||
"label.passwords-dont-match": "Las contraseñas no coinciden",
|
||||
"label.profile": "Perfil",
|
||||
"label.realtime": "Tiempo real",
|
||||
"label.realtime-logs": "Registros en tiempo real",
|
||||
"label.refresh": "Actualizar",
|
||||
"label.required": "Requerido",
|
||||
"label.required": "Obligatorio",
|
||||
"label.reset": "Reiniciar",
|
||||
"label.reset-website": "Reset statistics",
|
||||
"label.reset-website": "Reiniciar estadísticas",
|
||||
"label.save": "Guardar",
|
||||
"label.settings": "Configuraciones",
|
||||
"label.share-url": "Compartir URL",
|
||||
@ -62,8 +62,8 @@
|
||||
"label.websites": "Sitios",
|
||||
"message.active-users": "{x} {x, plural, one {activo} other {activos}}",
|
||||
"message.confirm-delete": "¿Estás seguro(a) de querer eliminar {target}?",
|
||||
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
|
||||
"message.copied": "Copiado!",
|
||||
"message.confirm-reset": "¿Seguro que deseas restablecer las estadisticas de {target}?",
|
||||
"message.copied": "¡Copiado!",
|
||||
"message.delete-warning": "Toda la información relacionada será eliminada.",
|
||||
"message.failure": "Algo falló.",
|
||||
"message.get-share-url": "Obtener URL para compartir",
|
||||
@ -71,33 +71,33 @@
|
||||
"message.go-to-settings": "Ir a la configuración",
|
||||
"message.incorrect-username-password": "Nombre de usuario o contraseña incorrectos.",
|
||||
"message.log.visitor": "Visitante desde {country} usando {browser} en {os} {device}",
|
||||
"message.new-version-available": "Una nueva versíon de umami {version} esta disponible!",
|
||||
"message.new-version-available": "¡Una nueva versíon de umami {version} esta disponible!",
|
||||
"message.no-data-available": "Sin información disponible.",
|
||||
"message.no-websites-configured": "No tienes ningún sitio configurado.",
|
||||
"message.page-not-found": "Page not found",
|
||||
"message.page-not-found": "Página no encontrada",
|
||||
"message.powered-by": "Desarrollado con {name}",
|
||||
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
|
||||
"message.reset-warning": "Todas las estadísticas de esta página serán eliminadas, pero el código de rastreo permanecerá intacto.",
|
||||
"message.save-success": "Guardado exitosamente.",
|
||||
"message.share-url": "Esta es la URL compartida públicamente para {target}.",
|
||||
"message.toggle-charts": "Toggle charts",
|
||||
"message.toggle-charts": "Alternar gráficas",
|
||||
"message.track-stats": "Para registrar estadísticas para {target}, copia el siguiente código dentro de la etiqueta {head} de tu sitio.",
|
||||
"message.type-delete": "Escribe {delete} abajo para confirmar.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-reset": "Escribe {reset} en la caja inferior para confirmar.",
|
||||
"metrics.actions": "Acciones",
|
||||
"metrics.average-visit-time": "Tiempo promedio de visita",
|
||||
"metrics.bounce-rate": "Porcentaje de rebote",
|
||||
"metrics.browsers": "Navegadores",
|
||||
"metrics.countries": "Países",
|
||||
"metrics.device.desktop": "Desktop",
|
||||
"metrics.device.laptop": "Laptop",
|
||||
"metrics.device.mobile": "Mobile",
|
||||
"metrics.device.desktop": "Escritorio",
|
||||
"metrics.device.laptop": "Portátil",
|
||||
"metrics.device.mobile": "Móvil",
|
||||
"metrics.device.tablet": "Tableta",
|
||||
"metrics.devices": "Dispositivos",
|
||||
"metrics.events": "Eventos",
|
||||
"metrics.filter.combined": "Combinado",
|
||||
"metrics.filter.domain-only": "Únicamente dominio",
|
||||
"metrics.filter.raw": "Personalizado",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.languages": "Idiomas",
|
||||
"metrics.operating-systems": "Sistemas operativos",
|
||||
"metrics.page-views": "Vistas",
|
||||
"metrics.pages": "Páginas",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"label.administrator": "Адміністратор",
|
||||
"label.all": "Всі",
|
||||
"label.all-events": "Всі події",
|
||||
"label.all-time": "All time",
|
||||
"label.all-time": "Весь час",
|
||||
"label.all-websites": "Всі сайти",
|
||||
"label.back": "Назад",
|
||||
"label.cancel": "Відмінити",
|
||||
@ -36,7 +36,7 @@
|
||||
"label.more": "Більше",
|
||||
"label.name": "Ім'я",
|
||||
"label.new-password": "Новий пароль",
|
||||
"label.owner": "Owner",
|
||||
"label.owner": "Власник",
|
||||
"label.password": "Пароль",
|
||||
"label.passwords-dont-match": "Паролі не співпадають",
|
||||
"label.profile": "Профіль",
|
||||
@ -45,7 +45,7 @@
|
||||
"label.refresh": "Оновити",
|
||||
"label.required": "Обов'язкове",
|
||||
"label.reset": "Скинути",
|
||||
"label.reset-website": "Reset statistics",
|
||||
"label.reset-website": "Скинути статистику сайту",
|
||||
"label.save": "Зберегти",
|
||||
"label.settings": "Налаштування",
|
||||
"label.share-url": "Поділитися посилання",
|
||||
@ -62,7 +62,7 @@
|
||||
"label.websites": "Сайти",
|
||||
"message.active-users": "{x} поточних відвідувачів",
|
||||
"message.confirm-delete": "Ви впевнені, що бажаєте видалити {target}?",
|
||||
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
|
||||
"message.confirm-reset": "Ви впевнені, що бажаєте скинути статистику для {target}?",
|
||||
"message.copied": "Скопійовано!",
|
||||
"message.delete-warning": "Усі пов'язані дані будуть видалені також.",
|
||||
"message.failure": "Щось пішло не так.",
|
||||
@ -76,13 +76,13 @@
|
||||
"message.no-websites-configured": "У вас немає налаштованих сайтів.",
|
||||
"message.page-not-found": "Сторінку не знайдено.",
|
||||
"message.powered-by": "На базі {name}",
|
||||
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
|
||||
"message.reset-warning": "Вся статистика для цього сайту буде видалена, проте код відслідковування буде продовжувати працювати.",
|
||||
"message.save-success": "Збережено успішно.",
|
||||
"message.share-url": "Це публічне посилання для {target}.",
|
||||
"message.toggle-charts": "Toggle charts",
|
||||
"message.toggle-charts": "Переключити графіки",
|
||||
"message.track-stats": "Аби відслідковувати статистику для {target}, розмістіть наступний код у {head} секції вашого сайту.",
|
||||
"message.type-delete": "Введіть {delete} у полі нижче щоб підтвердити.",
|
||||
"message.type-reset": "Type {reset} in the box below to confirm.",
|
||||
"message.type-delete": "Введіть {delete} у полі нижче для підтвердження.",
|
||||
"message.type-reset": "Введіть {reset} у полі нижче для підтвердження.",
|
||||
"metrics.actions": "Дії",
|
||||
"metrics.average-visit-time": "Середній час візиту",
|
||||
"metrics.bounce-rate": "Показник відмов",
|
||||
@ -97,7 +97,7 @@
|
||||
"metrics.filter.combined": "Об'єднані",
|
||||
"metrics.filter.domain-only": "Лише домен",
|
||||
"metrics.filter.raw": "Сирі дані",
|
||||
"metrics.languages": "Languages",
|
||||
"metrics.languages": "Мови",
|
||||
"metrics.operating-systems": "Операційні системи",
|
||||
"metrics.page-views": "Перегляди сторінок",
|
||||
"metrics.pages": "Сторінки",
|
||||
|
11
lib/auth.js
11
lib/auth.js
@ -1,12 +1,15 @@
|
||||
import { parse } from 'cookie';
|
||||
import { parseSecureToken, parseToken } from './crypto';
|
||||
import { AUTH_COOKIE_NAME, TOKEN_HEADER } from './constants';
|
||||
import { TOKEN_HEADER } from './constants';
|
||||
import { getWebsiteById } from './queries';
|
||||
|
||||
export async function getAuthToken(req) {
|
||||
const token = parse(req.headers.cookie || '')[AUTH_COOKIE_NAME];
|
||||
try {
|
||||
const token = req.headers.authorization;
|
||||
|
||||
return parseSecureToken(token);
|
||||
return parseSecureToken(token.split(' ')[1]);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function isValidToken(token, validation) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const AUTH_COOKIE_NAME = 'umami.auth';
|
||||
export const AUTH_TOKEN = 'umami.auth';
|
||||
export const LOCALE_CONFIG = 'umami.locale';
|
||||
export const TIMEZONE_CONFIG = 'umami.timezone';
|
||||
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
||||
@ -80,7 +80,8 @@ export const POSTGRESQL_DATE_FORMATS = {
|
||||
year: 'YYYY-01-01',
|
||||
};
|
||||
|
||||
export const DOMAIN_REGEX = /^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63})$/;
|
||||
export const DOMAIN_REGEX =
|
||||
/^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63})$/;
|
||||
|
||||
export const DESKTOP_SCREEN_WIDTH = 1920;
|
||||
export const LAPTOP_SCREEN_WIDTH = 1024;
|
||||
|
15
lib/web.js
15
lib/web.js
@ -1,13 +1,17 @@
|
||||
import { makeUrl } from './url';
|
||||
import { AUTH_TOKEN } from './constants';
|
||||
|
||||
export const apiRequest = (method, url, body, headers) =>
|
||||
fetch(url, {
|
||||
export const apiRequest = (method, url, body, headers) => {
|
||||
const authToken = getItem(AUTH_TOKEN);
|
||||
|
||||
return fetch(url, {
|
||||
method,
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
||||
...headers,
|
||||
},
|
||||
body,
|
||||
@ -18,6 +22,7 @@ export const apiRequest = (method, url, body, headers) =>
|
||||
|
||||
return res.text().then(data => ({ ok: res.ok, status: res.status, res: res, data }));
|
||||
});
|
||||
};
|
||||
|
||||
export const get = (url, params, headers) =>
|
||||
apiRequest('get', makeUrl(url, params), undefined, headers);
|
||||
@ -64,3 +69,9 @@ export const getItem = (key, session) =>
|
||||
typeof window !== 'undefined'
|
||||
? JSON.parse((session ? sessionStorage : localStorage).getItem(key))
|
||||
: null;
|
||||
|
||||
export const removeItem = (key, session) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
(session ? sessionStorage : localStorage).removeItem(key);
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "umami",
|
||||
"version": "1.25.0",
|
||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||
"description": "A simple, fast, website analytics alternative to Google Analytics.",
|
||||
"author": "Mike Cao <mike@mikecao.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://umami.is",
|
||||
@ -56,7 +56,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "4.5.0",
|
||||
"@prisma/client": "3.6.0",
|
||||
"@prisma/client": "3.8.1",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chalk": "^4.1.1",
|
||||
@ -77,7 +77,7 @@
|
||||
"jose": "2.0.5",
|
||||
"maxmind": "^4.3.2",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"next": "12.0.5",
|
||||
"next": "12.0.8",
|
||||
"prompts": "2.4.2",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.2",
|
||||
@ -124,7 +124,7 @@
|
||||
"postcss-rtlcss": "^3.3.2",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier-eslint": "^13.0.0",
|
||||
"prisma": "3.6.0",
|
||||
"prisma": "3.8.1",
|
||||
"rollup": "^2.48.0",
|
||||
"rollup-plugin-hashbang": "^2.2.2",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { serialize } from 'cookie';
|
||||
import { checkPassword, createSecureToken } from 'lib/crypto';
|
||||
import { getAccountByUsername } from 'lib/queries';
|
||||
import { AUTH_COOKIE_NAME } from 'lib/constants';
|
||||
import { ok, unauthorized, badRequest } from 'lib/response';
|
||||
|
||||
export default async (req, res) => {
|
||||
@ -16,14 +14,6 @@ export default async (req, res) => {
|
||||
if (account && (await checkPassword(password, account.password))) {
|
||||
const { user_id, username, is_admin } = account;
|
||||
const token = await createSecureToken({ user_id, username, is_admin });
|
||||
const cookie = serialize(AUTH_COOKIE_NAME, token, {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
sameSite: true,
|
||||
maxAge: 60 * 60 * 24 * 365,
|
||||
});
|
||||
|
||||
res.setHeader('Set-Cookie', [cookie]);
|
||||
|
||||
return ok(res, { token });
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { serialize } from 'cookie';
|
||||
import { AUTH_COOKIE_NAME } from 'lib/constants';
|
||||
import { ok } from 'lib/response';
|
||||
|
||||
export default async (req, res) => {
|
||||
const cookie = serialize(AUTH_COOKIE_NAME, '', {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
maxAge: 0,
|
||||
});
|
||||
|
||||
res.setHeader('Set-Cookie', [cookie]);
|
||||
|
||||
return ok(res);
|
||||
};
|
@ -114,17 +114,16 @@ import { removeTrailingSlash } from '../lib/url';
|
||||
};
|
||||
|
||||
const addEvent = element => {
|
||||
element.className &&
|
||||
element.className.split(' ').forEach(className => {
|
||||
if (!eventClass.test(className)) return;
|
||||
(element.getAttribute('class') || '').split(' ').forEach(className => {
|
||||
if (!eventClass.test(className)) return;
|
||||
|
||||
const [, type, value] = className.split('--');
|
||||
const listener = listeners[className]
|
||||
? listeners[className]
|
||||
: (listeners[className] = () => trackEvent(value, type));
|
||||
const [, type, value] = className.split('--');
|
||||
const listener = listeners[className]
|
||||
? listeners[className]
|
||||
: (listeners[className] = () => trackEvent(value, type));
|
||||
|
||||
element.addEventListener(type, listener, true);
|
||||
});
|
||||
element.addEventListener(type, listener, true);
|
||||
});
|
||||
};
|
||||
|
||||
const monitorMutate = mutations => {
|
||||
|
Loading…
Reference in New Issue
Block a user