@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.js
CHANGED
|
@@ -190,6 +190,13 @@ CREATE TABLE IF NOT EXISTS ${EVENTS_TABLE} (
|
|
|
190
190
|
utm_term Nullable(String),
|
|
191
191
|
utm_content Nullable(String),
|
|
192
192
|
ip Nullable(String),
|
|
193
|
+
os_version LowCardinality(Nullable(String)),
|
|
194
|
+
device_model LowCardinality(Nullable(String)),
|
|
195
|
+
device_brand LowCardinality(Nullable(String)),
|
|
196
|
+
app_version LowCardinality(Nullable(String)),
|
|
197
|
+
app_build Nullable(String),
|
|
198
|
+
sdk_name LowCardinality(Nullable(String)),
|
|
199
|
+
sdk_version LowCardinality(Nullable(String)),
|
|
193
200
|
created_at DateTime64(3) DEFAULT now64(3)
|
|
194
201
|
) ENGINE = MergeTree()
|
|
195
202
|
PARTITION BY toYYYYMM(timestamp)
|
|
@@ -201,6 +208,7 @@ CREATE TABLE IF NOT EXISTS ${SITES_TABLE} (
|
|
|
201
208
|
site_id String,
|
|
202
209
|
secret_key String,
|
|
203
210
|
name String,
|
|
211
|
+
type LowCardinality(Nullable(String)) DEFAULT 'web',
|
|
204
212
|
domain Nullable(String),
|
|
205
213
|
allowed_origins Nullable(String),
|
|
206
214
|
conversion_events Nullable(String),
|
|
@@ -216,6 +224,65 @@ function toCHDateTime(d) {
|
|
|
216
224
|
const iso = typeof d === "string" ? d : d.toISOString();
|
|
217
225
|
return iso.replace("T", " ").replace("Z", "");
|
|
218
226
|
}
|
|
227
|
+
function normalizedUtmSourceExpr() {
|
|
228
|
+
return `multiIf(
|
|
229
|
+
lower(utm_source) IN ('ig','instagram','instagram.com'), 'Instagram',
|
|
230
|
+
lower(utm_source) IN ('fb','facebook','facebook.com','fb.com'), 'Facebook',
|
|
231
|
+
lower(utm_source) IN ('tw','twitter','twitter.com','x','x.com','t.co'), 'X (Twitter)',
|
|
232
|
+
lower(utm_source) IN ('li','linkedin','linkedin.com'), 'LinkedIn',
|
|
233
|
+
lower(utm_source) IN ('yt','youtube','youtube.com'), 'YouTube',
|
|
234
|
+
lower(utm_source) IN ('goog','google','google.com'), 'Google',
|
|
235
|
+
lower(utm_source) IN ('gh','github','github.com'), 'GitHub',
|
|
236
|
+
lower(utm_source) IN ('reddit','reddit.com'), 'Reddit',
|
|
237
|
+
lower(utm_source) IN ('pinterest','pinterest.com'), 'Pinterest',
|
|
238
|
+
lower(utm_source) IN ('tiktok','tiktok.com'), 'TikTok',
|
|
239
|
+
lower(utm_source) IN ('openai','chatgpt','chat.openai.com'), 'OpenAI',
|
|
240
|
+
lower(utm_source) IN ('perplexity','perplexity.ai'), 'Perplexity',
|
|
241
|
+
utm_source
|
|
242
|
+
)`;
|
|
243
|
+
}
|
|
244
|
+
function normalizedUtmMediumExpr() {
|
|
245
|
+
return `multiIf(
|
|
246
|
+
lower(utm_medium) IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid'), 'Paid',
|
|
247
|
+
lower(utm_medium) IN ('organic'), 'Organic',
|
|
248
|
+
lower(utm_medium) IN ('social','social-media','social_media'), 'Social',
|
|
249
|
+
lower(utm_medium) IN ('email','e-mail','e_mail'), 'Email',
|
|
250
|
+
lower(utm_medium) IN ('display','banner','cpm'), 'Display',
|
|
251
|
+
lower(utm_medium) IN ('affiliate'), 'Affiliate',
|
|
252
|
+
lower(utm_medium) IN ('referral'), 'Referral',
|
|
253
|
+
utm_medium
|
|
254
|
+
)`;
|
|
255
|
+
}
|
|
256
|
+
function channelClassificationExpr() {
|
|
257
|
+
return `multiIf(
|
|
258
|
+
lower(ifNull(utm_medium,'')) IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid')
|
|
259
|
+
AND (lower(ifNull(utm_source,'')) IN ('google','goog','bing','yahoo','duckduckgo','ecosia','baidu','yandex')
|
|
260
|
+
OR multiSearchAnyCaseInsensitive(ifNull(referrer,''), ['google','bing','yahoo','duckduckgo','ecosia','baidu','yandex','search.brave']) > 0),
|
|
261
|
+
'Paid Search',
|
|
262
|
+
lower(ifNull(utm_medium,'')) IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid')
|
|
263
|
+
AND (lower(ifNull(utm_source,'')) IN ('instagram','ig','facebook','fb','twitter','tw','x','linkedin','li','youtube','yt','tiktok','pinterest','reddit','snapchat')
|
|
264
|
+
OR multiSearchAnyCaseInsensitive(ifNull(referrer,''), ['instagram','facebook','twitter','x.com','t.co','linkedin','youtube','tiktok','pinterest','reddit','snapchat']) > 0),
|
|
265
|
+
'Paid Social',
|
|
266
|
+
lower(ifNull(utm_medium,'')) IN ('email','e-mail','e_mail'),
|
|
267
|
+
'Email',
|
|
268
|
+
lower(ifNull(utm_medium,'')) IN ('display','banner','cpm'),
|
|
269
|
+
'Display',
|
|
270
|
+
lower(ifNull(utm_medium,'')) IN ('affiliate'),
|
|
271
|
+
'Affiliate',
|
|
272
|
+
multiSearchAnyCaseInsensitive(ifNull(referrer,''), ['google','bing','yahoo','duckduckgo','ecosia','baidu','yandex','search.brave']) > 0
|
|
273
|
+
AND (ifNull(utm_medium,'') = '' OR lower(utm_medium) NOT IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid')),
|
|
274
|
+
'Organic Search',
|
|
275
|
+
(multiSearchAnyCaseInsensitive(ifNull(referrer,''), ['instagram','facebook','twitter','x.com','t.co','linkedin','youtube','tiktok','pinterest','reddit','snapchat','mastodon','tumblr']) > 0
|
|
276
|
+
OR lower(ifNull(utm_source,'')) IN ('instagram','ig','facebook','fb','twitter','tw','x','linkedin','li','youtube','yt','tiktok','pinterest','reddit','snapchat'))
|
|
277
|
+
AND (ifNull(utm_medium,'') = '' OR lower(utm_medium) NOT IN ('cpc','ppc','paidsearch','paid-search','paid_search','paid')),
|
|
278
|
+
'Organic Social',
|
|
279
|
+
ifNull(referrer,'') != '' AND length(ifNull(referrer,'')) > 0,
|
|
280
|
+
'Referral',
|
|
281
|
+
(ifNull(utm_source,'') != '' OR ifNull(utm_medium,'') != '' OR ifNull(utm_campaign,'') != ''),
|
|
282
|
+
'Other',
|
|
283
|
+
'Direct'
|
|
284
|
+
)`;
|
|
285
|
+
}
|
|
219
286
|
function buildFilterConditions(filters) {
|
|
220
287
|
if (!filters) return { conditions: [], params: {} };
|
|
221
288
|
const map = {
|
|
@@ -226,6 +293,10 @@ function buildFilterConditions(filters) {
|
|
|
226
293
|
"device.type": "device_type",
|
|
227
294
|
"device.browser": "browser",
|
|
228
295
|
"device.os": "os",
|
|
296
|
+
"device.osVersion": "os_version",
|
|
297
|
+
"device.deviceModel": "device_model",
|
|
298
|
+
"device.deviceBrand": "device_brand",
|
|
299
|
+
"device.appVersion": "app_version",
|
|
229
300
|
"utm.source": "utm_source",
|
|
230
301
|
"utm.medium": "utm_medium",
|
|
231
302
|
"utm.campaign": "utm_campaign",
|
|
@@ -242,7 +313,14 @@ function buildFilterConditions(filters) {
|
|
|
242
313
|
const conditions = [];
|
|
243
314
|
const params = {};
|
|
244
315
|
for (const [key, value] of Object.entries(filters)) {
|
|
245
|
-
if (!value
|
|
316
|
+
if (!value) continue;
|
|
317
|
+
if (key === "channel") {
|
|
318
|
+
const paramKey2 = "f_channel";
|
|
319
|
+
conditions.push(`${channelClassificationExpr()} = {${paramKey2}:String}`);
|
|
320
|
+
params[paramKey2] = value;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (!map[key]) continue;
|
|
246
324
|
const paramKey = `f_${key.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
247
325
|
conditions.push(`${map[key]} = {${paramKey}:String}`);
|
|
248
326
|
params[paramKey] = value;
|
|
@@ -273,6 +351,14 @@ var ClickHouseAdapter = class {
|
|
|
273
351
|
await this.client.command({
|
|
274
352
|
query: `ALTER TABLE ${SITES_TABLE} ADD COLUMN IF NOT EXISTS conversion_events Nullable(String)`
|
|
275
353
|
});
|
|
354
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS os_version LowCardinality(Nullable(String))` });
|
|
355
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS device_model LowCardinality(Nullable(String))` });
|
|
356
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS device_brand LowCardinality(Nullable(String))` });
|
|
357
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS app_version LowCardinality(Nullable(String))` });
|
|
358
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS app_build Nullable(String)` });
|
|
359
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS sdk_name LowCardinality(Nullable(String))` });
|
|
360
|
+
await this.client.command({ query: `ALTER TABLE ${EVENTS_TABLE} ADD COLUMN IF NOT EXISTS sdk_version LowCardinality(Nullable(String))` });
|
|
361
|
+
await this.client.command({ query: `ALTER TABLE ${SITES_TABLE} ADD COLUMN IF NOT EXISTS type LowCardinality(Nullable(String)) DEFAULT 'web'` });
|
|
276
362
|
}
|
|
277
363
|
async close() {
|
|
278
364
|
await this.client.close();
|
|
@@ -315,7 +401,14 @@ var ClickHouseAdapter = class {
|
|
|
315
401
|
utm_campaign: e.utm?.campaign ?? null,
|
|
316
402
|
utm_term: e.utm?.term ?? null,
|
|
317
403
|
utm_content: e.utm?.content ?? null,
|
|
318
|
-
ip: e.ip ?? null
|
|
404
|
+
ip: e.ip ?? null,
|
|
405
|
+
os_version: e.device?.osVersion ?? null,
|
|
406
|
+
device_model: e.device?.deviceModel ?? null,
|
|
407
|
+
device_brand: e.device?.deviceBrand ?? null,
|
|
408
|
+
app_version: e.device?.appVersion ?? null,
|
|
409
|
+
app_build: e.device?.appBuild ?? null,
|
|
410
|
+
sdk_name: e.device?.sdkName ?? null,
|
|
411
|
+
sdk_version: e.device?.sdkVersion ?? null
|
|
319
412
|
}));
|
|
320
413
|
await this.client.insert({
|
|
321
414
|
table: EVENTS_TABLE,
|
|
@@ -666,15 +759,67 @@ var ClickHouseAdapter = class {
|
|
|
666
759
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
667
760
|
break;
|
|
668
761
|
}
|
|
762
|
+
case "top_os_versions": {
|
|
763
|
+
const rows = await this.queryRows(
|
|
764
|
+
`SELECT concat(os, ' ', ifNull(os_version, '')) AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
765
|
+
WHERE site_id = {siteId:String}
|
|
766
|
+
AND timestamp >= {from:String}
|
|
767
|
+
AND timestamp <= {to:String}
|
|
768
|
+
AND os IS NOT NULL
|
|
769
|
+
AND os_version IS NOT NULL
|
|
770
|
+
${filterSql}
|
|
771
|
+
GROUP BY key
|
|
772
|
+
ORDER BY value DESC
|
|
773
|
+
LIMIT {limit:UInt32}`,
|
|
774
|
+
{ ...params, ...filter.params }
|
|
775
|
+
);
|
|
776
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
777
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
case "top_device_models": {
|
|
781
|
+
const rows = await this.queryRows(
|
|
782
|
+
`SELECT trim(concat(ifNull(device_brand, ''), ' ', device_model)) AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
783
|
+
WHERE site_id = {siteId:String}
|
|
784
|
+
AND timestamp >= {from:String}
|
|
785
|
+
AND timestamp <= {to:String}
|
|
786
|
+
AND device_model IS NOT NULL
|
|
787
|
+
${filterSql}
|
|
788
|
+
GROUP BY key
|
|
789
|
+
ORDER BY value DESC
|
|
790
|
+
LIMIT {limit:UInt32}`,
|
|
791
|
+
{ ...params, ...filter.params }
|
|
792
|
+
);
|
|
793
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
794
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
case "top_app_versions": {
|
|
798
|
+
const rows = await this.queryRows(
|
|
799
|
+
`SELECT app_version AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
800
|
+
WHERE site_id = {siteId:String}
|
|
801
|
+
AND timestamp >= {from:String}
|
|
802
|
+
AND timestamp <= {to:String}
|
|
803
|
+
AND app_version IS NOT NULL
|
|
804
|
+
${filterSql}
|
|
805
|
+
GROUP BY app_version
|
|
806
|
+
ORDER BY value DESC
|
|
807
|
+
LIMIT {limit:UInt32}`,
|
|
808
|
+
{ ...params, ...filter.params }
|
|
809
|
+
);
|
|
810
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
811
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
669
814
|
case "top_utm_sources": {
|
|
670
815
|
const rows = await this.queryRows(
|
|
671
|
-
`SELECT
|
|
816
|
+
`SELECT ${normalizedUtmSourceExpr()} AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
672
817
|
WHERE site_id = {siteId:String}
|
|
673
818
|
AND timestamp >= {from:String}
|
|
674
819
|
AND timestamp <= {to:String}
|
|
675
820
|
AND utm_source IS NOT NULL AND utm_source != ''
|
|
676
821
|
${filterSql}
|
|
677
|
-
GROUP BY
|
|
822
|
+
GROUP BY key
|
|
678
823
|
ORDER BY value DESC
|
|
679
824
|
LIMIT {limit:UInt32}`,
|
|
680
825
|
{ ...params, ...filter.params }
|
|
@@ -685,13 +830,13 @@ var ClickHouseAdapter = class {
|
|
|
685
830
|
}
|
|
686
831
|
case "top_utm_mediums": {
|
|
687
832
|
const rows = await this.queryRows(
|
|
688
|
-
`SELECT
|
|
833
|
+
`SELECT ${normalizedUtmMediumExpr()} AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
689
834
|
WHERE site_id = {siteId:String}
|
|
690
835
|
AND timestamp >= {from:String}
|
|
691
836
|
AND timestamp <= {to:String}
|
|
692
837
|
AND utm_medium IS NOT NULL AND utm_medium != ''
|
|
693
838
|
${filterSql}
|
|
694
|
-
GROUP BY
|
|
839
|
+
GROUP BY key
|
|
695
840
|
ORDER BY value DESC
|
|
696
841
|
LIMIT {limit:UInt32}`,
|
|
697
842
|
{ ...params, ...filter.params }
|
|
@@ -717,6 +862,56 @@ var ClickHouseAdapter = class {
|
|
|
717
862
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
718
863
|
break;
|
|
719
864
|
}
|
|
865
|
+
case "top_utm_terms": {
|
|
866
|
+
const rows = await this.queryRows(
|
|
867
|
+
`SELECT utm_term AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
868
|
+
WHERE site_id = {siteId:String}
|
|
869
|
+
AND timestamp >= {from:String}
|
|
870
|
+
AND timestamp <= {to:String}
|
|
871
|
+
AND utm_term IS NOT NULL AND utm_term != ''
|
|
872
|
+
${filterSql}
|
|
873
|
+
GROUP BY utm_term
|
|
874
|
+
ORDER BY value DESC
|
|
875
|
+
LIMIT {limit:UInt32}`,
|
|
876
|
+
{ ...params, ...filter.params }
|
|
877
|
+
);
|
|
878
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
879
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
case "top_utm_contents": {
|
|
883
|
+
const rows = await this.queryRows(
|
|
884
|
+
`SELECT utm_content AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
885
|
+
WHERE site_id = {siteId:String}
|
|
886
|
+
AND timestamp >= {from:String}
|
|
887
|
+
AND timestamp <= {to:String}
|
|
888
|
+
AND utm_content IS NOT NULL AND utm_content != ''
|
|
889
|
+
${filterSql}
|
|
890
|
+
GROUP BY utm_content
|
|
891
|
+
ORDER BY value DESC
|
|
892
|
+
LIMIT {limit:UInt32}`,
|
|
893
|
+
{ ...params, ...filter.params }
|
|
894
|
+
);
|
|
895
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
896
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
case "top_channels": {
|
|
900
|
+
const rows = await this.queryRows(
|
|
901
|
+
`SELECT ${channelClassificationExpr()} AS key, uniq(visitor_id) AS value FROM ${EVENTS_TABLE}
|
|
902
|
+
WHERE site_id = {siteId:String}
|
|
903
|
+
AND timestamp >= {from:String}
|
|
904
|
+
AND timestamp <= {to:String}
|
|
905
|
+
${filterSql}
|
|
906
|
+
GROUP BY key
|
|
907
|
+
ORDER BY value DESC
|
|
908
|
+
LIMIT {limit:UInt32}`,
|
|
909
|
+
{ ...params, ...filter.params }
|
|
910
|
+
);
|
|
911
|
+
data = rows.map((r) => ({ key: r.key, value: Number(r.value) }));
|
|
912
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
720
915
|
}
|
|
721
916
|
const result = { metric: q.metric, period, data, total };
|
|
722
917
|
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
@@ -1119,7 +1314,7 @@ var ClickHouseAdapter = class {
|
|
|
1119
1314
|
async getMergedUserDetail(siteId, userId, visitorIds) {
|
|
1120
1315
|
const rows = await this.queryRows(
|
|
1121
1316
|
`SELECT
|
|
1122
|
-
anyLast(visitor_id) AS
|
|
1317
|
+
anyLast(visitor_id) AS last_visitor_id,
|
|
1123
1318
|
anyLast(traits) AS traits,
|
|
1124
1319
|
min(timestamp) AS firstSeen,
|
|
1125
1320
|
max(timestamp) AS lastSeen,
|
|
@@ -1151,7 +1346,7 @@ var ClickHouseAdapter = class {
|
|
|
1151
1346
|
if (rows.length === 0) return null;
|
|
1152
1347
|
const u = rows[0];
|
|
1153
1348
|
return {
|
|
1154
|
-
visitorId: String(u.
|
|
1349
|
+
visitorId: String(u.last_visitor_id),
|
|
1155
1350
|
visitorIds,
|
|
1156
1351
|
userId,
|
|
1157
1352
|
traits: this.parseJSON(u.traits),
|
|
@@ -1240,6 +1435,7 @@ var ClickHouseAdapter = class {
|
|
|
1240
1435
|
siteId: generateSiteId(),
|
|
1241
1436
|
secretKey: generateSecretKey(),
|
|
1242
1437
|
name: data.name,
|
|
1438
|
+
type: data.type ?? "web",
|
|
1243
1439
|
domain: data.domain,
|
|
1244
1440
|
allowedOrigins: data.allowedOrigins,
|
|
1245
1441
|
conversionEvents: data.conversionEvents,
|
|
@@ -1252,6 +1448,7 @@ var ClickHouseAdapter = class {
|
|
|
1252
1448
|
site_id: site.siteId,
|
|
1253
1449
|
secret_key: site.secretKey,
|
|
1254
1450
|
name: site.name,
|
|
1451
|
+
type: site.type ?? "web",
|
|
1255
1452
|
domain: site.domain ?? null,
|
|
1256
1453
|
allowed_origins: site.allowedOrigins ? JSON.stringify(site.allowedOrigins) : null,
|
|
1257
1454
|
conversion_events: site.conversionEvents ? JSON.stringify(site.conversionEvents) : null,
|
|
@@ -1266,7 +1463,7 @@ var ClickHouseAdapter = class {
|
|
|
1266
1463
|
}
|
|
1267
1464
|
async getSite(siteId) {
|
|
1268
1465
|
const rows = await this.queryRows(
|
|
1269
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1466
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1270
1467
|
FROM ${SITES_TABLE} FINAL
|
|
1271
1468
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
1272
1469
|
{ siteId }
|
|
@@ -1275,7 +1472,7 @@ var ClickHouseAdapter = class {
|
|
|
1275
1472
|
}
|
|
1276
1473
|
async getSiteBySecret(secretKey) {
|
|
1277
1474
|
const rows = await this.queryRows(
|
|
1278
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1475
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1279
1476
|
FROM ${SITES_TABLE} FINAL
|
|
1280
1477
|
WHERE secret_key = {secretKey:String} AND is_deleted = 0`,
|
|
1281
1478
|
{ secretKey }
|
|
@@ -1284,7 +1481,7 @@ var ClickHouseAdapter = class {
|
|
|
1284
1481
|
}
|
|
1285
1482
|
async listSites() {
|
|
1286
1483
|
const rows = await this.queryRows(
|
|
1287
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1484
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, updated_at
|
|
1288
1485
|
FROM ${SITES_TABLE} FINAL
|
|
1289
1486
|
WHERE is_deleted = 0
|
|
1290
1487
|
ORDER BY created_at DESC`,
|
|
@@ -1294,7 +1491,7 @@ var ClickHouseAdapter = class {
|
|
|
1294
1491
|
}
|
|
1295
1492
|
async updateSite(siteId, data) {
|
|
1296
1493
|
const currentRows = await this.queryRows(
|
|
1297
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, updated_at, version
|
|
1494
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, updated_at, version
|
|
1298
1495
|
FROM ${SITES_TABLE} FINAL
|
|
1299
1496
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
1300
1497
|
{ siteId }
|
|
@@ -1306,6 +1503,7 @@ var ClickHouseAdapter = class {
|
|
|
1306
1503
|
const nowCH = toCHDateTime(now);
|
|
1307
1504
|
const newVersion = Number(current.version) + 1;
|
|
1308
1505
|
const newName = data.name !== void 0 ? data.name : String(current.name);
|
|
1506
|
+
const newType = data.type !== void 0 ? data.type : current.type ? String(current.type) : "web";
|
|
1309
1507
|
const newDomain = data.domain !== void 0 ? data.domain || null : current.domain ? String(current.domain) : null;
|
|
1310
1508
|
const newOrigins = data.allowedOrigins !== void 0 ? data.allowedOrigins.length > 0 ? JSON.stringify(data.allowedOrigins) : null : current.allowed_origins ? String(current.allowed_origins) : null;
|
|
1311
1509
|
const newConversions = data.conversionEvents !== void 0 ? data.conversionEvents.length > 0 ? JSON.stringify(data.conversionEvents) : null : current.conversion_events ? String(current.conversion_events) : null;
|
|
@@ -1315,6 +1513,7 @@ var ClickHouseAdapter = class {
|
|
|
1315
1513
|
site_id: String(current.site_id),
|
|
1316
1514
|
secret_key: String(current.secret_key),
|
|
1317
1515
|
name: newName,
|
|
1516
|
+
type: newType,
|
|
1318
1517
|
domain: newDomain,
|
|
1319
1518
|
allowed_origins: newOrigins,
|
|
1320
1519
|
conversion_events: newConversions,
|
|
@@ -1329,6 +1528,7 @@ var ClickHouseAdapter = class {
|
|
|
1329
1528
|
siteId: String(current.site_id),
|
|
1330
1529
|
secretKey: String(current.secret_key),
|
|
1331
1530
|
name: newName,
|
|
1531
|
+
type: newType,
|
|
1332
1532
|
domain: newDomain ?? void 0,
|
|
1333
1533
|
allowedOrigins: newOrigins ? JSON.parse(newOrigins) : void 0,
|
|
1334
1534
|
conversionEvents: newConversions ? JSON.parse(newConversions) : void 0,
|
|
@@ -1338,7 +1538,7 @@ var ClickHouseAdapter = class {
|
|
|
1338
1538
|
}
|
|
1339
1539
|
async deleteSite(siteId) {
|
|
1340
1540
|
const currentRows = await this.queryRows(
|
|
1341
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, version
|
|
1541
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, version
|
|
1342
1542
|
FROM ${SITES_TABLE} FINAL
|
|
1343
1543
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
1344
1544
|
{ siteId }
|
|
@@ -1352,6 +1552,7 @@ var ClickHouseAdapter = class {
|
|
|
1352
1552
|
site_id: String(current.site_id),
|
|
1353
1553
|
secret_key: String(current.secret_key),
|
|
1354
1554
|
name: String(current.name),
|
|
1555
|
+
type: current.type ? String(current.type) : "web",
|
|
1355
1556
|
domain: current.domain ? String(current.domain) : null,
|
|
1356
1557
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
1357
1558
|
conversion_events: current.conversion_events ? String(current.conversion_events) : null,
|
|
@@ -1366,7 +1567,7 @@ var ClickHouseAdapter = class {
|
|
|
1366
1567
|
}
|
|
1367
1568
|
async regenerateSecret(siteId) {
|
|
1368
1569
|
const currentRows = await this.queryRows(
|
|
1369
|
-
`SELECT site_id, secret_key, name, domain, allowed_origins, conversion_events, created_at, version
|
|
1570
|
+
`SELECT site_id, secret_key, name, type, domain, allowed_origins, conversion_events, created_at, version
|
|
1370
1571
|
FROM ${SITES_TABLE} FINAL
|
|
1371
1572
|
WHERE site_id = {siteId:String} AND is_deleted = 0`,
|
|
1372
1573
|
{ siteId }
|
|
@@ -1383,6 +1584,7 @@ var ClickHouseAdapter = class {
|
|
|
1383
1584
|
site_id: String(current.site_id),
|
|
1384
1585
|
secret_key: newSecret,
|
|
1385
1586
|
name: String(current.name),
|
|
1587
|
+
type: current.type ? String(current.type) : "web",
|
|
1386
1588
|
domain: current.domain ? String(current.domain) : null,
|
|
1387
1589
|
allowed_origins: current.allowed_origins ? String(current.allowed_origins) : null,
|
|
1388
1590
|
conversion_events: current.conversion_events ? String(current.conversion_events) : null,
|
|
@@ -1397,6 +1599,7 @@ var ClickHouseAdapter = class {
|
|
|
1397
1599
|
siteId: String(current.site_id),
|
|
1398
1600
|
secretKey: newSecret,
|
|
1399
1601
|
name: String(current.name),
|
|
1602
|
+
type: current.type ? String(current.type) : "web",
|
|
1400
1603
|
domain: current.domain ? String(current.domain) : void 0,
|
|
1401
1604
|
allowedOrigins: current.allowed_origins ? JSON.parse(String(current.allowed_origins)) : void 0,
|
|
1402
1605
|
conversionEvents: current.conversion_events ? JSON.parse(String(current.conversion_events)) : void 0,
|
|
@@ -1418,6 +1621,7 @@ var ClickHouseAdapter = class {
|
|
|
1418
1621
|
siteId: String(row.site_id),
|
|
1419
1622
|
secretKey: String(row.secret_key),
|
|
1420
1623
|
name: String(row.name),
|
|
1624
|
+
type: row.type ? String(row.type) : "web",
|
|
1421
1625
|
domain: row.domain ? String(row.domain) : void 0,
|
|
1422
1626
|
allowedOrigins: row.allowed_origins ? JSON.parse(String(row.allowed_origins)) : void 0,
|
|
1423
1627
|
conversionEvents: row.conversion_events ? JSON.parse(String(row.conversion_events)) : void 0,
|
|
@@ -1481,6 +1685,130 @@ import { MongoClient } from "mongodb";
|
|
|
1481
1685
|
var EVENTS_COLLECTION = "litemetrics_events";
|
|
1482
1686
|
var SITES_COLLECTION = "litemetrics_sites";
|
|
1483
1687
|
var IDENTITY_MAP_COLLECTION = "litemetrics_identity_map";
|
|
1688
|
+
function normalizedUtmSourceSwitch() {
|
|
1689
|
+
return {
|
|
1690
|
+
$switch: {
|
|
1691
|
+
branches: [
|
|
1692
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["ig", "instagram", "instagram.com"]] }, then: "Instagram" },
|
|
1693
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["fb", "facebook", "facebook.com", "fb.com"]] }, then: "Facebook" },
|
|
1694
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["tw", "twitter", "twitter.com", "x", "x.com", "t.co"]] }, then: "X (Twitter)" },
|
|
1695
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["li", "linkedin", "linkedin.com"]] }, then: "LinkedIn" },
|
|
1696
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["yt", "youtube", "youtube.com"]] }, then: "YouTube" },
|
|
1697
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["goog", "google", "google.com"]] }, then: "Google" },
|
|
1698
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["gh", "github", "github.com"]] }, then: "GitHub" },
|
|
1699
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["reddit", "reddit.com"]] }, then: "Reddit" },
|
|
1700
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["pinterest", "pinterest.com"]] }, then: "Pinterest" },
|
|
1701
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["tiktok", "tiktok.com"]] }, then: "TikTok" },
|
|
1702
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["openai", "chatgpt", "chat.openai.com"]] }, then: "OpenAI" },
|
|
1703
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_source", ""] } }, ["perplexity", "perplexity.ai"]] }, then: "Perplexity" }
|
|
1704
|
+
],
|
|
1705
|
+
default: "$utm_source"
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
function normalizedUtmMediumSwitch() {
|
|
1710
|
+
return {
|
|
1711
|
+
$switch: {
|
|
1712
|
+
branches: [
|
|
1713
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["cpc", "ppc", "paidsearch", "paid-search", "paid_search", "paid"]] }, then: "Paid" },
|
|
1714
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["organic"]] }, then: "Organic" },
|
|
1715
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["social", "social-media", "social_media"]] }, then: "Social" },
|
|
1716
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["email", "e-mail", "e_mail"]] }, then: "Email" },
|
|
1717
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["display", "banner", "cpm"]] }, then: "Display" },
|
|
1718
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["affiliate"]] }, then: "Affiliate" },
|
|
1719
|
+
{ case: { $in: [{ $toLower: { $ifNull: ["$utm_medium", ""] } }, ["referral"]] }, then: "Referral" }
|
|
1720
|
+
],
|
|
1721
|
+
default: "$utm_medium"
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
var SEARCH_ENGINES = /google|bing|yahoo|duckduckgo|ecosia|baidu|yandex|search\.brave/i;
|
|
1726
|
+
var SOCIAL_NETWORKS = /instagram|facebook|twitter|x\.com|t\.co|linkedin|youtube|tiktok|pinterest|reddit|snapchat|mastodon|tumblr/i;
|
|
1727
|
+
var PAID_MEDIUMS = ["cpc", "ppc", "paidsearch", "paid-search", "paid_search", "paid"];
|
|
1728
|
+
var SOCIAL_SOURCES = ["instagram", "ig", "facebook", "fb", "twitter", "tw", "x", "linkedin", "li", "youtube", "yt", "tiktok", "pinterest", "reddit", "snapchat"];
|
|
1729
|
+
function channelClassificationSwitch() {
|
|
1730
|
+
const lMedium = { $toLower: { $ifNull: ["$utm_medium", ""] } };
|
|
1731
|
+
const lSource = { $toLower: { $ifNull: ["$utm_source", ""] } };
|
|
1732
|
+
const refStr = { $ifNull: ["$referrer", ""] };
|
|
1733
|
+
return {
|
|
1734
|
+
$switch: {
|
|
1735
|
+
branches: [
|
|
1736
|
+
// Paid Search
|
|
1737
|
+
{
|
|
1738
|
+
case: {
|
|
1739
|
+
$and: [
|
|
1740
|
+
{ $in: [lMedium, PAID_MEDIUMS] },
|
|
1741
|
+
{ $or: [
|
|
1742
|
+
{ $in: [lSource, ["google", "goog", "bing", "yahoo", "duckduckgo", "ecosia", "baidu", "yandex"]] },
|
|
1743
|
+
{ $regexMatch: { input: refStr, regex: SEARCH_ENGINES } }
|
|
1744
|
+
] }
|
|
1745
|
+
]
|
|
1746
|
+
},
|
|
1747
|
+
then: "Paid Search"
|
|
1748
|
+
},
|
|
1749
|
+
// Paid Social
|
|
1750
|
+
{
|
|
1751
|
+
case: {
|
|
1752
|
+
$and: [
|
|
1753
|
+
{ $in: [lMedium, PAID_MEDIUMS] },
|
|
1754
|
+
{ $or: [
|
|
1755
|
+
{ $in: [lSource, SOCIAL_SOURCES] },
|
|
1756
|
+
{ $regexMatch: { input: refStr, regex: SOCIAL_NETWORKS } }
|
|
1757
|
+
] }
|
|
1758
|
+
]
|
|
1759
|
+
},
|
|
1760
|
+
then: "Paid Social"
|
|
1761
|
+
},
|
|
1762
|
+
// Email
|
|
1763
|
+
{ case: { $in: [lMedium, ["email", "e-mail", "e_mail"]] }, then: "Email" },
|
|
1764
|
+
// Display
|
|
1765
|
+
{ case: { $in: [lMedium, ["display", "banner", "cpm"]] }, then: "Display" },
|
|
1766
|
+
// Affiliate
|
|
1767
|
+
{ case: { $in: [lMedium, ["affiliate"]] }, then: "Affiliate" },
|
|
1768
|
+
// Organic Search
|
|
1769
|
+
{
|
|
1770
|
+
case: {
|
|
1771
|
+
$and: [
|
|
1772
|
+
{ $regexMatch: { input: refStr, regex: SEARCH_ENGINES } },
|
|
1773
|
+
{ $not: [{ $in: [lMedium, PAID_MEDIUMS] }] }
|
|
1774
|
+
]
|
|
1775
|
+
},
|
|
1776
|
+
then: "Organic Search"
|
|
1777
|
+
},
|
|
1778
|
+
// Organic Social
|
|
1779
|
+
{
|
|
1780
|
+
case: {
|
|
1781
|
+
$and: [
|
|
1782
|
+
{ $or: [
|
|
1783
|
+
{ $regexMatch: { input: refStr, regex: SOCIAL_NETWORKS } },
|
|
1784
|
+
{ $in: [lSource, SOCIAL_SOURCES] }
|
|
1785
|
+
] },
|
|
1786
|
+
{ $not: [{ $in: [lMedium, PAID_MEDIUMS] }] }
|
|
1787
|
+
]
|
|
1788
|
+
},
|
|
1789
|
+
then: "Organic Social"
|
|
1790
|
+
},
|
|
1791
|
+
// Referral
|
|
1792
|
+
{
|
|
1793
|
+
case: { $and: [{ $ne: [refStr, ""] }, { $gt: [{ $strLenCP: refStr }, 0] }] },
|
|
1794
|
+
then: "Referral"
|
|
1795
|
+
},
|
|
1796
|
+
// Other (has UTM but no referrer)
|
|
1797
|
+
{
|
|
1798
|
+
case: {
|
|
1799
|
+
$or: [
|
|
1800
|
+
{ $and: [{ $ne: [{ $ifNull: ["$utm_source", ""] }, ""] }] },
|
|
1801
|
+
{ $and: [{ $ne: [{ $ifNull: ["$utm_medium", ""] }, ""] }] },
|
|
1802
|
+
{ $and: [{ $ne: [{ $ifNull: ["$utm_campaign", ""] }, ""] }] }
|
|
1803
|
+
]
|
|
1804
|
+
},
|
|
1805
|
+
then: "Other"
|
|
1806
|
+
}
|
|
1807
|
+
],
|
|
1808
|
+
default: "Direct"
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1484
1812
|
function buildFilterMatch(filters) {
|
|
1485
1813
|
if (!filters) return {};
|
|
1486
1814
|
const map = {
|
|
@@ -1491,6 +1819,10 @@ function buildFilterMatch(filters) {
|
|
|
1491
1819
|
"device.type": "device_type",
|
|
1492
1820
|
"device.browser": "browser",
|
|
1493
1821
|
"device.os": "os",
|
|
1822
|
+
"device.osVersion": "os_version",
|
|
1823
|
+
"device.deviceModel": "device_model",
|
|
1824
|
+
"device.deviceBrand": "device_brand",
|
|
1825
|
+
"device.appVersion": "app_version",
|
|
1494
1826
|
"utm.source": "utm_source",
|
|
1495
1827
|
"utm.medium": "utm_medium",
|
|
1496
1828
|
"utm.campaign": "utm_campaign",
|
|
@@ -1506,7 +1838,9 @@ function buildFilterMatch(filters) {
|
|
|
1506
1838
|
};
|
|
1507
1839
|
const match = {};
|
|
1508
1840
|
for (const [key, value] of Object.entries(filters)) {
|
|
1509
|
-
if (!value
|
|
1841
|
+
if (!value) continue;
|
|
1842
|
+
if (key === "channel") continue;
|
|
1843
|
+
if (!map[key]) continue;
|
|
1510
1844
|
match[map[key]] = value;
|
|
1511
1845
|
}
|
|
1512
1846
|
return match;
|
|
@@ -1575,6 +1909,13 @@ var MongoDBAdapter = class {
|
|
|
1575
1909
|
utm_term: e.utm?.term ?? null,
|
|
1576
1910
|
utm_content: e.utm?.content ?? null,
|
|
1577
1911
|
ip: e.ip ?? null,
|
|
1912
|
+
os_version: e.device?.osVersion ?? null,
|
|
1913
|
+
device_model: e.device?.deviceModel ?? null,
|
|
1914
|
+
device_brand: e.device?.deviceBrand ?? null,
|
|
1915
|
+
app_version: e.device?.appVersion ?? null,
|
|
1916
|
+
app_build: e.device?.appBuild ?? null,
|
|
1917
|
+
sdk_name: e.device?.sdkName ?? null,
|
|
1918
|
+
sdk_version: e.device?.sdkVersion ?? null,
|
|
1578
1919
|
created_at: /* @__PURE__ */ new Date()
|
|
1579
1920
|
}));
|
|
1580
1921
|
await this.collection.insertMany(docs);
|
|
@@ -1588,12 +1929,22 @@ var MongoDBAdapter = class {
|
|
|
1588
1929
|
timestamp: { $gte: new Date(dateRange.from), $lte: new Date(dateRange.to) }
|
|
1589
1930
|
};
|
|
1590
1931
|
const filterMatch = buildFilterMatch(q.filters);
|
|
1932
|
+
const matchStages = (extra) => {
|
|
1933
|
+
const stages = [
|
|
1934
|
+
{ $match: { ...baseMatch, ...filterMatch, ...extra } }
|
|
1935
|
+
];
|
|
1936
|
+
if (q.filters?.channel) {
|
|
1937
|
+
stages.push({ $addFields: { _channel: channelClassificationSwitch() } });
|
|
1938
|
+
stages.push({ $match: { _channel: q.filters.channel } });
|
|
1939
|
+
}
|
|
1940
|
+
return stages;
|
|
1941
|
+
};
|
|
1591
1942
|
let data = [];
|
|
1592
1943
|
let total = 0;
|
|
1593
1944
|
switch (q.metric) {
|
|
1594
1945
|
case "pageviews": {
|
|
1595
1946
|
const [result2] = await this.collection.aggregate([
|
|
1596
|
-
{
|
|
1947
|
+
...matchStages({ type: "pageview" }),
|
|
1597
1948
|
{ $count: "count" }
|
|
1598
1949
|
]).toArray();
|
|
1599
1950
|
total = result2?.count ?? 0;
|
|
@@ -1602,7 +1953,7 @@ var MongoDBAdapter = class {
|
|
|
1602
1953
|
}
|
|
1603
1954
|
case "visitors": {
|
|
1604
1955
|
const [result2] = await this.collection.aggregate([
|
|
1605
|
-
|
|
1956
|
+
...matchStages(),
|
|
1606
1957
|
{ $group: { _id: "$visitor_id" } },
|
|
1607
1958
|
{ $count: "count" }
|
|
1608
1959
|
]).toArray();
|
|
@@ -1612,7 +1963,7 @@ var MongoDBAdapter = class {
|
|
|
1612
1963
|
}
|
|
1613
1964
|
case "sessions": {
|
|
1614
1965
|
const [result2] = await this.collection.aggregate([
|
|
1615
|
-
|
|
1966
|
+
...matchStages(),
|
|
1616
1967
|
{ $group: { _id: "$session_id" } },
|
|
1617
1968
|
{ $count: "count" }
|
|
1618
1969
|
]).toArray();
|
|
@@ -1622,7 +1973,7 @@ var MongoDBAdapter = class {
|
|
|
1622
1973
|
}
|
|
1623
1974
|
case "events": {
|
|
1624
1975
|
const [result2] = await this.collection.aggregate([
|
|
1625
|
-
{
|
|
1976
|
+
...matchStages({ type: "event" }),
|
|
1626
1977
|
{ $count: "count" }
|
|
1627
1978
|
]).toArray();
|
|
1628
1979
|
total = result2?.count ?? 0;
|
|
@@ -1637,7 +1988,7 @@ var MongoDBAdapter = class {
|
|
|
1637
1988
|
break;
|
|
1638
1989
|
}
|
|
1639
1990
|
const [result2] = await this.collection.aggregate([
|
|
1640
|
-
{
|
|
1991
|
+
...matchStages({ type: "event", event_name: { $in: conversionEvents } }),
|
|
1641
1992
|
{ $count: "count" }
|
|
1642
1993
|
]).toArray();
|
|
1643
1994
|
total = result2?.count ?? 0;
|
|
@@ -1646,7 +1997,7 @@ var MongoDBAdapter = class {
|
|
|
1646
1997
|
}
|
|
1647
1998
|
case "top_pages": {
|
|
1648
1999
|
const rows = await this.collection.aggregate([
|
|
1649
|
-
{
|
|
2000
|
+
...matchStages({ type: "pageview", url: { $ne: null } }),
|
|
1650
2001
|
{ $group: { _id: "$url", value: { $sum: 1 } } },
|
|
1651
2002
|
{ $sort: { value: -1 } },
|
|
1652
2003
|
{ $limit: limit }
|
|
@@ -1657,7 +2008,7 @@ var MongoDBAdapter = class {
|
|
|
1657
2008
|
}
|
|
1658
2009
|
case "top_referrers": {
|
|
1659
2010
|
const rows = await this.collection.aggregate([
|
|
1660
|
-
{
|
|
2011
|
+
...matchStages({ type: "pageview", referrer: { $nin: [null, ""] } }),
|
|
1661
2012
|
{ $group: { _id: "$referrer", value: { $sum: 1 } } },
|
|
1662
2013
|
{ $sort: { value: -1 } },
|
|
1663
2014
|
{ $limit: limit }
|
|
@@ -1668,7 +2019,7 @@ var MongoDBAdapter = class {
|
|
|
1668
2019
|
}
|
|
1669
2020
|
case "top_countries": {
|
|
1670
2021
|
const rows = await this.collection.aggregate([
|
|
1671
|
-
{
|
|
2022
|
+
...matchStages({ country: { $ne: null } }),
|
|
1672
2023
|
{ $group: { _id: "$country", value: { $addToSet: "$visitor_id" } } },
|
|
1673
2024
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1674
2025
|
{ $sort: { value: -1 } },
|
|
@@ -1680,7 +2031,7 @@ var MongoDBAdapter = class {
|
|
|
1680
2031
|
}
|
|
1681
2032
|
case "top_cities": {
|
|
1682
2033
|
const rows = await this.collection.aggregate([
|
|
1683
|
-
{
|
|
2034
|
+
...matchStages({ city: { $ne: null } }),
|
|
1684
2035
|
{ $group: { _id: "$city", value: { $addToSet: "$visitor_id" } } },
|
|
1685
2036
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1686
2037
|
{ $sort: { value: -1 } },
|
|
@@ -1692,7 +2043,7 @@ var MongoDBAdapter = class {
|
|
|
1692
2043
|
}
|
|
1693
2044
|
case "top_events": {
|
|
1694
2045
|
const rows = await this.collection.aggregate([
|
|
1695
|
-
{
|
|
2046
|
+
...matchStages({ type: "event", event_name: { $ne: null } }),
|
|
1696
2047
|
{ $group: { _id: "$event_name", value: { $sum: 1 } } },
|
|
1697
2048
|
{ $sort: { value: -1 } },
|
|
1698
2049
|
{ $limit: limit }
|
|
@@ -1709,7 +2060,7 @@ var MongoDBAdapter = class {
|
|
|
1709
2060
|
break;
|
|
1710
2061
|
}
|
|
1711
2062
|
const rows = await this.collection.aggregate([
|
|
1712
|
-
{
|
|
2063
|
+
...matchStages({ type: "event", event_name: { $in: conversionEvents } }),
|
|
1713
2064
|
{ $group: { _id: "$event_name", value: { $sum: 1 } } },
|
|
1714
2065
|
{ $sort: { value: -1 } },
|
|
1715
2066
|
{ $limit: limit }
|
|
@@ -1720,7 +2071,7 @@ var MongoDBAdapter = class {
|
|
|
1720
2071
|
}
|
|
1721
2072
|
case "top_exit_pages": {
|
|
1722
2073
|
const rows = await this.collection.aggregate([
|
|
1723
|
-
{
|
|
2074
|
+
...matchStages({ type: "pageview", url: { $ne: null } }),
|
|
1724
2075
|
{ $sort: { timestamp: 1 } },
|
|
1725
2076
|
{ $group: { _id: "$session_id", url: { $last: "$url" } } },
|
|
1726
2077
|
{ $group: { _id: "$url", value: { $sum: 1 } } },
|
|
@@ -1733,7 +2084,7 @@ var MongoDBAdapter = class {
|
|
|
1733
2084
|
}
|
|
1734
2085
|
case "top_transitions": {
|
|
1735
2086
|
const rows = await this.collection.aggregate([
|
|
1736
|
-
{
|
|
2087
|
+
...matchStages({ type: "pageview", url: { $ne: null } }),
|
|
1737
2088
|
{
|
|
1738
2089
|
$setWindowFields: {
|
|
1739
2090
|
partitionBy: "$session_id",
|
|
@@ -1754,7 +2105,7 @@ var MongoDBAdapter = class {
|
|
|
1754
2105
|
}
|
|
1755
2106
|
case "top_scroll_pages": {
|
|
1756
2107
|
const rows = await this.collection.aggregate([
|
|
1757
|
-
{
|
|
2108
|
+
...matchStages({ type: "event", event_subtype: "scroll_depth", page_path: { $ne: null } }),
|
|
1758
2109
|
{ $group: { _id: "$page_path", value: { $sum: 1 } } },
|
|
1759
2110
|
{ $sort: { value: -1 } },
|
|
1760
2111
|
{ $limit: limit }
|
|
@@ -1765,15 +2116,11 @@ var MongoDBAdapter = class {
|
|
|
1765
2116
|
}
|
|
1766
2117
|
case "top_button_clicks": {
|
|
1767
2118
|
const rows = await this.collection.aggregate([
|
|
1768
|
-
{
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
event_subtype: "button_click",
|
|
1774
|
-
$or: [{ element_text: { $ne: null } }, { element_selector: { $ne: null } }]
|
|
1775
|
-
}
|
|
1776
|
-
},
|
|
2119
|
+
...matchStages({
|
|
2120
|
+
type: "event",
|
|
2121
|
+
event_subtype: "button_click",
|
|
2122
|
+
$or: [{ element_text: { $ne: null } }, { element_selector: { $ne: null } }]
|
|
2123
|
+
}),
|
|
1777
2124
|
{ $group: { _id: { $ifNull: ["$element_text", "$element_selector"] }, value: { $sum: 1 } } },
|
|
1778
2125
|
{ $sort: { value: -1 } },
|
|
1779
2126
|
{ $limit: limit }
|
|
@@ -1784,15 +2131,11 @@ var MongoDBAdapter = class {
|
|
|
1784
2131
|
}
|
|
1785
2132
|
case "top_link_targets": {
|
|
1786
2133
|
const rows = await this.collection.aggregate([
|
|
1787
|
-
{
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
event_subtype: { $in: ["link_click", "outbound_click"] },
|
|
1793
|
-
target_url_path: { $ne: null }
|
|
1794
|
-
}
|
|
1795
|
-
},
|
|
2134
|
+
...matchStages({
|
|
2135
|
+
type: "event",
|
|
2136
|
+
event_subtype: { $in: ["link_click", "outbound_click"] },
|
|
2137
|
+
target_url_path: { $ne: null }
|
|
2138
|
+
}),
|
|
1796
2139
|
{ $group: { _id: "$target_url_path", value: { $sum: 1 } } },
|
|
1797
2140
|
{ $sort: { value: -1 } },
|
|
1798
2141
|
{ $limit: limit }
|
|
@@ -1803,7 +2146,7 @@ var MongoDBAdapter = class {
|
|
|
1803
2146
|
}
|
|
1804
2147
|
case "top_devices": {
|
|
1805
2148
|
const rows = await this.collection.aggregate([
|
|
1806
|
-
{
|
|
2149
|
+
...matchStages({ device_type: { $ne: null } }),
|
|
1807
2150
|
{ $group: { _id: "$device_type", value: { $addToSet: "$visitor_id" } } },
|
|
1808
2151
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1809
2152
|
{ $sort: { value: -1 } },
|
|
@@ -1815,7 +2158,7 @@ var MongoDBAdapter = class {
|
|
|
1815
2158
|
}
|
|
1816
2159
|
case "top_browsers": {
|
|
1817
2160
|
const rows = await this.collection.aggregate([
|
|
1818
|
-
{
|
|
2161
|
+
...matchStages({ browser: { $ne: null } }),
|
|
1819
2162
|
{ $group: { _id: "$browser", value: { $addToSet: "$visitor_id" } } },
|
|
1820
2163
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1821
2164
|
{ $sort: { value: -1 } },
|
|
@@ -1827,7 +2170,7 @@ var MongoDBAdapter = class {
|
|
|
1827
2170
|
}
|
|
1828
2171
|
case "top_os": {
|
|
1829
2172
|
const rows = await this.collection.aggregate([
|
|
1830
|
-
{
|
|
2173
|
+
...matchStages({ os: { $ne: null } }),
|
|
1831
2174
|
{ $group: { _id: "$os", value: { $addToSet: "$visitor_id" } } },
|
|
1832
2175
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1833
2176
|
{ $sort: { value: -1 } },
|
|
@@ -1837,10 +2180,47 @@ var MongoDBAdapter = class {
|
|
|
1837
2180
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1838
2181
|
break;
|
|
1839
2182
|
}
|
|
2183
|
+
case "top_os_versions": {
|
|
2184
|
+
const rows = await this.collection.aggregate([
|
|
2185
|
+
...matchStages({ os: { $ne: null }, os_version: { $ne: null } }),
|
|
2186
|
+
{ $group: { _id: { $concat: ["$os", " ", "$os_version"] }, value: { $addToSet: "$visitor_id" } } },
|
|
2187
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2188
|
+
{ $sort: { value: -1 } },
|
|
2189
|
+
{ $limit: limit }
|
|
2190
|
+
]).toArray();
|
|
2191
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2192
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2193
|
+
break;
|
|
2194
|
+
}
|
|
2195
|
+
case "top_device_models": {
|
|
2196
|
+
const rows = await this.collection.aggregate([
|
|
2197
|
+
...matchStages({ device_model: { $ne: null } }),
|
|
2198
|
+
{ $group: { _id: { $trim: { input: { $concat: [{ $ifNull: ["$device_brand", ""] }, " ", "$device_model"] } } }, value: { $addToSet: "$visitor_id" } } },
|
|
2199
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2200
|
+
{ $sort: { value: -1 } },
|
|
2201
|
+
{ $limit: limit }
|
|
2202
|
+
]).toArray();
|
|
2203
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2204
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2205
|
+
break;
|
|
2206
|
+
}
|
|
2207
|
+
case "top_app_versions": {
|
|
2208
|
+
const rows = await this.collection.aggregate([
|
|
2209
|
+
...matchStages({ app_version: { $ne: null } }),
|
|
2210
|
+
{ $group: { _id: "$app_version", value: { $addToSet: "$visitor_id" } } },
|
|
2211
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2212
|
+
{ $sort: { value: -1 } },
|
|
2213
|
+
{ $limit: limit }
|
|
2214
|
+
]).toArray();
|
|
2215
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2216
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2217
|
+
break;
|
|
2218
|
+
}
|
|
1840
2219
|
case "top_utm_sources": {
|
|
1841
2220
|
const rows = await this.collection.aggregate([
|
|
1842
|
-
{
|
|
1843
|
-
{ $
|
|
2221
|
+
...matchStages({ utm_source: { $nin: [null, ""] } }),
|
|
2222
|
+
{ $addFields: { _normalized_source: normalizedUtmSourceSwitch() } },
|
|
2223
|
+
{ $group: { _id: "$_normalized_source", value: { $addToSet: "$visitor_id" } } },
|
|
1844
2224
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1845
2225
|
{ $sort: { value: -1 } },
|
|
1846
2226
|
{ $limit: limit }
|
|
@@ -1851,8 +2231,9 @@ var MongoDBAdapter = class {
|
|
|
1851
2231
|
}
|
|
1852
2232
|
case "top_utm_mediums": {
|
|
1853
2233
|
const rows = await this.collection.aggregate([
|
|
1854
|
-
{
|
|
1855
|
-
{ $
|
|
2234
|
+
...matchStages({ utm_medium: { $nin: [null, ""] } }),
|
|
2235
|
+
{ $addFields: { _normalized_medium: normalizedUtmMediumSwitch() } },
|
|
2236
|
+
{ $group: { _id: "$_normalized_medium", value: { $addToSet: "$visitor_id" } } },
|
|
1856
2237
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1857
2238
|
{ $sort: { value: -1 } },
|
|
1858
2239
|
{ $limit: limit }
|
|
@@ -1863,7 +2244,7 @@ var MongoDBAdapter = class {
|
|
|
1863
2244
|
}
|
|
1864
2245
|
case "top_utm_campaigns": {
|
|
1865
2246
|
const rows = await this.collection.aggregate([
|
|
1866
|
-
{
|
|
2247
|
+
...matchStages({ utm_campaign: { $nin: [null, ""] } }),
|
|
1867
2248
|
{ $group: { _id: "$utm_campaign", value: { $addToSet: "$visitor_id" } } },
|
|
1868
2249
|
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
1869
2250
|
{ $sort: { value: -1 } },
|
|
@@ -1873,6 +2254,43 @@ var MongoDBAdapter = class {
|
|
|
1873
2254
|
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
1874
2255
|
break;
|
|
1875
2256
|
}
|
|
2257
|
+
case "top_utm_terms": {
|
|
2258
|
+
const rows = await this.collection.aggregate([
|
|
2259
|
+
...matchStages({ utm_term: { $nin: [null, ""] } }),
|
|
2260
|
+
{ $group: { _id: "$utm_term", value: { $addToSet: "$visitor_id" } } },
|
|
2261
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2262
|
+
{ $sort: { value: -1 } },
|
|
2263
|
+
{ $limit: limit }
|
|
2264
|
+
]).toArray();
|
|
2265
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2266
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2267
|
+
break;
|
|
2268
|
+
}
|
|
2269
|
+
case "top_utm_contents": {
|
|
2270
|
+
const rows = await this.collection.aggregate([
|
|
2271
|
+
...matchStages({ utm_content: { $nin: [null, ""] } }),
|
|
2272
|
+
{ $group: { _id: "$utm_content", value: { $addToSet: "$visitor_id" } } },
|
|
2273
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2274
|
+
{ $sort: { value: -1 } },
|
|
2275
|
+
{ $limit: limit }
|
|
2276
|
+
]).toArray();
|
|
2277
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2278
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2279
|
+
break;
|
|
2280
|
+
}
|
|
2281
|
+
case "top_channels": {
|
|
2282
|
+
const rows = await this.collection.aggregate([
|
|
2283
|
+
...matchStages(),
|
|
2284
|
+
{ $addFields: { _channel: channelClassificationSwitch() } },
|
|
2285
|
+
{ $group: { _id: "$_channel", value: { $addToSet: "$visitor_id" } } },
|
|
2286
|
+
{ $project: { _id: 1, value: { $size: "$value" } } },
|
|
2287
|
+
{ $sort: { value: -1 } },
|
|
2288
|
+
{ $limit: limit }
|
|
2289
|
+
]).toArray();
|
|
2290
|
+
data = rows.map((r) => ({ key: r._id, value: r.value }));
|
|
2291
|
+
total = data.reduce((sum, d) => sum + d.value, 0);
|
|
2292
|
+
break;
|
|
2293
|
+
}
|
|
1876
2294
|
}
|
|
1877
2295
|
const result = { metric: q.metric, period, data, total };
|
|
1878
2296
|
if (q.compare && ["pageviews", "visitors", "sessions", "events", "conversions"].includes(q.metric)) {
|
|
@@ -2335,7 +2753,18 @@ var MongoDBAdapter = class {
|
|
|
2335
2753
|
userId: doc.user_id ?? void 0,
|
|
2336
2754
|
traits: doc.traits ?? void 0,
|
|
2337
2755
|
geo: doc.country ? { country: doc.country, city: doc.city ?? void 0, region: doc.region ?? void 0 } : void 0,
|
|
2338
|
-
device: doc.device_type ? {
|
|
2756
|
+
device: doc.device_type ? {
|
|
2757
|
+
type: doc.device_type,
|
|
2758
|
+
browser: doc.browser ?? "",
|
|
2759
|
+
os: doc.os ?? "",
|
|
2760
|
+
osVersion: doc.os_version ?? void 0,
|
|
2761
|
+
deviceModel: doc.device_model ?? void 0,
|
|
2762
|
+
deviceBrand: doc.device_brand ?? void 0,
|
|
2763
|
+
appVersion: doc.app_version ?? void 0,
|
|
2764
|
+
appBuild: doc.app_build ?? void 0,
|
|
2765
|
+
sdkName: doc.sdk_name ?? void 0,
|
|
2766
|
+
sdkVersion: doc.sdk_version ?? void 0
|
|
2767
|
+
} : void 0,
|
|
2339
2768
|
language: doc.language ?? void 0,
|
|
2340
2769
|
utm: doc.utm_source ? {
|
|
2341
2770
|
source: doc.utm_source ?? void 0,
|
|
@@ -2353,6 +2782,7 @@ var MongoDBAdapter = class {
|
|
|
2353
2782
|
site_id: generateSiteId(),
|
|
2354
2783
|
secret_key: generateSecretKey(),
|
|
2355
2784
|
name: data.name,
|
|
2785
|
+
type: data.type ?? "web",
|
|
2356
2786
|
domain: data.domain ?? null,
|
|
2357
2787
|
allowed_origins: data.allowedOrigins ?? null,
|
|
2358
2788
|
conversion_events: data.conversionEvents ?? null,
|
|
@@ -2377,6 +2807,7 @@ var MongoDBAdapter = class {
|
|
|
2377
2807
|
async updateSite(siteId, data) {
|
|
2378
2808
|
const updates = { updated_at: /* @__PURE__ */ new Date() };
|
|
2379
2809
|
if (data.name !== void 0) updates.name = data.name;
|
|
2810
|
+
if (data.type !== void 0) updates.type = data.type;
|
|
2380
2811
|
if (data.domain !== void 0) updates.domain = data.domain || null;
|
|
2381
2812
|
if (data.allowedOrigins !== void 0) updates.allowed_origins = data.allowedOrigins.length > 0 ? data.allowedOrigins : null;
|
|
2382
2813
|
if (data.conversionEvents !== void 0) updates.conversion_events = data.conversionEvents.length > 0 ? data.conversionEvents : null;
|
|
@@ -2408,6 +2839,7 @@ var MongoDBAdapter = class {
|
|
|
2408
2839
|
siteId: doc.site_id,
|
|
2409
2840
|
secretKey: doc.secret_key,
|
|
2410
2841
|
name: doc.name,
|
|
2842
|
+
type: doc.type ?? "web",
|
|
2411
2843
|
domain: doc.domain ?? void 0,
|
|
2412
2844
|
allowedOrigins: doc.allowed_origins ?? void 0,
|
|
2413
2845
|
conversionEvents: doc.conversion_events ?? void 0,
|
|
@@ -2676,9 +3108,26 @@ async function createCollector(config) {
|
|
|
2676
3108
|
return false;
|
|
2677
3109
|
}
|
|
2678
3110
|
function enrichEvents(events, ip, userAgent) {
|
|
2679
|
-
const
|
|
3111
|
+
const uaDevice = parseUserAgent(userAgent);
|
|
2680
3112
|
return events.map((event) => {
|
|
2681
3113
|
const geo = resolveGeo(ip, event.timezone);
|
|
3114
|
+
let device;
|
|
3115
|
+
if (event.mobile?.platform) {
|
|
3116
|
+
device = {
|
|
3117
|
+
type: "mobile",
|
|
3118
|
+
browser: "App",
|
|
3119
|
+
os: event.mobile.platform === "ios" ? "iOS" : "Android",
|
|
3120
|
+
osVersion: event.mobile.osVersion,
|
|
3121
|
+
deviceModel: event.mobile.deviceModel,
|
|
3122
|
+
deviceBrand: event.mobile.deviceBrand,
|
|
3123
|
+
appVersion: event.mobile.appVersion,
|
|
3124
|
+
appBuild: event.mobile.appBuild,
|
|
3125
|
+
sdkName: event.mobile.sdkName,
|
|
3126
|
+
sdkVersion: event.mobile.sdkVersion
|
|
3127
|
+
};
|
|
3128
|
+
} else {
|
|
3129
|
+
device = uaDevice;
|
|
3130
|
+
}
|
|
2682
3131
|
return { ...event, ip, geo, device };
|
|
2683
3132
|
});
|
|
2684
3133
|
}
|
|
@@ -2738,6 +3187,22 @@ async function createCollector(config) {
|
|
|
2738
3187
|
}
|
|
2739
3188
|
return req.ip || req.socket?.remoteAddress || req.connection?.remoteAddress || "";
|
|
2740
3189
|
}
|
|
3190
|
+
function extractRequestHostname(req) {
|
|
3191
|
+
const headerValue = (value) => {
|
|
3192
|
+
if (Array.isArray(value)) return value[0];
|
|
3193
|
+
if (typeof value === "string") return value;
|
|
3194
|
+
return void 0;
|
|
3195
|
+
};
|
|
3196
|
+
const origin = headerValue(req.headers?.origin);
|
|
3197
|
+
const referer = headerValue(req.headers?.referer) || headerValue(req.headers?.referrer);
|
|
3198
|
+
const raw = origin ?? referer;
|
|
3199
|
+
if (!raw || raw === "null") return void 0;
|
|
3200
|
+
try {
|
|
3201
|
+
return new URL(raw).hostname.toLowerCase();
|
|
3202
|
+
} catch {
|
|
3203
|
+
return void 0;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
2741
3206
|
function handler() {
|
|
2742
3207
|
return async (req, res) => {
|
|
2743
3208
|
res.setHeader?.("Access-Control-Allow-Origin", "*");
|
|
@@ -2763,6 +3228,12 @@ async function createCollector(config) {
|
|
|
2763
3228
|
sendJson(res, 400, { ok: false, error: "Too many events (max 100)" });
|
|
2764
3229
|
return;
|
|
2765
3230
|
}
|
|
3231
|
+
const siteIds = new Set(payload.events.map((event) => event.siteId).filter(Boolean));
|
|
3232
|
+
if (siteIds.size !== 1) {
|
|
3233
|
+
sendJson(res, 200, { ok: true });
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
const siteId = Array.from(siteIds)[0];
|
|
2766
3237
|
const userAgent = req.headers?.["user-agent"] || "";
|
|
2767
3238
|
if (isBot(userAgent)) {
|
|
2768
3239
|
sendJson(res, 200, { ok: true });
|
|
@@ -2770,26 +3241,15 @@ async function createCollector(config) {
|
|
|
2770
3241
|
}
|
|
2771
3242
|
const ip = extractIp(req);
|
|
2772
3243
|
const enriched = enrichEvents(payload.events, ip, userAgent);
|
|
2773
|
-
const
|
|
2774
|
-
if (
|
|
2775
|
-
const
|
|
2776
|
-
if (
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
return allowed.has(hostname);
|
|
2783
|
-
} catch {
|
|
2784
|
-
return true;
|
|
2785
|
-
}
|
|
2786
|
-
});
|
|
2787
|
-
if (filtered.length === 0) {
|
|
2788
|
-
sendJson(res, 200, { ok: true });
|
|
2789
|
-
return;
|
|
2790
|
-
}
|
|
2791
|
-
await processIdentity(filtered);
|
|
2792
|
-
await db.insertEvents(filtered);
|
|
3244
|
+
const site = await db.getSite(siteId);
|
|
3245
|
+
if (site?.allowedOrigins && site.allowedOrigins.length > 0) {
|
|
3246
|
+
const requestHostname = extractRequestHostname(req);
|
|
3247
|
+
if (!requestHostname) {
|
|
3248
|
+
sendJson(res, 200, { ok: true });
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
const allowed = new Set(site.allowedOrigins.map((h) => h.toLowerCase()));
|
|
3252
|
+
if (!allowed.has(requestHostname)) {
|
|
2793
3253
|
sendJson(res, 200, { ok: true });
|
|
2794
3254
|
return;
|
|
2795
3255
|
}
|