@litemetrics/node 0.1.0 → 0.1.1
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/README.md +108 -0
- package/dist/index.cjs +123 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +121 -26
- package/dist/index.js.map +1 -1
- package/package.json +12 -3
package/dist/index.d.cts
CHANGED
|
@@ -84,4 +84,6 @@ declare class MongoDBAdapter implements DBAdapter {
|
|
|
84
84
|
private toSite;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
declare function isBot(ua: string): boolean;
|
|
88
|
+
|
|
89
|
+
export { ClickHouseAdapter, type Collector, MongoDBAdapter, createCollector, isBot };
|
package/dist/index.d.ts
CHANGED
|
@@ -84,4 +84,6 @@ declare class MongoDBAdapter implements DBAdapter {
|
|
|
84
84
|
private toSite;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
declare function isBot(ua: string): boolean;
|
|
88
|
+
|
|
89
|
+
export { ClickHouseAdapter, type Collector, MongoDBAdapter, createCollector, isBot };
|
package/dist/index.js
CHANGED
|
@@ -192,6 +192,10 @@ CREATE TABLE IF NOT EXISTS ${SITES_TABLE} (
|
|
|
192
192
|
ORDER BY (site_id)
|
|
193
193
|
SETTINGS index_granularity = 8192
|
|
194
194
|
`;
|
|
195
|
+
function toCHDateTime(d) {
|
|
196
|
+
const iso = typeof d === "string" ? d : d.toISOString();
|
|
197
|
+
return iso.replace("T", " ").replace("Z", "");
|
|
198
|
+
}
|
|
195
199
|
var ClickHouseAdapter = class {
|
|
196
200
|
client;
|
|
197
201
|
constructor(url) {
|
|
@@ -215,7 +219,7 @@ var ClickHouseAdapter = class {
|
|
|
215
219
|
const rows = events.map((e) => ({
|
|
216
220
|
site_id: e.siteId,
|
|
217
221
|
type: e.type,
|
|
218
|
-
timestamp: new Date(e.timestamp)
|
|
222
|
+
timestamp: toCHDateTime(new Date(e.timestamp)),
|
|
219
223
|
session_id: e.sessionId,
|
|
220
224
|
visitor_id: e.visitorId,
|
|
221
225
|
url: e.url ?? null,
|
|
@@ -255,8 +259,8 @@ var ClickHouseAdapter = class {
|
|
|
255
259
|
const limit = q.limit ?? 10;
|
|
256
260
|
const params = {
|
|
257
261
|
siteId,
|
|
258
|
-
from: dateRange.from,
|
|
259
|
-
to: dateRange.to,
|
|
262
|
+
from: toCHDateTime(dateRange.from),
|
|
263
|
+
to: toCHDateTime(dateRange.to),
|
|
260
264
|
limit
|
|
261
265
|
};
|
|
262
266
|
let data = [];
|
|
@@ -504,8 +508,8 @@ var ClickHouseAdapter = class {
|
|
|
504
508
|
}
|
|
505
509
|
const rows = await this.queryRows(sql, {
|
|
506
510
|
siteId: params.siteId,
|
|
507
|
-
from: dateRange.from,
|
|
508
|
-
to: dateRange.to
|
|
511
|
+
from: toCHDateTime(dateRange.from),
|
|
512
|
+
to: toCHDateTime(dateRange.to)
|
|
509
513
|
});
|
|
510
514
|
const mappedRows = rows.map((r) => ({
|
|
511
515
|
_id: this.convertClickHouseBucket(r.bucket, granularity),
|
|
@@ -571,7 +575,7 @@ var ClickHouseAdapter = class {
|
|
|
571
575
|
GROUP BY visitor_id`,
|
|
572
576
|
{
|
|
573
577
|
siteId: params.siteId,
|
|
574
|
-
since: startDate
|
|
578
|
+
since: toCHDateTime(startDate)
|
|
575
579
|
}
|
|
576
580
|
);
|
|
577
581
|
const cohortMap = /* @__PURE__ */ new Map();
|
|
@@ -638,8 +642,8 @@ var ClickHouseAdapter = class {
|
|
|
638
642
|
dateTo: params.dateTo
|
|
639
643
|
});
|
|
640
644
|
conditions.push(`timestamp >= {from:String} AND timestamp <= {to:String}`);
|
|
641
|
-
queryParams.from = dateRange.from;
|
|
642
|
-
queryParams.to = dateRange.to;
|
|
645
|
+
queryParams.from = toCHDateTime(dateRange.from);
|
|
646
|
+
queryParams.to = toCHDateTime(dateRange.to);
|
|
643
647
|
}
|
|
644
648
|
const where = conditions.join(" AND ");
|
|
645
649
|
const [events, countRows] = await Promise.all([
|
|
@@ -745,15 +749,17 @@ var ClickHouseAdapter = class {
|
|
|
745
749
|
}
|
|
746
750
|
// ─── Site Management ──────────────────────────────────────
|
|
747
751
|
async createSite(data) {
|
|
748
|
-
const now =
|
|
752
|
+
const now = /* @__PURE__ */ new Date();
|
|
753
|
+
const nowISO = now.toISOString();
|
|
754
|
+
const nowCH = toCHDateTime(now);
|
|
749
755
|
const site = {
|
|
750
756
|
siteId: generateSiteId(),
|
|
751
757
|
secretKey: generateSecretKey(),
|
|
752
758
|
name: data.name,
|
|
753
759
|
domain: data.domain,
|
|
754
760
|
allowedOrigins: data.allowedOrigins,
|
|
755
|
-
createdAt:
|
|
756
|
-
updatedAt:
|
|
761
|
+
createdAt: nowISO,
|
|
762
|
+
updatedAt: nowISO
|
|
757
763
|
};
|
|
758
764
|
await this.client.insert({
|
|
759
765
|
table: SITES_TABLE,
|
|
@@ -763,8 +769,8 @@ var ClickHouseAdapter = class {
|
|
|
763
769
|
name: site.name,
|
|
764
770
|
domain: site.domain ?? null,
|
|
765
771
|
allowed_origins: site.allowedOrigins ? JSON.stringify(site.allowedOrigins) : null,
|
|
766
|
-
created_at:
|
|
767
|
-
updated_at:
|
|
772
|
+
created_at: nowCH,
|
|
773
|
+
updated_at: nowCH,
|
|
768
774
|
version: 1,
|
|
769
775
|
is_deleted: 0
|
|
770
776
|
}],
|
|
@@ -809,7 +815,9 @@ var ClickHouseAdapter = class {
|
|
|
809
815
|
);
|
|
810
816
|
if (currentRows.length === 0) return null;
|
|
811
817
|
const current = currentRows[0];
|
|
812
|
-
const now =
|
|
818
|
+
const now = /* @__PURE__ */ new Date();
|
|
819
|
+
const nowISO = now.toISOString();
|
|
820
|
+
const nowCH = toCHDateTime(now);
|
|
813
821
|
const newVersion = Number(current.version) + 1;
|
|
814
822
|
const newName = data.name !== void 0 ? data.name : String(current.name);
|
|
815
823
|
const newDomain = data.domain !== void 0 ? data.domain || null : current.domain ? String(current.domain) : null;
|
|
@@ -822,8 +830,8 @@ var ClickHouseAdapter = class {
|
|
|
822
830
|
name: newName,
|
|
823
831
|
domain: newDomain,
|
|
824
832
|
allowed_origins: newOrigins,
|
|
825
|
-
created_at: String(current.created_at),
|
|
826
|
-
updated_at:
|
|
833
|
+
created_at: toCHDateTime(String(current.created_at)),
|
|
834
|
+
updated_at: nowCH,
|
|
827
835
|
version: newVersion,
|
|
828
836
|
is_deleted: 0
|
|
829
837
|
}],
|
|
@@ -836,7 +844,7 @@ var ClickHouseAdapter = class {
|
|
|
836
844
|
domain: newDomain ?? void 0,
|
|
837
845
|
allowedOrigins: newOrigins ? JSON.parse(newOrigins) : void 0,
|
|
838
846
|
createdAt: String(current.created_at),
|
|
839
|
-
updatedAt:
|
|
847
|
+
updatedAt: nowISO
|
|
840
848
|
};
|
|
841
849
|
}
|
|
842
850
|
async deleteSite(siteId) {
|
|
@@ -848,7 +856,7 @@ var ClickHouseAdapter = class {
|
|
|
848
856
|
);
|
|
849
857
|
if (currentRows.length === 0) return false;
|
|
850
858
|
const current = currentRows[0];
|
|
851
|
-
const
|
|
859
|
+
const nowCH = toCHDateTime(/* @__PURE__ */ new Date());
|
|
852
860
|
await this.client.insert({
|
|
853
861
|
table: SITES_TABLE,
|
|
854
862
|
values: [{
|
|
@@ -857,8 +865,8 @@ var ClickHouseAdapter = class {
|
|
|
857
865
|
name: String(current.name),
|
|
858
866
|
domain: current.domain ? String(current.domain) : null,
|
|
859
867
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
860
|
-
created_at: String(current.created_at),
|
|
861
|
-
updated_at:
|
|
868
|
+
created_at: toCHDateTime(String(current.created_at)),
|
|
869
|
+
updated_at: nowCH,
|
|
862
870
|
version: Number(current.version) + 1,
|
|
863
871
|
is_deleted: 1
|
|
864
872
|
}],
|
|
@@ -875,7 +883,9 @@ var ClickHouseAdapter = class {
|
|
|
875
883
|
);
|
|
876
884
|
if (currentRows.length === 0) return null;
|
|
877
885
|
const current = currentRows[0];
|
|
878
|
-
const now =
|
|
886
|
+
const now = /* @__PURE__ */ new Date();
|
|
887
|
+
const nowISO = now.toISOString();
|
|
888
|
+
const nowCH = toCHDateTime(now);
|
|
879
889
|
const newSecret = generateSecretKey();
|
|
880
890
|
await this.client.insert({
|
|
881
891
|
table: SITES_TABLE,
|
|
@@ -885,8 +895,8 @@ var ClickHouseAdapter = class {
|
|
|
885
895
|
name: String(current.name),
|
|
886
896
|
domain: current.domain ? String(current.domain) : null,
|
|
887
897
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
888
|
-
created_at: String(current.created_at),
|
|
889
|
-
updated_at:
|
|
898
|
+
created_at: toCHDateTime(String(current.created_at)),
|
|
899
|
+
updated_at: nowCH,
|
|
890
900
|
version: Number(current.version) + 1,
|
|
891
901
|
is_deleted: 0
|
|
892
902
|
}],
|
|
@@ -899,7 +909,7 @@ var ClickHouseAdapter = class {
|
|
|
899
909
|
domain: current.domain ? String(current.domain) : void 0,
|
|
900
910
|
allowedOrigins: current.allowed_origins ? JSON.parse(String(current.allowed_origins)) : void 0,
|
|
901
911
|
createdAt: String(current.created_at),
|
|
902
|
-
updatedAt:
|
|
912
|
+
updatedAt: nowISO
|
|
903
913
|
};
|
|
904
914
|
}
|
|
905
915
|
// ─── Helpers ─────────────────────────────────────────────
|
|
@@ -1662,6 +1672,63 @@ function resolveDeviceType(type) {
|
|
|
1662
1672
|
return "desktop";
|
|
1663
1673
|
}
|
|
1664
1674
|
|
|
1675
|
+
// src/botfilter.ts
|
|
1676
|
+
var BOT_PATTERNS = [
|
|
1677
|
+
// Headless browsers
|
|
1678
|
+
/HeadlessChrome/i,
|
|
1679
|
+
/PhantomJS/i,
|
|
1680
|
+
/Selenium/i,
|
|
1681
|
+
/Puppeteer/i,
|
|
1682
|
+
/Playwright/i,
|
|
1683
|
+
// Common bots
|
|
1684
|
+
/bot\b/i,
|
|
1685
|
+
/spider/i,
|
|
1686
|
+
/crawl/i,
|
|
1687
|
+
/slurp/i,
|
|
1688
|
+
/mediapartners/i,
|
|
1689
|
+
/facebookexternalhit/i,
|
|
1690
|
+
/Twitterbot/i,
|
|
1691
|
+
/LinkedInBot/i,
|
|
1692
|
+
/WhatsApp/i,
|
|
1693
|
+
/Discordbot/i,
|
|
1694
|
+
/TelegramBot/i,
|
|
1695
|
+
/Applebot/i,
|
|
1696
|
+
/Baiduspider/i,
|
|
1697
|
+
/YandexBot/i,
|
|
1698
|
+
/DuckDuckBot/i,
|
|
1699
|
+
/Sogou/i,
|
|
1700
|
+
/Exabot/i,
|
|
1701
|
+
/ia_archiver/i,
|
|
1702
|
+
// HTTP libraries & API tools
|
|
1703
|
+
/PostmanRuntime/i,
|
|
1704
|
+
/axios/i,
|
|
1705
|
+
/node-fetch/i,
|
|
1706
|
+
/python-requests/i,
|
|
1707
|
+
/Go-http-client/i,
|
|
1708
|
+
/Java\//i,
|
|
1709
|
+
/libwww-perl/i,
|
|
1710
|
+
/wget/i,
|
|
1711
|
+
/curl/i,
|
|
1712
|
+
/httpie/i,
|
|
1713
|
+
// Monitoring / uptime
|
|
1714
|
+
/UptimeRobot/i,
|
|
1715
|
+
/Pingdom/i,
|
|
1716
|
+
/StatusCake/i,
|
|
1717
|
+
/Site24x7/i,
|
|
1718
|
+
/NewRelic/i,
|
|
1719
|
+
/Datadog/i,
|
|
1720
|
+
// Preview/embed
|
|
1721
|
+
/Slackbot/i,
|
|
1722
|
+
/Embedly/i,
|
|
1723
|
+
/Quora Link Preview/i,
|
|
1724
|
+
/redditbot/i,
|
|
1725
|
+
/Pinterestbot/i
|
|
1726
|
+
];
|
|
1727
|
+
function isBot(ua) {
|
|
1728
|
+
if (!ua || ua.length === 0) return true;
|
|
1729
|
+
return BOT_PATTERNS.some((re) => re.test(ua));
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1665
1732
|
// src/collector.ts
|
|
1666
1733
|
async function createCollector(config) {
|
|
1667
1734
|
const db = createAdapter(config.db);
|
|
@@ -1734,9 +1801,36 @@ async function createCollector(config) {
|
|
|
1734
1801
|
sendJson(res, 400, { ok: false, error: "Too many events (max 100)" });
|
|
1735
1802
|
return;
|
|
1736
1803
|
}
|
|
1737
|
-
const ip = extractIp(req);
|
|
1738
1804
|
const userAgent = req.headers?.["user-agent"] || "";
|
|
1805
|
+
if (isBot(userAgent)) {
|
|
1806
|
+
sendJson(res, 200, { ok: true });
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
const ip = extractIp(req);
|
|
1739
1810
|
const enriched = enrichEvents(payload.events, ip, userAgent);
|
|
1811
|
+
const siteId = enriched[0]?.siteId;
|
|
1812
|
+
if (siteId) {
|
|
1813
|
+
const site = await db.getSite(siteId);
|
|
1814
|
+
if (site?.allowedOrigins && site.allowedOrigins.length > 0) {
|
|
1815
|
+
const allowed = new Set(site.allowedOrigins.map((h) => h.toLowerCase()));
|
|
1816
|
+
const filtered = enriched.filter((event) => {
|
|
1817
|
+
if (!event.url) return true;
|
|
1818
|
+
try {
|
|
1819
|
+
const hostname = new URL(event.url).hostname.toLowerCase();
|
|
1820
|
+
return allowed.has(hostname);
|
|
1821
|
+
} catch {
|
|
1822
|
+
return true;
|
|
1823
|
+
}
|
|
1824
|
+
});
|
|
1825
|
+
if (filtered.length === 0) {
|
|
1826
|
+
sendJson(res, 200, { ok: true });
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
await db.insertEvents(filtered);
|
|
1830
|
+
sendJson(res, 200, { ok: true });
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1740
1834
|
await db.insertEvents(enriched);
|
|
1741
1835
|
sendJson(res, 200, { ok: true });
|
|
1742
1836
|
} catch (err) {
|
|
@@ -2076,6 +2170,7 @@ function sendJson(res, status, body) {
|
|
|
2076
2170
|
export {
|
|
2077
2171
|
ClickHouseAdapter,
|
|
2078
2172
|
MongoDBAdapter,
|
|
2079
|
-
createCollector
|
|
2173
|
+
createCollector,
|
|
2174
|
+
isBot
|
|
2080
2175
|
};
|
|
2081
2176
|
//# sourceMappingURL=index.js.map
|