Skip to content

Commit 9ec4704

Browse files
Merge pull request #4 from EcomDev/feature/support-csp
feat(beacon): Beacon Configuration supports various CSP modes of Magento
2 parents bda1e1a + a4109da commit 9ec4704

File tree

4 files changed

+51
-35
lines changed

4 files changed

+51
-35
lines changed

lib/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface BeaconConfig {
1313
}
1414

1515
export function loadConfiguration(): BeaconConfig | false {
16-
const beaconIngestElement = document.querySelector('script[type="ecomdev/rum-beacon-ingest"]');
16+
const beaconIngestElement = document.querySelector('script[type="text/x-rum-beacon-config"]');
1717
if (!beaconIngestElement) {
1818
return false;
1919
}

src/ViewModel/BeaconInfo.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
namespace EcomDev\RUMBeacon\ViewModel;
1111

1212
use EcomDev\RUMBeacon\State\CurrentPageInfo;
13+
use Magento\Csp\Model\Collector\DynamicCollector;
14+
use Magento\Csp\Model\Policy\FetchPolicy;
1315
use Magento\Framework\App\Config\ScopeConfigInterface;
1416
use Magento\Framework\UrlInterface;
1517
use Magento\Framework\View\Element\Block\ArgumentInterface;
@@ -26,6 +28,7 @@ public function __construct(
2628
private CurrentPageInfo $currentPageInfo,
2729
private ScopeConfigInterface $scopeConfig,
2830
private StoreManagerInterface $storeManager,
31+
private DynamicCollector $cspCollector,
2932
) {
3033
}
3134

@@ -56,6 +59,7 @@ public function ingestInfo(): array
5659
default => []
5760
};
5861

62+
$isEnabled = $this->scopeConfig->isSetFlag(self::CONFIG_IS_ENABLED, ScopeInterface::SCOPE_STORE);
5963
$ingestionUrl = $this->scopeConfig->getValue(self::CONFIG_INGESTION_URL, ScopeInterface::SCOPE_STORE);
6064
if (str_contains($ingestionUrl, Store::BASE_URL_PLACEHOLDER)) {
6165
$ingestionUrl = str_replace(
@@ -66,13 +70,22 @@ public function ingestInfo(): array
6670
),
6771
$ingestionUrl
6872
);
73+
} elseif ($isEnabled) {
74+
$hostName = parse_url($ingestionUrl, PHP_URL_HOST);
75+
$this->cspCollector->add(
76+
new FetchPolicy(
77+
'connect-src',
78+
false,
79+
[$hostName],
80+
)
81+
);
6982
}
7083

7184
return [
7285
'pageInfo' => $info,
7386
'isUrlCollected' => $isUrlCollected,
7487
'allowedQueryParams' => $allowedQueryParams,
75-
'isEnabled' => $this->scopeConfig->isSetFlag(self::CONFIG_IS_ENABLED, ScopeInterface::SCOPE_STORE),
88+
'isEnabled' => $isEnabled,
7689
'ingestionUrl' => $ingestionUrl,
7790
];
7891
}

src/view/frontend/templates/js-config.phtml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
<?php
22
/** @var $block \Magento\Framework\View\Element\Template */
3-
/** @var $beacon \Ecomdev\RUMBeacon\ViewModel\BeaconInfo */
4-
$beacon = $block->getBeaconInfo();
3+
/** @var $csp \Magento\Csp\Api\InlineUtilInterface */
4+
/** @var $secureRenderer \Magento\Framework\View\Helper\SecureHtmlRenderer */
5+
echo $secureRenderer->renderTag(
6+
'script',
7+
['type' => 'text/x-rum-beacon-config'],
8+
$block->getBeaconInfo()->ingestInfoJson(),
9+
false
10+
);
511
?>
6-
<script type="ecomdev/rum-beacon-ingest">
7-
<?php echo $block->getBeaconInfo()->ingestInfoJson(); ?>
8-
</script>

src/view/frontend/web/js/beacon.js

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
function Ct() {
2-
const e = document.querySelector('script[type="ecomdev/rum-beacon-ingest"]');
2+
const e = document.querySelector('script[type="text/x-rum-beacon-config"]');
33
if (!e)
44
return !1;
55
const t = JSON.parse(e.textContent);
@@ -36,28 +36,28 @@ class ot {
3636
this.o && r && i && t.startTime - i.startTime < 1e3 && t.startTime - r.startTime < 5e3 ? (this.o += t.value, this.i.push(t)) : (this.o = t.value, this.i = [t]), this.t?.(t);
3737
}
3838
}
39-
const P = () => {
39+
const I = () => {
4040
const e = performance.getEntriesByType("navigation")[0];
4141
if (e && e.responseStart > 0 && e.responseStart < performance.now()) return e;
4242
}, R = (e) => {
4343
if (document.readyState === "loading") return "loading";
4444
{
45-
const t = P();
45+
const t = I();
4646
if (t) {
4747
if (e < t.domInteractive) return "loading";
4848
if (t.domContentLoadedEventStart === 0 || e < t.domContentLoadedEventStart) return "dom-interactive";
4949
if (t.domComplete === 0 || e < t.domComplete) return "dom-content-loaded";
5050
}
5151
}
5252
return "complete";
53-
}, It = (e) => {
53+
}, xt = (e) => {
5454
const t = e.nodeName;
5555
return e.nodeType === 1 ? t.toLowerCase() : t.toUpperCase().replace(/^#/, "");
5656
}, Z = (e) => {
5757
let t = "";
5858
try {
5959
for (; e?.nodeType !== 9; ) {
60-
const r = e, i = r.id ? "#" + r.id : [It(r), ...Array.from(r.classList).sort()].join(".");
60+
const r = e, i = r.id ? "#" + r.id : [xt(r), ...Array.from(r.classList).sort()].join(".");
6161
if (t.length + i.length > 99) return t || i;
6262
if (t = t ? i + ">" + t : i, r.id) break;
6363
e = r.parentNode;
@@ -70,7 +70,7 @@ function w(e, t) {
7070
return Q.get(e) || Q.set(e, new t()), Q.get(e);
7171
}
7272
let yt = -1;
73-
const bt = () => yt, x = (e) => {
73+
const bt = () => yt, P = (e) => {
7474
addEventListener("pageshow", (t) => {
7575
t.persisted && (yt = t.timeStamp, e(t));
7676
}, !0);
@@ -81,11 +81,11 @@ const bt = () => yt, x = (e) => {
8181
};
8282
}, tt = (e) => {
8383
requestAnimationFrame(() => requestAnimationFrame(() => e()));
84-
}, F = () => P()?.activationStart ?? 0, S = (e, t = -1) => {
85-
const r = P();
84+
}, F = () => I()?.activationStart ?? 0, S = (e, t = -1) => {
85+
const r = I();
8686
let i = "navigate";
8787
return bt() >= 0 ? i = "back-forward-cache" : r && (document.prerendering || F() > 0 ? i = "prerender" : document.wasDiscarded ? i = "restore" : r.type && (i = r.type.replace(/_/g, "-"))), { name: e, value: t, rating: "good", delta: 0, entries: [], id: `v5-${Date.now()}-${Math.floor(8999999999999 * Math.random()) + 1e12}`, navigationType: i };
88-
}, I = (e, t, r = {}) => {
88+
}, x = (e, t, r = {}) => {
8989
try {
9090
if (PerformanceObserver.supportedEntryTypes.includes(e)) {
9191
const i = new PerformanceObserver((n) => {
@@ -105,15 +105,15 @@ const bt = () => yt, x = (e) => {
105105
};
106106
let M = -1;
107107
const st = () => document.visibilityState !== "hidden" || document.prerendering ? 1 / 0 : 0, j = (e) => {
108-
document.visibilityState === "hidden" && M > -1 && (M = e.type === "visibilitychange" ? e.timeStamp : 0, Pt());
108+
document.visibilityState === "hidden" && M > -1 && (M = e.type === "visibilitychange" ? e.timeStamp : 0, It());
109109
}, ct = () => {
110110
addEventListener("visibilitychange", j, !0), addEventListener("prerenderingchange", j, !0);
111-
}, Pt = () => {
111+
}, It = () => {
112112
removeEventListener("visibilitychange", j, !0), removeEventListener("prerenderingchange", j, !0);
113113
}, St = () => {
114114
if (M < 0) {
115115
const e = F();
116-
M = (document.prerendering ? void 0 : globalThis.performance.getEntriesByType("visibility-state").filter((r) => r.name === "hidden" && r.startTime > e)[0]?.startTime) ?? st(), ct(), x(() => {
116+
M = (document.prerendering ? void 0 : globalThis.performance.getEntriesByType("visibility-state").filter((r) => r.name === "hidden" && r.startTime > e)[0]?.startTime) ?? st(), ct(), P(() => {
117117
setTimeout(() => {
118118
M = st(), ct();
119119
});
@@ -128,16 +128,16 @@ const st = () => document.visibilityState !== "hidden" || document.prerendering
128128
U(() => {
129129
const r = St();
130130
let i, n = S("FCP");
131-
const o = I("paint", (a) => {
131+
const o = x("paint", (a) => {
132132
for (const s of a) s.name === "first-contentful-paint" && (o.disconnect(), s.startTime < r.firstHiddenTime && (n.value = Math.max(s.startTime - F(), 0), n.entries.push(s), i(!0)));
133133
});
134-
o && (i = b(e, n, ut, t.reportAllChanges), x((a) => {
134+
o && (i = b(e, n, ut, t.reportAllChanges), P((a) => {
135135
n = S("FCP"), i = b(e, n, ut, t.reportAllChanges), tt(() => {
136136
n.value = performance.now() - a.timeStamp, i(!0);
137137
});
138138
}));
139139
});
140-
}, lt = [0.1, 0.25], dt = (e) => e.find((t) => t.node?.nodeType === 1) || e[0], xt = (e, t = {}) => {
140+
}, lt = [0.1, 0.25], dt = (e) => e.find((t) => t.node?.nodeType === 1) || e[0], Pt = (e, t = {}) => {
141141
const r = w(t = Object.assign({}, t), ot), i = /* @__PURE__ */ new WeakMap();
142142
r.t = (n) => {
143143
if (n?.sources?.length) {
@@ -153,10 +153,10 @@ const st = () => document.visibilityState !== "hidden" || document.prerendering
153153
const u = w(o, ot), g = (h) => {
154154
for (const T of h) u.u(T);
155155
u.o > s.value && (s.value = u.o, s.entries = u.i, a());
156-
}, m = I("layout-shift", g);
156+
}, m = x("layout-shift", g);
157157
m && (a = b(n, s, lt, o.reportAllChanges), document.addEventListener("visibilitychange", () => {
158158
document.visibilityState === "hidden" && (g(m.takeRecords()), a(!0));
159-
}), x(() => {
159+
}), P(() => {
160160
u.o = 0, s = S("CLS", 0), a = b(n, s, lt, o.reportAllChanges), tt(() => a());
161161
}), setTimeout(a));
162162
}));
@@ -179,7 +179,7 @@ const st = () => document.visibilityState !== "hidden" || document.prerendering
179179
const i = ((n) => {
180180
let o = { timeToFirstByte: 0, firstByteToFCP: n.value, loadState: R(bt()) };
181181
if (n.entries.length) {
182-
const a = P(), s = n.entries.at(-1);
182+
const a = I(), s = n.entries.at(-1);
183183
if (a) {
184184
const u = a.activationStart || 0, g = Math.max(0, a.responseStart - u);
185185
o = { timeToFirstByte: g, firstByteToFCP: n.value - g, loadState: R(n.entries[0].startTime), navigationEntry: a, fcpEntry: s };
@@ -196,7 +196,7 @@ const Ft = (e) => {
196196
};
197197
let K;
198198
const gt = () => K ? Dt : performance.interactionCount ?? 0, kt = () => {
199-
"interactionCount" in performance || K || (K = I("event", Ft, { type: "event", buffered: !0, durationThreshold: 0 }));
199+
"interactionCount" in performance || K || (K = x("event", Ft, { type: "event", buffered: !0, durationThreshold: 0 }));
200200
};
201201
let ft = 0;
202202
class mt {
@@ -291,7 +291,7 @@ const X = (e) => {
291291
it >= D + _ + nt && (V = v.nextPaintTime - it), W && H && (v.longestScript = { entry: W, subpart: H, intersectingDuration: z }), v.totalScriptDuration = $, v.totalStyleAndLayoutDuration = A, v.totalPaintDuration = V, v.totalUnattributedDuration = v.nextPaintTime - D - $ - A - V;
292292
})(k), Object.assign(c, { attribution: k });
293293
};
294-
I("long-animation-frame", (c) => {
294+
x("long-animation-frame", (c) => {
295295
i = i.concat(c), g();
296296
}), ((c, l = {}) => {
297297
globalThis.PerformanceEventTiming && "interactionId" in PerformanceEventTiming.prototype && U(() => {
@@ -303,10 +303,10 @@ const X = (e) => {
303303
const L = p.M();
304304
L && L.T !== f.value && (f.value = L.T, f.entries = L.entries, d());
305305
});
306-
}, C = I("event", y, { durationThreshold: l.durationThreshold ?? 40 });
306+
}, C = x("event", y, { durationThreshold: l.durationThreshold ?? 40 });
307307
d = b(c, f, pt, l.reportAllChanges), C && (C.observe({ type: "first-input", buffered: !0 }), document.addEventListener("visibilitychange", () => {
308308
document.visibilityState === "hidden" && (y(C.takeRecords()), d(!0));
309-
}), x(() => {
309+
}), P(() => {
310310
p.v(), f = S("INP"), d = b(c, f, pt, l.reportAllChanges);
311311
}));
312312
});
@@ -335,14 +335,14 @@ const vt = [2500, 4e3], qt = (e, t = {}) => {
335335
const g = w(o, ht), m = (T) => {
336336
o.reportAllChanges || (T = T.slice(-1));
337337
for (const c of T) g.u(c), c.startTime < a.firstHiddenTime && (u.value = Math.max(c.startTime - F(), 0), u.entries = [c], s());
338-
}, h = I("largest-contentful-paint", m);
338+
}, h = x("largest-contentful-paint", m);
339339
if (h) {
340340
s = b(n, u, vt, o.reportAllChanges);
341341
const T = et(() => {
342342
m(h.takeRecords()), h.disconnect(), s(!0);
343343
});
344344
for (const c of ["keydown", "click", "visibilitychange"]) addEventListener(c, () => X(T), { capture: !0, once: !0 });
345-
x((c) => {
345+
P((c) => {
346346
u = S("LCP"), s = b(n, u, vt, o.reportAllChanges), tt(() => {
347347
u.value = performance.now() - c.timeStamp, s(!0);
348348
});
@@ -353,7 +353,7 @@ const vt = [2500, 4e3], qt = (e, t = {}) => {
353353
const o = ((a) => {
354354
let s = { timeToFirstByte: 0, resourceLoadDelay: 0, resourceLoadDuration: 0, elementRenderDelay: a.value };
355355
if (a.entries.length) {
356-
const u = P();
356+
const u = I();
357357
if (u) {
358358
const g = u.activationStart || 0, m = a.entries.at(-1), h = m.url && performance.getEntriesByType("resource").filter((d) => d.name === m.url)[0], T = Math.max(0, u.responseStart - g), c = Math.max(T, h ? (h.requestStart || h.startTime) - g : 0), l = Math.min(a.value, Math.max(c, h ? h.responseEnd - g : 0));
359359
s = { target: i.get(m), timeToFirstByte: T, resourceLoadDelay: c - T, resourceLoadDuration: l - c, elementRenderDelay: a.value - l, navigationEntry: u, lcpEntry: m }, m.url && (s.url = m.url), h && (s.lcpResourceEntry = h);
@@ -369,8 +369,8 @@ const vt = [2500, 4e3], qt = (e, t = {}) => {
369369
((r, i = {}) => {
370370
let n = S("TTFB"), o = b(r, n, Tt, i.reportAllChanges);
371371
Y(() => {
372-
const a = P();
373-
a && (n.value = Math.max(a.responseStart - F(), 0), n.entries = [a], o(!0), x(() => {
372+
const a = I();
373+
a && (n.value = Math.max(a.responseStart - F(), 0), n.entries = [a], o(!0), P(() => {
374374
n = S("TTFB", 0), o = b(r, n, Tt, i.reportAllChanges), o(!0);
375375
}));
376376
});
@@ -436,7 +436,7 @@ function Wt(e) {
436436
});
437437
}
438438
function Ht(e) {
439-
xt((t) => {
439+
Pt((t) => {
440440
e.cls = {
441441
value: t.value,
442442
navigationType: t.navigationType,

0 commit comments

Comments
 (0)