Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/browserstack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build and test compiled scripts
run: pnpm run build && pnpm run test:unit

- name: Test BrowserStack matrix selection
run: pnpm run test:matrix

Expand All @@ -69,7 +72,7 @@ jobs:

- name: Run tests with BrowserStack Local
if: steps.browserstack.outputs.skip != 'true'
run: pnpm run build && node ./test/index.js
run: node ./test/index.js
env:
CI: true
NODE_ENV: test
Expand Down
8 changes: 7 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ This repository contains the public scripts for Simple Analytics. When making ch

## Development

- Use **Node.js 22.16** (see `.nvmrc`).
- Use the Node.js version pinned in `.nvmrc` (currently **24.18.0**).
- Run `npm run build` to compile the scripts. This generates the files in `dist/` which should be committed.
- Format code with Prettier before committing. You can run `npx prettier -w .` or validate with `npm run prettier`.

## Script size and naming

- Write code in `src/` to minimize the bytes in the compiled scripts, not just the source. Reuse the predefined variables for repeated values: the compiler turns these into very short variable names, saving bytes wherever they are reused.
- Keep source code readable and check the compiled output and size after building to verify that a change actually saves bytes.
- Names in `overwriteOptions` and script attributes are public settings. Balance short names with readable, human-friendly names, using existing options and attributes as the reference (for example, `ignorePages` / `data-ignore-pages`). These names cannot be shortened by variable minification.

## Testing

- Execute `npm run test:unit` to run the unit tests
Expand Down
2 changes: 1 addition & 1 deletion dist/latest/auto-events.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 53 additions & 31 deletions dist/latest/cloudflare.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Simple Analytics - Privacy-first analytics (docs.simpleanalytics.com/script; 2026-09-16; b9db; v12) */
/* Simple Analytics - Privacy-first analytics (docs.simpleanalytics.com/script; 2026-10-06; 282d; v12) */
/* eslint-env browser */

(function (
Expand Down Expand Up @@ -144,6 +144,10 @@

if (logSettings) warn("Settings", overwriteOptions);

// A server-created initial pageview can be enriched by this script.
var pageLoadId =
overwriteOptions.pageLoadId || attr(scriptElement, "page-load-id");

// Customers can skip data points
var ignoreMetrics = convertCommaSeparatedToArray(
overwriteOptions.ignoreMetrics || attr(scriptElement, "ignore-metrics")
Expand Down Expand Up @@ -471,10 +475,12 @@

https: loc.protocol == https,
timezone: timezone,
page_id: collectDataOnLeave ? uuid() : undefinedVar,
page_id: pageLoadId || (collectDataOnLeave ? uuid() : undefinedVar),

// se = sessions
session_id: collectMetricByString("se") ? uuid() : undefinedVar,
session_id: collectMetricByString("se")
? pageLoadId || uuid()
: undefinedVar,
});

payload.sri = falseVar;
Expand Down Expand Up @@ -551,6 +557,8 @@

var sendOnLeave = function (id, push) {
if (!collectDataOnLeave) return;
// Don't enrich an ignored server page before an allowed manual pageview.
if (pageLoadId && !lastSendPath && !getPath()) return;

var append = assign(basePayload, {
type: "append",
Expand Down Expand Up @@ -670,38 +678,39 @@

var previousReferrer;

// Send page view and append data to it
var sendPageView = function (
// Capture each accepted pageview before waiting for client hints.
var preparePageView = function (
isPushState,
deleteSourceInfo,
sameSite,
search,
metadata,
callback
metadata
) {
if (isPushState) sendOnLeave("" + payload.page_id, trueVar);
if (collectDataOnLeave) payload.page_id = uuid();
var originalId = pages === 0 ? pageLoadId : undefinedVar;
payload.page_id =
originalId || (collectDataOnLeave ? uuid() : undefinedVar);

var currentPage = definedHostname + getPath();
var currentPage = definedHostname + page.path;
var query = getQueryParams(deleteSourceInfo, search);

sendData(
{
id: payload.page_id,
type: pageviewText,
referrer: !deleteSourceInfo || sameSite ? referrer : null,
query: query,
k: k,

metadata: stringify(metadata),
},
callback
);
var data = assign(page, {
id: payload.page_id,
page_id: payload.page_id,
original_id: originalId,
type: originalId ? "append" : pageviewText,
referrer: !deleteSourceInfo || sameSite ? referrer : null,
query: query,
k: k,

metadata: stringify(metadata),
});

previousReferrer = referrer;
referrer = currentPage;

pages++;
return data;
};

var sameSite, userNavigated;
Expand All @@ -724,6 +733,13 @@
// Obfuscate personal data in URL by dropping the search and hash
var path = getPath(pathOverwrite);

if (!path && !lastSendPath && pageLoadId) {
// An ignored initial page must not receive event or leave enrichment.
payload.page_id = collectDataOnLeave ? uuid() : undefinedVar;
if (payload.session_id === pageLoadId) payload.session_id = uuid();
pageLoadId = undefinedVar;
}

// Don't send the last path again (this could happen when pushState is used to change the path hash or search)
if (!path || lastSendPath == path) return;

Expand Down Expand Up @@ -793,18 +809,18 @@
path: page.path,
});

var delSrc = isPushState || userNavigated || !collectMetricByString("r");
var pageviewData = preparePageView(
isPushState,
delSrc, // r = referrers
sameSite,
querySearch,
metadata
);

var triggerSendPageView = function () {
fetchedHighEntropyValues = trueVar;
var delSrc =
isPushState || userNavigated || !collectMetricByString("r");
sendPageView(
isPushState,
delSrc, // r = referrers
sameSite,
querySearch,
metadata,
callback
);
sendData(pageviewData, callback);
};

if (!fetchedHighEntropyValues) {
Expand Down Expand Up @@ -939,6 +955,12 @@
var eventParams = { type: eventText, event: event };
var firstPage = !userNavigated && pages < 2;

// Manual collection may not have declared an allowed pageview yet.
if (pageLoadId && !lastSendPath && !getPath()) {
eventParams.page_id = undefinedVar;
eventParams.session_id = undefinedVar;
}

metadata = appendMetadata(metadata, eventParams);

if (event) {
Expand Down
4 changes: 2 additions & 2 deletions dist/latest/custom/app.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/latest/custom/app.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/latest/custom/auto-events.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading