Added CORS middleware. Updated umami script.

This commit is contained in:
Mike Cao 2020-07-18 10:36:46 -07:00
parent bb04015b46
commit 58a1c63407
10 changed files with 144 additions and 78 deletions

20
lib/middleware.js Normal file
View File

@ -0,0 +1,20 @@
import cors from 'cors';
export function use(middleware) {
return (req, res) =>
new Promise((resolve, reject) => {
middleware(req, res, result => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
}
export const allowPost = use(
cors({
origin: '*',
methods: ['POST', 'OPTIONS'],
}),
);

View File

@ -61,48 +61,55 @@ export async function getCountry(req, ip) {
} }
export async function parseSessionRequest(req) { export async function parseSessionRequest(req) {
const ip = getIpAddress(req); if (req.method === 'POST') {
const { website_id, screen, language } = req.body; const ip = getIpAddress(req);
const { userAgent, browser, os } = getDevice(req); const { website_id, hostname, screen, language } = req.body;
const country = await getCountry(req, ip); const { userAgent, browser, os } = getDevice(req);
const session_id = hash(`${website_id}${ip}${userAgent}${os}`); const country = await getCountry(req, ip);
const session_id = hash(`${website_id}${hostname}${ip}${userAgent}${os}`);
return { return {
website_id, website_id,
session_id, session_id,
browser, hostname,
os, browser,
screen, os,
language, screen,
country, language,
}; country,
};
}
return {};
} }
export function parseCollectRequest(req) { export function parseCollectRequest(req) {
const { type, payload } = req.body; if (req.method === 'POST') {
const { type, payload } = req.body;
if (payload.session) { if (payload.session) {
const { const {
url,
referrer,
session: { website_id, session_id, time, hash: validationHash },
} = payload;
if (
validHash(website_id) &&
validHash(session_id) &&
validHash(validationHash) &&
hash(`${website_id}${session_id}${time}`) === validationHash
) {
return {
valid: true,
type,
session_id,
url, url,
referrer, referrer,
}; session: { website_id, session_id, time, hash: validationHash },
} = payload;
if (
validHash(website_id) &&
validHash(session_id) &&
validHash(validationHash) &&
hash(`${website_id}${session_id}${time}`) === validationHash
) {
return {
success: 1,
type,
session_id,
url,
referrer,
};
}
} }
} }
return { valid: false }; return { success: 0 };
} }

View File

@ -29,6 +29,7 @@
"@prisma/client": "2.2.2", "@prisma/client": "2.2.2",
"chart.js": "^2.9.3", "chart.js": "^2.9.3",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"cors": "^2.8.5",
"date-fns": "^2.14.0", "date-fns": "^2.14.0",
"detect-browser": "^5.1.1", "detect-browser": "^5.1.1",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",

View File

@ -1,18 +1,21 @@
import { parseCollectRequest } from 'lib/utils'; import { parseCollectRequest } from 'lib/utils';
import { savePageView } from 'lib/db'; import { savePageView } from 'lib/db';
import { allowPost } from 'lib/middleware';
export default async (req, res) => { export default async (req, res) => {
await allowPost(req, res);
const values = parseCollectRequest(req); const values = parseCollectRequest(req);
if (values.valid) { if (values.success) {
const { type, session_id, url, referrer } = values; const { type, session_id, url, referrer } = values;
if (type === 'pageview') { if (type === 'pageview') {
await savePageView(session_id, url, referrer); await savePageView(session_id, url, referrer).catch(() => {
values.success = 0;
});
} }
} }
res.setHeader('Access-Control-Allow-Origin', '*'); res.status(200).json({ success: values.success });
res.status(200).json({ status: values.valid });
}; };

View File

@ -1,11 +1,16 @@
import { getWebsite, getSession, createSession } from 'lib/db'; import { getWebsite, getSession, createSession } from 'lib/db';
import { hash, parseSessionRequest } from 'lib/utils'; import { hash, parseSessionRequest } from 'lib/utils';
import { allowPost } from 'lib/middleware';
export default async (req, res) => { export default async (req, res) => {
let result = { time: Date.now() }; await allowPost(req, res);
let result = { success: 0, time: Date.now() };
const { const {
website_id, website_id,
session_id, session_id,
hostname,
browser, browser,
os, os,
screen, screen,
@ -13,24 +18,32 @@ export default async (req, res) => {
country, country,
} = await parseSessionRequest(req); } = await parseSessionRequest(req);
const website = await getWebsite(website_id); if (website_id && session_id) {
const website = await getWebsite(website_id);
if (website) { if (website) {
const session = await getSession(session_id); const session = await getSession(session_id);
if (!session) { if (!session) {
await createSession(website_id, session_id, { browser, os, screen, language, country }); await createSession(website_id, session_id, {
hostname,
browser,
os,
screen,
language,
country,
});
}
result = {
...result,
success: 1,
session_id,
website_id,
hash: hash(`${website_id}${session_id}${result.time}`),
};
} }
result = {
...result,
session_id,
website_id,
hash: hash(`${website_id}${session_id}${result.time}`),
};
} }
res.setHeader('Access-Control-Allow-Origin', '*');
res.status(200).json(result); res.status(200).json(result);
}; };

View File

@ -34,6 +34,7 @@ model session {
browser String? browser String?
country String? country String?
created_at DateTime? @default(now()) created_at DateTime? @default(now())
hostname String?
language String? language String?
os String? os String?
screen String? screen String?

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -11,16 +11,37 @@ const {
document, document,
} = window; } = window;
function post(url, params) { const post = (url, params) =>
return fetch(url, { fetch(url, {
method: 'post', method: 'post',
cache: 'no-cache', cache: 'no-cache',
headers: { headers: {
Accept: 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: params, body: JSON.stringify(params),
}).then(res => res.json()); }).then(res => res.json());
}
const createSession = data =>
post(`${HOST_URL}/api/session`, data).then(({ success, ...session }) => {
if (success) {
store.setItem(SESSION_VAR, JSON.stringify(session));
return success;
}
});
const getSession = () => JSON.parse(store.getItem(SESSION_VAR));
const pageView = (url, referrer) =>
post(`${HOST_URL}/api/collect`, {
type: 'pageview',
payload: { url, referrer, session: getSession() },
}).then(({ success }) => {
if (!success) {
store.removeItem(SESSION_VAR);
}
return success;
});
const script = document.querySelector('script[data-website-id]'); const script = document.querySelector('script[data-website-id]');
@ -31,26 +52,12 @@ if (script) {
const referrer = document.referrer; const referrer = document.referrer;
const screen = `${width}x${height}`; const screen = `${width}x${height}`;
const url = `${pathname}${search}`; const url = `${pathname}${search}`;
const data = { website_id, hostname, url, screen, language };
if (!store.getItem(SESSION_VAR)) { if (!store.getItem(SESSION_VAR)) {
post(`${HOST_URL}/api/session`, { createSession(data).then(success => success && pageView(url, referrer));
website_id, } else {
hostname, pageView(url, referrer).then(success => !success && createSession(data));
url,
screen,
language,
}).then(session => {
store.setItem(SESSION_VAR, JSON.stringify(session));
});
} }
post(`${HOST_URL}/api/collect`, {
type: 'pageview',
payload: { url, referrer, session: JSON.parse(store.getItem(SESSION_VAR)) },
}).then(response => {
if (!response.status) {
store.removeItem(SESSION_VAR);
}
});
} }
} }

View File

@ -1,6 +1,6 @@
create table website ( create table website (
website_id uuid primary key, website_id uuid primary key,
hostname varchar(255) unique not null, hostname varchar(100) unique not null,
created_at timestamp with time zone default current_timestamp created_at timestamp with time zone default current_timestamp
); );
@ -8,6 +8,7 @@ create table session (
session_id uuid primary key, session_id uuid primary key,
website_id uuid references website(website_id) on delete cascade, website_id uuid references website(website_id) on delete cascade,
created_at timestamp with time zone default current_timestamp, created_at timestamp with time zone default current_timestamp,
hostname varchar(100),
browser varchar(20), browser varchar(20),
os varchar(20), os varchar(20),
screen varchar(11), screen varchar(11),

View File

@ -2619,6 +2619,14 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
cosmiconfig@^5.0.0: cosmiconfig@^5.0.0:
version "5.2.1" version "5.2.1"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
@ -5513,7 +5521,7 @@ num2fraction@^1.2.2:
resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@ -8386,6 +8394,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0" spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0" spdx-expression-parse "^3.0.0"
vary@^1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
vendors@^1.0.0: vendors@^1.0.0:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e"