@litemetrics/node 0.1.3 → 0.2.0
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 +1 -1
- package/dist/index.cjs +537 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +537 -77
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -229,6 +229,13 @@ CREATE TABLE IF NOT EXISTS ${EVENTS_TABLE} (
|
|
|
229
229
|
utm_term Nullable(String),
|
|
230
230
|
utm_content Nullable(String),
|
|
231
231
|
ip Nullable(String),
|
|
232
|
+
os_version LowCardinality(Nullable(String)),
|
|
233
|
+
device_model LowCardinality(Nullable(String)),
|
|
234
|
+
device_brand LowCardinality(Nullable(String)),
|
|
235
|
+
app_version LowCardinality(Nullable(String)),
|
|
236
|
+
app_build Nullable(String),
|
|
237
|
+
sdk_name LowCardinality(Nullable(String)),
|
|
238
|
+
sdk_version LowCardinality(Nullable(String)),
|
|
232
239
|
created_at DateTime64(3) DEFAULT now64(3)
|
|
233
240
|
) ENGINE = MergeTree()
|
|
234
241
|
PARTITION BY toYYYYMM(timestamp)
|
|
@@ -240,6 +247,7 @@ CREATE TABLE IF NOT EXISTS ${SITES_TABLE} (
|
|
|
240
247
|
site_id String,
|
|
241
248
|
secret_key String,
|
|
242
249
|
name String,
|
|
250
|
+
type LowCardinality(Nullable(String)) DEFAULT 'web',
|
|
243
251
|
domain Nullable(String),
|
|
244
252
|
allowed_origins Nullable(String),
|
|
245
253
|
conversion_events Nullable(String),
|
|
@@ -255,6 +263,65 @@ function toCHDateTime(d) {
|
|
|
255
263
|
const iso = typeof d === "string" ? d : d.toISOString();
|
|
256
264
|
return iso.replace("T", " ").replace("Z", "");
|
|
257
265
|
}
|
|
266
|
+
function normalizedUtmSourceExpr() {
|
|
267
|
+
return `multiIf(
|
|
268
|
+
lower(utm_source) IN ('ig','instagram','instagram.com'), 'Instagram',
|
|
269
|
+
lower(utm_source) IN ('fb','facebook','facebook.com','fb.com'), 'Facebook',
|
|
270
|
+
lower(utm_source) IN ('tw','twitter','twitter.com','x','x.com','t.co'), 'X (Twitter)',
|
|
271
|
+
lower(utm_source) IN ('li','linkedin','linkedin.com'), 'LinkedIn',
|
|
272
|
+
lower(utm_source) IN ('yt','youtube','youtube.com'), 'YouTube',
|
|
273
|
+
lower(utm_source) IN ('goog','google','google.com'), 'Google',
|
|
274
|
+
lower(utm_source) IN ('gh','github','github.com'), 'GitHub',
|
|
275
|
+
lower(utm_source) IN ('reddit','reddit.com'), 'Reddit',
|
|
276
|
+
lower(utm_source) IN ('pinterest','pinterest.com'), 'Pinterest',
|
|
277
|
+
lower(utm_source) IN ('tiktok','tiktok.com'), 'TikTok',
|
|
278
|
+
lower(utm_source) IN ('openai','chatgpt','chat.openai.com'), 'OpenAI',
|
|
279
|
+
lower(utm_source) IN ('perplexity','perplexity.ai'), 'Perplexity',
|
|
280
|
+
utm_source
|
|
281
|
+
)`;
|
|
282
|
+
}
|
|
283
|
+
function normalizedUtmMediumExpr() {
|
|
284
|
+
return `multiIf(
|
|
285
|
+
lower(utm_medium) IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid'), 'Paid',
|
|
286
|
+
lower(utm_medium) IN ('organic'), 'Organic',
|
|
287
|
+
lower(utm_medium) IN ('social','social-media','social_media'), 'Social',
|
|
288
|
+
lower(utm_medium) IN ('email','e-mail','e_mail'), 'Email',
|
|
289
|
+
lower(utm_medium) IN ('display','banner','cpm'), 'Display',
|
|
290
|
+
lower(utm_medium) IN ('affiliate'), 'Affiliate',
|
|
291
|
+
lower(utm_medium) IN ('referral'), 'Referral',
|
|
292
|
+
utm_medium
|
|
293
|
+
)`;
|
|
294
|
+
}
|
|
295
|
+
function channelClassificationExpr() {
|
|
296
|
+
return `multiIf(
|
|
297
|
+
lower(ifNull(utm_medium,'')) IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid')
|
|
298
|
+
AND (lower(ifNull(utm_source,'')) IN ('google','goog','bing','yahoo','duckduckgo','ecosia','baidu','yandex')
|
|
299
|
+
OR multiSearchAnyCaseInsensitive(ifNull(referrer,''), ['google','bing','yahoo','duckduckgo','ecosia','baidu','yandex','search.brave']) > 0),
|
|
300
|
+
'Paid Search',
|
|
301
|
+
lower(ifNull(utm_medium,'')) IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid')
|
|
302
|
+
AND (lower(ifNull(utm_source,'')) IN ('instagram','ig','facebook','fb','twitter','tw','x','linkedin','li','youtube','yt','tiktok','pinterest','reddit','snapchat')
|
|
303
|
+
OR multiSearchAnyCaseInsensitive(ifNull(referrer,''), ['instagram','facebook','twitter','x.com','t.co','linkedin','youtube','tiktok','pinterest','reddit','snapchat']) > 0),
|
|
304
|
+
'Paid Social',
|
|
305
|
+
lower(ifNull(utm_medium,'')) IN ('email','e-mail','e_mail'),
|
|
306
|
+
'Email',
|
|
307
|
+
lower(ifNull(utm_medium,'')) IN ('display','banner','cpm'),
|
|
308
|
+
'Display',
|
|
309
|
+
lower(ifNull(utm_medium,'')) IN ('affiliate'),
|
|
310
|
+
'Affiliate',
|
|
311
|
+
multiSearchAnyCaseInsensitive(ifNull(referrer,''), ['google','bing','yahoo','duckduckgo','ecosia','baidu','yandex','search.brave']) > 0
|
|
312
|
+
AND (ifNull(utm_medium,'') = '' OR lower(utm_medium) NOT IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid')),
|
|
313
|
+
'Organic Search',
|
|
314
|
+
(multiSearchAnyCaseInsensitive(ifNull(referrer,''), ['instagram','facebook','twitter','x.com','t.co','linkedin','youtube','tiktok','pinterest','reddit','snapchat','mastodon','tumblr']) > 0
|
|
315
|
+
OR lower(ifNull(utm_source,'')) IN ('instagram','ig','facebook','fb','twitter','tw','x','linkedin','li','youtube','yt','tiktok','pinterest','reddit','snapchat'))
|
|
316
|
+
AND (ifNull(utm_medium,'') = '' OR lower(utm_medium) NOT IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid')),
|
|
317
|
+
'Organic Social',
|
|
318
|
+
ifNull(referrer,'') != '' AND length(ifNull(referrer,'')) > 0,
|
|
319
|
+
'Referral',
|
|
320
|
+
(ifNull(utm_source,'') != '' OR ifNull(utm_medium,'') != '' OR ifNull(utm_campaign,'') != ''),
|
|
321
|
+
'Other',
|
|
322
|
+
'Direct'
|
|
323
|
+
)`;
|
|
324
|
+
}
|
|
258
325
|
function buildFilterConditions(filters) {
|
|
259
326
|
if (!filters) return { conditions: [], params: {} };
|
|
260
327
|
const map = {
|
|
@@ -265,6 +332,10 @@ function buildFilterConditions(filters) {
|
|
|
265
332
|
"device.type": "device_type",
|
|
266
333
|
"device.browser": "browser",
|
|
267
334
|
"device.os": "os",
|
|
335
|
+
"device.osVersion": "os_version",
|
|
336
|
+
"device.deviceModel": "device_model",
|
|
337
|
+
"device.deviceBrand": "device_brand",
|
|
338
|
+
"device.appVersion": "app_version",
|
|
268
339
|
"utm.source": "utm_source",
|
|
269
340
|
"utm.medium": "utm_medium",
|
|
270
341
|
"utm.campaign": "utm_campaign",
|
|
@@ -281,7 +352,14 @@ function buildFilterConditions(filters) {
|
|
|
281
352
|
const conditions = [];
|
|
282
353
|
const params = {};
|
|
283
354
|
for (const [key, value] of Object.entries(filters)) {
|
|
284
|
-
if (!value
|
|
355
|
+
if (!value) continue;
|
|
356
|
+
if (key === "channel") {
|
|
357
|
+
const paramKey2 = "f_channel";
|
|
358
|
+
conditions.push(`${channelClassificationExpr()} = {${paramKey2}:String}`);
|
|
359
|
+
params[paramKey2] = value;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (!map[key]) continue;
|
|
285
363
|
const paramKey = `f_${key.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
286
364
|
conditions.push(`${map[key]} = {${paramKey}:String}`);
|
|
287
365
|
params[paramKey] = value;
|
|
@@ -312,6 +390,14 @@ var ClickHouseAdapter = class {
|
|
|
312
390
|
await this.client.command({
|
|
313
391
|
query: `ALTER TABLE ${SITES_TABLE} ADD COLUMN IF NOT EXISTS conversion_events Nullable(String)`
|
|
314
392
|
});
|
|
393
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS os_version LowCardinality(Nullable(String))` });
|
|
394
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS device_model LowCardinality(Nullable(String))` });
|
|
395
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS device_brand LowCardinality(Nullable(String))` });
|
|
396
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS app_version LowCardinality(Nullable(String))` });
|
|
397
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS app_build Nullable(String)` });
|
|
398
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS sdk_name LowCardinality(Nullable(String))` });
|
|
399
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS sdk_version LowCardinality(Nullable(String))` });
|
|
400
|
+
await this.client.command({ query: `ALTER TABLE ${SITES_TABLE} ADD COLUMN IF NOT EXISTS type LowCardinality(Nullable(String)) DEFAULT 'web'` });
|
|
315
401
|
}
|
|
316
402
|
async close() {
|
|
317
403
|
await this.client.close();
|
|
@@ -354,7 +440,14 @@ var ClickHouseAdapter = class {
|
|
|
354
440
|
utm_campaign: e.utm?.campaign ?? null,
|
|
355
441
|
utm_term: e.utm?.term ?? null,
|
|
356
442
|
utm_content: e.utm?.content ?? null,
|
|
357
|
-
ip: e.ip ?? null
|
|
443
|
+
ip: e.ip ?? null,
|
|
444
|
+
os_version: e.device?.osVersion ?? null,
|
|
445
|
+
device_model: e.device?.deviceModel ?? null,
|
|
446
|
+
device_brand: e.device?.deviceBrand ?? null,
|
|
447
|
+
app_version: e.device?.appVersion ?? null,
|
|
448
|
+
app_build: e.device?.appBuild ?? null,
|
|
449
|
+
sdk_name: e.device?.sdkName ?? null,
|
|
450
|
+
sdk_version: e.device?.sdkVersion ?? null
|
|
358
451
|
}));
|
|
359
452
|
await this.client.insert({
|
|
360
453
|
table: EVENTS_TABLE,
|
|
@@ -705,15 +798,67 @@ var ClickHouseAdapter = class {
|
|
|
705
798
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
706
799
|
break;
|
|
707
800
|
}
|
|
801
|
+
case "top_os_versions": {
|
|
802
|
+
const rows = await this.queryRows(
|
|
803
|
+
`SELECT concat(os, ' ', ifNull(os_version, '')) AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
804
|
+
WHERE site_id = {siteId:String}
|
|
805
|
+
AND timestamp >= {from:String}
|
|
806
|
+
AND timestamp <= {to:String}
|
|
807
|
+
AND os IS NOT NULL
|
|
808
|
+
AND os_version IS NOT NULL
|
|
809
|
+
${filterSql}
|
|
810
|
+
GROUP BY key
|
|
811
|
+
ORDER BY value DESC
|
|
812
|
+
LIMIT {limit:UInt32}`,
|
|
813
|
+
{ ...params, ...filter.params }
|
|
814
|
+
);
|
|
815
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
816
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
case "top_device_models": {
|
|
820
|
+
const rows = await this.queryRows(
|
|
821
|
+
`SELECT trim(concat(ifNull(device_brand, ''), ' ', device_model)) AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
822
|
+
WHERE site_id = {siteId:String}
|
|
823
|
+
AND timestamp >= {from:String}
|
|
824
|
+
AND timestamp <= {to:String}
|
|
825
|
+
AND device_model IS NOT NULL
|
|
826
|
+
${filterSql}
|
|
827
|
+
GROUP BY key
|
|
828
|
+
ORDER BY value DESC
|
|
829
|
+
LIMIT {limit:UInt32}`,
|
|
830
|
+
{ ...params, ...filter.params }
|
|
831
|
+
);
|
|
832
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
833
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
case "top_app_versions": {
|
|
837
|
+
const rows = await this.queryRows(
|
|
838
|
+
`SELECT app_version AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
839
|
+
WHERE site_id = {siteId:String}
|
|
840
|
+
AND timestamp >= {from:String}
|
|
841
|
+
AND timestamp <= {to:String}
|
|
842
|
+
AND app_version IS NOT NULL
|
|
843
|
+
${filterSql}
|
|
844
|
+
GROUP BY app_version
|
|
845
|
+
ORDER BY value DESC
|
|
846
|
+
LIMIT {limit:UInt32}`,
|
|
847
|
+
{ ...params, ...filter.params }
|
|
848
|
+
);
|
|
849
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
850
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
708
853
|
case "top_utm_sources": {
|
|
709
854
|
const rows = await this.queryRows(
|
|
710
|
-
`SELECT
|
|
855
|
+
`SELECT ${normalizedUtmSourceExpr()} AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
711
856
|
WHERE site_id = {siteId:String}
|
|
712
857
|
AND timestamp >= {from:String}
|
|
713
858
|
AND timestamp <= {to:String}
|
|
714
859
|
AND utm_source IS NOT NULL AND utm_source != ''
|
|
715
860
|
${filterSql}
|
|
716
|
-
GROUP BY
|
|
861
|
+
GROUP BY key
|
|
717
862
|
ORDER BY value DESC
|
|
718
863
|
LIMIT {limit:UInt32}`,
|
|
719
864
|
{ ...params, ...filter.params }
|
|
@@ -724,13 +869,13 @@ var ClickHouseAdapter = class {
|
|
|
724
869
|
}
|
|
725
870
|
case "top_utm_mediums": {
|
|
726
871
|
const rows = await this.queryRows(
|
|
727
|
-
`SELECT
|
|
872
|
+
`SELECT ${normalizedUtmMediumExpr()} AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
728
873
|
WHERE site_id = {siteId:String}
|
|
729
874
|
AND timestamp >= {from:String}
|
|
730
875
|
AND timestamp <= {to:String}
|
|
731
876
|
AND utm_medium IS NOT NULL AND utm_medium != ''
|
|
732
877
|
${filterSql}
|
|
733
|
-
GROUP BY
|
|
878
|
+
GROUP BY key
|
|
734
879
|
ORDER BY value DESC
|
|
735
880
|
LIMIT {limit:UInt32}`,
|
|
736
881
|
{ ...params, ...filter.params }
|
|
@@ -756,6 +901,56 @@ var ClickHouseAdapter = class {
|
|
|
756
901
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
757
902
|
break;
|
|
758
903
|
}
|
|
904
|
+
case "top_utm_terms": {
|
|
905
|
+
const rows = await this.queryRows(
|
|
906
|
+
`SELECT utm_term AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
907
|
+
WHERE site_id = {siteId:String}
|
|
908
|
+
AND timestamp >= {from:String}
|
|
909
|
+
AND timestamp <= {to:String}
|
|
910
|
+
AND utm_term IS NOT NULL AND utm_term != ''
|
|
911
|
+
${filterSql}
|
|
912
|
+
GROUP BY utm_term
|
|
913
|
+
ORDER BY value DESC
|
|
914
|
+
LIMIT {limit:UInt32}`,
|
|
915
|
+
{ ...params, ...filter.params }
|
|
916
|
+
);
|
|
917
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
918
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
case "top_utm_contents": {
|
|
922
|
+
const rows = await this.queryRows(
|
|
923
|
+
`SELECT utm_content AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
924
|
+
WHERE site_id = {siteId:String}
|
|
925
|
+
AND timestamp >= {from:String}
|
|
926
|
+
AND timestamp <= {to:String}
|
|
927
|
+
AND utm_content IS NOT NULL AND utm_content != ''
|
|
928
|
+
${filterSql}
|
|
929
|
+
GROUP BY utm_content
|
|
930
|
+
ORDER BY value DESC
|
|
931
|
+
LIMIT {limit:UInt32}`,
|
|
932
|
+
{ ...params, ...filter.params }
|
|
933
|
+
);
|
|
934
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
935
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
case "top_channels": {
|
|
939
|
+
const rows = await this.queryRows(
|
|
940
|
+
`SELECT ${channelClassificationExpr()} AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
941
|
+
WHERE site_id = {siteId:String}
|
|
942
|
+
AND timestamp >= {from:String}
|
|
943
|
+
AND timestamp <= {to:String}
|
|
944
|
+
${filterSql}
|
|
945
|
+
GROUP BY key
|
|
946
|
+
ORDER BY value DESC
|
|
947
|
+
LIMIT {limit:UInt32}`,
|
|
948
|
+
{ ...params, ...filter.params }
|
|
949
|
+
);
|
|
950
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
951
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
759
954
|
}
|
|
760
955
|
const result = { metric: q.metric, period, data, total };
|
|
761
956
|
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
@@ -1158,7 +1353,7 @@ var ClickHouseAdapter = class {
|
|
|
1158
1353
|
async getMergedUserDetail(siteId, userId, visitorIds) {
|
|
1159
1354
|
const rows = await this.queryRows(
|
|
1160
1355
|
`SELECT
|
|
1161
|
-
anyLast(visitor_id) AS
|
|
1356
|
+
anyLast(visitor_id) AS last_visitor_id,
|
|
1162
1357
|
anyLast(traits) AS traits,
|
|
1163
1358
|
min(timestamp) AS firstSeen,
|
|
1164
1359
|
max(timestamp) AS lastSeen,
|
|
@@ -1190,7 +1385,7 @@ var ClickHouseAdapter = class {
|
|
|
1190
1385
|
if (rows.length === 0) return null;
|
|
1191
1386
|
const u = rows[0];
|
|
1192
1387
|
return {
|
|
1193
|
-
visitorId: String(u.
|
|
1388
|
+
visitorId: String(u.last_visitor_id),
|
|
1194
1389
|
visitorIds,
|
|
1195
1390
|
userId,
|
|
1196
1391
|
traits: this.parseJSON(u.traits),
|
|
@@ -1279,6 +1474,7 @@ var ClickHouseAdapter = class {
|
|
|
1279
1474
|
siteId: generateSiteId(),
|
|
1280
1475
|
secretKey: generateSecretKey(),
|
|
1281
1476
|
name: data.name,
|
|
1477
|
+
type: data.type ?? "web",
|
|
1282
1478
|
domain: data.domain,
|
|
1283
1479
|
allowedOrigins: data.allowedOrigins,
|
|
1284
1480
|
conversionEvents: data.conversionEvents,
|
|
@@ -1291,6 +1487,7 @@ var ClickHouseAdapter = class {
|
|
|
1291
1487
|
site_id: site.siteId,
|
|
1292
1488
|
secret_key: site.secretKey,
|
|
1293
1489
|
name: site.name,
|
|
1490
|
+
type: site.type ?? "web",
|
|
1294
1491
|
domain: site.domain ?? null,
|
|
1295
1492
|
allowed_origins: site.allowedOrigins ? JSON.stringify(site.allowedOrigins) : null,
|
|
1296
1493
|
conversion_events: site.conversionEvents ? JSON.stringify(site.conversionEvents) : null,
|
|
@@ -1305,7 +1502,7 @@ var ClickHouseAdapter = class {
|
|
|
1305
1502
|
}
|
|
1306
1503
|
async getSite(siteId) {
|
|
1307
1504
|
const rows = await this.queryRows(
|
|
1308
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1505
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1309
1506
|
FROM ${SITES_TABLE} FINAL
|
|
1310
1507
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
1311
1508
|
{ siteId }
|
|
@@ -1314,7 +1511,7 @@ var ClickHouseAdapter = class {
|
|
|
1314
1511
|
}
|
|
1315
1512
|
async getSiteBySecret(secretKey) {
|
|
1316
1513
|
const rows = await this.queryRows(
|
|
1317
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1514
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1318
1515
|
FROM ${SITES_TABLE} FINAL
|
|
1319
1516
|
WHERE secret_key = {secretKey:String} AND is_deleted = 0`,
|
|
1320
1517
|
{ secretKey }
|
|
@@ -1323,7 +1520,7 @@ var ClickHouseAdapter = class {
|
|
|
1323
1520
|
}
|
|
1324
1521
|
async listSites() {
|
|
1325
1522
|
const rows = await this.queryRows(
|
|
1326
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1523
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1327
1524
|
FROM ${SITES_TABLE} FINAL
|
|
1328
1525
|
WHERE is_deleted = 0
|
|
1329
1526
|
ORDER BY created_at DESC`,
|
|
@@ -1333,7 +1530,7 @@ var ClickHouseAdapter = class {
|
|
|
1333
1530
|
}
|
|
1334
1531
|
async updateSite(siteId, data) {
|
|
1335
1532
|
const currentRows = await this.queryRows(
|
|
1336
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at, version
|
|
1533
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, updated_at, version
|
|
1337
1534
|
FROM ${SITES_TABLE} FINAL
|
|
1338
1535
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
1339
1536
|
{ siteId }
|
|
@@ -1345,6 +1542,7 @@ var ClickHouseAdapter = class {
|
|
|
1345
1542
|
const nowCH = toCHDateTime(now);
|
|
1346
1543
|
const newVersion = Number(current.version) + 1;
|
|
1347
1544
|
const newName = data.name !== void 0 ? data.name : String(current.name);
|
|
1545
|
+
const newType = data.type !== void 0 ? data.type : current.type ? String(current.type) : "web";
|
|
1348
1546
|
const newDomain = data.domain !== void 0 ? data.domain || null : current.domain ? String(current.domain) : null;
|
|
1349
1547
|
const newOrigins = data.allowedOrigins !== void 0 ? data.allowedOrigins.length > 0 ? JSON.stringify(data.allowedOrigins) : null : current.allowed_origins ? String(current.allowed_origins) : null;
|
|
1350
1548
|
const newConversions = data.conversionEvents !== void 0 ? data.conversionEvents.length > 0 ? JSON.stringify(data.conversionEvents) : null : current.conversion_events ? String(current.conversion_events) : null;
|
|
@@ -1354,6 +1552,7 @@ var ClickHouseAdapter = class {
|
|
|
1354
1552
|
site_id: String(current.site_id),
|
|
1355
1553
|
secret_key: String(current.secret_key),
|
|
1356
1554
|
name: newName,
|
|
1555
|
+
type: newType,
|
|
1357
1556
|
domain: newDomain,
|
|
1358
1557
|
allowed_origins: newOrigins,
|
|
1359
1558
|
conversion_events: newConversions,
|
|
@@ -1368,6 +1567,7 @@ var ClickHouseAdapter = class {
|
|
|
1368
1567
|
siteId: String(current.site_id),
|
|
1369
1568
|
secretKey: String(current.secret_key),
|
|
1370
1569
|
name: newName,
|
|
1570
|
+
type: newType,
|
|
1371
1571
|
domain: newDomain ?? void 0,
|
|
1372
1572
|
allowedOrigins: newOrigins ? JSON.parse(newOrigins) : void 0,
|
|
1373
1573
|
conversionEvents: newConversions ? JSON.parse(newConversions) : void 0,
|
|
@@ -1377,7 +1577,7 @@ var ClickHouseAdapter = class {
|
|
|
1377
1577
|
}
|
|
1378
1578
|
async deleteSite(siteId) {
|
|
1379
1579
|
const currentRows = await this.queryRows(
|
|
1380
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, version
|
|
1580
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, version
|
|
1381
1581
|
FROM ${SITES_TABLE} FINAL
|
|
1382
1582
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
1383
1583
|
{ siteId }
|
|
@@ -1391,6 +1591,7 @@ var ClickHouseAdapter = class {
|
|
|
1391
1591
|
site_id: String(current.site_id),
|
|
1392
1592
|
secret_key: String(current.secret_key),
|
|
1393
1593
|
name: String(current.name),
|
|
1594
|
+
type: current.type ? String(current.type) : "web",
|
|
1394
1595
|
domain: current.domain ? String(current.domain) : null,
|
|
1395
1596
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
1396
1597
|
conversion_events: current.conversion_events ? String(current.conversion_events) : null,
|
|
@@ -1405,7 +1606,7 @@ var ClickHouseAdapter = class {
|
|
|
1405
1606
|
}
|
|
1406
1607
|
async regenerateSecret(siteId) {
|
|
1407
1608
|
const currentRows = await this.queryRows(
|
|
1408
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, version
|
|
1609
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, version
|
|
1409
1610
|
FROM ${SITES_TABLE} FINAL
|
|
1410
1611
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
1411
1612
|
{ siteId }
|
|
@@ -1422,6 +1623,7 @@ var ClickHouseAdapter = class {
|
|
|
1422
1623
|
site_id: String(current.site_id),
|
|
1423
1624
|
secret_key: newSecret,
|
|
1424
1625
|
name: String(current.name),
|
|
1626
|
+
type: current.type ? String(current.type) : "web",
|
|
1425
1627
|
domain: current.domain ? String(current.domain) : null,
|
|
1426
1628
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
1427
1629
|
conversion_events: current.conversion_events ? String(current.conversion_events) : null,
|
|
@@ -1436,6 +1638,7 @@ var ClickHouseAdapter = class {
|
|
|
1436
1638
|
siteId: String(current.site_id),
|
|
1437
1639
|
secretKey: newSecret,
|
|
1438
1640
|
name: String(current.name),
|
|
1641
|
+
type: current.type ? String(current.type) : "web",
|
|
1439
1642
|
domain: current.domain ? String(current.domain) : void 0,
|
|
1440
1643
|
allowedOrigins: current.allowed_origins ? JSON.parse(String(current.allowed_origins)) : void 0,
|
|
1441
1644
|
conversionEvents: current.conversion_events ? JSON.parse(String(current.conversion_events)) : void 0,
|
|
@@ -1457,6 +1660,7 @@ var ClickHouseAdapter = class {
|
|
|
1457
1660
|
siteId: String(row.site_id),
|
|
1458
1661
|
secretKey: String(row.secret_key),
|
|
1459
1662
|
name: String(row.name),
|
|
1663
|
+
type: row.type ? String(row.type) : "web",
|
|
1460
1664
|
domain: row.domain ? String(row.domain) : void 0,
|
|
1461
1665
|
allowedOrigins: row.allowed_origins ? JSON.parse(String(row.allowed_origins)) : void 0,
|
|
1462
1666
|
conversionEvents: row.conversion_events ? JSON.parse(String(row.conversion_events)) : void 0,
|
|
@@ -1520,6 +1724,130 @@ var import_mongodb = require("mongodb");
|
|
|
1520
1724
|
var EVENTS_COLLECTION = "litemetrics_events";
|
|
1521
1725
|
var SITES_COLLECTION = "litemetrics_sites";
|
|
1522
1726
|
var IDENTITY_MAP_COLLECTION = "litemetrics_identity_map";
|
|
1727
|
+
function normalizedUtmSourceSwitch() {
|
|
1728
|
+
return {
|
|
1729
|
+
$switch: {
|
|
1730
|
+
branches: [
|
|
1731
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["ig", "instagram", "instagram.com"]] }, then: "Instagram" },
|
|
1732
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["fb", "facebook", "facebook.com", "fb.com"]] }, then: "Facebook" },
|
|
1733
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["tw", "twitter", "twitter.com", "x", "x.com", "t.co"]] }, then: "X (Twitter)" },
|
|
1734
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["li", "linkedin", "linkedin.com"]] }, then: "LinkedIn" },
|
|
1735
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["yt", "youtube", "youtube.com"]] }, then: "YouTube" },
|
|
1736
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["goog", "google", "google.com"]] }, then: "Google" },
|
|
1737
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["gh", "github", "github.com"]] }, then: "GitHub" },
|
|
1738
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["reddit", "reddit.com"]] }, then: "Reddit" },
|
|
1739
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["pinterest", "pinterest.com"]] }, then: "Pinterest" },
|
|
1740
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["tiktok", "tiktok.com"]] }, then: "TikTok" },
|
|
1741
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["openai", "chatgpt", "chat.openai.com"]] }, then: "OpenAI" },
|
|
1742
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["perplexity", "perplexity.ai"]] }, then: "Perplexity" }
|
|
1743
|
+
],
|
|
1744
|
+
default: "$utm_source"
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
function normalizedUtmMediumSwitch() {
|
|
1749
|
+
return {
|
|
1750
|
+
$switch: {
|
|
1751
|
+
branches: [
|
|
1752
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["cpc", "ppc", "paidsearch", "paid-search", "paid_search", "paid"]] }, then: "Paid" },
|
|
1753
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["organic"]] }, then: "Organic" },
|
|
1754
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["social", "social-media", "social_media"]] }, then: "Social" },
|
|
1755
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["email", "e-mail", "e_mail"]] }, then: "Email" },
|
|
1756
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["display", "banner", "cpm"]] }, then: "Display" },
|
|
1757
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["affiliate"]] }, then: "Affiliate" },
|
|
1758
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["referral"]] }, then: "Referral" }
|
|
1759
|
+
],
|
|
1760
|
+
default: "$utm_medium"
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
var SEARCH_ENGINES = /google|bing|yahoo|duckduckgo|ecosia|baidu|yandex|search\.brave/i;
|
|
1765
|
+
var SOCIAL_NETWORKS = /instagram|facebook|twitter|x\.com|t\.co|linkedin|youtube|tiktok|pinterest|reddit|snapchat|mastodon|tumblr/i;
|
|
1766
|
+
var PAID_MEDIUMS = ["cpc", "ppc", "paidsearch", "paid-search", "paid_search", "paid"];
|
|
1767
|
+
var SOCIAL_SOURCES = ["instagram", "ig", "facebook", "fb", "twitter", "tw", "x", "linkedin", "li", "youtube", "yt", "tiktok", "pinterest", "reddit", "snapchat"];
|
|
1768
|
+
function channelClassificationSwitch() {
|
|
1769
|
+
const lMedium = { $toLower: { $ifNull: ["$utm_medium", ""] } };
|
|
1770
|
+
const lSource = { $toLower: { $ifNull: ["$utm_source", ""] } };
|
|
1771
|
+
const refStr = { $ifNull: ["$referrer", ""] };
|
|
1772
|
+
return {
|
|
1773
|
+
$switch: {
|
|
1774
|
+
branches: [
|
|
1775
|
+
// Paid Search
|
|
1776
|
+
{
|
|
1777
|
+
case: {
|
|
1778
|
+
$and: [
|
|
1779
|
+
{ $in: [lMedium, PAID_MEDIUMS] },
|
|
1780
|
+
{ $or: [
|
|
1781
|
+
{ $in: [lSource, ["google", "goog", "bing", "yahoo", "duckduckgo", "ecosia", "baidu", "yandex"]] },
|
|
1782
|
+
{ $regexMatch: { input: refStr, regex: SEARCH_ENGINES } }
|
|
1783
|
+
] }
|
|
1784
|
+
]
|
|
1785
|
+
},
|
|
1786
|
+
then: "Paid Search"
|
|
1787
|
+
},
|
|
1788
|
+
// Paid Social
|
|
1789
|
+
{
|
|
1790
|
+
case: {
|
|
1791
|
+
$and: [
|
|
1792
|
+
{ $in: [lMedium, PAID_MEDIUMS] },
|
|
1793
|
+
{ $or: [
|
|
1794
|
+
{ $in: [lSource, SOCIAL_SOURCES] },
|
|
1795
|
+
{ $regexMatch: { input: refStr, regex: SOCIAL_NETWORKS } }
|
|
1796
|
+
] }
|
|
1797
|
+
]
|
|
1798
|
+
},
|
|
1799
|
+
then: "Paid Social"
|
|
1800
|
+
},
|
|
1801
|
+
// Email
|
|
1802
|
+
{ case: { $in: [lMedium, ["email", "e-mail", "e_mail"]] }, then: "Email" },
|
|
1803
|
+
// Display
|
|
1804
|
+
{ case: { $in: [lMedium, ["display", "banner", "cpm"]] }, then: "Display" },
|
|
1805
|
+
// Affiliate
|
|
1806
|
+
{ case: { $in: [lMedium, ["affiliate"]] }, then: "Affiliate" },
|
|
1807
|
+
// Organic Search
|
|
1808
|
+
{
|
|
1809
|
+
case: {
|
|
1810
|
+
$and: [
|
|
1811
|
+
{ $regexMatch: { input: refStr, regex: SEARCH_ENGINES } },
|
|
1812
|
+
{ $not: [{ $in: [lMedium, PAID_MEDIUMS] }] }
|
|
1813
|
+
]
|
|
1814
|
+
},
|
|
1815
|
+
then: "Organic Search"
|
|
1816
|
+
},
|
|
1817
|
+
// Organic Social
|
|
1818
|
+
{
|
|
1819
|
+
case: {
|
|
1820
|
+
$and: [
|
|
1821
|
+
{ $or: [
|
|
1822
|
+
{ $regexMatch: { input: refStr, regex: SOCIAL_NETWORKS } },
|
|
1823
|
+
{ $in: [lSource, SOCIAL_SOURCES] }
|
|
1824
|
+
] },
|
|
1825
|
+
{ $not: [{ $in: [lMedium, PAID_MEDIUMS] }] }
|
|
1826
|
+
]
|
|
1827
|
+
},
|
|
1828
|
+
then: "Organic Social"
|
|
1829
|
+
},
|
|
1830
|
+
// Referral
|
|
1831
|
+
{
|
|
1832
|
+
case: { $and: [{ $ne: [refStr, ""] }, { $gt: [{ $strLenCP: refStr }, 0] }] },
|
|
1833
|
+
then: "Referral"
|
|
1834
|
+
},
|
|
1835
|
+
// Other (has UTM but no referrer)
|
|
1836
|
+
{
|
|
1837
|
+
case: {
|
|
1838
|
+
$or: [
|
|
1839
|
+
{ $and: [{ $ne: [{ $ifNull: ["$utm_source", ""] }, ""] }] },
|
|
1840
|
+
{ $and: [{ $ne: [{ $ifNull: ["$utm_medium", ""] }, ""] }] },
|
|
1841
|
+
{ $and: [{ $ne: [{ $ifNull: ["$utm_campaign", ""] }, ""] }] }
|
|
1842
|
+
]
|
|
1843
|
+
},
|
|
1844
|
+
then: "Other"
|
|
1845
|
+
}
|
|
1846
|
+
],
|
|
1847
|
+
default: "Direct"
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1523
1851
|
function buildFilterMatch(filters) {
|
|
1524
1852
|
if (!filters) return {};
|
|
1525
1853
|
const map = {
|
|
@@ -1530,6 +1858,10 @@ function buildFilterMatch(filters) {
|
|
|
1530
1858
|
"device.type": "device_type",
|
|
1531
1859
|
"device.browser": "browser",
|
|
1532
1860
|
"device.os": "os",
|
|
1861
|
+
"device.osVersion": "os_version",
|
|
1862
|
+
"device.deviceModel": "device_model",
|
|
1863
|
+
"device.deviceBrand": "device_brand",
|
|
1864
|
+
"device.appVersion": "app_version",
|
|
1533
1865
|
"utm.source": "utm_source",
|
|
1534
1866
|
"utm.medium": "utm_medium",
|
|
1535
1867
|
"utm.campaign": "utm_campaign",
|
|
@@ -1545,7 +1877,9 @@ function buildFilterMatch(filters) {
|
|
|
1545
1877
|
};
|
|
1546
1878
|
const match = {};
|
|
1547
1879
|
for (const [key, value] of Object.entries(filters)) {
|
|
1548
|
-
if (!value
|
|
1880
|
+
if (!value) continue;
|
|
1881
|
+
if (key === "channel") continue;
|
|
1882
|
+
if (!map[key]) continue;
|
|
1549
1883
|
match[map[key]] = value;
|
|
1550
1884
|
}
|
|
1551
1885
|
return match;
|
|
@@ -1614,6 +1948,13 @@ var MongoDBAdapter = class {
|
|
|
1614
1948
|
utm_term: e.utm?.term ?? null,
|
|
1615
1949
|
utm_content: e.utm?.content ?? null,
|
|
1616
1950
|
ip: e.ip ?? null,
|
|
1951
|
+
os_version: e.device?.osVersion ?? null,
|
|
1952
|
+
device_model: e.device?.deviceModel ?? null,
|
|
1953
|
+
device_brand: e.device?.deviceBrand ?? null,
|
|
1954
|
+
app_version: e.device?.appVersion ?? null,
|
|
1955
|
+
app_build: e.device?.appBuild ?? null,
|
|
1956
|
+
sdk_name: e.device?.sdkName ?? null,
|
|
1957
|
+
sdk_version: e.device?.sdkVersion ?? null,
|
|
1617
1958
|
created_at: /* @__PURE__ */ new Date()
|
|
1618
1959
|
}));
|
|
1619
1960
|
await this.collection.insertMany(docs);
|
|
@@ -1627,12 +1968,22 @@ var MongoDBAdapter = class {
|
|
|
1627
1968
|
timestamp: { $gte: new Date(dateRange.from), $lte: new Date(dateRange.to) }
|
|
1628
1969
|
};
|
|
1629
1970
|
const filterMatch = buildFilterMatch(q.filters);
|
|
1971
|
+
const matchStages = (extra) => {
|
|
1972
|
+
const stages = [
|
|
1973
|
+
{ $match: { ...baseMatch, ...filterMatch, ...extra } }
|
|
1974
|
+
];
|
|
1975
|
+
if (q.filters?.channel) {
|
|
1976
|
+
stages.push({ $addFields: { _channel: channelClassificationSwitch() } });
|
|
1977
|
+
stages.push({ $match: { _channel: q.filters.channel } });
|
|
1978
|
+
}
|
|
1979
|
+
return stages;
|
|
1980
|
+
};
|
|
1630
1981
|
let data = [];
|
|
1631
1982
|
let total = 0;
|
|
1632
1983
|
switch (q.metric) {
|
|
1633
1984
|
case "pageviews": {
|
|
1634
1985
|
const [result2] = await this.collection.aggregate([
|
|
1635
|
-
{
|
|
1986
|
+
...matchStages({ type: "pageview" }),
|
|
1636
1987
|
{ $count: "count" }
|
|
1637
1988
|
]).toArray();
|
|
1638
1989
|
total = result2?.count ?? 0;
|
|
@@ -1641,7 +1992,7 @@ var MongoDBAdapter = class {
|
|
|
1641
1992
|
}
|
|
1642
1993
|
case "visitors": {
|
|
1643
1994
|
const [result2] = await this.collection.aggregate([
|
|
1644
|
-
|
|
1995
|
+
...matchStages(),
|
|
1645
1996
|
{ $group: { _id: "$visitor_id" } },
|
|
1646
1997
|
{ $count: "count" }
|
|
1647
1998
|
]).toArray();
|
|
@@ -1651,7 +2002,7 @@ var MongoDBAdapter = class {
|
|
|
1651
2002
|
}
|
|
1652
2003
|
case "sessions": {
|
|
1653
2004
|
const [result2] = await this.collection.aggregate([
|
|
1654
|
-
|
|
2005
|
+
...matchStages(),
|
|
1655
2006
|
{ $group: { _id: "$session_id" } },
|
|
1656
2007
|
{ $count: "count" }
|
|
1657
2008
|
]).toArray();
|
|
@@ -1661,7 +2012,7 @@ var MongoDBAdapter = class {
|
|
|
1661
2012
|
}
|
|
1662
2013
|
case "events": {
|
|
1663
2014
|
const [result2] = await this.collection.aggregate([
|
|
1664
|
-
{
|
|
2015
|
+
...matchStages({ type: "event" }),
|
|
1665
2016
|
{ $count: "count" }
|
|
1666
2017
|
]).toArray();
|
|
1667
2018
|
total = result2?.count ?? 0;
|
|
@@ -1676,7 +2027,7 @@ var MongoDBAdapter = class {
|
|
|
1676
2027
|
break;
|
|
1677
2028
|
}
|
|
1678
2029
|
const [result2] = await this.collection.aggregate([
|
|
1679
|
-
{
|
|
2030
|
+
...matchStages({ type: "event", event_name: { $in: conversionEvents } }),
|
|
1680
2031
|
{ $count: "count" }
|
|
1681
2032
|
]).toArray();
|
|
1682
2033
|
total = result2?.count ?? 0;
|
|
@@ -1685,7 +2036,7 @@ var MongoDBAdapter = class {
|
|
|
1685
2036
|
}
|
|
1686
2037
|
case "top_pages": {
|
|
1687
2038
|
const rows = await this.collection.aggregate([
|
|
1688
|
-
{
|
|
2039
|
+
...matchStages({ type: "pageview", url: { $ne: null } }),
|
|
1689
2040
|
{ $group: { _id: "$url", value: { $sum: 1 } } },
|
|
1690
2041
|
{ $sort: { value: -1 } },
|
|
1691
2042
|
{ $limit: limit }
|
|
@@ -1696,7 +2047,7 @@ var MongoDBAdapter = class {
|
|
|
1696
2047
|
}
|
|
1697
2048
|
case "top_referrers": {
|
|
1698
2049
|
const rows = await this.collection.aggregate([
|
|
1699
|
-
{
|
|
2050
|
+
...matchStages({ type: "pageview", referrer: { $nin: [null, ""] } }),
|
|
1700
2051
|
{ $group: { _id: "$referrer", value: { $sum: 1 } } },
|
|
1701
2052
|
{ $sort: { value: -1 } },
|
|
1702
2053
|
{ $limit: limit }
|
|
@@ -1707,7 +2058,7 @@ var MongoDBAdapter = class {
|
|
|
1707
2058
|
}
|
|
1708
2059
|
case "top_countries": {
|
|
1709
2060
|
const rows = await this.collection.aggregate([
|
|
1710
|
-
{
|
|
2061
|
+
...matchStages({ country: { $ne: null } }),
|
|
1711
2062
|
{ $group: { _id: "$country", value: { $addToSet: "$visitor_id" } } },
|
|
1712
2063
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1713
2064
|
{ $sort: { value: -1 } },
|
|
@@ -1719,7 +2070,7 @@ var MongoDBAdapter = class {
|
|
|
1719
2070
|
}
|
|
1720
2071
|
case "top_cities": {
|
|
1721
2072
|
const rows = await this.collection.aggregate([
|
|
1722
|
-
{
|
|
2073
|
+
...matchStages({ city: { $ne: null } }),
|
|
1723
2074
|
{ $group: { _id: "$city", value: { $addToSet: "$visitor_id" } } },
|
|
1724
2075
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1725
2076
|
{ $sort: { value: -1 } },
|
|
@@ -1731,7 +2082,7 @@ var MongoDBAdapter = class {
|
|
|
1731
2082
|
}
|
|
1732
2083
|
case "top_events": {
|
|
1733
2084
|
const rows = await this.collection.aggregate([
|
|
1734
|
-
{
|
|
2085
|
+
...matchStages({ type: "event", event_name: { $ne: null } }),
|
|
1735
2086
|
{ $group: { _id: "$event_name", value: { $sum: 1 } } },
|
|
1736
2087
|
{ $sort: { value: -1 } },
|
|
1737
2088
|
{ $limit: limit }
|
|
@@ -1748,7 +2099,7 @@ var MongoDBAdapter = class {
|
|
|
1748
2099
|
break;
|
|
1749
2100
|
}
|
|
1750
2101
|
const rows = await this.collection.aggregate([
|
|
1751
|
-
{
|
|
2102
|
+
...matchStages({ type: "event", event_name: { $in: conversionEvents } }),
|
|
1752
2103
|
{ $group: { _id: "$event_name", value: { $sum: 1 } } },
|
|
1753
2104
|
{ $sort: { value: -1 } },
|
|
1754
2105
|
{ $limit: limit }
|
|
@@ -1759,7 +2110,7 @@ var MongoDBAdapter = class {
|
|
|
1759
2110
|
}
|
|
1760
2111
|
case "top_exit_pages": {
|
|
1761
2112
|
const rows = await this.collection.aggregate([
|
|
1762
|
-
{
|
|
2113
|
+
...matchStages({ type: "pageview", url: { $ne: null } }),
|
|
1763
2114
|
{ $sort: { timestamp: 1 } },
|
|
1764
2115
|
{ $group: { _id: "$session_id", url: { $last: "$url" } } },
|
|
1765
2116
|
{ $group: { _id: "$url", value: { $sum: 1 } } },
|
|
@@ -1772,7 +2123,7 @@ var MongoDBAdapter = class {
|
|
|
1772
2123
|
}
|
|
1773
2124
|
case "top_transitions": {
|
|
1774
2125
|
const rows = await this.collection.aggregate([
|
|
1775
|
-
{
|
|
2126
|
+
...matchStages({ type: "pageview", url: { $ne: null } }),
|
|
1776
2127
|
{
|
|
1777
2128
|
$setWindowFields: {
|
|
1778
2129
|
partitionBy: "$session_id",
|
|
@@ -1793,7 +2144,7 @@ var MongoDBAdapter = class {
|
|
|
1793
2144
|
}
|
|
1794
2145
|
case "top_scroll_pages": {
|
|
1795
2146
|
const rows = await this.collection.aggregate([
|
|
1796
|
-
{
|
|
2147
|
+
...matchStages({ type: "event", event_subtype: "scroll_depth", page_path: { $ne: null } }),
|
|
1797
2148
|
{ $group: { _id: "$page_path", value: { $sum: 1 } } },
|
|
1798
2149
|
{ $sort: { value: -1 } },
|
|
1799
2150
|
{ $limit: limit }
|
|
@@ -1804,15 +2155,11 @@ var MongoDBAdapter = class {
|
|
|
1804
2155
|
}
|
|
1805
2156
|
case "top_button_clicks": {
|
|
1806
2157
|
const rows = await this.collection.aggregate([
|
|
1807
|
-
{
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
event_subtype: "button_click",
|
|
1813
|
-
$or: [{ element_text: { $ne: null } }, { element_selector: { $ne: null } }]
|
|
1814
|
-
}
|
|
1815
|
-
},
|
|
2158
|
+
...matchStages({
|
|
2159
|
+
type: "event",
|
|
2160
|
+
event_subtype: "button_click",
|
|
2161
|
+
$or: [{ element_text: { $ne: null } }, { element_selector: { $ne: null } }]
|
|
2162
|
+
}),
|
|
1816
2163
|
{ $group: { _id: { $ifNull: ["$element_text", "$element_selector"] }, value: { $sum: 1 } } },
|
|
1817
2164
|
{ $sort: { value: -1 } },
|
|
1818
2165
|
{ $limit: limit }
|
|
@@ -1823,15 +2170,11 @@ var MongoDBAdapter = class {
|
|
|
1823
2170
|
}
|
|
1824
2171
|
case "top_link_targets": {
|
|
1825
2172
|
const rows = await this.collection.aggregate([
|
|
1826
|
-
{
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
event_subtype: { $in: ["link_click", "outbound_click"] },
|
|
1832
|
-
target_url_path: { $ne: null }
|
|
1833
|
-
}
|
|
1834
|
-
},
|
|
2173
|
+
...matchStages({
|
|
2174
|
+
type: "event",
|
|
2175
|
+
event_subtype: { $in: ["link_click", "outbound_click"] },
|
|
2176
|
+
target_url_path: { $ne: null }
|
|
2177
|
+
}),
|
|
1835
2178
|
{ $group: { _id: "$target_url_path", value: { $sum: 1 } } },
|
|
1836
2179
|
{ $sort: { value: -1 } },
|
|
1837
2180
|
{ $limit: limit }
|
|
@@ -1842,7 +2185,7 @@ var MongoDBAdapter = class {
|
|
|
1842
2185
|
}
|
|
1843
2186
|
case "top_devices": {
|
|
1844
2187
|
const rows = await this.collection.aggregate([
|
|
1845
|
-
{
|
|
2188
|
+
...matchStages({ device_type: { $ne: null } }),
|
|
1846
2189
|
{ $group: { _id: "$device_type", value: { $addToSet: "$visitor_id" } } },
|
|
1847
2190
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1848
2191
|
{ $sort: { value: -1 } },
|
|
@@ -1854,7 +2197,7 @@ var MongoDBAdapter = class {
|
|
|
1854
2197
|
}
|
|
1855
2198
|
case "top_browsers": {
|
|
1856
2199
|
const rows = await this.collection.aggregate([
|
|
1857
|
-
{
|
|
2200
|
+
...matchStages({ browser: { $ne: null } }),
|
|
1858
2201
|
{ $group: { _id: "$browser", value: { $addToSet: "$visitor_id" } } },
|
|
1859
2202
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1860
2203
|
{ $sort: { value: -1 } },
|
|
@@ -1866,7 +2209,7 @@ var MongoDBAdapter = class {
|
|
|
1866
2209
|
}
|
|
1867
2210
|
case "top_os": {
|
|
1868
2211
|
const rows = await this.collection.aggregate([
|
|
1869
|
-
{
|
|
2212
|
+
...matchStages({ os: { $ne: null } }),
|
|
1870
2213
|
{ $group: { _id: "$os", value: { $addToSet: "$visitor_id" } } },
|
|
1871
2214
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1872
2215
|
{ $sort: { value: -1 } },
|
|
@@ -1876,10 +2219,47 @@ var MongoDBAdapter = class {
|
|
|
1876
2219
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1877
2220
|
break;
|
|
1878
2221
|
}
|
|
2222
|
+
case "top_os_versions": {
|
|
2223
|
+
const rows = await this.collection.aggregate([
|
|
2224
|
+
...matchStages({ os: { $ne: null }, os_version: { $ne: null } }),
|
|
2225
|
+
{ $group: { _id: { $concat: ["$os", " ", "$os_version"] }, value: { $addToSet: "$visitor_id" } } },
|
|
2226
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2227
|
+
{ $sort: { value: -1 } },
|
|
2228
|
+
{ $limit: limit }
|
|
2229
|
+
]).toArray();
|
|
2230
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2231
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2232
|
+
break;
|
|
2233
|
+
}
|
|
2234
|
+
case "top_device_models": {
|
|
2235
|
+
const rows = await this.collection.aggregate([
|
|
2236
|
+
...matchStages({ device_model: { $ne: null } }),
|
|
2237
|
+
{ $group: { _id: { $trim: { input: { $concat: [{ $ifNull: ["$device_brand", ""] }, " ", "$device_model"] } } }, value: { $addToSet: "$visitor_id" } } },
|
|
2238
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2239
|
+
{ $sort: { value: -1 } },
|
|
2240
|
+
{ $limit: limit }
|
|
2241
|
+
]).toArray();
|
|
2242
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2243
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2244
|
+
break;
|
|
2245
|
+
}
|
|
2246
|
+
case "top_app_versions": {
|
|
2247
|
+
const rows = await this.collection.aggregate([
|
|
2248
|
+
...matchStages({ app_version: { $ne: null } }),
|
|
2249
|
+
{ $group: { _id: "$app_version", value: { $addToSet: "$visitor_id" } } },
|
|
2250
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2251
|
+
{ $sort: { value: -1 } },
|
|
2252
|
+
{ $limit: limit }
|
|
2253
|
+
]).toArray();
|
|
2254
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2255
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2256
|
+
break;
|
|
2257
|
+
}
|
|
1879
2258
|
case "top_utm_sources": {
|
|
1880
2259
|
const rows = await this.collection.aggregate([
|
|
1881
|
-
{
|
|
1882
|
-
{ $
|
|
2260
|
+
...matchStages({ utm_source: { $nin: [null, ""] } }),
|
|
2261
|
+
{ $addFields: { _normalized_source: normalizedUtmSourceSwitch() } },
|
|
2262
|
+
{ $group: { _id: "$_normalized_source", value: { $addToSet: "$visitor_id" } } },
|
|
1883
2263
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1884
2264
|
{ $sort: { value: -1 } },
|
|
1885
2265
|
{ $limit: limit }
|
|
@@ -1890,8 +2270,9 @@ var MongoDBAdapter = class {
|
|
|
1890
2270
|
}
|
|
1891
2271
|
case "top_utm_mediums": {
|
|
1892
2272
|
const rows = await this.collection.aggregate([
|
|
1893
|
-
{
|
|
1894
|
-
{ $
|
|
2273
|
+
...matchStages({ utm_medium: { $nin: [null, ""] } }),
|
|
2274
|
+
{ $addFields: { _normalized_medium: normalizedUtmMediumSwitch() } },
|
|
2275
|
+
{ $group: { _id: "$_normalized_medium", value: { $addToSet: "$visitor_id" } } },
|
|
1895
2276
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1896
2277
|
{ $sort: { value: -1 } },
|
|
1897
2278
|
{ $limit: limit }
|
|
@@ -1902,7 +2283,7 @@ var MongoDBAdapter = class {
|
|
|
1902
2283
|
}
|
|
1903
2284
|
case "top_utm_campaigns": {
|
|
1904
2285
|
const rows = await this.collection.aggregate([
|
|
1905
|
-
{
|
|
2286
|
+
...matchStages({ utm_campaign: { $nin: [null, ""] } }),
|
|
1906
2287
|
{ $group: { _id: "$utm_campaign", value: { $addToSet: "$visitor_id" } } },
|
|
1907
2288
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1908
2289
|
{ $sort: { value: -1 } },
|
|
@@ -1912,6 +2293,43 @@ var MongoDBAdapter = class {
|
|
|
1912
2293
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1913
2294
|
break;
|
|
1914
2295
|
}
|
|
2296
|
+
case "top_utm_terms": {
|
|
2297
|
+
const rows = await this.collection.aggregate([
|
|
2298
|
+
...matchStages({ utm_term: { $nin: [null, ""] } }),
|
|
2299
|
+
{ $group: { _id: "$utm_term", value: { $addToSet: "$visitor_id" } } },
|
|
2300
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2301
|
+
{ $sort: { value: -1 } },
|
|
2302
|
+
{ $limit: limit }
|
|
2303
|
+
]).toArray();
|
|
2304
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2305
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2306
|
+
break;
|
|
2307
|
+
}
|
|
2308
|
+
case "top_utm_contents": {
|
|
2309
|
+
const rows = await this.collection.aggregate([
|
|
2310
|
+
...matchStages({ utm_content: { $nin: [null, ""] } }),
|
|
2311
|
+
{ $group: { _id: "$utm_content", value: { $addToSet: "$visitor_id" } } },
|
|
2312
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2313
|
+
{ $sort: { value: -1 } },
|
|
2314
|
+
{ $limit: limit }
|
|
2315
|
+
]).toArray();
|
|
2316
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2317
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2318
|
+
break;
|
|
2319
|
+
}
|
|
2320
|
+
case "top_channels": {
|
|
2321
|
+
const rows = await this.collection.aggregate([
|
|
2322
|
+
...matchStages(),
|
|
2323
|
+
{ $addFields: { _channel: channelClassificationSwitch() } },
|
|
2324
|
+
{ $group: { _id: "$_channel", value: { $addToSet: "$visitor_id" } } },
|
|
2325
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2326
|
+
{ $sort: { value: -1 } },
|
|
2327
|
+
{ $limit: limit }
|
|
2328
|
+
]).toArray();
|
|
2329
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2330
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2331
|
+
break;
|
|
2332
|
+
}
|
|
1915
2333
|
}
|
|
1916
2334
|
const result = { metric: q.metric, period, data, total };
|
|
1917
2335
|
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
@@ -2374,7 +2792,18 @@ var MongoDBAdapter = class {
|
|
|
2374
2792
|
userId: doc.user_id ?? void 0,
|
|
2375
2793
|
traits: doc.traits ?? void 0,
|
|
2376
2794
|
geo: doc.country ? { country: doc.country, city: doc.city ?? void 0, region: doc.region ?? void 0 } : void 0,
|
|
2377
|
-
device: doc.device_type ? {
|
|
2795
|
+
device: doc.device_type ? {
|
|
2796
|
+
type: doc.device_type,
|
|
2797
|
+
browser: doc.browser ?? "",
|
|
2798
|
+
os: doc.os ?? "",
|
|
2799
|
+
osVersion: doc.os_version ?? void 0,
|
|
2800
|
+
deviceModel: doc.device_model ?? void 0,
|
|
2801
|
+
deviceBrand: doc.device_brand ?? void 0,
|
|
2802
|
+
appVersion: doc.app_version ?? void 0,
|
|
2803
|
+
appBuild: doc.app_build ?? void 0,
|
|
2804
|
+
sdkName: doc.sdk_name ?? void 0,
|
|
2805
|
+
sdkVersion: doc.sdk_version ?? void 0
|
|
2806
|
+
} : void 0,
|
|
2378
2807
|
language: doc.language ?? void 0,
|
|
2379
2808
|
utm: doc.utm_source ? {
|
|
2380
2809
|
source: doc.utm_source ?? void 0,
|
|
@@ -2392,6 +2821,7 @@ var MongoDBAdapter = class {
|
|
|
2392
2821
|
site_id: generateSiteId(),
|
|
2393
2822
|
secret_key: generateSecretKey(),
|
|
2394
2823
|
name: data.name,
|
|
2824
|
+
type: data.type ?? "web",
|
|
2395
2825
|
domain: data.domain ?? null,
|
|
2396
2826
|
allowed_origins: data.allowedOrigins ?? null,
|
|
2397
2827
|
conversion_events: data.conversionEvents ?? null,
|
|
@@ -2416,6 +2846,7 @@ var MongoDBAdapter = class {
|
|
|
2416
2846
|
async updateSite(siteId, data) {
|
|
2417
2847
|
const updates = { updated_at: /* @__PURE__ */ new Date() };
|
|
2418
2848
|
if (data.name !== void 0) updates.name = data.name;
|
|
2849
|
+
if (data.type !== void 0) updates.type = data.type;
|
|
2419
2850
|
if (data.domain !== void 0) updates.domain = data.domain || null;
|
|
2420
2851
|
if (data.allowedOrigins !== void 0) updates.allowed_origins = data.allowedOrigins.length > 0 ? data.allowedOrigins : null;
|
|
2421
2852
|
if (data.conversionEvents !== void 0) updates.conversion_events = data.conversionEvents.length > 0 ? data.conversionEvents : null;
|
|
@@ -2447,6 +2878,7 @@ var MongoDBAdapter = class {
|
|
|
2447
2878
|
siteId: doc.site_id,
|
|
2448
2879
|
secretKey: doc.secret_key,
|
|
2449
2880
|
name: doc.name,
|
|
2881
|
+
type: doc.type ?? "web",
|
|
2450
2882
|
domain: doc.domain ?? void 0,
|
|
2451
2883
|
allowedOrigins: doc.allowed_origins ?? void 0,
|
|
2452
2884
|
conversionEvents: doc.conversion_events ?? void 0,
|
|
@@ -2715,9 +3147,26 @@ async function createCollector(config) {
|
|
|
2715
3147
|
return false;
|
|
2716
3148
|
}
|
|
2717
3149
|
function enrichEvents(events, ip, userAgent) {
|
|
2718
|
-
const
|
|
3150
|
+
const uaDevice = parseUserAgent(userAgent);
|
|
2719
3151
|
return events.map((event) => {
|
|
2720
3152
|
const geo = resolveGeo(ip, event.timezone);
|
|
3153
|
+
let device;
|
|
3154
|
+
if (event.mobile?.platform) {
|
|
3155
|
+
device = {
|
|
3156
|
+
type: "mobile",
|
|
3157
|
+
browser: "App",
|
|
3158
|
+
os: event.mobile.platform === "ios" ? "iOS" : "Android",
|
|
3159
|
+
osVersion: event.mobile.osVersion,
|
|
3160
|
+
deviceModel: event.mobile.deviceModel,
|
|
3161
|
+
deviceBrand: event.mobile.deviceBrand,
|
|
3162
|
+
appVersion: event.mobile.appVersion,
|
|
3163
|
+
appBuild: event.mobile.appBuild,
|
|
3164
|
+
sdkName: event.mobile.sdkName,
|
|
3165
|
+
sdkVersion: event.mobile.sdkVersion
|
|
3166
|
+
};
|
|
3167
|
+
} else {
|
|
3168
|
+
device = uaDevice;
|
|
3169
|
+
}
|
|
2721
3170
|
return { ...event, ip, geo, device };
|
|
2722
3171
|
});
|
|
2723
3172
|
}
|
|
@@ -2777,6 +3226,22 @@ async function createCollector(config) {
|
|
|
2777
3226
|
}
|
|
2778
3227
|
return req.ip || req.socket?.remoteAddress || req.connection?.remoteAddress || "";
|
|
2779
3228
|
}
|
|
3229
|
+
function extractRequestHostname(req) {
|
|
3230
|
+
const headerValue = (value) => {
|
|
3231
|
+
if (Array.isArray(value)) return value[0];
|
|
3232
|
+
if (typeof value === "string") return value;
|
|
3233
|
+
return void 0;
|
|
3234
|
+
};
|
|
3235
|
+
const origin = headerValue(req.headers?.origin);
|
|
3236
|
+
const referer = headerValue(req.headers?.referer) || headerValue(req.headers?.referrer);
|
|
3237
|
+
const raw = origin ?? referer;
|
|
3238
|
+
if (!raw || raw === "null") return void 0;
|
|
3239
|
+
try {
|
|
3240
|
+
return new URL(raw).hostname.toLowerCase();
|
|
3241
|
+
} catch {
|
|
3242
|
+
return void 0;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
2780
3245
|
function handler() {
|
|
2781
3246
|
return async (req, res) => {
|
|
2782
3247
|
res.setHeader?.("Access-Control-Allow-Origin", "*");
|
|
@@ -2802,6 +3267,12 @@ async function createCollector(config) {
|
|
|
2802
3267
|
sendJson(res, 400, { ok: false, error: "Too many events (max 100)" });
|
|
2803
3268
|
return;
|
|
2804
3269
|
}
|
|
3270
|
+
const siteIds = new Set(payload.events.map((event) => event.siteId).filter(Boolean));
|
|
3271
|
+
if (siteIds.size !== 1) {
|
|
3272
|
+
sendJson(res, 200, { ok: true });
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
const siteId = Array.from(siteIds)[0];
|
|
2805
3276
|
const userAgent = req.headers?.["user-agent"] || "";
|
|
2806
3277
|
if (isBot(userAgent)) {
|
|
2807
3278
|
sendJson(res, 200, { ok: true });
|
|
@@ -2809,26 +3280,15 @@ async function createCollector(config) {
|
|
|
2809
3280
|
}
|
|
2810
3281
|
const ip = extractIp(req);
|
|
2811
3282
|
const enriched = enrichEvents(payload.events, ip, userAgent);
|
|
2812
|
-
const
|
|
2813
|
-
if (
|
|
2814
|
-
const
|
|
2815
|
-
if (
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
return allowed.has(hostname);
|
|
2822
|
-
} catch {
|
|
2823
|
-
return true;
|
|
2824
|
-
}
|
|
2825
|
-
});
|
|
2826
|
-
if (filtered.length === 0) {
|
|
2827
|
-
sendJson(res, 200, { ok: true });
|
|
2828
|
-
return;
|
|
2829
|
-
}
|
|
2830
|
-
await processIdentity(filtered);
|
|
2831
|
-
await db.insertEvents(filtered);
|
|
3283
|
+
const site = await db.getSite(siteId);
|
|
3284
|
+
if (site?.allowedOrigins && site.allowedOrigins.length > 0) {
|
|
3285
|
+
const requestHostname = extractRequestHostname(req);
|
|
3286
|
+
if (!requestHostname) {
|
|
3287
|
+
sendJson(res, 200, { ok: true });
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
const allowed = new Set(site.allowedOrigins.map((h) => h.toLowerCase()));
|
|
3291
|
+
if (!allowed.has(requestHostname)) {
|
|
2832
3292
|
sendJson(res, 200, { ok: true });
|
|
2833
3293
|
return;
|
|
2834
3294
|
}
|