@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.cjs
CHANGED
|
@@ -179,6 +179,18 @@ function generateSecretKey() {
|
|
|
179
179
|
// src/adapters/clickhouse.ts
|
|
180
180
|
var EVENTS_TABLE = "litemetrics_events";
|
|
181
181
|
var SITES_TABLE = "litemetrics_sites";
|
|
182
|
+
var IDENTITY_MAP_TABLE = "litemetrics_identity_map";
|
|
183
|
+
var CREATE_IDENTITY_MAP_TABLE = `
|
|
184
|
+
CREATE TABLE IF NOT EXISTS ${IDENTITY_MAP_TABLE} (
|
|
185
|
+
site_id LowCardinality(String),
|
|
186
|
+
visitor_id String,
|
|
187
|
+
user_id String,
|
|
188
|
+
identified_at DateTime64(3),
|
|
189
|
+
created_at DateTime64(3) DEFAULT now64(3)
|
|
190
|
+
) ENGINE = ReplacingMergeTree(created_at)
|
|
191
|
+
ORDER BY (site_id, visitor_id)
|
|
192
|
+
SETTINGS index_granularity = 8192
|
|
193
|
+
`;
|
|
182
194
|
var CREATE_EVENTS_TABLE = `
|
|
183
195
|
CREATE TABLE IF NOT EXISTS ${EVENTS_TABLE} (
|
|
184
196
|
event_id UUID DEFAULT generateUUIDv4(),
|
|
@@ -289,6 +301,7 @@ var ClickHouseAdapter = class {
|
|
|
289
301
|
async init() {
|
|
290
302
|
await this.client.command({ query: CREATE_EVENTS_TABLE });
|
|
291
303
|
await this.client.command({ query: CREATE_SITES_TABLE });
|
|
304
|
+
await this.client.command({ query: CREATE_IDENTITY_MAP_TABLE });
|
|
292
305
|
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS event_source LowCardinality(Nullable(String))` });
|
|
293
306
|
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS event_subtype LowCardinality(Nullable(String))` });
|
|
294
307
|
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS page_path Nullable(String)` });
|
|
@@ -546,8 +559,8 @@ var ClickHouseAdapter = class {
|
|
|
546
559
|
}
|
|
547
560
|
case "top_exit_pages": {
|
|
548
561
|
const rows = await this.queryRows(
|
|
549
|
-
`SELECT
|
|
550
|
-
SELECT session_id, argMax(url, timestamp) AS
|
|
562
|
+
`SELECT exit_url AS key, count() AS value FROM (
|
|
563
|
+
SELECT session_id, argMax(url, timestamp) AS exit_url
|
|
551
564
|
FROM ${EVENTS_TABLE}
|
|
552
565
|
WHERE site_id = {siteId:String}
|
|
553
566
|
AND timestamp >= {from:String}
|
|
@@ -556,7 +569,7 @@ var ClickHouseAdapter = class {
|
|
|
556
569
|
AND url IS NOT NULL${filterSql}
|
|
557
570
|
GROUP BY session_id
|
|
558
571
|
)
|
|
559
|
-
GROUP BY
|
|
572
|
+
GROUP BY exit_url
|
|
560
573
|
ORDER BY value DESC
|
|
561
574
|
LIMIT {limit:UInt32}`,
|
|
562
575
|
{ ...params, ...filter.params }
|
|
@@ -567,9 +580,9 @@ var ClickHouseAdapter = class {
|
|
|
567
580
|
}
|
|
568
581
|
case "top_transitions": {
|
|
569
582
|
const rows = await this.queryRows(
|
|
570
|
-
`SELECT concat(prev_url, ' \u2192 ',
|
|
571
|
-
SELECT session_id, url,
|
|
572
|
-
|
|
583
|
+
`SELECT concat(prev_url, ' \u2192 ', curr_url) AS key, count() AS value FROM (
|
|
584
|
+
SELECT session_id, url AS curr_url,
|
|
585
|
+
lagInFrame(url, 1) OVER (PARTITION BY session_id ORDER BY timestamp ASC) AS prev_url
|
|
573
586
|
FROM ${EVENTS_TABLE}
|
|
574
587
|
WHERE site_id = {siteId:String}
|
|
575
588
|
AND timestamp >= {from:String}
|
|
@@ -577,7 +590,7 @@ var ClickHouseAdapter = class {
|
|
|
577
590
|
AND type = 'pageview'
|
|
578
591
|
AND url IS NOT NULL${filterSql}
|
|
579
592
|
)
|
|
580
|
-
WHERE prev_url IS NOT NULL
|
|
593
|
+
WHERE prev_url IS NOT NULL AND prev_url != ''
|
|
581
594
|
GROUP BY key
|
|
582
595
|
ORDER BY value DESC
|
|
583
596
|
LIMIT {limit:UInt32}`,
|
|
@@ -692,6 +705,57 @@ var ClickHouseAdapter = class {
|
|
|
692
705
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
693
706
|
break;
|
|
694
707
|
}
|
|
708
|
+
case "top_utm_sources": {
|
|
709
|
+
const rows = await this.queryRows(
|
|
710
|
+
`SELECT utm_source AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
711
|
+
WHERE site_id = {siteId:String}
|
|
712
|
+
AND timestamp >= {from:String}
|
|
713
|
+
AND timestamp <= {to:String}
|
|
714
|
+
AND utm_source IS NOT NULL AND utm_source != ''
|
|
715
|
+
${filterSql}
|
|
716
|
+
GROUP BY utm_source
|
|
717
|
+
ORDER BY value DESC
|
|
718
|
+
LIMIT {limit:UInt32}`,
|
|
719
|
+
{ ...params, ...filter.params }
|
|
720
|
+
);
|
|
721
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
722
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
case "top_utm_mediums": {
|
|
726
|
+
const rows = await this.queryRows(
|
|
727
|
+
`SELECT utm_medium AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
728
|
+
WHERE site_id = {siteId:String}
|
|
729
|
+
AND timestamp >= {from:String}
|
|
730
|
+
AND timestamp <= {to:String}
|
|
731
|
+
AND utm_medium IS NOT NULL AND utm_medium != ''
|
|
732
|
+
${filterSql}
|
|
733
|
+
GROUP BY utm_medium
|
|
734
|
+
ORDER BY value DESC
|
|
735
|
+
LIMIT {limit:UInt32}`,
|
|
736
|
+
{ ...params, ...filter.params }
|
|
737
|
+
);
|
|
738
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
739
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
case "top_utm_campaigns": {
|
|
743
|
+
const rows = await this.queryRows(
|
|
744
|
+
`SELECT utm_campaign AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
745
|
+
WHERE site_id = {siteId:String}
|
|
746
|
+
AND timestamp >= {from:String}
|
|
747
|
+
AND timestamp <= {to:String}
|
|
748
|
+
AND utm_campaign IS NOT NULL AND utm_campaign != ''
|
|
749
|
+
${filterSql}
|
|
750
|
+
GROUP BY utm_campaign
|
|
751
|
+
ORDER BY value DESC
|
|
752
|
+
LIMIT {limit:UInt32}`,
|
|
753
|
+
{ ...params, ...filter.params }
|
|
754
|
+
);
|
|
755
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
756
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
695
759
|
}
|
|
696
760
|
const result = { metric: q.metric, period, data, total };
|
|
697
761
|
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
@@ -945,45 +1009,59 @@ var ClickHouseAdapter = class {
|
|
|
945
1009
|
const where = conditions.join(" AND ");
|
|
946
1010
|
const [userRows, countRows] = await Promise.all([
|
|
947
1011
|
this.queryRows(
|
|
948
|
-
`
|
|
949
|
-
visitor_id,
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1012
|
+
`WITH identity AS (
|
|
1013
|
+
SELECT visitor_id, user_id
|
|
1014
|
+
FROM ${IDENTITY_MAP_TABLE} FINAL
|
|
1015
|
+
WHERE site_id = {siteId:String}
|
|
1016
|
+
)
|
|
1017
|
+
SELECT
|
|
1018
|
+
if(i.user_id IS NOT NULL AND i.user_id != '', i.user_id, e.visitor_id) AS group_key,
|
|
1019
|
+
anyLast(e.visitor_id) AS visitor_id,
|
|
1020
|
+
anyLast(i.user_id) AS userId,
|
|
1021
|
+
anyLast(e.traits) AS traits,
|
|
1022
|
+
min(e.timestamp) AS firstSeen,
|
|
1023
|
+
max(e.timestamp) AS lastSeen,
|
|
954
1024
|
count() AS totalEvents,
|
|
955
|
-
countIf(type = 'pageview') AS totalPageviews,
|
|
956
|
-
uniq(session_id) AS totalSessions,
|
|
957
|
-
anyLast(url) AS lastUrl,
|
|
958
|
-
anyLast(referrer) AS referrer,
|
|
959
|
-
anyLast(device_type) AS device_type,
|
|
960
|
-
anyLast(browser) AS browser,
|
|
961
|
-
anyLast(os) AS os,
|
|
962
|
-
anyLast(country) AS country,
|
|
963
|
-
anyLast(city) AS city,
|
|
964
|
-
anyLast(region) AS region,
|
|
965
|
-
anyLast(language) AS language,
|
|
966
|
-
anyLast(timezone) AS timezone,
|
|
967
|
-
anyLast(screen_width) AS screen_width,
|
|
968
|
-
anyLast(screen_height) AS screen_height,
|
|
969
|
-
anyLast(utm_source) AS utm_source,
|
|
970
|
-
anyLast(utm_medium) AS utm_medium,
|
|
971
|
-
anyLast(utm_campaign) AS utm_campaign,
|
|
972
|
-
anyLast(utm_term) AS utm_term,
|
|
973
|
-
anyLast(utm_content) AS utm_content
|
|
974
|
-
FROM ${EVENTS_TABLE}
|
|
975
|
-
|
|
976
|
-
|
|
1025
|
+
countIf(e.type = 'pageview') AS totalPageviews,
|
|
1026
|
+
uniq(e.session_id) AS totalSessions,
|
|
1027
|
+
anyLast(e.url) AS lastUrl,
|
|
1028
|
+
anyLast(e.referrer) AS referrer,
|
|
1029
|
+
anyLast(e.device_type) AS device_type,
|
|
1030
|
+
anyLast(e.browser) AS browser,
|
|
1031
|
+
anyLast(e.os) AS os,
|
|
1032
|
+
anyLast(e.country) AS country,
|
|
1033
|
+
anyLast(e.city) AS city,
|
|
1034
|
+
anyLast(e.region) AS region,
|
|
1035
|
+
anyLast(e.language) AS language,
|
|
1036
|
+
anyLast(e.timezone) AS timezone,
|
|
1037
|
+
anyLast(e.screen_width) AS screen_width,
|
|
1038
|
+
anyLast(e.screen_height) AS screen_height,
|
|
1039
|
+
anyLast(e.utm_source) AS utm_source,
|
|
1040
|
+
anyLast(e.utm_medium) AS utm_medium,
|
|
1041
|
+
anyLast(e.utm_campaign) AS utm_campaign,
|
|
1042
|
+
anyLast(e.utm_term) AS utm_term,
|
|
1043
|
+
anyLast(e.utm_content) AS utm_content
|
|
1044
|
+
FROM ${EVENTS_TABLE} e
|
|
1045
|
+
LEFT JOIN identity i ON e.visitor_id = i.visitor_id
|
|
1046
|
+
WHERE e.site_id = {siteId:String}${where.includes("ILIKE") ? ` AND (e.visitor_id ILIKE {search:String} OR i.user_id ILIKE {search:String})` : ""}
|
|
1047
|
+
GROUP BY group_key
|
|
977
1048
|
ORDER BY lastSeen DESC
|
|
978
1049
|
LIMIT {limit:UInt32}
|
|
979
1050
|
OFFSET {offset:UInt32}`,
|
|
980
1051
|
queryParams
|
|
981
1052
|
),
|
|
982
1053
|
this.queryRows(
|
|
983
|
-
`
|
|
984
|
-
SELECT visitor_id
|
|
985
|
-
|
|
986
|
-
|
|
1054
|
+
`WITH identity AS (
|
|
1055
|
+
SELECT visitor_id, user_id
|
|
1056
|
+
FROM ${IDENTITY_MAP_TABLE} FINAL
|
|
1057
|
+
WHERE site_id = {siteId:String}
|
|
1058
|
+
)
|
|
1059
|
+
SELECT count() AS total FROM (
|
|
1060
|
+
SELECT if(i.user_id IS NOT NULL AND i.user_id != '', i.user_id, e.visitor_id) AS group_key
|
|
1061
|
+
FROM ${EVENTS_TABLE} e
|
|
1062
|
+
LEFT JOIN identity i ON e.visitor_id = i.visitor_id
|
|
1063
|
+
WHERE e.site_id = {siteId:String}${where.includes("ILIKE") ? ` AND (e.visitor_id ILIKE {search:String} OR i.user_id ILIKE {search:String})` : ""}
|
|
1064
|
+
GROUP BY group_key
|
|
987
1065
|
)`,
|
|
988
1066
|
queryParams
|
|
989
1067
|
)
|
|
@@ -1019,13 +1097,178 @@ var ClickHouseAdapter = class {
|
|
|
1019
1097
|
offset
|
|
1020
1098
|
};
|
|
1021
1099
|
}
|
|
1022
|
-
async getUserDetail(siteId,
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1100
|
+
async getUserDetail(siteId, identifier) {
|
|
1101
|
+
const visitorIds = await this.getVisitorIdsForUser(siteId, identifier);
|
|
1102
|
+
if (visitorIds.length > 0) {
|
|
1103
|
+
return this.getMergedUserDetail(siteId, identifier, visitorIds);
|
|
1104
|
+
}
|
|
1105
|
+
const userId = await this.getUserIdForVisitor(siteId, identifier);
|
|
1106
|
+
if (userId) {
|
|
1107
|
+
const allVisitorIds = await this.getVisitorIdsForUser(siteId, userId);
|
|
1108
|
+
return this.getMergedUserDetail(siteId, userId, allVisitorIds.length > 0 ? allVisitorIds : [identifier]);
|
|
1109
|
+
}
|
|
1110
|
+
const result = await this.listUsers({ siteId, search: identifier, limit: 1 });
|
|
1111
|
+
const user = result.users.find((u) => u.visitorId === identifier);
|
|
1025
1112
|
return user ?? null;
|
|
1026
1113
|
}
|
|
1027
|
-
async getUserEvents(siteId,
|
|
1028
|
-
|
|
1114
|
+
async getUserEvents(siteId, identifier, params) {
|
|
1115
|
+
const visitorIds = await this.getVisitorIdsForUser(siteId, identifier);
|
|
1116
|
+
if (visitorIds.length > 0) {
|
|
1117
|
+
return this.listEventsForVisitorIds(siteId, visitorIds, params);
|
|
1118
|
+
}
|
|
1119
|
+
const userId = await this.getUserIdForVisitor(siteId, identifier);
|
|
1120
|
+
if (userId) {
|
|
1121
|
+
const allVisitorIds = await this.getVisitorIdsForUser(siteId, userId);
|
|
1122
|
+
if (allVisitorIds.length > 0) {
|
|
1123
|
+
return this.listEventsForVisitorIds(siteId, allVisitorIds, params);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return this.listEvents({ ...params, siteId, visitorId: identifier });
|
|
1127
|
+
}
|
|
1128
|
+
// ─── Identity Mapping ──────────────────────────────────────
|
|
1129
|
+
async upsertIdentity(siteId, visitorId, userId) {
|
|
1130
|
+
await this.client.insert({
|
|
1131
|
+
table: IDENTITY_MAP_TABLE,
|
|
1132
|
+
values: [{
|
|
1133
|
+
site_id: siteId,
|
|
1134
|
+
visitor_id: visitorId,
|
|
1135
|
+
user_id: userId,
|
|
1136
|
+
identified_at: toCHDateTime(/* @__PURE__ */ new Date())
|
|
1137
|
+
}],
|
|
1138
|
+
format: "JSONEachRow"
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
async getVisitorIdsForUser(siteId, userId) {
|
|
1142
|
+
const rows = await this.queryRows(
|
|
1143
|
+
`SELECT visitor_id FROM ${IDENTITY_MAP_TABLE} FINAL
|
|
1144
|
+
WHERE site_id = {siteId:String} AND user_id = {userId:String}`,
|
|
1145
|
+
{ siteId, userId }
|
|
1146
|
+
);
|
|
1147
|
+
return rows.map((r) => r.visitor_id);
|
|
1148
|
+
}
|
|
1149
|
+
async getUserIdForVisitor(siteId, visitorId) {
|
|
1150
|
+
const rows = await this.queryRows(
|
|
1151
|
+
`SELECT user_id FROM ${IDENTITY_MAP_TABLE} FINAL
|
|
1152
|
+
WHERE site_id = {siteId:String} AND visitor_id = {visitorId:String}
|
|
1153
|
+
LIMIT 1`,
|
|
1154
|
+
{ siteId, visitorId }
|
|
1155
|
+
);
|
|
1156
|
+
return rows.length > 0 ? rows[0].user_id : null;
|
|
1157
|
+
}
|
|
1158
|
+
async getMergedUserDetail(siteId, userId, visitorIds) {
|
|
1159
|
+
const rows = await this.queryRows(
|
|
1160
|
+
`SELECT
|
|
1161
|
+
anyLast(visitor_id) AS visitor_id,
|
|
1162
|
+
anyLast(traits) AS traits,
|
|
1163
|
+
min(timestamp) AS firstSeen,
|
|
1164
|
+
max(timestamp) AS lastSeen,
|
|
1165
|
+
count() AS totalEvents,
|
|
1166
|
+
countIf(type = 'pageview') AS totalPageviews,
|
|
1167
|
+
uniq(session_id) AS totalSessions,
|
|
1168
|
+
anyLast(url) AS lastUrl,
|
|
1169
|
+
anyLast(referrer) AS referrer,
|
|
1170
|
+
anyLast(device_type) AS device_type,
|
|
1171
|
+
anyLast(browser) AS browser,
|
|
1172
|
+
anyLast(os) AS os,
|
|
1173
|
+
anyLast(country) AS country,
|
|
1174
|
+
anyLast(city) AS city,
|
|
1175
|
+
anyLast(region) AS region,
|
|
1176
|
+
anyLast(language) AS language,
|
|
1177
|
+
anyLast(timezone) AS timezone,
|
|
1178
|
+
anyLast(screen_width) AS screen_width,
|
|
1179
|
+
anyLast(screen_height) AS screen_height,
|
|
1180
|
+
anyLast(utm_source) AS utm_source,
|
|
1181
|
+
anyLast(utm_medium) AS utm_medium,
|
|
1182
|
+
anyLast(utm_campaign) AS utm_campaign,
|
|
1183
|
+
anyLast(utm_term) AS utm_term,
|
|
1184
|
+
anyLast(utm_content) AS utm_content
|
|
1185
|
+
FROM ${EVENTS_TABLE}
|
|
1186
|
+
WHERE site_id = {siteId:String}
|
|
1187
|
+
AND visitor_id IN {visitorIds:Array(String)}`,
|
|
1188
|
+
{ siteId, visitorIds }
|
|
1189
|
+
);
|
|
1190
|
+
if (rows.length === 0) return null;
|
|
1191
|
+
const u = rows[0];
|
|
1192
|
+
return {
|
|
1193
|
+
visitorId: String(u.visitor_id),
|
|
1194
|
+
visitorIds,
|
|
1195
|
+
userId,
|
|
1196
|
+
traits: this.parseJSON(u.traits),
|
|
1197
|
+
firstSeen: new Date(String(u.firstSeen)).toISOString(),
|
|
1198
|
+
lastSeen: new Date(String(u.lastSeen)).toISOString(),
|
|
1199
|
+
totalEvents: Number(u.totalEvents),
|
|
1200
|
+
totalPageviews: Number(u.totalPageviews),
|
|
1201
|
+
totalSessions: Number(u.totalSessions),
|
|
1202
|
+
lastUrl: u.lastUrl ? String(u.lastUrl) : void 0,
|
|
1203
|
+
referrer: u.referrer ? String(u.referrer) : void 0,
|
|
1204
|
+
device: u.device_type ? { type: String(u.device_type), browser: String(u.browser ?? ""), os: String(u.os ?? "") } : void 0,
|
|
1205
|
+
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,
|
|
1206
|
+
language: u.language ? String(u.language) : void 0,
|
|
1207
|
+
timezone: u.timezone ? String(u.timezone) : void 0,
|
|
1208
|
+
screen: u.screen_width || u.screen_height ? { width: Number(u.screen_width ?? 0), height: Number(u.screen_height ?? 0) } : void 0,
|
|
1209
|
+
utm: u.utm_source ? {
|
|
1210
|
+
source: String(u.utm_source),
|
|
1211
|
+
medium: u.utm_medium ? String(u.utm_medium) : void 0,
|
|
1212
|
+
campaign: u.utm_campaign ? String(u.utm_campaign) : void 0,
|
|
1213
|
+
term: u.utm_term ? String(u.utm_term) : void 0,
|
|
1214
|
+
content: u.utm_content ? String(u.utm_content) : void 0
|
|
1215
|
+
} : void 0
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
async listEventsForVisitorIds(siteId, visitorIds, params) {
|
|
1219
|
+
const limit = Math.min(params.limit ?? 50, 200);
|
|
1220
|
+
const offset = params.offset ?? 0;
|
|
1221
|
+
const conditions = [`site_id = {siteId:String}`, `visitor_id IN {visitorIds:Array(String)}`];
|
|
1222
|
+
const queryParams = { siteId, visitorIds, limit, offset };
|
|
1223
|
+
if (params.type) {
|
|
1224
|
+
conditions.push(`type = {type:String}`);
|
|
1225
|
+
queryParams.type = params.type;
|
|
1226
|
+
}
|
|
1227
|
+
if (params.eventName) {
|
|
1228
|
+
conditions.push(`event_name = {eventName:String}`);
|
|
1229
|
+
queryParams.eventName = params.eventName;
|
|
1230
|
+
}
|
|
1231
|
+
if (params.eventNames && params.eventNames.length > 0) {
|
|
1232
|
+
conditions.push(`event_name IN {eventNames:Array(String)}`);
|
|
1233
|
+
queryParams.eventNames = params.eventNames;
|
|
1234
|
+
}
|
|
1235
|
+
if (params.period || params.dateFrom) {
|
|
1236
|
+
const { dateRange } = resolvePeriod({
|
|
1237
|
+
period: params.period,
|
|
1238
|
+
dateFrom: params.dateFrom,
|
|
1239
|
+
dateTo: params.dateTo
|
|
1240
|
+
});
|
|
1241
|
+
conditions.push(`timestamp >= {from:String} AND timestamp <= {to:String}`);
|
|
1242
|
+
queryParams.from = toCHDateTime(dateRange.from);
|
|
1243
|
+
queryParams.to = toCHDateTime(dateRange.to);
|
|
1244
|
+
}
|
|
1245
|
+
const where = conditions.join(" AND ");
|
|
1246
|
+
const [events, countRows] = await Promise.all([
|
|
1247
|
+
this.queryRows(
|
|
1248
|
+
`SELECT event_id, type, timestamp, session_id, visitor_id, url, referrer, title,
|
|
1249
|
+
event_name, properties, event_source, event_subtype, page_path, target_url_path,
|
|
1250
|
+
element_selector, element_text, scroll_depth_pct,
|
|
1251
|
+
user_id, traits, country, city, region,
|
|
1252
|
+
device_type, browser, os, language,
|
|
1253
|
+
utm_source, utm_medium, utm_campaign, utm_term, utm_content
|
|
1254
|
+
FROM ${EVENTS_TABLE}
|
|
1255
|
+
WHERE ${where}
|
|
1256
|
+
ORDER BY timestamp DESC
|
|
1257
|
+
LIMIT {limit:UInt32}
|
|
1258
|
+
OFFSET {offset:UInt32}`,
|
|
1259
|
+
queryParams
|
|
1260
|
+
),
|
|
1261
|
+
this.queryRows(
|
|
1262
|
+
`SELECT count() AS total FROM ${EVENTS_TABLE} WHERE ${where}`,
|
|
1263
|
+
queryParams
|
|
1264
|
+
)
|
|
1265
|
+
]);
|
|
1266
|
+
return {
|
|
1267
|
+
events: events.map((e) => this.toEventListItem(e)),
|
|
1268
|
+
total: Number(countRows[0]?.total ?? 0),
|
|
1269
|
+
limit,
|
|
1270
|
+
offset
|
|
1271
|
+
};
|
|
1029
1272
|
}
|
|
1030
1273
|
// ─── Site Management ──────────────────────────────────────
|
|
1031
1274
|
async createSite(data) {
|
|
@@ -1276,6 +1519,7 @@ var ClickHouseAdapter = class {
|
|
|
1276
1519
|
var import_mongodb = require("mongodb");
|
|
1277
1520
|
var EVENTS_COLLECTION = "litemetrics_events";
|
|
1278
1521
|
var SITES_COLLECTION = "litemetrics_sites";
|
|
1522
|
+
var IDENTITY_MAP_COLLECTION = "litemetrics_identity_map";
|
|
1279
1523
|
function buildFilterMatch(filters) {
|
|
1280
1524
|
if (!filters) return {};
|
|
1281
1525
|
const map = {
|
|
@@ -1311,6 +1555,7 @@ var MongoDBAdapter = class {
|
|
|
1311
1555
|
db;
|
|
1312
1556
|
collection;
|
|
1313
1557
|
sites;
|
|
1558
|
+
identityMap;
|
|
1314
1559
|
constructor(url) {
|
|
1315
1560
|
this.client = new import_mongodb.MongoClient(url);
|
|
1316
1561
|
}
|
|
@@ -1319,13 +1564,16 @@ var MongoDBAdapter = class {
|
|
|
1319
1564
|
this.db = this.client.db();
|
|
1320
1565
|
this.collection = this.db.collection(EVENTS_COLLECTION);
|
|
1321
1566
|
this.sites = this.db.collection(SITES_COLLECTION);
|
|
1567
|
+
this.identityMap = this.db.collection(IDENTITY_MAP_COLLECTION);
|
|
1322
1568
|
await Promise.all([
|
|
1323
1569
|
this.collection.createIndex({ site_id: 1, timestamp: -1 }),
|
|
1324
1570
|
this.collection.createIndex({ site_id: 1, type: 1 }),
|
|
1325
1571
|
this.collection.createIndex({ site_id: 1, visitor_id: 1 }),
|
|
1326
1572
|
this.collection.createIndex({ site_id: 1, session_id: 1 }),
|
|
1327
1573
|
this.sites.createIndex({ site_id: 1 }, { unique: true }),
|
|
1328
|
-
this.sites.createIndex({ secret_key: 1 })
|
|
1574
|
+
this.sites.createIndex({ secret_key: 1 }),
|
|
1575
|
+
this.identityMap.createIndex({ site_id: 1, visitor_id: 1 }, { unique: true }),
|
|
1576
|
+
this.identityMap.createIndex({ site_id: 1, user_id: 1 })
|
|
1329
1577
|
]);
|
|
1330
1578
|
}
|
|
1331
1579
|
async insertEvents(events) {
|
|
@@ -1628,6 +1876,42 @@ var MongoDBAdapter = class {
|
|
|
1628
1876
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1629
1877
|
break;
|
|
1630
1878
|
}
|
|
1879
|
+
case "top_utm_sources": {
|
|
1880
|
+
const rows = await this.collection.aggregate([
|
|
1881
|
+
{ $match: { ...baseMatch, ...filterMatch, utm_source: { $nin: [null, ""] } } },
|
|
1882
|
+
{ $group: { _id: "$utm_source", value: { $addToSet: "$visitor_id" } } },
|
|
1883
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1884
|
+
{ $sort: { value: -1 } },
|
|
1885
|
+
{ $limit: limit }
|
|
1886
|
+
]).toArray();
|
|
1887
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1888
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1889
|
+
break;
|
|
1890
|
+
}
|
|
1891
|
+
case "top_utm_mediums": {
|
|
1892
|
+
const rows = await this.collection.aggregate([
|
|
1893
|
+
{ $match: { ...baseMatch, ...filterMatch, utm_medium: { $nin: [null, ""] } } },
|
|
1894
|
+
{ $group: { _id: "$utm_medium", value: { $addToSet: "$visitor_id" } } },
|
|
1895
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1896
|
+
{ $sort: { value: -1 } },
|
|
1897
|
+
{ $limit: limit }
|
|
1898
|
+
]).toArray();
|
|
1899
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1900
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
case "top_utm_campaigns": {
|
|
1904
|
+
const rows = await this.collection.aggregate([
|
|
1905
|
+
{ $match: { ...baseMatch, ...filterMatch, utm_campaign: { $nin: [null, ""] } } },
|
|
1906
|
+
{ $group: { _id: "$utm_campaign", value: { $addToSet: "$visitor_id" } } },
|
|
1907
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1908
|
+
{ $sort: { value: -1 } },
|
|
1909
|
+
{ $limit: limit }
|
|
1910
|
+
]).toArray();
|
|
1911
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
1912
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1913
|
+
break;
|
|
1914
|
+
}
|
|
1631
1915
|
}
|
|
1632
1916
|
const result = { metric: q.metric, period, data, total };
|
|
1633
1917
|
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
@@ -1816,24 +2100,72 @@ var MongoDBAdapter = class {
|
|
|
1816
2100
|
offset
|
|
1817
2101
|
};
|
|
1818
2102
|
}
|
|
2103
|
+
// ─── Identity Mapping ──────────────────────────────────────
|
|
2104
|
+
async upsertIdentity(siteId, visitorId, userId) {
|
|
2105
|
+
await this.identityMap.updateOne(
|
|
2106
|
+
{ site_id: siteId, visitor_id: visitorId },
|
|
2107
|
+
{
|
|
2108
|
+
$set: { user_id: userId, identified_at: /* @__PURE__ */ new Date() },
|
|
2109
|
+
$setOnInsert: { site_id: siteId, visitor_id: visitorId, created_at: /* @__PURE__ */ new Date() }
|
|
2110
|
+
},
|
|
2111
|
+
{ upsert: true }
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
async getVisitorIdsForUser(siteId, userId) {
|
|
2115
|
+
const docs = await this.identityMap.find({ site_id: siteId, user_id: userId }).toArray();
|
|
2116
|
+
return docs.map((d) => d.visitor_id);
|
|
2117
|
+
}
|
|
2118
|
+
async getUserIdForVisitor(siteId, visitorId) {
|
|
2119
|
+
const doc = await this.identityMap.findOne({ site_id: siteId, visitor_id: visitorId });
|
|
2120
|
+
return doc?.user_id ?? null;
|
|
2121
|
+
}
|
|
1819
2122
|
// ─── User Listing ──────────────────────────────────────
|
|
1820
2123
|
async listUsers(params) {
|
|
1821
2124
|
const limit = Math.min(params.limit ?? 50, 200);
|
|
1822
2125
|
const offset = params.offset ?? 0;
|
|
1823
2126
|
const match = { site_id: params.siteId };
|
|
1824
|
-
if (params.search) {
|
|
1825
|
-
match.$or = [
|
|
1826
|
-
{ visitor_id: { $regex: params.search, $options: "i" } },
|
|
1827
|
-
{ user_id: { $regex: params.search, $options: "i" } }
|
|
1828
|
-
];
|
|
1829
|
-
}
|
|
1830
2127
|
const pipeline = [
|
|
1831
2128
|
{ $match: match },
|
|
2129
|
+
// Join with identity map to resolve visitor → user
|
|
2130
|
+
{
|
|
2131
|
+
$lookup: {
|
|
2132
|
+
from: IDENTITY_MAP_COLLECTION,
|
|
2133
|
+
let: { vid: "$visitor_id", sid: "$site_id" },
|
|
2134
|
+
pipeline: [
|
|
2135
|
+
{ $match: { $expr: { $and: [{ $eq: ["$visitor_id", "$$vid"] }, { $eq: ["$site_id", "$$sid"] }] } } }
|
|
2136
|
+
],
|
|
2137
|
+
as: "_identity"
|
|
2138
|
+
}
|
|
2139
|
+
},
|
|
2140
|
+
{
|
|
2141
|
+
$addFields: {
|
|
2142
|
+
_resolved_id: {
|
|
2143
|
+
$ifNull: [{ $arrayElemAt: ["$_identity.user_id", 0] }, "$visitor_id"]
|
|
2144
|
+
},
|
|
2145
|
+
_resolved_user_id: {
|
|
2146
|
+
$arrayElemAt: ["$_identity.user_id", 0]
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
];
|
|
2151
|
+
if (params.search) {
|
|
2152
|
+
pipeline.push({
|
|
2153
|
+
$match: {
|
|
2154
|
+
$or: [
|
|
2155
|
+
{ visitor_id: { $regex: params.search, $options: "i" } },
|
|
2156
|
+
{ user_id: { $regex: params.search, $options: "i" } },
|
|
2157
|
+
{ _resolved_user_id: { $regex: params.search, $options: "i" } }
|
|
2158
|
+
]
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
pipeline.push(
|
|
1832
2163
|
{ $sort: { timestamp: 1 } },
|
|
1833
2164
|
{
|
|
1834
2165
|
$group: {
|
|
1835
|
-
_id: "$
|
|
1836
|
-
|
|
2166
|
+
_id: "$_resolved_id",
|
|
2167
|
+
visitorIds: { $addToSet: "$visitor_id" },
|
|
2168
|
+
userId: { $last: { $ifNull: ["$_resolved_user_id", "$user_id"] } },
|
|
1837
2169
|
traits: { $last: "$traits" },
|
|
1838
2170
|
firstSeen: { $min: "$timestamp" },
|
|
1839
2171
|
lastSeen: { $max: "$timestamp" },
|
|
@@ -1866,10 +2198,11 @@ var MongoDBAdapter = class {
|
|
|
1866
2198
|
count: [{ $count: "total" }]
|
|
1867
2199
|
}
|
|
1868
2200
|
}
|
|
1869
|
-
|
|
2201
|
+
);
|
|
1870
2202
|
const [result] = await this.collection.aggregate(pipeline).toArray();
|
|
1871
2203
|
const users = (result?.data ?? []).map((u) => ({
|
|
1872
|
-
visitorId: u._id,
|
|
2204
|
+
visitorId: u.visitorIds[0] ?? u._id,
|
|
2205
|
+
visitorIds: u.visitorIds.length > 1 ? u.visitorIds : void 0,
|
|
1873
2206
|
userId: u.userId ?? void 0,
|
|
1874
2207
|
traits: u.traits ?? void 0,
|
|
1875
2208
|
firstSeen: u.firstSeen.toISOString(),
|
|
@@ -1899,13 +2232,125 @@ var MongoDBAdapter = class {
|
|
|
1899
2232
|
offset
|
|
1900
2233
|
};
|
|
1901
2234
|
}
|
|
1902
|
-
async getUserDetail(siteId,
|
|
1903
|
-
const
|
|
1904
|
-
|
|
1905
|
-
|
|
2235
|
+
async getUserDetail(siteId, identifier) {
|
|
2236
|
+
const visitorIds = await this.getVisitorIdsForUser(siteId, identifier);
|
|
2237
|
+
if (visitorIds.length > 0) {
|
|
2238
|
+
return this.getMergedUserDetail(siteId, identifier, visitorIds);
|
|
2239
|
+
}
|
|
2240
|
+
const userId = await this.getUserIdForVisitor(siteId, identifier);
|
|
2241
|
+
if (userId) {
|
|
2242
|
+
const allVisitorIds = await this.getVisitorIdsForUser(siteId, userId);
|
|
2243
|
+
return this.getMergedUserDetail(siteId, userId, allVisitorIds);
|
|
2244
|
+
}
|
|
2245
|
+
return this.getMergedUserDetail(siteId, void 0, [identifier]);
|
|
2246
|
+
}
|
|
2247
|
+
async getUserEvents(siteId, identifier, params) {
|
|
2248
|
+
let visitorIds = await this.getVisitorIdsForUser(siteId, identifier);
|
|
2249
|
+
if (visitorIds.length === 0) {
|
|
2250
|
+
const userId = await this.getUserIdForVisitor(siteId, identifier);
|
|
2251
|
+
if (userId) {
|
|
2252
|
+
visitorIds = await this.getVisitorIdsForUser(siteId, userId);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (visitorIds.length === 0) {
|
|
2256
|
+
visitorIds = [identifier];
|
|
2257
|
+
}
|
|
2258
|
+
return this.listEventsForVisitorIds(siteId, visitorIds, params);
|
|
2259
|
+
}
|
|
2260
|
+
async getMergedUserDetail(siteId, userId, visitorIds) {
|
|
2261
|
+
const pipeline = [
|
|
2262
|
+
{ $match: { site_id: siteId, visitor_id: { $in: visitorIds } } },
|
|
2263
|
+
{ $sort: { timestamp: 1 } },
|
|
2264
|
+
{
|
|
2265
|
+
$group: {
|
|
2266
|
+
_id: null,
|
|
2267
|
+
visitorIds: { $addToSet: "$visitor_id" },
|
|
2268
|
+
traits: { $last: "$traits" },
|
|
2269
|
+
firstSeen: { $min: "$timestamp" },
|
|
2270
|
+
lastSeen: { $max: "$timestamp" },
|
|
2271
|
+
totalEvents: { $sum: 1 },
|
|
2272
|
+
totalPageviews: { $sum: { $cond: [{ $eq: ["$type", "pageview"] }, 1, 0] } },
|
|
2273
|
+
sessions: { $addToSet: "$session_id" },
|
|
2274
|
+
lastUrl: { $last: "$url" },
|
|
2275
|
+
referrer: { $last: "$referrer" },
|
|
2276
|
+
device_type: { $last: "$device_type" },
|
|
2277
|
+
browser: { $last: "$browser" },
|
|
2278
|
+
os: { $last: "$os" },
|
|
2279
|
+
country: { $last: "$country" },
|
|
2280
|
+
city: { $last: "$city" },
|
|
2281
|
+
region: { $last: "$region" },
|
|
2282
|
+
language: { $last: "$language" },
|
|
2283
|
+
timezone: { $last: "$timezone" },
|
|
2284
|
+
screen_width: { $last: "$screen_width" },
|
|
2285
|
+
screen_height: { $last: "$screen_height" },
|
|
2286
|
+
utm_source: { $last: "$utm_source" },
|
|
2287
|
+
utm_medium: { $last: "$utm_medium" },
|
|
2288
|
+
utm_campaign: { $last: "$utm_campaign" },
|
|
2289
|
+
utm_term: { $last: "$utm_term" },
|
|
2290
|
+
utm_content: { $last: "$utm_content" }
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
];
|
|
2294
|
+
const [row] = await this.collection.aggregate(pipeline).toArray();
|
|
2295
|
+
if (!row) return null;
|
|
2296
|
+
return {
|
|
2297
|
+
visitorId: visitorIds[0],
|
|
2298
|
+
visitorIds: row.visitorIds.length > 1 ? row.visitorIds : void 0,
|
|
2299
|
+
userId: userId ?? void 0,
|
|
2300
|
+
traits: row.traits ?? void 0,
|
|
2301
|
+
firstSeen: row.firstSeen.toISOString(),
|
|
2302
|
+
lastSeen: row.lastSeen.toISOString(),
|
|
2303
|
+
totalEvents: row.totalEvents,
|
|
2304
|
+
totalPageviews: row.totalPageviews,
|
|
2305
|
+
totalSessions: row.sessions.length,
|
|
2306
|
+
lastUrl: row.lastUrl ?? void 0,
|
|
2307
|
+
referrer: row.referrer ?? void 0,
|
|
2308
|
+
device: row.device_type ? { type: row.device_type, browser: row.browser ?? "", os: row.os ?? "" } : void 0,
|
|
2309
|
+
geo: row.country ? { country: row.country, city: row.city ?? void 0, region: row.region ?? void 0 } : void 0,
|
|
2310
|
+
language: row.language ?? void 0,
|
|
2311
|
+
timezone: row.timezone ?? void 0,
|
|
2312
|
+
screen: row.screen_width || row.screen_height ? { width: row.screen_width ?? 0, height: row.screen_height ?? 0 } : void 0,
|
|
2313
|
+
utm: row.utm_source ? {
|
|
2314
|
+
source: row.utm_source ?? void 0,
|
|
2315
|
+
medium: row.utm_medium ?? void 0,
|
|
2316
|
+
campaign: row.utm_campaign ?? void 0,
|
|
2317
|
+
term: row.utm_term ?? void 0,
|
|
2318
|
+
content: row.utm_content ?? void 0
|
|
2319
|
+
} : void 0
|
|
2320
|
+
};
|
|
1906
2321
|
}
|
|
1907
|
-
async
|
|
1908
|
-
|
|
2322
|
+
async listEventsForVisitorIds(siteId, visitorIds, params) {
|
|
2323
|
+
const limit = Math.min(params.limit ?? 50, 200);
|
|
2324
|
+
const offset = params.offset ?? 0;
|
|
2325
|
+
const match = {
|
|
2326
|
+
site_id: siteId,
|
|
2327
|
+
visitor_id: { $in: visitorIds }
|
|
2328
|
+
};
|
|
2329
|
+
if (params.type) match.type = params.type;
|
|
2330
|
+
if (params.eventName) {
|
|
2331
|
+
match.event_name = params.eventName;
|
|
2332
|
+
} else if (params.eventNames && params.eventNames.length > 0) {
|
|
2333
|
+
match.event_name = { $in: params.eventNames };
|
|
2334
|
+
}
|
|
2335
|
+
if (params.eventSource) match.event_source = params.eventSource;
|
|
2336
|
+
if (params.period || params.dateFrom) {
|
|
2337
|
+
const { dateRange } = resolvePeriod({
|
|
2338
|
+
period: params.period,
|
|
2339
|
+
dateFrom: params.dateFrom,
|
|
2340
|
+
dateTo: params.dateTo
|
|
2341
|
+
});
|
|
2342
|
+
match.timestamp = { $gte: new Date(dateRange.from), $lte: new Date(dateRange.to) };
|
|
2343
|
+
}
|
|
2344
|
+
const [events, countResult] = await Promise.all([
|
|
2345
|
+
this.collection.find(match).sort({ timestamp: -1 }).skip(offset).limit(limit).toArray(),
|
|
2346
|
+
this.collection.countDocuments(match)
|
|
2347
|
+
]);
|
|
2348
|
+
return {
|
|
2349
|
+
events: events.map((e) => this.toEventListItem(e)),
|
|
2350
|
+
total: countResult,
|
|
2351
|
+
limit,
|
|
2352
|
+
offset
|
|
2353
|
+
};
|
|
1909
2354
|
}
|
|
1910
2355
|
toEventListItem(doc) {
|
|
1911
2356
|
return {
|
|
@@ -2276,6 +2721,51 @@ async function createCollector(config) {
|
|
|
2276
2721
|
return { ...event, ip, geo, device };
|
|
2277
2722
|
});
|
|
2278
2723
|
}
|
|
2724
|
+
const identityCache = /* @__PURE__ */ new Map();
|
|
2725
|
+
const IDENTITY_CACHE_TTL = 5 * 60 * 1e3;
|
|
2726
|
+
function getCachedUserId(siteId, visitorId) {
|
|
2727
|
+
const key = `${siteId}:${visitorId}`;
|
|
2728
|
+
const entry = identityCache.get(key);
|
|
2729
|
+
if (!entry) return void 0;
|
|
2730
|
+
if (Date.now() > entry.expires) {
|
|
2731
|
+
identityCache.delete(key);
|
|
2732
|
+
return void 0;
|
|
2733
|
+
}
|
|
2734
|
+
return entry.userId;
|
|
2735
|
+
}
|
|
2736
|
+
function setCachedUserId(siteId, visitorId, userId) {
|
|
2737
|
+
const key = `${siteId}:${visitorId}`;
|
|
2738
|
+
identityCache.set(key, { userId, expires: Date.now() + IDENTITY_CACHE_TTL });
|
|
2739
|
+
if (identityCache.size > 1e4) {
|
|
2740
|
+
const now = Date.now();
|
|
2741
|
+
for (const [k, v] of identityCache) {
|
|
2742
|
+
if (now > v.expires) identityCache.delete(k);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
async function processIdentity(events) {
|
|
2747
|
+
for (const event of events) {
|
|
2748
|
+
if (!event.visitorId || event.visitorId === "server") continue;
|
|
2749
|
+
if (event.type === "identify" && event.userId) {
|
|
2750
|
+
await db.upsertIdentity(event.siteId, event.visitorId, event.userId);
|
|
2751
|
+
setCachedUserId(event.siteId, event.visitorId, event.userId);
|
|
2752
|
+
} else if (!event.userId) {
|
|
2753
|
+
const cached = getCachedUserId(event.siteId, event.visitorId);
|
|
2754
|
+
if (cached) {
|
|
2755
|
+
event.userId = cached;
|
|
2756
|
+
} else {
|
|
2757
|
+
const resolved = await db.getUserIdForVisitor(event.siteId, event.visitorId);
|
|
2758
|
+
if (resolved) {
|
|
2759
|
+
event.userId = resolved;
|
|
2760
|
+
setCachedUserId(event.siteId, event.visitorId, resolved);
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
} else if (event.userId) {
|
|
2764
|
+
setCachedUserId(event.siteId, event.visitorId, event.userId);
|
|
2765
|
+
await db.upsertIdentity(event.siteId, event.visitorId, event.userId);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2279
2769
|
function extractIp(req) {
|
|
2280
2770
|
if (config.trustProxy ?? true) {
|
|
2281
2771
|
const forwarded = req.headers?.["x-forwarded-for"];
|
|
@@ -2337,11 +2827,13 @@ async function createCollector(config) {
|
|
|
2337
2827
|
sendJson(res, 200, { ok: true });
|
|
2338
2828
|
return;
|
|
2339
2829
|
}
|
|
2830
|
+
await processIdentity(filtered);
|
|
2340
2831
|
await db.insertEvents(filtered);
|
|
2341
2832
|
sendJson(res, 200, { ok: true });
|
|
2342
2833
|
return;
|
|
2343
2834
|
}
|
|
2344
2835
|
}
|
|
2836
|
+
await processIdentity(enriched);
|
|
2345
2837
|
await db.insertEvents(enriched);
|
|
2346
2838
|
sendJson(res, 200, { ok: true });
|
|
2347
2839
|
} catch (err) {
|
|
@@ -2602,11 +3094,11 @@ async function createCollector(config) {
|
|
|
2602
3094
|
async listUsers(params) {
|
|
2603
3095
|
return db.listUsers(params);
|
|
2604
3096
|
},
|
|
2605
|
-
async getUserDetail(siteId,
|
|
2606
|
-
return db.getUserDetail(siteId,
|
|
3097
|
+
async getUserDetail(siteId, identifier) {
|
|
3098
|
+
return db.getUserDetail(siteId, identifier);
|
|
2607
3099
|
},
|
|
2608
|
-
async getUserEvents(siteId,
|
|
2609
|
-
return db.getUserEvents(siteId,
|
|
3100
|
+
async getUserEvents(siteId, identifier, params) {
|
|
3101
|
+
return db.getUserEvents(siteId, identifier, params);
|
|
2610
3102
|
},
|
|
2611
3103
|
async track(siteId, name, properties, options) {
|
|
2612
3104
|
const event = {
|