@litemetrics/node 0.1.2 → 0.1.3
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 +16 -0
- package/dist/index.cjs +557 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -6
- package/dist/index.d.ts +17 -6
- package/dist/index.js +557 -65
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -140,6 +140,18 @@ function generateSecretKey() {
|
|
|
140
140
|
// src/adapters/clickhouse.ts
|
|
141
141
|
var EVENTS_TABLE = "litemetrics_events";
|
|
142
142
|
var SITES_TABLE = "litemetrics_sites";
|
|
143
|
+
var IDENTITY_MAP_TABLE = "litemetrics_identity_map";
|
|
144
|
+
var CREATE_IDENTITY_MAP_TABLE = `
|
|
145
|
+
CREATE TABLE IF NOT EXISTS ${IDENTITY_MAP_TABLE} (
|
|
146
|
+
site_id LowCardinality(String),
|
|
147
|
+
visitor_id String,
|
|
148
|
+
user_id String,
|
|
149
|
+
identified_at DateTime64(3),
|
|
150
|
+
created_at DateTime64(3) DEFAULT now64(3)
|
|
151
|
+
) ENGINE = ReplacingMergeTree(created_at)
|
|
152
|
+
ORDER BY (site_id, visitor_id)
|
|
153
|
+
SETTINGS index_granularity = 8192
|
|
154
|
+
`;
|
|
143
155
|
var CREATE_EVENTS_TABLE = `
|
|
144
156
|
CREATE TABLE IF NOT EXISTS ${EVENTS_TABLE} (
|
|
145
157
|
event_id UUID DEFAULT generateUUIDv4(),
|
|
@@ -250,6 +262,7 @@ var ClickHouseAdapter = class {
|
|
|
250
262
|
async init() {
|
|
251
263
|
await this.client.command({ query: CREATE_EVENTS_TABLE });
|
|
252
264
|
await this.client.command({ query: CREATE_SITES_TABLE });
|
|
265
|
+
await this.client.command({ query: CREATE_IDENTITY_MAP_TABLE });
|
|
253
266
|
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS event_source LowCardinality(Nullable(String))` });
|
|
254
267
|
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS event_subtype LowCardinality(Nullable(String))` });
|
|
255
268
|
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS page_path Nullable(String)` });
|
|
@@ -507,8 +520,8 @@ var ClickHouseAdapter = class {
|
|
|
507
520
|
}
|
|
508
521
|
case "top_exit_pages": {
|
|
509
522
|
const rows = await this.queryRows(
|
|
510
|
-
`SELECT
|
|
511
|
-
SELECT session_id, argMax(url, timestamp) AS
|
|
523
|
+
`SELECT exit_url AS key, count() AS value FROM (
|
|
524
|
+
SELECT session_id, argMax(url, timestamp) AS exit_url
|
|
512
525
|
FROM ${EVENTS_TABLE}
|
|
513
526
|
WHERE site_id = {siteId:String}
|
|
514
527
|
AND timestamp >= {from:String}
|
|
@@ -517,7 +530,7 @@ var ClickHouseAdapter = class {
|
|
|
517
530
|
AND url IS NOT NULL${filterSql}
|
|
518
531
|
GROUP BY session_id
|
|
519
532
|
)
|
|
520
|
-
GROUP BY
|
|
533
|
+
GROUP BY exit_url
|
|
521
534
|
ORDER BY value DESC
|
|
522
535
|
LIMIT {limit:UInt32}`,
|
|
523
536
|
{ ...params, ...filter.params }
|
|
@@ -528,9 +541,9 @@ var ClickHouseAdapter = class {
|
|
|
528
541
|
}
|
|
529
542
|
case "top_transitions": {
|
|
530
543
|
const rows = await this.queryRows(
|
|
531
|
-
`SELECT concat(prev_url, ' \u2192 ',
|
|
532
|
-
SELECT session_id, url,
|
|
533
|
-
|
|
544
|
+
`SELECT concat(prev_url, ' \u2192 ', curr_url) AS key, count() AS value FROM (
|
|
545
|
+
SELECT session_id, url AS curr_url,
|
|
546
|
+
lagInFrame(url, 1) OVER (PARTITION BY session_id ORDER BY timestamp ASC) AS prev_url
|
|
534
547
|
FROM ${EVENTS_TABLE}
|
|
535
548
|
WHERE site_id = {siteId:String}
|
|
536
549
|
AND timestamp >= {from:String}
|
|
@@ -538,7 +551,7 @@ var ClickHouseAdapter = class {
|
|
|
538
551
|
AND type = 'pageview'
|
|
539
552
|
AND url IS NOT NULL${filterSql}
|
|
540
553
|
)
|
|
541
|
-
WHERE prev_url IS NOT NULL
|
|
554
|
+
WHERE prev_url IS NOT NULL AND prev_url != ''
|
|
542
555
|
GROUP BY key
|
|
543
556
|
ORDER BY value DESC
|
|
544
557
|
LIMIT {limit:UInt32}`,
|
|
@@ -653,6 +666,57 @@ var ClickHouseAdapter = class {
|
|
|
653
666
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
654
667
|
break;
|
|
655
668
|
}
|
|
669
|
+
case "top_utm_sources": {
|
|
670
|
+
const rows = await this.queryRows(
|
|
671
|
+
`SELECT utm_source AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
672
|
+
WHERE site_id = {siteId:String}
|
|
673
|
+
AND timestamp >= {from:String}
|
|
674
|
+
AND timestamp <= {to:String}
|
|
675
|
+
AND utm_source IS NOT NULL AND utm_source != ''
|
|
676
|
+
${filterSql}
|
|
677
|
+
GROUP BY utm_source
|
|
678
|
+
ORDER BY value DESC
|
|
679
|
+
LIMIT {limit:UInt32}`,
|
|
680
|
+
{ ...params, ...filter.params }
|
|
681
|
+
);
|
|
682
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
683
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case "top_utm_mediums": {
|
|
687
|
+
const rows = await this.queryRows(
|
|
688
|
+
`SELECT utm_medium AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
689
|
+
WHERE site_id = {siteId:String}
|
|
690
|
+
AND timestamp >= {from:String}
|
|
691
|
+
AND timestamp <= {to:String}
|
|
692
|
+
AND utm_medium IS NOT NULL AND utm_medium != ''
|
|
693
|
+
${filterSql}
|
|
694
|
+
GROUP BY utm_medium
|
|
695
|
+
ORDER BY value DESC
|
|
696
|
+
LIMIT {limit:UInt32}`,
|
|
697
|
+
{ ...params, ...filter.params }
|
|
698
|
+
);
|
|
699
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
700
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
case "top_utm_campaigns": {
|
|
704
|
+
const rows = await this.queryRows(
|
|
705
|
+
`SELECT utm_campaign AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
706
|
+
WHERE site_id = {siteId:String}
|
|
707
|
+
AND timestamp >= {from:String}
|
|
708
|
+
AND timestamp <= {to:String}
|
|
709
|
+
AND utm_campaign IS NOT NULL AND utm_campaign != ''
|
|
710
|
+
${filterSql}
|
|
711
|
+
GROUP BY utm_campaign
|
|
712
|
+
ORDER BY value DESC
|
|
713
|
+
LIMIT {limit:UInt32}`,
|
|
714
|
+
{ ...params, ...filter.params }
|
|
715
|
+
);
|
|
716
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
717
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
656
720
|
}
|
|
657
721
|
const result = { metric: q.metric, period, data, total };
|
|
658
722
|
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
@@ -906,45 +970,59 @@ var ClickHouseAdapter = class {
|
|
|
906
970
|
const where = conditions.join(" AND ");
|
|
907
971
|
const [userRows, countRows] = await Promise.all([
|
|
908
972
|
this.queryRows(
|
|
909
|
-
`
|
|
910
|
-
visitor_id,
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
973
|
+
`WITH identity AS (
|
|
974
|
+
SELECT visitor_id, user_id
|
|
975
|
+
FROM ${IDENTITY_MAP_TABLE} FINAL
|
|
976
|
+
WHERE site_id = {siteId:String}
|
|
977
|
+
)
|
|
978
|
+
SELECT
|
|
979
|
+
if(i.user_id IS NOT NULL AND i.user_id != '', i.user_id, e.visitor_id) AS group_key,
|
|
980
|
+
anyLast(e.visitor_id) AS visitor_id,
|
|
981
|
+
anyLast(i.user_id) AS userId,
|
|
982
|
+
anyLast(e.traits) AS traits,
|
|
983
|
+
min(e.timestamp) AS firstSeen,
|
|
984
|
+
max(e.timestamp) AS lastSeen,
|
|
915
985
|
count() AS totalEvents,
|
|
916
|
-
countIf(type = 'pageview') AS totalPageviews,
|
|
917
|
-
uniq(session_id) AS totalSessions,
|
|
918
|
-
anyLast(url) AS lastUrl,
|
|
919
|
-
anyLast(referrer) AS referrer,
|
|
920
|
-
anyLast(device_type) AS device_type,
|
|
921
|
-
anyLast(browser) AS browser,
|
|
922
|
-
anyLast(os) AS os,
|
|
923
|
-
anyLast(country) AS country,
|
|
924
|
-
anyLast(city) AS city,
|
|
925
|
-
anyLast(region) AS region,
|
|
926
|
-
anyLast(language) AS language,
|
|
927
|
-
anyLast(timezone) AS timezone,
|
|
928
|
-
anyLast(screen_width) AS screen_width,
|
|
929
|
-
anyLast(screen_height) AS screen_height,
|
|
930
|
-
anyLast(utm_source) AS utm_source,
|
|
931
|
-
anyLast(utm_medium) AS utm_medium,
|
|
932
|
-
anyLast(utm_campaign) AS utm_campaign,
|
|
933
|
-
anyLast(utm_term) AS utm_term,
|
|
934
|
-
anyLast(utm_content) AS utm_content
|
|
935
|
-
FROM ${EVENTS_TABLE}
|
|
936
|
-
|
|
937
|
-
|
|
986
|
+
countIf(e.type = 'pageview') AS totalPageviews,
|
|
987
|
+
uniq(e.session_id) AS totalSessions,
|
|
988
|
+
anyLast(e.url) AS lastUrl,
|
|
989
|
+
anyLast(e.referrer) AS referrer,
|
|
990
|
+
anyLast(e.device_type) AS device_type,
|
|
991
|
+
anyLast(e.browser) AS browser,
|
|
992
|
+
anyLast(e.os) AS os,
|
|
993
|
+
anyLast(e.country) AS country,
|
|
994
|
+
anyLast(e.city) AS city,
|
|
995
|
+
anyLast(e.region) AS region,
|
|
996
|
+
anyLast(e.language) AS language,
|
|
997
|
+
anyLast(e.timezone) AS timezone,
|
|
998
|
+
anyLast(e.screen_width) AS screen_width,
|
|
999
|
+
anyLast(e.screen_height) AS screen_height,
|
|
1000
|
+
anyLast(e.utm_source) AS utm_source,
|
|
1001
|
+
anyLast(e.utm_medium) AS utm_medium,
|
|
1002
|
+
anyLast(e.utm_campaign) AS utm_campaign,
|
|
1003
|
+
anyLast(e.utm_term) AS utm_term,
|
|
1004
|
+
anyLast(e.utm_content) AS utm_content
|
|
1005
|
+
FROM ${EVENTS_TABLE} e
|
|
1006
|
+
LEFT JOIN identity i ON e.visitor_id = i.visitor_id
|
|
1007
|
+
WHERE e.site_id = {siteId:String}${where.includes("ILIKE") ? ` AND (e.visitor_id ILIKE {search:String} OR i.user_id ILIKE {search:String})` : ""}
|
|
1008
|
+
GROUP BY group_key
|
|
938
1009
|
ORDER BY lastSeen DESC
|
|
939
1010
|
LIMIT {limit:UInt32}
|
|
940
1011
|
OFFSET {offset:UInt32}`,
|
|
941
1012
|
queryParams
|
|
942
1013
|
),
|
|
943
1014
|
this.queryRows(
|
|
944
|
-
`
|
|
945
|
-
SELECT visitor_id
|
|
946
|
-
|
|
947
|
-
|
|
1015
|
+
`WITH identity AS (
|
|
1016
|
+
SELECT visitor_id, user_id
|
|
1017
|
+
FROM ${IDENTITY_MAP_TABLE} FINAL
|
|
1018
|
+
WHERE site_id = {siteId:String}
|
|
1019
|
+
)
|
|
1020
|
+
SELECT count() AS total FROM (
|
|
1021
|
+
SELECT if(i.user_id IS NOT NULL AND i.user_id != '', i.user_id, e.visitor_id) AS group_key
|
|
1022
|
+
FROM ${EVENTS_TABLE} e
|
|
1023
|
+
LEFT JOIN identity i ON e.visitor_id = i.visitor_id
|
|
1024
|
+
WHERE e.site_id = {siteId:String}${where.includes("ILIKE") ? ` AND (e.visitor_id ILIKE {search:String} OR i.user_id ILIKE {search:String})` : ""}
|
|
1025
|
+
GROUP BY group_key
|
|
948
1026
|
)`,
|
|
949
1027
|
queryParams
|
|
950
1028
|
)
|
|
@@ -980,13 +1058,178 @@ var ClickHouseAdapter = class {
|
|
|
980
1058
|
offset
|
|
981
1059
|
};
|
|
982
1060
|
}
|
|
983
|
-
async getUserDetail(siteId,
|
|
984
|
-
const
|
|
985
|
-
|
|
1061
|
+
async getUserDetail(siteId, identifier) {
|
|
1062
|
+
const visitorIds = await this.getVisitorIdsForUser(siteId, identifier);
|
|
1063
|
+
if (visitorIds.length > 0) {
|
|
1064
|
+
return this.getMergedUserDetail(siteId, identifier, visitorIds);
|
|
1065
|
+
}
|
|
1066
|
+
const userId = await this.getUserIdForVisitor(siteId, identifier);
|
|
1067
|
+
if (userId) {
|
|
1068
|
+
const allVisitorIds = await this.getVisitorIdsForUser(siteId, userId);
|
|
1069
|
+
return this.getMergedUserDetail(siteId, userId, allVisitorIds.length > 0 ? allVisitorIds : [identifier]);
|
|
1070
|
+
}
|
|
1071
|
+
const result = await this.listUsers({ siteId, search: identifier, limit: 1 });
|
|
1072
|
+
const user = result.users.find((u) => u.visitorId === identifier);
|
|
986
1073
|
return user ?? null;
|
|
987
1074
|
}
|
|
988
|
-
async getUserEvents(siteId,
|
|
989
|
-
|
|
1075
|
+
async getUserEvents(siteId, identifier, params) {
|
|
1076
|
+
const visitorIds = await this.getVisitorIdsForUser(siteId, identifier);
|
|
1077
|
+
if (visitorIds.length > 0) {
|
|
1078
|
+
return this.listEventsForVisitorIds(siteId, visitorIds, params);
|
|
1079
|
+
}
|
|
1080
|
+
const userId = await this.getUserIdForVisitor(siteId, identifier);
|
|
1081
|
+
if (userId) {
|
|
1082
|
+
const allVisitorIds = await this.getVisitorIdsForUser(siteId, userId);
|
|
1083
|
+
if (allVisitorIds.length > 0) {
|
|
1084
|
+
return this.listEventsForVisitorIds(siteId, allVisitorIds, params);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return this.listEvents({ ...params, siteId, visitorId: identifier });
|
|
1088
|
+
}
|
|
1089
|
+
// ─── Identity Mapping ──────────────────────────────────────
|
|
1090
|
+
async upsertIdentity(siteId, visitorId, userId) {
|
|
1091
|
+
await this.client.insert({
|
|
1092
|
+
table: IDENTITY_MAP_TABLE,
|
|
1093
|
+
values: [{
|
|
1094
|
+
site_id: siteId,
|
|
1095
|
+
visitor_id: visitorId,
|
|
1096
|
+
user_id: userId,
|
|
1097
|
+
identified_at: toCHDateTime(/* @__PURE__ */ new Date())
|
|
1098
|
+
}],
|
|
1099
|
+
format: "JSONEachRow"
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
async getVisitorIdsForUser(siteId, userId) {
|
|
1103
|
+
const rows = await this.queryRows(
|
|
1104
|
+
`SELECT visitor_id FROM ${IDENTITY_MAP_TABLE} FINAL
|
|
1105
|
+
WHERE site_id = {siteId:String} AND user_id = {userId:String}`,
|
|
1106
|
+
{ siteId, userId }
|
|
1107
|
+
);
|
|
1108
|
+
return rows.map((r) => r.visitor_id);
|
|
1109
|
+
}
|
|
1110
|
+
async getUserIdForVisitor(siteId, visitorId) {
|
|
1111
|
+
const rows = await this.queryRows(
|
|
1112
|
+
`SELECT user_id FROM ${IDENTITY_MAP_TABLE} FINAL
|
|
1113
|
+
WHERE site_id = {siteId:String} AND visitor_id = {visitorId:String}
|
|
1114
|
+
LIMIT 1`,
|
|
1115
|
+
{ siteId, visitorId }
|
|
1116
|
+
);
|
|
1117
|
+
return rows.length > 0 ? rows[0].user_id : null;
|
|
1118
|
+
}
|
|
1119
|
+
async getMergedUserDetail(siteId, userId, visitorIds) {
|
|
1120
|
+
const rows = await this.queryRows(
|
|
1121
|
+
`SELECT
|
|
1122
|
+
anyLast(visitor_id) AS visitor_id,
|
|
1123
|
+
anyLast(traits) AS traits,
|
|
1124
|
+
min(timestamp) AS firstSeen,
|
|
1125
|
+
max(timestamp) AS lastSeen,
|
|
1126
|
+
count() AS totalEvents,
|
|
1127
|
+
countIf(type = 'pageview') AS totalPageviews,
|
|
1128
|
+
uniq(session_id) AS totalSessions,
|
|
1129
|
+
anyLast(url) AS lastUrl,
|
|
1130
|
+
anyLast(referrer) AS referrer,
|
|
1131
|
+
anyLast(device_type) AS device_type,
|
|
1132
|
+
anyLast(browser) AS browser,
|
|
1133
|
+
anyLast(os) AS os,
|
|
1134
|
+
anyLast(country) AS country,
|
|
1135
|
+
anyLast(city) AS city,
|
|
1136
|
+
anyLast(region) AS region,
|
|
1137
|
+
anyLast(language) AS language,
|
|
1138
|
+
anyLast(timezone) AS timezone,
|
|
1139
|
+
anyLast(screen_width) AS screen_width,
|
|
1140
|
+
anyLast(screen_height) AS screen_height,
|
|
1141
|
+
anyLast(utm_source) AS utm_source,
|
|
1142
|
+
anyLast(utm_medium) AS utm_medium,
|
|
1143
|
+
anyLast(utm_campaign) AS utm_campaign,
|
|
1144
|
+
anyLast(utm_term) AS utm_term,
|
|
1145
|
+
anyLast(utm_content) AS utm_content
|
|
1146
|
+
FROM ${EVENTS_TABLE}
|
|
1147
|
+
WHERE site_id = {siteId:String}
|
|
1148
|
+
AND visitor_id IN {visitorIds:Array(String)}`,
|
|
1149
|
+
{ siteId, visitorIds }
|
|
1150
|
+
);
|
|
1151
|
+
if (rows.length === 0) return null;
|
|
1152
|
+
const u = rows[0];
|
|
1153
|
+
return {
|
|
1154
|
+
visitorId: String(u.visitor_id),
|
|
1155
|
+
visitorIds,
|
|
1156
|
+
userId,
|
|
1157
|
+
traits: this.parseJSON(u.traits),
|
|
1158
|
+
firstSeen: new Date(String(u.firstSeen)).toISOString(),
|
|
1159
|
+
lastSeen: new Date(String(u.lastSeen)).toISOString(),
|
|
1160
|
+
totalEvents: Number(u.totalEvents),
|
|
1161
|
+
totalPageviews: Number(u.totalPageviews),
|
|
1162
|
+
totalSessions: Number(u.totalSessions),
|
|
1163
|
+
lastUrl: u.lastUrl ? String(u.lastUrl) : void 0,
|
|
1164
|
+
referrer: u.referrer ? String(u.referrer) : void 0,
|
|
1165
|
+
device: u.device_type ? { type: String(u.device_type), browser: String(u.browser ?? ""), os: String(u.os ?? "") } : void 0,
|
|
1166
|
+
geo: u.country ? { country: String(u.country), city: u.city ? String(u.city) : void 0, region: u.region ? String(u.region) : void 0 } : void 0,
|
|
1167
|
+
language: u.language ? String(u.language) : void 0,
|
|
1168
|
+
timezone: u.timezone ? String(u.timezone) : void 0,
|
|
1169
|
+
screen: u.screen_width || u.screen_height ? { width: Number(u.screen_width ?? 0), height: Number(u.screen_height ?? 0) } : void 0,
|
|
1170
|
+
utm: u.utm_source ? {
|
|
1171
|
+
source: String(u.utm_source),
|
|
1172
|
+
medium: u.utm_medium ? String(u.utm_medium) : void 0,
|
|
1173
|
+
campaign: u.utm_campaign ? String(u.utm_campaign) : void 0,
|
|
1174
|
+
term: u.utm_term ? String(u.utm_term) : void 0,
|
|
1175
|
+
content: u.utm_content ? String(u.utm_content) : void 0
|
|
1176
|
+
} : void 0
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
async listEventsForVisitorIds(siteId, visitorIds, params) {
|
|
1180
|
+
const limit = Math.min(params.limit ?? 50, 200);
|
|
1181
|
+
const offset = params.offset ?? 0;
|
|
1182
|
+
const conditions = [`site_id = {siteId:String}`, `visitor_id IN {visitorIds:Array(String)}`];
|
|
1183
|
+
const queryParams = { siteId, visitorIds, limit, offset };
|
|
1184
|
+
if (params.type) {
|
|
1185
|
+
conditions.push(`type = {type:String}`);
|
|
1186
|
+
queryParams.type = params.type;
|
|
1187
|
+
}
|
|
1188
|
+
if (params.eventName) {
|
|
1189
|
+
conditions.push(`event_name = {eventName:String}`);
|
|
1190
|
+
queryParams.eventName = params.eventName;
|
|
1191
|
+
}
|
|
1192
|
+
if (params.eventNames && params.eventNames.length > 0) {
|
|
1193
|
+
conditions.push(`event_name IN {eventNames:Array(String)}`);
|
|
1194
|
+
queryParams.eventNames = params.eventNames;
|
|
1195
|
+
}
|
|
1196
|
+
if (params.period || params.dateFrom) {
|
|
1197
|
+
const { dateRange } = resolvePeriod({
|
|
1198
|
+
period: params.period,
|
|
1199
|
+
dateFrom: params.dateFrom,
|
|
1200
|
+
dateTo: params.dateTo
|
|
1201
|
+
});
|
|
1202
|
+
conditions.push(`timestamp >= {from:String} AND timestamp <= {to:String}`);
|
|
1203
|
+
queryParams.from = toCHDateTime(dateRange.from);
|
|
1204
|
+
queryParams.to = toCHDateTime(dateRange.to);
|
|
1205
|
+
}
|
|
1206
|
+
const where = conditions.join(" AND ");
|
|
1207
|
+
const [events, countRows] = await Promise.all([
|
|
1208
|
+
this.queryRows(
|
|
1209
|
+
`SELECT event_id, type, timestamp, session_id, visitor_id, url, referrer, title,
|
|
1210
|
+
event_name, properties, event_source, event_subtype, page_path, target_url_path,
|
|
1211
|
+
element_selector, element_text, scroll_depth_pct,
|
|
1212
|
+
user_id, traits, country, city, region,
|
|
1213
|
+
device_type, browser, os, language,
|
|
1214
|
+
utm_source, utm_medium, utm_campaign, utm_term, utm_content
|
|
1215
|
+
FROM ${EVENTS_TABLE}
|
|
1216
|
+
WHERE ${where}
|
|
1217
|
+
ORDER BY timestamp DESC
|
|
1218
|
+
LIMIT {limit:UInt32}
|
|
1219
|
+
OFFSET {offset:UInt32}`,
|
|
1220
|
+
queryParams
|
|
1221
|
+
),
|
|
1222
|
+
this.queryRows(
|
|
1223
|
+
`SELECT count() AS total FROM ${EVENTS_TABLE} WHERE ${where}`,
|
|
1224
|
+
queryParams
|
|
1225
|
+
)
|
|
1226
|
+
]);
|
|
1227
|
+
return {
|
|
1228
|
+
events: events.map((e) => this.toEventListItem(e)),
|
|
1229
|
+
total: Number(countRows[0]?.total ?? 0),
|
|
1230
|
+
limit,
|
|
1231
|
+
offset
|
|
1232
|
+
};
|
|
990
1233
|
}
|
|
991
1234
|
// ─── Site Management ──────────────────────────────────────
|
|
992
1235
|
async createSite(data) {
|
|
@@ -1237,6 +1480,7 @@ var ClickHouseAdapter = class {
|
|
|
1237
1480
|
import { MongoClient } from "mongodb";
|
|
1238
1481
|
var EVENTS_COLLECTION = "litemetrics_events";
|
|
1239
1482
|
var SITES_COLLECTION = "litemetrics_sites";
|
|
1483
|
+
var IDENTITY_MAP_COLLECTION = "litemetrics_identity_map";
|
|
1240
1484
|
function buildFilterMatch(filters) {
|
|
1241
1485
|
if (!filters) return {};
|
|
1242
1486
|
const map = {
|
|
@@ -1272,6 +1516,7 @@ var MongoDBAdapter = class {
|
|
|
1272
1516
|
db;
|
|
1273
1517
|
collection;
|
|
1274
1518
|
sites;
|
|
1519
|
+
identityMap;
|
|
1275
1520
|
constructor(url) {
|
|
1276
1521
|
this.client = new MongoClient(url);
|
|
1277
1522
|
}
|
|
@@ -1280,13 +1525,16 @@ var MongoDBAdapter = class {
|
|
|
1280
1525
|
this.db = this.client.db();
|
|
1281
1526
|
this.collection = this.db.collection(EVENTS_COLLECTION);
|
|
1282
1527
|
this.sites = this.db.collection(SITES_COLLECTION);
|
|
1528
|
+
this.identityMap = this.db.collection(IDENTITY_MAP_COLLECTION);
|
|
1283
1529
|
await Promise.all([
|
|
1284
1530
|
this.collection.createIndex({ site_id: 1, timestamp: -1 }),
|
|
1285
1531
|
this.collection.createIndex({ site_id: 1, type: 1 }),
|
|
1286
1532
|
this.collection.createIndex({ site_id: 1, visitor_id: 1 }),
|
|
1287
1533
|
this.collection.createIndex({ site_id: 1, session_id: 1 }),
|
|
1288
1534
|
this.sites.createIndex({ site_id: 1 }, { unique: true }),
|
|
1289
|
-
this.sites.createIndex({ secret_key: 1 })
|
|
1535
|
+
this.sites.createIndex({ secret_key: 1 }),
|
|
1536
|
+
this.identityMap.createIndex({ site_id: 1, visitor_id: 1 }, { unique: true }),
|
|
1537
|
+
this.identityMap.createIndex({ site_id: 1, user_id: 1 })
|
|
1290
1538
|
]);
|
|
1291
1539
|
}
|
|
1292
1540
|
async insertEvents(events) {
|
|
@@ -1589,6 +1837,42 @@ var MongoDBAdapter = class {
|
|
|
1589
1837
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1590
1838
|
break;
|
|
1591
1839
|
}
|
|
1840
|
+
case "top_utm_sources": {
|
|
1841
|
+
const rows = await this.collection.aggregate([
|
|
1842
|
+
{ $match: { ...baseMatch, ...filterMatch, utm_source: { $nin: [null, ""] } } },
|
|
1843
|
+
{ $group: { _id: "$utm_source", value: { $addToSet: "$visitor_id" } } },
|
|
1844
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1845
|
+
{ $sort: { value: -1 } },
|
|
1846
|
+
{ $limit: limit }
|
|
1847
|
+
]).toArray();
|
|
1848
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1849
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1850
|
+
break;
|
|
1851
|
+
}
|
|
1852
|
+
case "top_utm_mediums": {
|
|
1853
|
+
const rows = await this.collection.aggregate([
|
|
1854
|
+
{ $match: { ...baseMatch, ...filterMatch, utm_medium: { $nin: [null, ""] } } },
|
|
1855
|
+
{ $group: { _id: "$utm_medium", value: { $addToSet: "$visitor_id" } } },
|
|
1856
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1857
|
+
{ $sort: { value: -1 } },
|
|
1858
|
+
{ $limit: limit }
|
|
1859
|
+
]).toArray();
|
|
1860
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1861
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1862
|
+
break;
|
|
1863
|
+
}
|
|
1864
|
+
case "top_utm_campaigns": {
|
|
1865
|
+
const rows = await this.collection.aggregate([
|
|
1866
|
+
{ $match: { ...baseMatch, ...filterMatch, utm_campaign: { $nin: [null, ""] } } },
|
|
1867
|
+
{ $group: { _id: "$utm_campaign", value: { $addToSet: "$visitor_id" } } },
|
|
1868
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1869
|
+
{ $sort: { value: -1 } },
|
|
1870
|
+
{ $limit: limit }
|
|
1871
|
+
]).toArray();
|
|
1872
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1873
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1874
|
+
break;
|
|
1875
|
+
}
|
|
1592
1876
|
}
|
|
1593
1877
|
const result = { metric: q.metric, period, data, total };
|
|
1594
1878
|
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
@@ -1777,24 +2061,72 @@ var MongoDBAdapter = class {
|
|
|
1777
2061
|
offset
|
|
1778
2062
|
};
|
|
1779
2063
|
}
|
|
2064
|
+
// ─── Identity Mapping ──────────────────────────────────────
|
|
2065
|
+
async upsertIdentity(siteId, visitorId, userId) {
|
|
2066
|
+
await this.identityMap.updateOne(
|
|
2067
|
+
{ site_id: siteId, visitor_id: visitorId },
|
|
2068
|
+
{
|
|
2069
|
+
$set: { user_id: userId, identified_at: /* @__PURE__ */ new Date() },
|
|
2070
|
+
$setOnInsert: { site_id: siteId, visitor_id: visitorId, created_at: /* @__PURE__ */ new Date() }
|
|
2071
|
+
},
|
|
2072
|
+
{ upsert: true }
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
async getVisitorIdsForUser(siteId, userId) {
|
|
2076
|
+
const docs = await this.identityMap.find({ site_id: siteId, user_id: userId }).toArray();
|
|
2077
|
+
return docs.map((d) => d.visitor_id);
|
|
2078
|
+
}
|
|
2079
|
+
async getUserIdForVisitor(siteId, visitorId) {
|
|
2080
|
+
const doc = await this.identityMap.findOne({ site_id: siteId, visitor_id: visitorId });
|
|
2081
|
+
return doc?.user_id ?? null;
|
|
2082
|
+
}
|
|
1780
2083
|
// ─── User Listing ──────────────────────────────────────
|
|
1781
2084
|
async listUsers(params) {
|
|
1782
2085
|
const limit = Math.min(params.limit ?? 50, 200);
|
|
1783
2086
|
const offset = params.offset ?? 0;
|
|
1784
2087
|
const match = { site_id: params.siteId };
|
|
1785
|
-
if (params.search) {
|
|
1786
|
-
match.$or = [
|
|
1787
|
-
{ visitor_id: { $regex: params.search, $options: "i" } },
|
|
1788
|
-
{ user_id: { $regex: params.search, $options: "i" } }
|
|
1789
|
-
];
|
|
1790
|
-
}
|
|
1791
2088
|
const pipeline = [
|
|
1792
2089
|
{ $match: match },
|
|
2090
|
+
// Join with identity map to resolve visitor → user
|
|
2091
|
+
{
|
|
2092
|
+
$lookup: {
|
|
2093
|
+
from: IDENTITY_MAP_COLLECTION,
|
|
2094
|
+
let: { vid: "$visitor_id", sid: "$site_id" },
|
|
2095
|
+
pipeline: [
|
|
2096
|
+
{ $match: { $expr: { $and: [{ $eq: ["$visitor_id", "$$vid"] }, { $eq: ["$site_id", "$$sid"] }] } } }
|
|
2097
|
+
],
|
|
2098
|
+
as: "_identity"
|
|
2099
|
+
}
|
|
2100
|
+
},
|
|
2101
|
+
{
|
|
2102
|
+
$addFields: {
|
|
2103
|
+
_resolved_id: {
|
|
2104
|
+
$ifNull: [{ $arrayElemAt: ["$_identity.user_id", 0] }, "$visitor_id"]
|
|
2105
|
+
},
|
|
2106
|
+
_resolved_user_id: {
|
|
2107
|
+
$arrayElemAt: ["$_identity.user_id", 0]
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
];
|
|
2112
|
+
if (params.search) {
|
|
2113
|
+
pipeline.push({
|
|
2114
|
+
$match: {
|
|
2115
|
+
$or: [
|
|
2116
|
+
{ visitor_id: { $regex: params.search, $options: "i" } },
|
|
2117
|
+
{ user_id: { $regex: params.search, $options: "i" } },
|
|
2118
|
+
{ _resolved_user_id: { $regex: params.search, $options: "i" } }
|
|
2119
|
+
]
|
|
2120
|
+
}
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
pipeline.push(
|
|
1793
2124
|
{ $sort: { timestamp: 1 } },
|
|
1794
2125
|
{
|
|
1795
2126
|
$group: {
|
|
1796
|
-
_id: "$
|
|
1797
|
-
|
|
2127
|
+
_id: "$_resolved_id",
|
|
2128
|
+
visitorIds: { $addToSet: "$visitor_id" },
|
|
2129
|
+
userId: { $last: { $ifNull: ["$_resolved_user_id", "$user_id"] } },
|
|
1798
2130
|
traits: { $last: "$traits" },
|
|
1799
2131
|
firstSeen: { $min: "$timestamp" },
|
|
1800
2132
|
lastSeen: { $max: "$timestamp" },
|
|
@@ -1827,10 +2159,11 @@ var MongoDBAdapter = class {
|
|
|
1827
2159
|
count: [{ $count: "total" }]
|
|
1828
2160
|
}
|
|
1829
2161
|
}
|
|
1830
|
-
|
|
2162
|
+
);
|
|
1831
2163
|
const [result] = await this.collection.aggregate(pipeline).toArray();
|
|
1832
2164
|
const users = (result?.data ?? []).map((u) => ({
|
|
1833
|
-
visitorId: u._id,
|
|
2165
|
+
visitorId: u.visitorIds[0] ?? u._id,
|
|
2166
|
+
visitorIds: u.visitorIds.length > 1 ? u.visitorIds : void 0,
|
|
1834
2167
|
userId: u.userId ?? void 0,
|
|
1835
2168
|
traits: u.traits ?? void 0,
|
|
1836
2169
|
firstSeen: u.firstSeen.toISOString(),
|
|
@@ -1860,13 +2193,125 @@ var MongoDBAdapter = class {
|
|
|
1860
2193
|
offset
|
|
1861
2194
|
};
|
|
1862
2195
|
}
|
|
1863
|
-
async getUserDetail(siteId,
|
|
1864
|
-
const
|
|
1865
|
-
|
|
1866
|
-
|
|
2196
|
+
async getUserDetail(siteId, identifier) {
|
|
2197
|
+
const visitorIds = await this.getVisitorIdsForUser(siteId, identifier);
|
|
2198
|
+
if (visitorIds.length > 0) {
|
|
2199
|
+
return this.getMergedUserDetail(siteId, identifier, visitorIds);
|
|
2200
|
+
}
|
|
2201
|
+
const userId = await this.getUserIdForVisitor(siteId, identifier);
|
|
2202
|
+
if (userId) {
|
|
2203
|
+
const allVisitorIds = await this.getVisitorIdsForUser(siteId, userId);
|
|
2204
|
+
return this.getMergedUserDetail(siteId, userId, allVisitorIds);
|
|
2205
|
+
}
|
|
2206
|
+
return this.getMergedUserDetail(siteId, void 0, [identifier]);
|
|
2207
|
+
}
|
|
2208
|
+
async getUserEvents(siteId, identifier, params) {
|
|
2209
|
+
let visitorIds = await this.getVisitorIdsForUser(siteId, identifier);
|
|
2210
|
+
if (visitorIds.length === 0) {
|
|
2211
|
+
const userId = await this.getUserIdForVisitor(siteId, identifier);
|
|
2212
|
+
if (userId) {
|
|
2213
|
+
visitorIds = await this.getVisitorIdsForUser(siteId, userId);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
if (visitorIds.length === 0) {
|
|
2217
|
+
visitorIds = [identifier];
|
|
2218
|
+
}
|
|
2219
|
+
return this.listEventsForVisitorIds(siteId, visitorIds, params);
|
|
2220
|
+
}
|
|
2221
|
+
async getMergedUserDetail(siteId, userId, visitorIds) {
|
|
2222
|
+
const pipeline = [
|
|
2223
|
+
{ $match: { site_id: siteId, visitor_id: { $in: visitorIds } } },
|
|
2224
|
+
{ $sort: { timestamp: 1 } },
|
|
2225
|
+
{
|
|
2226
|
+
$group: {
|
|
2227
|
+
_id: null,
|
|
2228
|
+
visitorIds: { $addToSet: "$visitor_id" },
|
|
2229
|
+
traits: { $last: "$traits" },
|
|
2230
|
+
firstSeen: { $min: "$timestamp" },
|
|
2231
|
+
lastSeen: { $max: "$timestamp" },
|
|
2232
|
+
totalEvents: { $sum: 1 },
|
|
2233
|
+
totalPageviews: { $sum: { $cond: [{ $eq: ["$type", "pageview"] }, 1, 0] } },
|
|
2234
|
+
sessions: { $addToSet: "$session_id" },
|
|
2235
|
+
lastUrl: { $last: "$url" },
|
|
2236
|
+
referrer: { $last: "$referrer" },
|
|
2237
|
+
device_type: { $last: "$device_type" },
|
|
2238
|
+
browser: { $last: "$browser" },
|
|
2239
|
+
os: { $last: "$os" },
|
|
2240
|
+
country: { $last: "$country" },
|
|
2241
|
+
city: { $last: "$city" },
|
|
2242
|
+
region: { $last: "$region" },
|
|
2243
|
+
language: { $last: "$language" },
|
|
2244
|
+
timezone: { $last: "$timezone" },
|
|
2245
|
+
screen_width: { $last: "$screen_width" },
|
|
2246
|
+
screen_height: { $last: "$screen_height" },
|
|
2247
|
+
utm_source: { $last: "$utm_source" },
|
|
2248
|
+
utm_medium: { $last: "$utm_medium" },
|
|
2249
|
+
utm_campaign: { $last: "$utm_campaign" },
|
|
2250
|
+
utm_term: { $last: "$utm_term" },
|
|
2251
|
+
utm_content: { $last: "$utm_content" }
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
];
|
|
2255
|
+
const [row] = await this.collection.aggregate(pipeline).toArray();
|
|
2256
|
+
if (!row) return null;
|
|
2257
|
+
return {
|
|
2258
|
+
visitorId: visitorIds[0],
|
|
2259
|
+
visitorIds: row.visitorIds.length > 1 ? row.visitorIds : void 0,
|
|
2260
|
+
userId: userId ?? void 0,
|
|
2261
|
+
traits: row.traits ?? void 0,
|
|
2262
|
+
firstSeen: row.firstSeen.toISOString(),
|
|
2263
|
+
lastSeen: row.lastSeen.toISOString(),
|
|
2264
|
+
totalEvents: row.totalEvents,
|
|
2265
|
+
totalPageviews: row.totalPageviews,
|
|
2266
|
+
totalSessions: row.sessions.length,
|
|
2267
|
+
lastUrl: row.lastUrl ?? void 0,
|
|
2268
|
+
referrer: row.referrer ?? void 0,
|
|
2269
|
+
device: row.device_type ? { type: row.device_type, browser: row.browser ?? "", os: row.os ?? "" } : void 0,
|
|
2270
|
+
geo: row.country ? { country: row.country, city: row.city ?? void 0, region: row.region ?? void 0 } : void 0,
|
|
2271
|
+
language: row.language ?? void 0,
|
|
2272
|
+
timezone: row.timezone ?? void 0,
|
|
2273
|
+
screen: row.screen_width || row.screen_height ? { width: row.screen_width ?? 0, height: row.screen_height ?? 0 } : void 0,
|
|
2274
|
+
utm: row.utm_source ? {
|
|
2275
|
+
source: row.utm_source ?? void 0,
|
|
2276
|
+
medium: row.utm_medium ?? void 0,
|
|
2277
|
+
campaign: row.utm_campaign ?? void 0,
|
|
2278
|
+
term: row.utm_term ?? void 0,
|
|
2279
|
+
content: row.utm_content ?? void 0
|
|
2280
|
+
} : void 0
|
|
2281
|
+
};
|
|
1867
2282
|
}
|
|
1868
|
-
async
|
|
1869
|
-
|
|
2283
|
+
async listEventsForVisitorIds(siteId, visitorIds, params) {
|
|
2284
|
+
const limit = Math.min(params.limit ?? 50, 200);
|
|
2285
|
+
const offset = params.offset ?? 0;
|
|
2286
|
+
const match = {
|
|
2287
|
+
site_id: siteId,
|
|
2288
|
+
visitor_id: { $in: visitorIds }
|
|
2289
|
+
};
|
|
2290
|
+
if (params.type) match.type = params.type;
|
|
2291
|
+
if (params.eventName) {
|
|
2292
|
+
match.event_name = params.eventName;
|
|
2293
|
+
} else if (params.eventNames && params.eventNames.length > 0) {
|
|
2294
|
+
match.event_name = { $in: params.eventNames };
|
|
2295
|
+
}
|
|
2296
|
+
if (params.eventSource) match.event_source = params.eventSource;
|
|
2297
|
+
if (params.period || params.dateFrom) {
|
|
2298
|
+
const { dateRange } = resolvePeriod({
|
|
2299
|
+
period: params.period,
|
|
2300
|
+
dateFrom: params.dateFrom,
|
|
2301
|
+
dateTo: params.dateTo
|
|
2302
|
+
});
|
|
2303
|
+
match.timestamp = { $gte: new Date(dateRange.from), $lte: new Date(dateRange.to) };
|
|
2304
|
+
}
|
|
2305
|
+
const [events, countResult] = await Promise.all([
|
|
2306
|
+
this.collection.find(match).sort({ timestamp: -1 }).skip(offset).limit(limit).toArray(),
|
|
2307
|
+
this.collection.countDocuments(match)
|
|
2308
|
+
]);
|
|
2309
|
+
return {
|
|
2310
|
+
events: events.map((e) => this.toEventListItem(e)),
|
|
2311
|
+
total: countResult,
|
|
2312
|
+
limit,
|
|
2313
|
+
offset
|
|
2314
|
+
};
|
|
1870
2315
|
}
|
|
1871
2316
|
toEventListItem(doc) {
|
|
1872
2317
|
return {
|
|
@@ -2237,6 +2682,51 @@ async function createCollector(config) {
|
|
|
2237
2682
|
return { ...event, ip, geo, device };
|
|
2238
2683
|
});
|
|
2239
2684
|
}
|
|
2685
|
+
const identityCache = /* @__PURE__ */ new Map();
|
|
2686
|
+
const IDENTITY_CACHE_TTL = 5 * 60 * 1e3;
|
|
2687
|
+
function getCachedUserId(siteId, visitorId) {
|
|
2688
|
+
const key = `${siteId}:${visitorId}`;
|
|
2689
|
+
const entry = identityCache.get(key);
|
|
2690
|
+
if (!entry) return void 0;
|
|
2691
|
+
if (Date.now() > entry.expires) {
|
|
2692
|
+
identityCache.delete(key);
|
|
2693
|
+
return void 0;
|
|
2694
|
+
}
|
|
2695
|
+
return entry.userId;
|
|
2696
|
+
}
|
|
2697
|
+
function setCachedUserId(siteId, visitorId, userId) {
|
|
2698
|
+
const key = `${siteId}:${visitorId}`;
|
|
2699
|
+
identityCache.set(key, { userId, expires: Date.now() + IDENTITY_CACHE_TTL });
|
|
2700
|
+
if (identityCache.size > 1e4) {
|
|
2701
|
+
const now = Date.now();
|
|
2702
|
+
for (const [k, v] of identityCache) {
|
|
2703
|
+
if (now > v.expires) identityCache.delete(k);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
async function processIdentity(events) {
|
|
2708
|
+
for (const event of events) {
|
|
2709
|
+
if (!event.visitorId || event.visitorId === "server") continue;
|
|
2710
|
+
if (event.type === "identify" && event.userId) {
|
|
2711
|
+
await db.upsertIdentity(event.siteId, event.visitorId, event.userId);
|
|
2712
|
+
setCachedUserId(event.siteId, event.visitorId, event.userId);
|
|
2713
|
+
} else if (!event.userId) {
|
|
2714
|
+
const cached = getCachedUserId(event.siteId, event.visitorId);
|
|
2715
|
+
if (cached) {
|
|
2716
|
+
event.userId = cached;
|
|
2717
|
+
} else {
|
|
2718
|
+
const resolved = await db.getUserIdForVisitor(event.siteId, event.visitorId);
|
|
2719
|
+
if (resolved) {
|
|
2720
|
+
event.userId = resolved;
|
|
2721
|
+
setCachedUserId(event.siteId, event.visitorId, resolved);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
} else if (event.userId) {
|
|
2725
|
+
setCachedUserId(event.siteId, event.visitorId, event.userId);
|
|
2726
|
+
await db.upsertIdentity(event.siteId, event.visitorId, event.userId);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2240
2730
|
function extractIp(req) {
|
|
2241
2731
|
if (config.trustProxy ?? true) {
|
|
2242
2732
|
const forwarded = req.headers?.["x-forwarded-for"];
|
|
@@ -2298,11 +2788,13 @@ async function createCollector(config) {
|
|
|
2298
2788
|
sendJson(res, 200, { ok: true });
|
|
2299
2789
|
return;
|
|
2300
2790
|
}
|
|
2791
|
+
await processIdentity(filtered);
|
|
2301
2792
|
await db.insertEvents(filtered);
|
|
2302
2793
|
sendJson(res, 200, { ok: true });
|
|
2303
2794
|
return;
|
|
2304
2795
|
}
|
|
2305
2796
|
}
|
|
2797
|
+
await processIdentity(enriched);
|
|
2306
2798
|
await db.insertEvents(enriched);
|
|
2307
2799
|
sendJson(res, 200, { ok: true });
|
|
2308
2800
|
} catch (err) {
|
|
@@ -2563,11 +3055,11 @@ async function createCollector(config) {
|
|
|
2563
3055
|
async listUsers(params) {
|
|
2564
3056
|
return db.listUsers(params);
|
|
2565
3057
|
},
|
|
2566
|
-
async getUserDetail(siteId,
|
|
2567
|
-
return db.getUserDetail(siteId,
|
|
3058
|
+
async getUserDetail(siteId, identifier) {
|
|
3059
|
+
return db.getUserDetail(siteId, identifier);
|
|
2568
3060
|
},
|
|
2569
|
-
async getUserEvents(siteId,
|
|
2570
|
-
return db.getUserEvents(siteId,
|
|
3061
|
+
async getUserEvents(siteId, identifier, params) {
|
|
3062
|
+
return db.getUserEvents(siteId, identifier, params);
|
|
2571
3063
|
},
|
|
2572
3064
|
async track(siteId, name, properties, options) {
|
|
2573
3065
|
const event = {
|