@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/dist/index.d.cts CHANGED
@@ -84,4 +84,6 @@ declare class MongoDBAdapter implements DBAdapter {
84
84
  private toSite;
85
85
  }
86
86
 
87
- export { ClickHouseAdapter, type Collector, MongoDBAdapter, createCollector };
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
- export { ClickHouseAdapter, type Collector, MongoDBAdapter, createCollector };
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).toISOString(),
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.toISOString()
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 = (/* @__PURE__ */ new Date()).toISOString();
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: now,
756
- updatedAt: now
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: now,
767
- updated_at: now,
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 = (/* @__PURE__ */ new Date()).toISOString();
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: now,
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: now
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 now = (/* @__PURE__ */ new Date()).toISOString();
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: now,
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 = (/* @__PURE__ */ new Date()).toISOString();
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: now,
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: now
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