@huloglobal/vendure-plugin-visitor-analytics 0.18.2 → 0.18.4
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/CHANGELOG.md +58 -0
- package/README.md +20 -8
- package/dist/abandoned-cart.controller.d.ts.map +1 -1
- package/dist/abandoned-cart.controller.js +55 -38
- package/dist/abandoned-cart.controller.js.map +1 -1
- package/dist/abandoned-cart.scanner.d.ts +4 -0
- package/dist/abandoned-cart.scanner.d.ts.map +1 -1
- package/dist/abandoned-cart.scanner.js +28 -0
- package/dist/abandoned-cart.scanner.js.map +1 -1
- package/dist/abandoned-cart.service.d.ts +9 -0
- package/dist/abandoned-cart.service.d.ts.map +1 -1
- package/dist/abandoned-cart.service.js +125 -84
- package/dist/abandoned-cart.service.js.map +1 -1
- package/dist/admin-api.js +22 -22
- package/dist/admin-api.js.map +1 -1
- package/dist/ingest-hardening.test.js +6 -6
- package/dist/ingest-hardening.test.js.map +1 -1
- package/dist/ingest-utils.d.ts +6 -0
- package/dist/ingest-utils.d.ts.map +1 -0
- package/dist/ingest-utils.js +34 -0
- package/dist/ingest-utils.js.map +1 -0
- package/dist/journey-buffs.service.js +21 -21
- package/dist/recommendations.controller.d.ts +10 -4
- package/dist/recommendations.controller.d.ts.map +1 -1
- package/dist/recommendations.controller.js +9 -6
- package/dist/recommendations.controller.js.map +1 -1
- package/dist/recommendations.service.d.ts +28 -8
- package/dist/recommendations.service.d.ts.map +1 -1
- package/dist/recommendations.service.js +164 -78
- package/dist/recommendations.service.js.map +1 -1
- package/dist/search-analytics.service.d.ts.map +1 -1
- package/dist/search-analytics.service.js +22 -18
- package/dist/search-analytics.service.js.map +1 -1
- package/dist/sql-fragments.d.ts +25 -0
- package/dist/sql-fragments.d.ts.map +1 -0
- package/dist/sql-fragments.js +31 -0
- package/dist/sql-fragments.js.map +1 -0
- package/dist/visitor-tracking.controller.d.ts +8 -1
- package/dist/visitor-tracking.controller.d.ts.map +1 -1
- package/dist/visitor-tracking.controller.js +219 -134
- package/dist/visitor-tracking.controller.js.map +1 -1
- package/dist/visitor-tracking.service.d.ts +0 -4
- package/dist/visitor-tracking.service.d.ts.map +1 -1
- package/dist/visitor-tracking.service.js +3 -32
- package/dist/visitor-tracking.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.VisitorTrackingController = void 0;
|
|
16
|
+
exports.drained = drained;
|
|
16
17
|
const common_1 = require("@nestjs/common");
|
|
17
18
|
const vendure_licence_sdk_1 = require("@huloglobal/vendure-licence-sdk");
|
|
18
19
|
const core_1 = require("@vendure/core");
|
|
@@ -64,6 +65,14 @@ function requireAdmin(ctx, res, perms = [core_1.Permission.ReadCustomer]) {
|
|
|
64
65
|
}
|
|
65
66
|
return true;
|
|
66
67
|
}
|
|
68
|
+
/** Back-pressure for streamed responses: resolves on 'drain' or when the client goes away. */
|
|
69
|
+
function drained(res) {
|
|
70
|
+
return new Promise(resolve => {
|
|
71
|
+
const done = () => { res.off('drain', done); res.off('close', done); resolve(); };
|
|
72
|
+
res.once('drain', done);
|
|
73
|
+
res.once('close', done);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
67
76
|
function clampInt(raw, fallback, min, max) {
|
|
68
77
|
const n = parseInt(String(raw !== null && raw !== void 0 ? raw : fallback), 10);
|
|
69
78
|
if (isNaN(n))
|
|
@@ -106,7 +115,8 @@ function parseChannelId(raw) {
|
|
|
106
115
|
function channelWhere(channelId, alias) {
|
|
107
116
|
if (channelId == null)
|
|
108
117
|
return { sql: '', params: [] };
|
|
109
|
-
|
|
118
|
+
// Backticked: `channelId` is a quoted camelCase column on Postgres.
|
|
119
|
+
const col = alias ? `${alias}.\`channelId\`` : '`channelId`';
|
|
110
120
|
return { sql: `AND ${col} = ?`, params: [channelId] };
|
|
111
121
|
}
|
|
112
122
|
const proxy_headers_1 = require("./proxy-headers");
|
|
@@ -140,6 +150,9 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
140
150
|
this.licenceStore = new vendure_licence_sdk_1.LicenceStore((sql, params) => (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(sql, params));
|
|
141
151
|
/** Buy-from-admin auto-install client. */
|
|
142
152
|
this.purchaseClaim = null;
|
|
153
|
+
// ------------------------------------------------------------------
|
|
154
|
+
/** userId → customerId, cached 10 min (bounded). Null when the request carries no session user. */
|
|
155
|
+
this.customerByUser = new Map();
|
|
143
156
|
}
|
|
144
157
|
onApplicationBootstrap() {
|
|
145
158
|
void this.purchaseClaimClient().resume();
|
|
@@ -293,14 +306,18 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
293
306
|
}
|
|
294
307
|
return this.purchaseClaim;
|
|
295
308
|
}
|
|
296
|
-
|
|
309
|
+
/** CORS preflight for the beacon (a JSON `fetch` from another origin sends one). */
|
|
310
|
+
trackPreflight(req, res) {
|
|
311
|
+
this.applyCors(req, res);
|
|
312
|
+
res.setHeader('Access-Control-Max-Age', '600');
|
|
313
|
+
return res.status(204).end();
|
|
314
|
+
}
|
|
315
|
+
async track(ctx, body, req, res) {
|
|
297
316
|
var _a;
|
|
298
317
|
// Lenient CORS — analytics ingestion must work cross-origin from
|
|
299
318
|
// both storefronts (and dev hosts) without complex config.
|
|
300
319
|
this.applyCors(req, res);
|
|
301
320
|
(0, vendure_licence_sdk_1.applySecurityHeaders)(res);
|
|
302
|
-
if (req.method === 'OPTIONS')
|
|
303
|
-
return res.status(204).end();
|
|
304
321
|
if (this.rateLimited(req, res, 'track'))
|
|
305
322
|
return;
|
|
306
323
|
// Tier-gate: unlicensed installs are capped at 100 events / UTC day.
|
|
@@ -361,7 +378,10 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
361
378
|
issuedSession = true;
|
|
362
379
|
}
|
|
363
380
|
const channelId = clampInt(body === null || body === void 0 ? void 0 : body.channelId, 1, 1, 2147483647);
|
|
364
|
-
|
|
381
|
+
// The customer comes from the Vendure session, never from the body: an
|
|
382
|
+
// anonymous beacon used to be able to file events under any customer id.
|
|
383
|
+
// The body value is honoured only when it matches the session's customer.
|
|
384
|
+
const customerId = await this.resolveCustomerId(ctx);
|
|
365
385
|
const events = Array.isArray(body === null || body === void 0 ? void 0 : body.events) ? body.events.slice(0, 50) : [];
|
|
366
386
|
const ip = realIp(req);
|
|
367
387
|
// Prefer the upstream proxy's resolved country / region (Cloudflare,
|
|
@@ -405,30 +425,30 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
405
425
|
const days = Math.min(Math.max(parseInt(String(req.query.days || '30'), 10) || 30, 1), 365);
|
|
406
426
|
const channelId = parseChannelId(req.query.channelId);
|
|
407
427
|
const w = channelWhere(channelId);
|
|
408
|
-
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT DATE(createdAt) AS day,
|
|
409
|
-
COUNT(DISTINCT visitorId) AS visitors,
|
|
410
|
-
COUNT(DISTINCT sessionId) AS sessions,
|
|
428
|
+
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT DATE(\`createdAt\`) AS day,
|
|
429
|
+
COUNT(DISTINCT \`visitorId\`) AS visitors,
|
|
430
|
+
COUNT(DISTINCT \`sessionId\`) AS sessions,
|
|
411
431
|
COUNT(*) AS events,
|
|
412
432
|
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS pageviews
|
|
413
433
|
FROM visitor_event
|
|
414
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
415
|
-
GROUP BY DATE(createdAt)
|
|
434
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
435
|
+
GROUP BY DATE(\`createdAt\`)
|
|
416
436
|
ORDER BY day`, [days, ...w.params]);
|
|
417
|
-
const [{ totalVisitors, totalSessions, totalPageviews, avgTimeMs }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT visitorId) AS totalVisitors
|
|
418
|
-
COUNT(DISTINCT sessionId) AS totalSessions
|
|
419
|
-
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS totalPageviews
|
|
420
|
-
AVG(CASE WHEN type='unload' AND timeOnPageMs > 0 THEN timeOnPageMs END) AS avgTimeMs
|
|
437
|
+
const [{ totalVisitors, totalSessions, totalPageviews, avgTimeMs }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT \`visitorId\`) AS \`totalVisitors\`,
|
|
438
|
+
COUNT(DISTINCT \`sessionId\`) AS \`totalSessions\`,
|
|
439
|
+
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS \`totalPageviews\`,
|
|
440
|
+
AVG(CASE WHEN type='unload' AND \`timeOnPageMs\` > 0 THEN \`timeOnPageMs\` END) AS \`avgTimeMs\`
|
|
421
441
|
FROM visitor_event
|
|
422
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}`, [days, ...w.params]);
|
|
442
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}`, [days, ...w.params]);
|
|
423
443
|
// Previous period of identical length, immediately prior —
|
|
424
444
|
// powers the KPI delta chips ("+12% vs previous 30 days").
|
|
425
|
-
const [prev] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT visitorId) AS totalVisitors
|
|
426
|
-
COUNT(DISTINCT sessionId) AS totalSessions
|
|
427
|
-
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS totalPageviews
|
|
428
|
-
AVG(CASE WHEN type='unload' AND timeOnPageMs > 0 THEN timeOnPageMs END) AS avgTimeMs
|
|
445
|
+
const [prev] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT \`visitorId\`) AS \`totalVisitors\`,
|
|
446
|
+
COUNT(DISTINCT \`sessionId\`) AS \`totalSessions\`,
|
|
447
|
+
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS \`totalPageviews\`,
|
|
448
|
+
AVG(CASE WHEN type='unload' AND \`timeOnPageMs\` > 0 THEN \`timeOnPageMs\` END) AS \`avgTimeMs\`
|
|
429
449
|
FROM visitor_event
|
|
430
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY)
|
|
431
|
-
AND createdAt < DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}`, [days * 2, days, ...w.params]);
|
|
450
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY)
|
|
451
|
+
AND \`createdAt\` < DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}`, [days * 2, days, ...w.params]);
|
|
432
452
|
return res.json({
|
|
433
453
|
days, channelId,
|
|
434
454
|
totals: {
|
|
@@ -464,33 +484,33 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
464
484
|
const channelId = parseChannelId(req.query.channelId);
|
|
465
485
|
const w = channelWhere(channelId);
|
|
466
486
|
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT source, medium, COALESCE(MAX(campaign), '') AS campaign,
|
|
467
|
-
COUNT(DISTINCT visitorId) AS visitors,
|
|
468
|
-
COUNT(DISTINCT sessionId) AS sessions,
|
|
469
|
-
SUM(reached_product) AS productViewers
|
|
470
|
-
SUM(reached_checkout) AS checkoutReached
|
|
487
|
+
COUNT(DISTINCT \`visitorId\`) AS visitors,
|
|
488
|
+
COUNT(DISTINCT \`sessionId\`) AS sessions,
|
|
489
|
+
SUM(reached_product) AS \`productViewers\`,
|
|
490
|
+
SUM(reached_checkout) AS \`checkoutReached\`
|
|
471
491
|
FROM (
|
|
472
|
-
SELECT visitorId
|
|
473
|
-
COALESCE(utmSource
|
|
474
|
-
COALESCE(utmMedium
|
|
475
|
-
utmCampaign AS campaign,
|
|
492
|
+
SELECT \`visitorId\`, \`sessionId\`,
|
|
493
|
+
COALESCE(\`utmSource\`, \`referrerDomain\`, '(direct)') AS source,
|
|
494
|
+
COALESCE(\`utmMedium\`, IF(\`referrerDomain\` IS NOT NULL, 'referral', 'none')) AS medium,
|
|
495
|
+
\`utmCampaign\` AS campaign,
|
|
476
496
|
MAX(CASE WHEN url LIKE '/products/%' AND type='pageview' THEN 1 ELSE 0 END) AS reached_product,
|
|
477
497
|
MAX(CASE WHEN (url LIKE '%/checkout%' OR url LIKE '%cart%') AND type='pageview' THEN 1 ELSE 0 END) AS reached_checkout
|
|
478
498
|
FROM visitor_event
|
|
479
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
480
|
-
GROUP BY visitorId
|
|
481
|
-
COALESCE(utmSource
|
|
482
|
-
COALESCE(utmMedium
|
|
483
|
-
utmCampaign
|
|
499
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
500
|
+
GROUP BY \`visitorId\`, \`sessionId\`,
|
|
501
|
+
COALESCE(\`utmSource\`, \`referrerDomain\`, '(direct)'),
|
|
502
|
+
COALESCE(\`utmMedium\`, IF(\`referrerDomain\` IS NOT NULL, 'referral', 'none')),
|
|
503
|
+
\`utmCampaign\`
|
|
484
504
|
) by_session
|
|
485
505
|
GROUP BY source, medium
|
|
486
506
|
ORDER BY visitors DESC
|
|
487
507
|
LIMIT ? OFFSET ?`, [days, ...w.params, take, skip]);
|
|
488
508
|
const [{ total }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(*) AS total FROM (
|
|
489
509
|
SELECT DISTINCT
|
|
490
|
-
COALESCE(utmSource
|
|
491
|
-
COALESCE(utmMedium
|
|
510
|
+
COALESCE(\`utmSource\`, \`referrerDomain\`, '(direct)') AS source,
|
|
511
|
+
COALESCE(\`utmMedium\`, IF(\`referrerDomain\` IS NOT NULL, 'referral', 'none')) AS medium
|
|
492
512
|
FROM visitor_event
|
|
493
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
513
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
494
514
|
) sources`, [days, ...w.params]);
|
|
495
515
|
return res.json({
|
|
496
516
|
sources: rows.map((r) => ({
|
|
@@ -517,16 +537,16 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
517
537
|
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT url,
|
|
518
538
|
MAX(title) AS title,
|
|
519
539
|
COUNT(*) AS views,
|
|
520
|
-
COUNT(DISTINCT visitorId) AS uniqueVisitors
|
|
521
|
-
AVG(NULLIF(timeOnPageMs
|
|
540
|
+
COUNT(DISTINCT \`visitorId\`) AS \`uniqueVisitors\`,
|
|
541
|
+
AVG(NULLIF(\`timeOnPageMs\`, 0)) AS \`avgTimeMs\`
|
|
522
542
|
FROM visitor_event
|
|
523
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
543
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
524
544
|
AND type IN ('pageview', 'unload')
|
|
525
545
|
GROUP BY url
|
|
526
546
|
ORDER BY views DESC
|
|
527
547
|
LIMIT ? OFFSET ?`, [days, ...w.params, take, skip]);
|
|
528
548
|
const [{ total }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT url) AS total FROM visitor_event
|
|
529
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
549
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
530
550
|
AND type IN ('pageview', 'unload')`, [days, ...w.params]);
|
|
531
551
|
return res.json({
|
|
532
552
|
pages: rows.map((r) => ({
|
|
@@ -553,8 +573,8 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
553
573
|
];
|
|
554
574
|
const result = [];
|
|
555
575
|
for (const s of stages) {
|
|
556
|
-
const [{ n }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT visitorId) AS n FROM visitor_event
|
|
557
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
576
|
+
const [{ n }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT \`visitorId\`) AS n FROM visitor_event
|
|
577
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
558
578
|
AND type='pageview' AND (${s.where})`, [days, ...w.params]);
|
|
559
579
|
result.push({ key: s.key, label: s.label, visitors: Number(n) || 0 });
|
|
560
580
|
}
|
|
@@ -574,10 +594,10 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
574
594
|
MAX(title) AS title,
|
|
575
595
|
COUNT(*) AS exits
|
|
576
596
|
FROM (
|
|
577
|
-
SELECT sessionId
|
|
578
|
-
ROW_NUMBER() OVER (PARTITION BY sessionId ORDER BY createdAt DESC) AS rn
|
|
597
|
+
SELECT \`sessionId\`, url, title, \`createdAt\`,
|
|
598
|
+
ROW_NUMBER() OVER (PARTITION BY \`sessionId\` ORDER BY \`createdAt\` DESC) AS rn
|
|
579
599
|
FROM visitor_event
|
|
580
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
600
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
581
601
|
AND type='pageview'
|
|
582
602
|
) last_pages
|
|
583
603
|
WHERE rn = 1
|
|
@@ -585,10 +605,10 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
585
605
|
ORDER BY exits DESC
|
|
586
606
|
LIMIT ? OFFSET ?`, [days, ...w.params, take, skip]);
|
|
587
607
|
const [{ total }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT url) AS total FROM (
|
|
588
|
-
SELECT sessionId
|
|
589
|
-
ROW_NUMBER() OVER (PARTITION BY sessionId ORDER BY createdAt DESC) AS rn
|
|
608
|
+
SELECT \`sessionId\`, url,
|
|
609
|
+
ROW_NUMBER() OVER (PARTITION BY \`sessionId\` ORDER BY \`createdAt\` DESC) AS rn
|
|
590
610
|
FROM visitor_event
|
|
591
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
611
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
592
612
|
AND type='pageview'
|
|
593
613
|
) lp WHERE rn = 1`, [days, ...w.params]);
|
|
594
614
|
return res.json({
|
|
@@ -611,16 +631,16 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
611
631
|
const w = channelWhere(channelId);
|
|
612
632
|
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT type,
|
|
613
633
|
COUNT(*) AS count,
|
|
614
|
-
COUNT(DISTINCT visitorId) AS uniqueVisitors
|
|
615
|
-
COUNT(DISTINCT sessionId) AS sessions
|
|
634
|
+
COUNT(DISTINCT \`visitorId\`) AS \`uniqueVisitors\`,
|
|
635
|
+
COUNT(DISTINCT \`sessionId\`) AS sessions
|
|
616
636
|
FROM visitor_event
|
|
617
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
637
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
618
638
|
AND type NOT IN ('pageview', 'unload')
|
|
619
639
|
GROUP BY type
|
|
620
640
|
ORDER BY count DESC
|
|
621
641
|
LIMIT ? OFFSET ?`, [days, ...w.params, take, skip]);
|
|
622
642
|
const [{ total }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT type) AS total FROM visitor_event
|
|
623
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
643
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
624
644
|
AND type NOT IN ('pageview', 'unload')`, [days, ...w.params]);
|
|
625
645
|
return res.json({
|
|
626
646
|
events: rows.map((r) => ({
|
|
@@ -672,9 +692,9 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
672
692
|
const days = clampInt(req.query.days, 30, 1, 365);
|
|
673
693
|
const channelId = parseChannelId(req.query.channelId);
|
|
674
694
|
const w = channelWhere(channelId);
|
|
675
|
-
const grouped = (col) => (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT ${col} AS label, COUNT(DISTINCT visitorId) AS visitors
|
|
695
|
+
const grouped = (col) => (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT ${col} AS label, COUNT(DISTINCT \`visitorId\`) AS visitors
|
|
676
696
|
FROM visitor_event
|
|
677
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
697
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
678
698
|
AND ${col} IS NOT NULL AND ${col} <> ''
|
|
679
699
|
GROUP BY ${col}
|
|
680
700
|
ORDER BY visitors DESC
|
|
@@ -706,18 +726,28 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
706
726
|
res.setHeader('Connection', 'keep-alive');
|
|
707
727
|
res.setHeader('X-Accel-Buffering', 'no'); // disable nginx buffering
|
|
708
728
|
(_a = res.flushHeaders) === null || _a === void 0 ? void 0 : _a.call(res);
|
|
729
|
+
// One query in flight at a time: a slow tick used to pile up behind
|
|
730
|
+
// itself every 5 s. A `: keepalive` comment every 15 s keeps proxies
|
|
731
|
+
// from closing an idle stream between data frames.
|
|
732
|
+
let inFlight = false;
|
|
733
|
+
let closed = false;
|
|
709
734
|
const tick = async () => {
|
|
735
|
+
if (inFlight || closed)
|
|
736
|
+
return;
|
|
737
|
+
inFlight = true;
|
|
710
738
|
try {
|
|
711
|
-
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT visitorId
|
|
739
|
+
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT \`visitorId\`,
|
|
712
740
|
MAX(url) AS url,
|
|
713
741
|
MAX(country) AS country,
|
|
714
|
-
TIMESTAMPDIFF(SECOND, MAX(createdAt), NOW()) AS secondsAgo
|
|
742
|
+
TIMESTAMPDIFF(SECOND, MAX(\`createdAt\`), NOW()) AS \`secondsAgo\`
|
|
715
743
|
FROM visitor_event
|
|
716
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) ${w.sql}
|
|
744
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) ${w.sql}
|
|
717
745
|
AND type = 'pageview'
|
|
718
|
-
GROUP BY visitorId
|
|
719
|
-
ORDER BY MAX(createdAt) DESC
|
|
746
|
+
GROUP BY \`visitorId\`
|
|
747
|
+
ORDER BY MAX(\`createdAt\`) DESC
|
|
720
748
|
LIMIT 20`, [...w.params]);
|
|
749
|
+
if (closed)
|
|
750
|
+
return;
|
|
721
751
|
const payload = {
|
|
722
752
|
ts: new Date().toISOString(),
|
|
723
753
|
activeCount: rows.length,
|
|
@@ -735,12 +765,18 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
735
765
|
// tick will retry. SSE clients reconnect automatically
|
|
736
766
|
// if the connection drops.
|
|
737
767
|
}
|
|
768
|
+
finally {
|
|
769
|
+
inFlight = false;
|
|
770
|
+
}
|
|
738
771
|
};
|
|
739
772
|
// Fire immediately, then every 5s. Stop when the client disconnects.
|
|
740
773
|
await tick();
|
|
741
774
|
const interval = setInterval(() => { tick().catch(() => undefined); }, 5000);
|
|
742
|
-
|
|
743
|
-
|
|
775
|
+
const keepalive = setInterval(() => { if (!closed)
|
|
776
|
+
res.write(': keepalive\n\n'); }, 15000);
|
|
777
|
+
const stop = () => { closed = true; clearInterval(interval); clearInterval(keepalive); };
|
|
778
|
+
req.on('close', stop);
|
|
779
|
+
req.on('end', stop);
|
|
744
780
|
}
|
|
745
781
|
/** Journey timeline for one visitor — every event in order. */
|
|
746
782
|
async journey(ctx, visitorId, res) {
|
|
@@ -763,11 +799,11 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
763
799
|
const skip = clampInt(req.query.skip, 0, 0, 1000000);
|
|
764
800
|
const channelId = parseChannelId(req.query.channelId);
|
|
765
801
|
const w = channelWhere(channelId);
|
|
766
|
-
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT visitorId
|
|
767
|
-
MAX(customerId) AS customerId
|
|
768
|
-
MIN(createdAt) AS firstSeenAt
|
|
769
|
-
MAX(createdAt) AS lastSeenAt
|
|
770
|
-
COUNT(DISTINCT sessionId) AS sessions,
|
|
802
|
+
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT \`visitorId\`,
|
|
803
|
+
MAX(\`customerId\`) AS \`customerId\`,
|
|
804
|
+
MIN(\`createdAt\`) AS \`firstSeenAt\`,
|
|
805
|
+
MAX(\`createdAt\`) AS \`lastSeenAt\`,
|
|
806
|
+
COUNT(DISTINCT \`sessionId\`) AS sessions,
|
|
771
807
|
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS pageviews,
|
|
772
808
|
MAX(country) AS country,
|
|
773
809
|
MAX(city) AS city,
|
|
@@ -775,12 +811,12 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
775
811
|
MAX(os) AS os,
|
|
776
812
|
MAX(device) AS device
|
|
777
813
|
FROM visitor_event
|
|
778
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
779
|
-
GROUP BY visitorId
|
|
780
|
-
ORDER BY MAX(createdAt) DESC
|
|
814
|
+
WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
815
|
+
GROUP BY \`visitorId\`
|
|
816
|
+
ORDER BY MAX(\`createdAt\`) DESC
|
|
781
817
|
LIMIT ? OFFSET ?`, [days, ...w.params, take, skip]);
|
|
782
|
-
const [{ total }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT visitorId) AS total
|
|
783
|
-
FROM visitor_event WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
818
|
+
const [{ total }] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT COUNT(DISTINCT \`visitorId\`) AS total
|
|
819
|
+
FROM visitor_event WHERE \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
784
820
|
`, [days, ...w.params]);
|
|
785
821
|
return res.json({
|
|
786
822
|
visitors: rows.map((r) => ({
|
|
@@ -808,49 +844,49 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
808
844
|
return;
|
|
809
845
|
const visitorId = String(visitorIdRaw).slice(0, 64);
|
|
810
846
|
// One row with the latest non-null value of every column.
|
|
811
|
-
const [latest] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT visitorId
|
|
812
|
-
MAX(customerId) AS customerId
|
|
813
|
-
MIN(createdAt) AS firstSeenAt
|
|
814
|
-
MAX(createdAt) AS lastSeenAt
|
|
815
|
-
COUNT(DISTINCT sessionId) AS totalSessions
|
|
816
|
-
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS totalPageviews
|
|
817
|
-
SUM(CASE WHEN type='unload' THEN 1 ELSE 0 END) AS totalUnloads
|
|
818
|
-
SUM(CASE WHEN type='event' THEN 1 ELSE 0 END) AS totalEvents
|
|
819
|
-
SUM(timeOnPageMs) AS totalTimeMs
|
|
847
|
+
const [latest] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT \`visitorId\`,
|
|
848
|
+
MAX(\`customerId\`) AS \`customerId\`,
|
|
849
|
+
MIN(\`createdAt\`) AS \`firstSeenAt\`,
|
|
850
|
+
MAX(\`createdAt\`) AS \`lastSeenAt\`,
|
|
851
|
+
COUNT(DISTINCT \`sessionId\`) AS \`totalSessions\`,
|
|
852
|
+
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS \`totalPageviews\`,
|
|
853
|
+
SUM(CASE WHEN type='unload' THEN 1 ELSE 0 END) AS \`totalUnloads\`,
|
|
854
|
+
SUM(CASE WHEN type='event' THEN 1 ELSE 0 END) AS \`totalEvents\`,
|
|
855
|
+
SUM(\`timeOnPageMs\`) AS \`totalTimeMs\`,
|
|
820
856
|
MAX(ip) AS ip,
|
|
821
|
-
MAX(ipHash) AS ipHash
|
|
822
|
-
MAX(userAgent) AS userAgent
|
|
857
|
+
MAX(\`ipHash\`) AS \`ipHash\`,
|
|
858
|
+
MAX(\`userAgent\`) AS \`userAgent\`,
|
|
823
859
|
MAX(browser) AS browser,
|
|
824
|
-
MAX(browserVersion) AS browserVersion
|
|
860
|
+
MAX(\`browserVersion\`) AS \`browserVersion\`,
|
|
825
861
|
MAX(os) AS os,
|
|
826
|
-
MAX(osVersion) AS osVersion
|
|
862
|
+
MAX(\`osVersion\`) AS \`osVersion\`,
|
|
827
863
|
MAX(device) AS device,
|
|
828
|
-
MAX(acceptLanguage) AS acceptLanguage
|
|
864
|
+
MAX(\`acceptLanguage\`) AS \`acceptLanguage\`,
|
|
829
865
|
MAX(country) AS country,
|
|
830
866
|
MAX(region) AS region,
|
|
831
867
|
MAX(city) AS city,
|
|
832
868
|
MAX(timezone) AS timezone,
|
|
833
|
-
MAX(channelId) AS channelId
|
|
869
|
+
MAX(\`channelId\`) AS \`channelId\`
|
|
834
870
|
FROM visitor_event
|
|
835
|
-
WHERE visitorId = ?
|
|
836
|
-
GROUP BY visitorId
|
|
871
|
+
WHERE \`visitorId\` = ?
|
|
872
|
+
GROUP BY \`visitorId\``, [visitorId]);
|
|
837
873
|
if (!latest)
|
|
838
874
|
return res.status(404).json({ error: 'visitor not found' });
|
|
839
|
-
const sessions = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT sessionId
|
|
840
|
-
MIN(createdAt) AS startedAt
|
|
841
|
-
MAX(createdAt) AS endedAt
|
|
875
|
+
const sessions = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT \`sessionId\`,
|
|
876
|
+
MIN(\`createdAt\`) AS \`startedAt\`,
|
|
877
|
+
MAX(\`createdAt\`) AS \`endedAt\`,
|
|
842
878
|
COUNT(*) AS events,
|
|
843
879
|
SUM(CASE WHEN type='pageview' THEN 1 ELSE 0 END) AS pageviews,
|
|
844
|
-
SUM(timeOnPageMs) AS timeMs
|
|
845
|
-
MIN(CASE WHEN type='pageview' THEN url END) AS entryUrl
|
|
880
|
+
SUM(\`timeOnPageMs\`) AS \`timeMs\`,
|
|
881
|
+
MIN(CASE WHEN type='pageview' THEN url END) AS \`entryUrl\`
|
|
846
882
|
FROM visitor_event
|
|
847
|
-
WHERE visitorId = ?
|
|
848
|
-
GROUP BY sessionId
|
|
849
|
-
ORDER BY MIN(createdAt) DESC
|
|
883
|
+
WHERE \`visitorId\` = ?
|
|
884
|
+
GROUP BY \`sessionId\`
|
|
885
|
+
ORDER BY MIN(\`createdAt\`) DESC
|
|
850
886
|
LIMIT 50`, [visitorId]);
|
|
851
887
|
let customer = null;
|
|
852
888
|
if (latest.customerId) {
|
|
853
|
-
const [c] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT id, firstName
|
|
889
|
+
const [c] = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT id, \`firstName\`, \`lastName\`, \`emailAddress\`
|
|
854
890
|
FROM customer WHERE id = ? LIMIT 1`, [Number(latest.customerId)]);
|
|
855
891
|
customer = c || null;
|
|
856
892
|
}
|
|
@@ -894,19 +930,42 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
894
930
|
})),
|
|
895
931
|
});
|
|
896
932
|
}
|
|
897
|
-
|
|
933
|
+
async resolveCustomerId(ctx) {
|
|
934
|
+
var _a;
|
|
935
|
+
const userId = ctx === null || ctx === void 0 ? void 0 : ctx.activeUserId;
|
|
936
|
+
if (!userId)
|
|
937
|
+
return null;
|
|
938
|
+
const key = String(userId);
|
|
939
|
+
const now = Date.now();
|
|
940
|
+
const hit = this.customerByUser.get(key);
|
|
941
|
+
if (hit && hit.exp > now)
|
|
942
|
+
return hit.id;
|
|
943
|
+
let id = null;
|
|
944
|
+
try {
|
|
945
|
+
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT id FROM customer WHERE \`userId\` = ? AND \`deletedAt\` IS NULL LIMIT 1`, [userId]);
|
|
946
|
+
id = ((_a = rows === null || rows === void 0 ? void 0 : rows[0]) === null || _a === void 0 ? void 0 : _a.id) != null ? Number(rows[0].id) || null : null;
|
|
947
|
+
}
|
|
948
|
+
catch {
|
|
949
|
+
id = null;
|
|
950
|
+
}
|
|
951
|
+
if (this.customerByUser.size > 5000)
|
|
952
|
+
this.customerByUser.clear();
|
|
953
|
+
this.customerByUser.set(key, { id, exp: now + 10 * 60000 });
|
|
954
|
+
return id;
|
|
955
|
+
}
|
|
898
956
|
applyCors(req, res) {
|
|
899
957
|
const origin = String(req.headers.origin || '');
|
|
900
958
|
const allowList = (0, plugin_1.getOptions)().corsAllowedOrigins || [];
|
|
901
|
-
// When an allowlist is configured we reflect only matching
|
|
902
|
-
//
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
allow = allowList.includes(origin) ? origin : 'null';
|
|
906
|
-
}
|
|
907
|
-
res.setHeader('Access-Control-Allow-Origin', allow);
|
|
959
|
+
// When an allowlist is configured we reflect only matching origins;
|
|
960
|
+
// otherwise we reflect any (legacy behaviour, looser). A disallowed
|
|
961
|
+
// origin gets NO Allow-Origin header — `null` is itself a valid
|
|
962
|
+
// origin value (sandboxed iframes, file://) and would let it through.
|
|
908
963
|
res.setHeader('Vary', 'Origin');
|
|
909
|
-
|
|
964
|
+
if (allowList.length && !allowList.includes(origin))
|
|
965
|
+
return;
|
|
966
|
+
res.setHeader('Access-Control-Allow-Origin', origin || '*');
|
|
967
|
+
if (origin)
|
|
968
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
910
969
|
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
911
970
|
res.setHeader('Access-Control-Allow-Headers', 'content-type');
|
|
912
971
|
}
|
|
@@ -1027,15 +1086,15 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
1027
1086
|
return;
|
|
1028
1087
|
const days = Math.min(Math.max(parseInt(String(req.query.days || '30'), 10) || 30, 1), 365);
|
|
1029
1088
|
const channelId = parseInt(String(req.query.channelId || '1'), 10) || 1;
|
|
1030
|
-
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT g.id, g.name, g
|
|
1031
|
-
COUNT(DISTINCT v
|
|
1089
|
+
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT g.id, g.name, g.\`urlPattern\`, g.\`valueMinor\`, g.enabled,
|
|
1090
|
+
COUNT(DISTINCT v.\`visitorId\`) AS \`uniqueVisitors\`,
|
|
1032
1091
|
COUNT(v.id) AS completions
|
|
1033
1092
|
FROM conversion_goal g
|
|
1034
1093
|
LEFT JOIN visitor_event v
|
|
1035
|
-
ON v
|
|
1036
|
-
AND v
|
|
1037
|
-
AND v
|
|
1038
|
-
WHERE g
|
|
1094
|
+
ON v.\`goalId\` = g.id
|
|
1095
|
+
AND v.\`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY)
|
|
1096
|
+
AND v.\`isBot\` = FALSE
|
|
1097
|
+
WHERE g.\`channelId\` = ?
|
|
1039
1098
|
GROUP BY g.id`, [days, channelId]);
|
|
1040
1099
|
return res.json({ days, channelId, goals: rows.map((r) => ({
|
|
1041
1100
|
...r,
|
|
@@ -1052,14 +1111,6 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
1052
1111
|
const days = Math.min(Math.max(parseInt(String(req.query.days || '7'), 10) || 7, 1), 90);
|
|
1053
1112
|
const channelId = parseChannelId(req.query.channelId);
|
|
1054
1113
|
const w = channelWhere(channelId);
|
|
1055
|
-
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT createdAt, visitorId, sessionId, customerId, channelId,
|
|
1056
|
-
type, url, title, referrerDomain,
|
|
1057
|
-
country, region, city, browser, os, device,
|
|
1058
|
-
isBot, goalId, utmSource, utmMedium, utmCampaign
|
|
1059
|
-
FROM visitor_event
|
|
1060
|
-
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
1061
|
-
ORDER BY createdAt DESC
|
|
1062
|
-
LIMIT 200000`, [days, ...w.params]);
|
|
1063
1114
|
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
1064
1115
|
res.setHeader('Content-Disposition', `attachment; filename="visitors-${new Date().toISOString().slice(0, 10)}.csv"`);
|
|
1065
1116
|
const esc = (v) => {
|
|
@@ -1072,15 +1123,40 @@ let VisitorTrackingController = class VisitorTrackingController {
|
|
|
1072
1123
|
return /[",\n]/.test(s) ? `"${s}"` : s;
|
|
1073
1124
|
};
|
|
1074
1125
|
res.write('createdAt,visitorId,sessionId,customerId,channelId,type,url,title,referrerDomain,country,region,city,browser,os,device,isBot,goalId,utmSource,utmMedium,utmCampaign\n');
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1126
|
+
// Streamed in id-ordered chunks: the export used to materialise up to
|
|
1127
|
+
// 200 000 rows before writing the first byte.
|
|
1128
|
+
const CHUNK = 5000;
|
|
1129
|
+
const MAX_ROWS = 200000;
|
|
1130
|
+
let lastId = 0;
|
|
1131
|
+
let written = 0;
|
|
1132
|
+
while (written < MAX_ROWS) {
|
|
1133
|
+
const rows = await (0, vendure_licence_sdk_1.adapterFor)(this.connection.rawConnection).query(`SELECT id, \`createdAt\`, \`visitorId\`, \`sessionId\`, \`customerId\`, \`channelId\`,
|
|
1134
|
+
type, url, title, \`referrerDomain\`,
|
|
1135
|
+
country, region, city, browser, os, device,
|
|
1136
|
+
\`isBot\`, \`goalId\`, \`utmSource\`, \`utmMedium\`, \`utmCampaign\`
|
|
1137
|
+
FROM visitor_event
|
|
1138
|
+
WHERE id > ? AND \`createdAt\` >= DATE_SUB(NOW(), INTERVAL ? DAY) ${w.sql}
|
|
1139
|
+
ORDER BY id
|
|
1140
|
+
LIMIT ?`, [lastId, days, ...w.params, Math.min(CHUNK, MAX_ROWS - written)]);
|
|
1141
|
+
if (!rows.length)
|
|
1142
|
+
break;
|
|
1143
|
+
let out = '';
|
|
1144
|
+
for (const r of rows) {
|
|
1145
|
+
out += [
|
|
1146
|
+
esc(r.createdAt instanceof Date ? r.createdAt.toISOString() : r.createdAt),
|
|
1147
|
+
esc(r.visitorId), esc(r.sessionId), esc(r.customerId), esc(r.channelId),
|
|
1148
|
+
esc(r.type), esc(r.url), esc(r.title), esc(r.referrerDomain),
|
|
1149
|
+
esc(r.country), esc(r.region), esc(r.city), esc(r.browser), esc(r.os), esc(r.device),
|
|
1150
|
+
esc(r.isBot === true ? 1 : r.isBot === false ? 0 : r.isBot), esc(r.goalId),
|
|
1151
|
+
esc(r.utmSource), esc(r.utmMedium), esc(r.utmCampaign),
|
|
1152
|
+
].join(',') + '\n';
|
|
1153
|
+
}
|
|
1154
|
+
lastId = Number(rows[rows.length - 1].id);
|
|
1155
|
+
written += rows.length;
|
|
1156
|
+
if (!res.write(out))
|
|
1157
|
+
await drained(res);
|
|
1158
|
+
if (res.destroyed || res.writableEnded)
|
|
1159
|
+
return;
|
|
1084
1160
|
}
|
|
1085
1161
|
return res.end();
|
|
1086
1162
|
}
|
|
@@ -1146,13 +1222,22 @@ __decorate([
|
|
|
1146
1222
|
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
1147
1223
|
__metadata("design:returntype", Promise)
|
|
1148
1224
|
], VisitorTrackingController.prototype, "licencePortalLink", null);
|
|
1225
|
+
__decorate([
|
|
1226
|
+
(0, common_1.Options)('track'),
|
|
1227
|
+
__param(0, (0, common_1.Req)()),
|
|
1228
|
+
__param(1, (0, common_1.Res)()),
|
|
1229
|
+
__metadata("design:type", Function),
|
|
1230
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
1231
|
+
__metadata("design:returntype", void 0)
|
|
1232
|
+
], VisitorTrackingController.prototype, "trackPreflight", null);
|
|
1149
1233
|
__decorate([
|
|
1150
1234
|
(0, common_1.Post)('track'),
|
|
1151
|
-
__param(0, (0,
|
|
1152
|
-
__param(1, (0, common_1.
|
|
1153
|
-
__param(2, (0, common_1.
|
|
1235
|
+
__param(0, (0, core_1.Ctx)()),
|
|
1236
|
+
__param(1, (0, common_1.Body)()),
|
|
1237
|
+
__param(2, (0, common_1.Req)()),
|
|
1238
|
+
__param(3, (0, common_1.Res)()),
|
|
1154
1239
|
__metadata("design:type", Function),
|
|
1155
|
-
__metadata("design:paramtypes", [Object, Object, Object]),
|
|
1240
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object, Object, Object]),
|
|
1156
1241
|
__metadata("design:returntype", Promise)
|
|
1157
1242
|
], VisitorTrackingController.prototype, "track", null);
|
|
1158
1243
|
__decorate([
|