Skip to content

v12 - #40

Open
adriaandotcom wants to merge 78 commits into
mainfrom
v12
Open

v12#40
adriaandotcom wants to merge 78 commits into
mainfrom
v12

Conversation

@adriaandotcom

@adriaandotcom adriaandotcom commented May 12, 2023 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes #97

Prepare public script v12:

Before merging, update beta.js as well or inform customer team 13489e0b-295c-476b-b955-46389fe10895.

Security implications

Has security impact - described as: Query parameter values are never collected by this addition. Name collection is limited to parameters with non-empty values and can be disabled.

Testing

  • Frozen pnpm install passed with Node.js 24.18.0
  • Rebuilt all distribution files
  • Full unit suite passed: 16 tests
  • Focused pageview query suite passed: 4 tests
  • Prettier check passed for the edited source and unit test
  • BrowserStack workflow passed

Checklist

  • Linked to an issue
  • Tested
  • Asked for a review

@adriaandotcom
adriaandotcom marked this pull request as draft March 3, 2026 10:41
@simple-analytics-ai
simple-analytics-ai Bot marked this pull request as ready for review September 16, 2026 10:47
simple-analytics-ai Bot and others added 2 commits October 15, 2026 15:08
* Document tracker compatibility and size priorities for agents

* Update AGENTS.md

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: Simple Analytics Codex <codex@simpleanalytics.invalid>
Co-authored-by: Adriaan <1079135+adriaandotcom@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Support inline event attributes in the main v12 tracker

Requested: integrate documented inline events without overengineering, keep the script small and website behavior intact, and open a separate PR based on and targeting v12.

Use delegated click/submit handlers and keepalive GETs with the existing image fallback. Preserve installed inline helpers and make confirmed 404 detection opt-in. Add focused tests and patch the vulnerable Handlebars build dependency.

* Simplify inline events after review

Requested: remove compatibility checks for the beta helper, measure repeated attribute strings, clarify the DOM alias, and shorten submit-button metadata extraction.

Keep literals because sharing them increases gzip size. Prefer trimmed, capped submitter labels and scan title/body text for 404s. Address the related bot feedback by running the complete unit suite in CI.

* Normalize inline submit button label whitespace

Requested: address scripts PR #106 review comment 4282258759 so formatting a button across multiple lines does not fragment button_text metadata.

Collapse whitespace before trimming and applying the existing 200-character limit. Add a regression for equivalent compact and indented button markup, and regenerate the affected tracker bundles.

Verification: regression fails on the previous compiled tracker; all 33 unit tests pass after the fix. Build, formatting, and diff checks pass.

---------

Co-authored-by: Simple Analytics Codex <codex@simpleanalytics.invalid>
Comment thread src/default.js

var getQueryParams = function (ignoreSource, overwriteSearch) {
/** if ignoremetrics **/
k = !ignoreSource && collectMetricByString("q") ? "" : falseVar;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k is gated only on querykeys, so it bypasses the existing query opt-outs: with data-ignore-metrics="utm" and ?utm_source=news&token=abc the pageview now sends k=utm_source,token even though query is empty (verified against dist/latest/latest.dev.js). A site that explicitly turned off UTM collection starts sending those parameter names without changing its embed. Gate the name collection on collectMetricByString("ut") as well, or reuse the same filter result so ignored params are excluded from k.

Comment thread src/default.js
callbackRaw
) {
if (!callbackRaw && isFunction(metadata)) callbackRaw = metadata;
var callback = isFunction(callbackRaw) ? callbackRaw : function () {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callback is dropped whenever pageview returns early at if (!path || lastSendPath == path) return; (line 848) — i.e. for a repeated path or an ignored page. sa_pageview("/same", cb1); sa_pageview("/same", cb2) only fires cb1, so a caller that continues navigation from the callback hangs forever. Call callback() before that early return.

Comment thread src/default.js
@@ -837,7 +914,10 @@

/** if uniques **/
// We set unique variable based on pushstate or back navigation, if no match we check the referrer
page.unique = isPushState || userNavigated ? falseVar : !sameSite;
page.unique =
/__cf_/.test(getReferrer()) || isPushState || userNavigated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getReferrer() strips the query string (([^?#]+)(.*) → $4), so __cf_* params can never reach this test and the Cloudflare under-attack handling is dead code — a visit with referrer https://external.com/page?__cf_chl_tk=abc still reports unique=true (verified against the built latest.dev.js). Test the raw referrer instead:

Suggested change
/__cf_/.test(getReferrer()) || isPushState || userNavigated
/__cf_/.test(doc.referrer) || isPushState || userNavigated

Comment thread src/auto-events.js
@@ -37,6 +37,7 @@
"outbound",
"emails",
"downloads",
"phones",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding phones to the default collect list changes behaviour for every existing auto-events installation: tel: clicks start producing new phone_* events with a phone metadata field, which shifts event lists and datapoint counts without the customer changing their embed. Consider shipping it opt-in (data-collect="outbound,emails,downloads,phones") unless the default change is deliberate.

Suggested change
"phones",

Comment thread src/auto-events.js
@@ -175,6 +186,10 @@
// Collect email clicks
} else if (optionsLink.emails && /^mailto:/i.test(link.href)) {
collect = "email";

// Collect telephone clicks
} else if (optionsLink.phones && /^tel:/i.test(link.href)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tel: links take the addEventListener branch, and for phone the callback runs synchronously and sets document.location = href without checking whether the click was cancelled — so a site that calls preventDefault() on tel: links (custom dialer modal, confirmation step) now gets forced into the tel: navigation anyway (reproduced in jsdom). Skip the forced navigation when event.defaultPrevented is set, and let the browser own the navigation.

@github-actions

Copy link
Copy Markdown

Documentation update

_docs/20_script-settings/04_ignore-metrics.md

Location: In the metrics table, and in the "Ignore all metrics" embed example

Copy and paste this into the documentation repository:

| Metric               | Slug           | Inferrered metric      |
| -------------------- | -------------- | ---------------------- |
| Referrer             | `referrer`     |                        |
| UTM codes            | `utm`          | [`ref` param][0]       |
| Country / time zone  | `country`      |                        |
| Page-load IDs        | `session`      |                        |
| Time on page         | `timeonpage`   | Data point ID, Page ID |
| Scrolled             | `scrolled`     | Data point ID, Page ID |
| User Agent           | `useragent`    |                        |
| Screen size          | `screensize`   |                        |
| Viewport size        | `viewportsize` |                        |
| Language             | `language`     |                        |
| Query parameter names | `querykeys`   |                        |

## Query parameter names

From script version 12 our script collects the **names** of the query parameters in the visited URL (for example `gclid,ref` for `?gclid=123&ref=newsletter`). Parameter **values** are never collected, and parameters without a value are skipped. This helps you see which campaign and tracking parameters are used on your website without storing any of their content.

Parameter names are only collected on the first page view of a visit, and they are not collected when you ignore the `referrer` metric. To turn the collection off completely, use the `querykeys` slug:

```html
<script
  data-ignore-metrics="querykeys"
  src="https://scripts.simpleanalyticscdn.com/latest.js"
></script>

Ignore all metrics

To ignore all metrics that you can ignore, you can use this embed script:

<script
  data-ignore-metrics="referrer,utm,country,session,timeonpage,scrolled,useragent,screensize,viewportsize,language,querykeys"
  src="https://scripts.simpleanalyticscdn.com/latest.js"
></script>

### [`_docs/55_events/05_automated-events.md`](https://github.com/simpleanalytics/docs/blob/master/_docs/55_events/05_automated-events.md)

**Location:** `After the "Downloads" section, and in the settings example under "Script"`

Copy and paste this into the documentation repository:

```md
## Phone numbers

Do you have links to phone numbers on your website? Then it could make sense to see how many clicks you get on your `tel` links. The phone number from the link is used as event name (for example `phone_31_6_1234_5678`) and is added as `phone` metadata.

## Script

> This script is additional to the normal embed script. Make sure to also include [that script](/script).

Or if you want to change some settings (these are the defaults):

<!-- prettier-ignore -->
```html
<script async src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<script
  async
  data-collect="outbound,emails,downloads,phones"
  data-extensions="pdf,csv,docx,xlsx,zip,doc,xls"
  data-use-title="true"
  data-full-urls="false"
  src="https://scripts.simpleanalyticscdn.com/auto-events.js"></script>

Leave phones out of data-collect if you don't want to collect clicks on tel links. Links that already have a data-simple-event attribute are skipped by the automated events script, so your own event keeps working.


### [`_docs/20_install-script/02_custom.md`](https://github.com/simpleanalytics/docs/blob/master/_docs/20_install-script/02_custom.md)

**Location:** `Section "Use custom collection anyway"`

Copy and paste this into the documentation repository:

```md
## Use custom collection anyway

There might be situation where you don't want to auto collect page views. When you add the `data-auto-collect="false"` attribute the script will not send page views by itself. From script version 12 the `sa_pageview` function is always available, also when auto collection is on, so you can send extra page views for ignored pages or custom flows. When you call this function, the script will send a page view with the path you give as a parameter. If you don't give a path it will use `window.location.pathname` as a default.

```html
<script>
  function saLoaded() {
    // Always check for the function before using it.
    // The script might be blocked or not loaded for other reasons.
    if (window.sa_pageview) window.sa_pageview(window.location.pathname);
  }
</script>

<script
  async
  data-auto-collect="false"
  onload="saLoaded()"
  src="https://scripts.simpleanalyticscdn.com/latest.js"
></script>
<noscript
  ><img
    src="https://queue.simpleanalyticscdn.com/noscript.gif"
    alt=""
    referrerpolicy="no-referrer-when-downgrade"
/></noscript>

Query parameters in the path

When the path contains a query string, the query is split off the path and handled by the same rules as an automatically collected page view. This means sa_pageview("/search?utm_source=newsletter&secret=123") is reported as path /search with query utm_source=newsletter, and secret is dropped unless you allow it with data-allow-params.

Callback

You can pass a callback that runs after the page view request finished. The callback is the second argument, or the third when you also pass metadata:

<script>
  window.sa_pageview("/checkout", function () {
    // The page view was sent
  });

  window.sa_pageview("/checkout", { plan: "pro" }, function () {
    // The page view including metadata was sent
  });
</script>

The callback does not run when the page view is not sent, for example for an ignored page or when the same path was just sent.


### [`_docs/20_script-settings/03_overwrite-domain.md`](https://github.com/simpleanalytics/docs/blob/master/_docs/20_script-settings/03_overwrite-domain.md)

**Location:** `New section at the end of the page, before "Noscript"`

Copy and paste this into the documentation repository:

```md
## Overwrite referrer

Some setups lose the original referrer, for example when your visitors pass through a payment provider, a consent wall, or a redirect service. From script version 12 you can tell our script which referrer to use with the `data-referrer` attribute:

<!-- prettier-ignore -->
```html
<script async data-referrer="https://example.com/campaign" data-hostname="example.com" src="https://scripts.simpleanalyticscdn.com/latest.js"></script>

The value is cleaned the same way as a normal referrer: the protocol, www prefix, query string, and hash are removed. When you leave the attribute out, the browser referrer is used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Differentiate paid and organic referral traffic

2 participants