weifuwu 0.17.5 → 0.17.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics.d.ts +7 -4
- package/dist/index.js +79 -57
- package/package.json +1 -1
package/dist/analytics.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Handler, Middleware } from './types.ts';
|
|
2
2
|
export interface AnalyticsOptions {
|
|
3
|
+
excluded?: string[];
|
|
3
4
|
pg?: {
|
|
4
|
-
sql: any
|
|
5
|
+
sql: (strings: TemplateStringsArray, ...values: any[]) => Promise<any[]>;
|
|
5
6
|
};
|
|
6
|
-
excluded?: string[];
|
|
7
7
|
}
|
|
8
|
-
export declare function analytics(options?: AnalyticsOptions):
|
|
8
|
+
export declare function analytics(options?: AnalyticsOptions): {
|
|
9
|
+
middleware: Middleware;
|
|
10
|
+
handler: Handler;
|
|
11
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -6819,11 +6819,10 @@ var MemStore = class {
|
|
|
6819
6819
|
}
|
|
6820
6820
|
let page = this.pages.get(path2);
|
|
6821
6821
|
if (!page) {
|
|
6822
|
-
page = { count: 0
|
|
6822
|
+
page = { count: 0 };
|
|
6823
6823
|
this.pages.set(path2, page);
|
|
6824
6824
|
}
|
|
6825
6825
|
page.count++;
|
|
6826
|
-
page.dates.add(date);
|
|
6827
6826
|
if (refDomain) {
|
|
6828
6827
|
let refs = this.refs.get(date);
|
|
6829
6828
|
if (!refs) {
|
|
@@ -6852,8 +6851,6 @@ var MemStore = class {
|
|
|
6852
6851
|
for (const p of day.uv) allUv.add(p);
|
|
6853
6852
|
}
|
|
6854
6853
|
for (const [path2, page] of this.pages) {
|
|
6855
|
-
let count = 0;
|
|
6856
|
-
for (const d of page.dates) if (d >= sinceStr) count += page.count / page.dates.size;
|
|
6857
6854
|
pageMap.set(path2, page.count);
|
|
6858
6855
|
}
|
|
6859
6856
|
const topPages = [...pageMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20).map(([path2, count]) => ({ path: path2, pv: count }));
|
|
@@ -6874,44 +6871,41 @@ var MemStore = class {
|
|
|
6874
6871
|
};
|
|
6875
6872
|
}
|
|
6876
6873
|
};
|
|
6877
|
-
function
|
|
6878
|
-
const
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6874
|
+
async function queryPg(sql2, days) {
|
|
6875
|
+
const since = /* @__PURE__ */ new Date();
|
|
6876
|
+
since.setDate(since.getDate() - days);
|
|
6877
|
+
const daily = await sql2`
|
|
6878
|
+
SELECT date, SUM(count) as pv, COUNT(DISTINCT path) as uv
|
|
6879
|
+
FROM __analytics WHERE date >= ${since.toISOString().slice(0, 10)}
|
|
6880
|
+
GROUP BY date ORDER BY date
|
|
6881
|
+
`;
|
|
6882
|
+
const pageRows = await sql2`
|
|
6883
|
+
SELECT path, SUM(count) as pv
|
|
6884
|
+
FROM __analytics WHERE date >= ${since.toISOString().slice(0, 10)}
|
|
6885
|
+
GROUP BY path ORDER BY pv DESC LIMIT 20
|
|
6886
|
+
`;
|
|
6887
|
+
const totalRes = await sql2`
|
|
6888
|
+
SELECT COALESCE(SUM(count), 0) as total_pv,
|
|
6889
|
+
COALESCE(SUM(mobile), 0) as total_mobile,
|
|
6890
|
+
COALESCE(SUM(desktop), 0) as total_desktop
|
|
6891
|
+
FROM __analytics WHERE date >= ${since.toISOString().slice(0, 10)}
|
|
6892
|
+
`;
|
|
6893
|
+
const total = totalRes[0];
|
|
6894
|
+
const totalMobileDesktop = total.total_mobile + total.total_desktop || 1;
|
|
6895
|
+
return {
|
|
6896
|
+
total_pv: total.total_pv,
|
|
6897
|
+
total_uv: pageRows.length,
|
|
6898
|
+
daily: daily.map((d) => ({ date: d.date, pv: Number(d.pv), uv: Number(d.uv) })),
|
|
6899
|
+
top_pages: pageRows.map((p) => ({ path: p.path, pv: Number(p.pv) })),
|
|
6900
|
+
referrers: [],
|
|
6901
|
+
devices: {
|
|
6902
|
+
mobile: Math.round(total.total_mobile / totalMobileDesktop * 1e3) / 10,
|
|
6903
|
+
desktop: Math.round(total.total_desktop / totalMobileDesktop * 1e3) / 10
|
|
6893
6904
|
}
|
|
6894
|
-
return next(req, ctx);
|
|
6895
6905
|
};
|
|
6896
|
-
const router = new Router();
|
|
6897
|
-
router.use(middleware);
|
|
6898
|
-
router.get("/__analytics/data", async (req) => {
|
|
6899
|
-
const url = new URL(req.url);
|
|
6900
|
-
const days = Math.min(Math.max(Number(url.searchParams.get("days")) || 7, 1), 365);
|
|
6901
|
-
return Response.json(store.query(days));
|
|
6902
|
-
});
|
|
6903
|
-
router.get("/analytics", async (req) => {
|
|
6904
|
-
const url = new URL(req.url);
|
|
6905
|
-
const days = Math.min(Math.max(Number(url.searchParams.get("days")) || 7, 1), 365);
|
|
6906
|
-
const data = store.query(days);
|
|
6907
|
-
return new Response(renderDashboard(days, data), {
|
|
6908
|
-
headers: { "content-type": "text/html; charset=utf-8" }
|
|
6909
|
-
});
|
|
6910
|
-
});
|
|
6911
|
-
return router;
|
|
6912
6906
|
}
|
|
6913
6907
|
function renderDashboard(days, data) {
|
|
6914
|
-
const { total_pv, total_uv, daily, top_pages, referrers
|
|
6908
|
+
const { total_pv, total_uv, daily, top_pages, referrers } = data;
|
|
6915
6909
|
const maxPv = Math.max(...daily.map((d) => d.pv), 1);
|
|
6916
6910
|
const bars = daily.map(
|
|
6917
6911
|
(d) => `<div class="bar-wrap"><div class="bar" style="height:${d.pv / maxPv * 100}%"></div><span class="bar-label">${d.date.slice(5)}</span></div>`
|
|
@@ -6924,10 +6918,7 @@ function renderDashboard(days, data) {
|
|
|
6924
6918
|
).join("");
|
|
6925
6919
|
return `<!DOCTYPE html>
|
|
6926
6920
|
<html lang="en">
|
|
6927
|
-
<head>
|
|
6928
|
-
<meta charset="utf-8"/>
|
|
6929
|
-
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
6930
|
-
<title>Analytics - weifuwu</title>
|
|
6921
|
+
<head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Analytics - weifuwu</title>
|
|
6931
6922
|
<style>
|
|
6932
6923
|
*,:before,:after{box-sizing:border-box;margin:0;padding:0}
|
|
6933
6924
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f8f9fa;color:#333;padding:24px;max-width:960px;margin:0 auto}
|
|
@@ -6940,7 +6931,7 @@ h1{font-size:24px;font-weight:700;margin-bottom:24px}
|
|
|
6940
6931
|
.section h2{font-size:14px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:.05em;margin-bottom:16px}
|
|
6941
6932
|
.chart{display:flex;align-items:flex-end;gap:4px;height:160px;padding-top:8px}
|
|
6942
6933
|
.bar-wrap{flex:1;display:flex;flex-direction:column;align-items:center;height:100%;justify-content:flex-end}
|
|
6943
|
-
.bar{width:100%;background:#2563eb;border-radius:4px 4px 0 0;min-height:2px
|
|
6934
|
+
.bar{width:100%;background:#2563eb;border-radius:4px 4px 0 0;min-height:2px}
|
|
6944
6935
|
.bar-label{font-size:10px;color:#888;margin-top:6px;white-space:nowrap}
|
|
6945
6936
|
table{width:100%;border-collapse:collapse;font-size:13px}
|
|
6946
6937
|
th{text-align:left;padding:6px 8px;color:#888;font-weight:500;border-bottom:1px solid #eee}
|
|
@@ -6948,27 +6939,58 @@ td{padding:6px 8px;border-bottom:1px solid #f0f0f0}
|
|
|
6948
6939
|
.num{text-align:right;font-variant-numeric:tabular-nums}
|
|
6949
6940
|
.path{font-family:ui-monospace,SFMono-Regular,monospace;font-size:12px}
|
|
6950
6941
|
tr:hover td{background:#f8faff}
|
|
6951
|
-
</style>
|
|
6952
|
-
</head>
|
|
6942
|
+
</style></head>
|
|
6953
6943
|
<body>
|
|
6954
6944
|
<h1>Analytics</h1>
|
|
6955
6945
|
<div class="cards">
|
|
6956
6946
|
<div class="card"><div class="val">${total_pv}</div><div class="lbl">Page Views (${days}d)</div></div>
|
|
6957
6947
|
<div class="card"><div class="val">${total_uv}</div><div class="lbl">Unique Pages</div></div>
|
|
6958
|
-
<div class="card"><div class="val">${devices.mobile}%</div><div class="lbl">Mobile</div></div>
|
|
6959
|
-
<div class="card"><div class="val">${devices.desktop}%</div><div class="lbl">Desktop</div></div>
|
|
6960
|
-
</div>
|
|
6961
|
-
<div class="section">
|
|
6962
|
-
<h2>Daily Page Views</h2>
|
|
6963
|
-
<div class="chart">${bars}</div>
|
|
6964
|
-
</div>
|
|
6965
|
-
<div class="section">
|
|
6966
|
-
<h2>Top Pages</h2>
|
|
6967
|
-
<table><thead><tr><th style="width:32px">#</th><th>Path</th><th style="width:64px">Views</th></tr></thead><tbody>${rows}</tbody></table>
|
|
6948
|
+
<div class="card"><div class="val">${data.devices.mobile}%</div><div class="lbl">Mobile</div></div>
|
|
6949
|
+
<div class="card"><div class="val">${data.devices.desktop}%</div><div class="lbl">Desktop</div></div>
|
|
6968
6950
|
</div>
|
|
6951
|
+
<div class="section"><h2>Daily Page Views</h2><div class="chart">${bars}</div></div>
|
|
6952
|
+
<div class="section"><h2>Top Pages</h2>
|
|
6953
|
+
<table><thead><tr><th style="width:32px">#</th><th>Path</th><th style="width:64px">Views</th></tr></thead><tbody>${rows}</tbody></table></div>
|
|
6969
6954
|
${referrers.length ? `<div class="section"><h2>Referrers</h2><table><thead><tr><th>Domain</th><th style="width:64px">Views</th></tr></thead><tbody>${refRows}</tbody></table></div>` : ""}
|
|
6970
|
-
</body
|
|
6971
|
-
|
|
6955
|
+
</body></html>`;
|
|
6956
|
+
}
|
|
6957
|
+
function analytics(options) {
|
|
6958
|
+
const excluded = options?.excluded ?? DEFAULT_EXCLUDED;
|
|
6959
|
+
const pg = options?.pg;
|
|
6960
|
+
const store = pg ? null : new MemStore();
|
|
6961
|
+
const middleware = async (req, ctx, next) => {
|
|
6962
|
+
const url = new URL(req.url);
|
|
6963
|
+
const path2 = url.pathname;
|
|
6964
|
+
if (excluded.some((e) => path2.startsWith(e))) return next(req, ctx);
|
|
6965
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6966
|
+
const ref = req.headers.get("referer") || "";
|
|
6967
|
+
const ua = req.headers.get("user-agent") || "";
|
|
6968
|
+
const mobile = /mobile|android|iphone|ipad/i.test(ua);
|
|
6969
|
+
if (pg) {
|
|
6970
|
+
await pg.sql`
|
|
6971
|
+
INSERT INTO __analytics (date, path, count, mobile, desktop)
|
|
6972
|
+
VALUES (${date}, ${path2}, 1, ${mobile ? 1 : 0}, ${mobile ? 0 : 1})
|
|
6973
|
+
ON CONFLICT (date, path) DO UPDATE SET
|
|
6974
|
+
count = __analytics.count + 1,
|
|
6975
|
+
mobile = __analytics.mobile + ${mobile ? 1 : 0},
|
|
6976
|
+
desktop = __analytics.desktop + ${mobile ? 0 : 1}
|
|
6977
|
+
`;
|
|
6978
|
+
} else {
|
|
6979
|
+
const refDomain = ref ? new URL(ref).hostname.replace(/^www\./, "") : "";
|
|
6980
|
+
store.record(path2, date, refDomain, mobile);
|
|
6981
|
+
}
|
|
6982
|
+
return next(req, ctx);
|
|
6983
|
+
};
|
|
6984
|
+
const handler = async (req) => {
|
|
6985
|
+
const url = new URL(req.url);
|
|
6986
|
+
const days = Math.min(Math.max(Number(url.searchParams.get("days")) || 7, 1), 365);
|
|
6987
|
+
const data = pg ? await queryPg(pg.sql, days) : store.query(days);
|
|
6988
|
+
if (url.pathname === "/__analytics/data") return Response.json(data);
|
|
6989
|
+
return new Response(renderDashboard(days, data), {
|
|
6990
|
+
headers: { "content-type": "text/html; charset=utf-8" }
|
|
6991
|
+
});
|
|
6992
|
+
};
|
|
6993
|
+
return { middleware, handler };
|
|
6972
6994
|
}
|
|
6973
6995
|
|
|
6974
6996
|
// preferences.ts
|