diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fcd1de..a6aef96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,12 @@ Notable changes to Basicrum Analytics are recorded here. -## [0.1.0] - 2026-09-23 +## [0.1.0] - 2026-09-24 ### Added +- Illustrated setup instructions for collector details, Magento configuration, + consent integration, and query-string privacy controls. - The owner-approved root MIT license, matching the existing Composer license declaration and preserving bundled third-party notices. - Magento-aware PHPStan level 8, Magento coding standards, precise runtime @@ -43,7 +45,7 @@ Notable changes to Basicrum Analytics are recorded here. page, keeping their settings and guidance immediately visible. - **Breaking:** normalized the technical Magento module identifier and PHP namespace to the “Basicrum” spelling: `Basicrum_Analytics` and - `Basicrum\\Analytics`. The lowercase configuration paths remain unchanged. + `Basicrum\Analytics`. The lowercase configuration paths remain unchanged. This pre-release break was accepted because the extension has no installations to migrate. - Removed the explicit Composer package version. Release versions now come @@ -69,6 +71,11 @@ Notable changes to Basicrum Analytics are recorded here. ### Fixed +- Remove obsolete consent-cookie cleanup from the readable and minified + loaders. Browser tests verify no cookies before consent and only the `RT` + measurement cookie after initialization. +- Clarify automatic Beacon Endpoint origin registration in storefront CSP and + the cache refresh required after configuration changes. - The Admin integration test opens Magento's native Basicrum navigation group before selecting the exact settings link, which is hidden when collapsed. - Validate package metadata through Composer's validating VCS importer in CI, diff --git a/README.md b/README.md index 1a43e84..426ba6d 100644 --- a/README.md +++ b/README.md @@ -62,29 +62,39 @@ headless/PWA storefronts, Varnish, and third-party optimizers are not verified. ## Configuration +### Get your collector details + +In the [Basicrum portal](https://app.beta.basicrum.com/), open +**Account > Settings > Sites**. Choose **Add Site** or an existing site, then +copy its **Beacon Endpoint** and **Brum Site ID**. Use your own site's values; +the screenshots show an example. + +![Basicrum portal showing the site's Brum Site ID and Beacon Endpoint](docs/images/collector-details.png) + +### Enable Basicrum in Magento + Open **Stores > Configuration > Basicrum > Basicrum Analytics**. Settings support Magento default, website, and store inheritance. +Set **Enable Basicrum** to **Yes**, enter your collector details, and click +**Save Config**. Consent is still required by default. + +![Basicrum Analytics enabled in Magento Admin with collector details and monitoring status](docs/images/plugin-settings.png) + Required settings: - **Enable Basicrum**: new installations default to No. - **Beacon Endpoint**: a valid HTTP or HTTPS collector URL without embedded - credentials or a fragment. Endpoint query strings remain supported for - compatibility. HTTPS is enforced unless the explicit development exception - is enabled. + credentials or a fragment. Endpoint query strings are supported. HTTPS is + enforced unless the explicit development exception is enabled. - **Brum Site ID**: a UUIDv4 copied from the Basicrum backoffice. If Basicrum is disabled or required configuration is missing or invalid, no monitoring scripts are emitted. The Monitoring Status row explains why monitoring is inactive. Settings are validated on save and at runtime. -Collection controls: +### Other collection settings -- **Require Consent Before Monitoring** defaults to Yes. Select No only for a - deliberate immediate-loading policy. -- **Strip Query Strings** defaults to No. When enabled, Boomerang replaces - complete query strings in page, navigation, referrer, and resource URLs with - `?qs-redacted` before beacon transmission. - **Wait After Onload** defaults to No with a zero delay. Its configured delay is bounded to 30,000 milliseconds. - **Allow HTTP Beacon Endpoint** defaults to No and is intended only for local @@ -101,6 +111,13 @@ action uses `unknown`. ## Consent integration +Under **Visitor Consent**, **Require Consent Before Monitoring** defaults to +**Yes**. Boomerang, measurement cookies, and beacons wait for your consent tool +to allow monitoring on each page. Select **No** only for a deliberate +immediate-loading policy. + +![Visitor Consent settings showing required consent and the manual callback API](docs/images/visitor-consent-settings.png) + Basicrum provides manual, page-level callbacks for your consent platform. It does not display a banner, automatically connect to consent providers, infer consent from cookies, or persist its own consent decision. @@ -123,13 +140,12 @@ if (typeof window.OPT_OUT_BASICRUM_LOADER_WRAPPER === "function") { ``` The consent wrapper is inert until allow. Repeated allow calls load Boomerang -at most once. Denial before the first allow cleans measurement and legacy -consent cookies without preventing a later allow on that page. Withdrawal -during download prevents the arriving bundle from initializing. Withdrawal -after initialization disables further collection, cancels a pending Wait After -Onload timer, and removes `RT`, `BA`, `BRUM_CONSENT`, and `BOOMR_CONSENT` -cookies where JavaScript can reach them. Data already transmitted cannot be -retracted. +at most once. Once initialized, Boomerang uses the first-party `RT` measurement +cookie; Basicrum does not create a consent cookie. Denial before the first allow +does not prevent a later allow on that page. Withdrawal during download prevents +the arriving bundle from initializing. Withdrawal after initialization disables +further collection, cancels a pending Wait After Onload timer, and removes +accessible measurement cookies. Data already transmitted cannot be retracted. After withdrawal once loading has started, re-grant requires a page reload. This intentionally prevents a same-page restart from a partially initialized @@ -137,6 +153,15 @@ state. The callbacks are registered by the footer loader; calls made before registration are not queued. Connect both allow and deny/change events in the site's consent tool on every page. +## Strip query strings + +Under **Privacy**, set **Strip Query Strings** to **Yes** to redact queries +from page, navigation, referrer, and resource URLs. The default is **No**. +For example, `/search?q=boots` becomes `/search?qs-redacted`; URL paths remain. +This does not change query parameters in your configured Beacon Endpoint. + +![Privacy settings with Strip Query Strings enabled](docs/images/strip-query-strings-settings.png) + ## Caching and CSP After changing module configuration, clean Magento configuration, layout, @@ -145,13 +170,21 @@ new static assets and invalidate any CDN or optimizer cache that can retain old HTML or JavaScript. Magento's versioned static asset URLs provide browser cache invalidation only after the deployment/content version changes. +Then visit the storefront, grant consent through your consent tool, and check +the browser's Network panel for a beacon to your endpoint containing your +`brum_site_id`. With consent required, no beacon should appear before opt-in. + The loader and Boomerang are first-party Magento static assets. Inline scripts use Magento's `SecureHtmlRenderer` for CSP-compatible rendering. -When monitoring configuration is active and valid, Basicrum adds the Beacon -Endpoint origin (scheme, host, and optional port) to storefront `connect-src` -and `img-src` CSP policies. Paths and query strings are excluded. Disabled or -invalid configuration adds no collector origin. No wildcard is added. +When you configure the **Beacon Endpoint** in Magento Admin and enable Basicrum +with valid settings, the module automatically allows the endpoint's domain in +the storefront `connect-src` and `img-src` CSP directives. No separate manual +CSP whitelist entry is needed for that endpoint. The allowed value is its origin +(scheme, host, and optional port), not the full URL: paths and query strings are +excluded. Disabled or invalid configuration adds no collector origin, and no +wildcard is added. Clean the caches described above after saving settings so +cached storefront responses use the updated policy. If your store uses script delay, combination, or other optimization extensions, verify consent handling and script loading on staging before deployment. diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 79a4b29..2dfd95b 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -38,5 +38,5 @@ The Boomerang copyright notice and BSD license remain applicable to this file. `64f19d9e5a9fbe580c12c19796e86e3ad0dd17ff` The consent wrapper adds Basicrum lifecycle, withdrawal, wait cancellation, -and legacy-cookie cleanup around the loader. Those adaptations do not change +and measurement-cookie cleanup around the loader. Those adaptations do not change the license that applies to the original loader snippet. diff --git a/docs/images/collector-details.png b/docs/images/collector-details.png new file mode 100644 index 0000000..ba8a938 Binary files /dev/null and b/docs/images/collector-details.png differ diff --git a/docs/images/plugin-settings.png b/docs/images/plugin-settings.png new file mode 100644 index 0000000..c0e6400 Binary files /dev/null and b/docs/images/plugin-settings.png differ diff --git a/docs/images/strip-query-strings-settings.png b/docs/images/strip-query-strings-settings.png new file mode 100644 index 0000000..ed7ff2a Binary files /dev/null and b/docs/images/strip-query-strings-settings.png differ diff --git a/docs/images/visitor-consent-settings.png b/docs/images/visitor-consent-settings.png new file mode 100644 index 0000000..83ac473 Binary files /dev/null and b/docs/images/visitor-consent-settings.png differ diff --git a/tests/js/loaders.spec.js b/tests/js/loaders.spec.js index e0fe57e..93c6aaa 100644 --- a/tests/js/loaders.spec.js +++ b/tests/js/loaders.spec.js @@ -67,7 +67,7 @@ async function preparePage(page, options = {}) { if (options.cookies) { await page.context().addCookies(options.cookies.map((name) => ({ name, - value: "legacy", + value: "existing", domain: "shop.example.test", path: "/" }))); @@ -139,8 +139,8 @@ for (const consentLoader of [ "consent-boomerang-loader-v1-15.min.js" ]) { test.describe(`consent wrapper: ${consentLoader}`, () => { - test("stays inert despite a legacy allow cookie", async ({ page }) => { - const harness = await preparePage(page, { cookies: ["BRUM_CONSENT"] }); + test("stays inert despite existing measurement cookies", async ({ page }) => { + const harness = await preparePage(page, { cookies: ["RT", "BA"] }); await page.addScriptTag({ path: loaderPath(consentLoader) }); await page.waitForTimeout(200); @@ -179,7 +179,7 @@ for (const consentLoader of [ }); test("denial before loading cleans cookies and permits a later allow", async ({ page }) => { - const cookieNames = ["RT", "BA", "BRUM_CONSENT", "BOOMR_CONSENT"]; + const cookieNames = ["RT", "BA"]; await preparePage(page, { cookies: cookieNames }); await page.addScriptTag({ path: loaderPath(consentLoader) }); await page.evaluate(() => window.OPT_OUT_BASICRUM_LOADER_WRAPPER()); @@ -214,7 +214,7 @@ for (const consentLoader of [ }); test("withdrawal after initialization disables collection and clears cookies", async ({ page }) => { - const cookieNames = ["RT", "BA", "BRUM_CONSENT", "BOOMR_CONSENT"]; + const cookieNames = ["RT", "BA"]; await preparePage(page, { cookies: cookieNames }); await page.addScriptTag({ path: loaderPath(consentLoader) }); await page.evaluate(() => window.OPT_IN_BASICRUM_LOADER_WRAPPER()); @@ -233,7 +233,7 @@ for (const consentLoader of [ executions: 1, disableCalls: 1, cookies: "", - removals: ["BA", "BOOMR_CONSENT", "BRUM_CONSENT", "RT"] + removals: ["BA", "RT"] }); }); }); diff --git a/tests/js/real-boomerang.spec.js b/tests/js/real-boomerang.spec.js index 36f5b25..ae3396e 100644 --- a/tests/js/real-boomerang.spec.js +++ b/tests/js/real-boomerang.spec.js @@ -180,7 +180,7 @@ for (const consentLoader of [ await page.waitForTimeout(300); expect(gate.boomerangRequests()).toBe(0); expect(gate.beaconRequests()).toBe(0); - expect((await context.cookies(shopUrl)).some((cookie) => ["RT", "BA"].includes(cookie.name))).toBe(false); + expect(await context.cookies(shopUrl)).toEqual([]); await page.evaluate(() => { window.OPT_IN_BASICRUM_LOADER_WRAPPER(); @@ -194,8 +194,7 @@ for (const consentLoader of [ const parameters = requestParameters(gate.beaconRequestData()[0]); expect(parameters.get("p_gen")).toBe("mage2"); expect(parameters.get("brum_site_id")).toBe(siteId); - expect((await context.cookies(shopUrl)).some((cookie) => cookie.name === "RT")).toBe(true); - expect((await context.cookies(shopUrl)).some((cookie) => cookie.name === "BRUM_CONSENT")).toBe(false); + expect((await context.cookies(shopUrl)).map((cookie) => cookie.name).sort()).toEqual(["RT"]); }); test("denial before loading remains eligible for a later allow", async ({ page }) => { diff --git a/view/frontend/web/js/loaders/consent-boomerang-loader-v1-15.js b/view/frontend/web/js/loaders/consent-boomerang-loader-v1-15.js index 861cc6c..7aa1eea 100644 --- a/view/frontend/web/js/loaders/consent-boomerang-loader-v1-15.js +++ b/view/frontend/web/js/loaders/consent-boomerang-loader-v1-15.js @@ -202,7 +202,7 @@ /* END BASICRUM STANDARD LOADER */ }; - // Remove cookies created by older Basicrum consent loaders and Boomerang. + // Remove Boomerang measurement cookies. function removeCookie(name) { var hostname = mainWin.location && mainWin.location.hostname; var cookie = name + "=; path=/; max-age=0; SameSite=Strict"; @@ -248,14 +248,10 @@ if (mainWin.BOOMR.utils && typeof mainWin.BOOMR.utils.removeCookie === "function") { mainWin.BOOMR.utils.removeCookie("RT"); mainWin.BOOMR.utils.removeCookie("BA"); - mainWin.BOOMR.utils.removeCookie("BRUM_CONSENT"); - mainWin.BOOMR.utils.removeCookie("BOOMR_CONSENT"); } } removeCookie("RT"); removeCookie("BA"); - removeCookie("BRUM_CONSENT"); - removeCookie("BOOMR_CONSENT"); }; })(window); diff --git a/view/frontend/web/js/loaders/consent-boomerang-loader-v1-15.min.js b/view/frontend/web/js/loaders/consent-boomerang-loader-v1-15.min.js index 9e4aeec..3993507 100644 --- a/view/frontend/web/js/loaders/consent-boomerang-loader-v1-15.min.js +++ b/view/frontend/web/js/loaders/consent-boomerang-loader-v1-15.min.js @@ -1,2 +1,2 @@ /*! Basicrum consent wrapper: exposes OPT_IN_BASICRUM_LOADER_WRAPPER() and OPT_OUT_BASICRUM_LOADER_WRAPPER(). */ -(function(r){r.OPT_IN_BASICRUM_LOADER_WRAPPER=function(){(function(){if(window.BOOMR&&(window.BOOMR.version||window.BOOMR.snippetExecuted)){return}window.BOOMR=window.BOOMR||{};if(!Object.prototype.hasOwnProperty.call(window.BOOMR,"url")||!window.BOOMR.url){return}window.BOOMR.snippetStart=(new Date).getTime();window.BOOMR.snippetExecuted=true;window.BOOMR.snippetVersion=15;var e=document.currentScript||document.getElementsByTagName("script")[0],d=e.parentNode,O=false,t=3e3;function n(){if(O){return}var e=document.createElement("script");e.id="boomr-scr-as";e.src=window.BOOMR.url;e.async=true;d.appendChild(e);O=true}function i(e){O=true;var t,i=document,n,o,r,a=window;window.BOOMR.snippetMethod=e?"if":"i";n=function(e,t){var n=i.createElement("script");n.id=t||"boomr-if-as";n.src=window.BOOMR.url;BOOMR_lstart=(new Date).getTime();e=e||i.body;e.appendChild(n)};if(!window.addEventListener&&window.attachEvent&&navigator.userAgent.match(/MSIE [678]\./)){window.BOOMR.snippetMethod="s";n(d,"boomr-async");return}o=document.createElement("IFRAME");o.src="about:blank";o.title="";o.role="presentation";o.loading="eager";r=(o.frameElement||o).style;r.width=0;r.height=0;r.border=0;r.display="none";d.appendChild(o);try{a=o.contentWindow;i=a.document.open()}catch(e){t=document.domain;o.src="javascript:var d=document.open();d.domain='"+t+"';void 0;";a=o.contentWindow;i=a.document.open()}a._boomrl=function(){n()};if(a.addEventListener){a.addEventListener("load",a._boomrl,false)}else if(a.attachEvent){a.attachEvent("onload",a._boomrl)}i.close()}var o=document.createElement("link");if(o.relList&&typeof o.relList.supports==="function"&&o.relList.supports("preload")&&"as"in o){window.BOOMR.snippetMethod="p";o.href=window.BOOMR.url;o.rel="preload";o.as="script";o.addEventListener("load",n);o.addEventListener("error",function(){i(true)});setTimeout(function(){if(!O){i(true)}},t);BOOMR_lstart=(new Date).getTime();d.appendChild(o)}else{i(false)}function r(e){window.BOOMR_onload=e&&e.timeStamp||(new Date).getTime()}if(window.addEventListener){window.addEventListener("load",r,false)}else if(window.attachEvent){window.attachEvent("onload",r)}})()};function t(e){var t=r.location&&r.location.hostname;var n=e+"=; path=/; max-age=0; SameSite=Strict";var i;var o;r.document.cookie=n;if(t){i=t.split(".");for(o=0;o