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
2 changes: 1 addition & 1 deletion test-server/configurator-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Copied in from packages/analytics-browser/lib/scripts after a build; see README.md.
# Bundles and their .map files, copied in by sync-vendor.mjs after a build; see README.md.
vendor/
48 changes: 41 additions & 7 deletions test-server/configurator-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,26 @@ configurator can generate the code, but only an extension can put it on someone

## Setting it up

The SDK bundles aren't checked in. Build them once, then vendor them in:
It isn't in the Chrome Web Store, and a `.crx` can't be dragged into Chrome any more, so either route ends
at Load unpacked on a folder.

**From the test server.** `/configurator-extension.zip` is this directory, vendored bundles and all, zipped
on request by `test-server/extension-archive.js`. Download it from the configurator's install panel or
directly, unzip it, and load the `configurator-extension` folder it leaves behind. Nothing to build, and
the archive always matches the checkout that served it. `vite build` emits the same file, so a hosted copy
of the configurator offers the same download.

**From this checkout.** The SDK bundles aren't checked in. Build them once, then vendor them in:

```bash
pnpm --dir packages/analytics-browser build
pnpm --dir packages/plugin-session-replay-browser build
node test-server/configurator-extension/sync-vendor.mjs
```

Load `test-server/configurator-extension` at `chrome://extensions` with developer mode on, and start the
test server with `pnpm dev`. After editing any file here, hit the extension's reload icon — that also
clears which tabs were being instrumented.
Then load `test-server/configurator-extension` itself at `chrome://extensions` with developer mode on, and
start the test server with `pnpm dev`. This is the one to use while working on the extension: after editing
any file here, hit its reload icon — that also clears which tabs were being instrumented.

## Using it

Expand All @@ -25,6 +34,12 @@ to the "Run on URL" button, and click it. The page hands the configuration to th
that URL in a new tab and initialises the SDK there. The note beside the button says what was installed,
and the tab's console logs every event the SDK builds.

Filling in "Mock Referrer" alongside that URL makes `document.referrer` read whatever you put there, so
attribution can be tried without having to arrive from the referring site. "Clean Session", which is on
unless you turn it off, deletes Amplitude's stored state for the site first, so every run starts with a new
device ID, a new session and no prior campaign — which is what makes a referrer worth mocking in the first
place. Untick it to pick up where the last run left off.

**On the tab you're looking at.** Click the toolbar button to instrument the current tab with whatever the
configurator sent last, or a debug-everything default if it hasn't sent anything yet. The badge reads `on`,
and clicking again switches it off. Either way the tab reloads, since that's the only way to catch a page
Expand All @@ -34,9 +49,12 @@ from the start.

`configurator-bridge.js` sits on the configurator page and relays `window.postMessage` requests to
`background.js`. Its `matches` in the manifest cover the hosts the test server uses — `localhost` and
`127.0.0.1` over http for `pnpm dev`, `local.website.com` over https for `pnpm dev:ssh` — and nothing
else. Ports aren't part of a match pattern, so any port is covered, but serving the configurator from a
host that isn't listed is why the page would report no extension. It's scoped to the configurator's own
`127.0.0.1` over http for `pnpm dev`, `local.website.com` over https for `pnpm dev:ssh` — plus the Netlify
site the `pnpm build:configurator` artifact is shared from, and nothing else. Ports aren't part of a match
pattern, so any port is covered, but serving the configurator from a host that isn't listed is why the page
would report no extension. Hosted origins are listed one at a time rather than as `https://*.netlify.app/`:
a wildcard there would offer the relay to every site on a shared domain, and the relay leads to a service
worker that can inject the SDK into any tab. It's scoped to the configurator's own
path rather than a host wildcard so the relay isn't offered to every site, given how much the extension is
allowed to do. Going through the page rather than `chrome.runtime.sendMessage` means the page needs no
extension ID and no `externally_connectable` entry, and it lets the extension announce itself so the
Expand Down Expand Up @@ -83,6 +101,22 @@ page uses, and travels as JSON. Regexes have nowhere to live in JSON, so `toJson
`base::IsStringUTF8`, which rejects Unicode non-characters, and the session replay bundle contains four
literal U+FFFE characters. Chrome rejects the whole file with "It isn't UTF-8 encoded", which is why
`sync-vendor.mjs` escapes them on the way in rather than a plain `cp`.
- **Both landing options are spent on the first page of a run.** A mocked referrer and a cleared session
describe arriving at a site rather than being on one, so `takePayload()` hands them to the commit that
opens the run and takes them off the stored payload. Clearing on every commit would hand out a new device
ID and session on every page and no session would last more than one pageview; a referrer mocked again
would keep claiming the visitor came from elsewhere when they came from the previous page. Pages after
the first therefore report their real referrer and keep the session that was just started.
- **A mocked referrer is only the JS view.** `handOver` shadows `document.referrer` with an own property,
which is what the campaign parser and the page-URL enrichment plugin read. The `Referer` header the page
was actually fetched with is untouched, so anything server-side still sees the truth — and modifying that
header wouldn't help, because Chrome derives `document.referrer` from the navigation's referrer rather
than from a header a `declarativeNetRequest` rule rewrote.
- **Clearing the session takes every Amplitude key with it.** It sweeps by prefix — `AMP_` and the legacy
lowercase `amp_` — across cookies, `localStorage` and `sessionStorage`, so it also clears the state of the
site's *own* Amplitude instance if it has one, in your browser only. Nothing else the site stores is
touched, and IndexedDB is left alone: session replay's recorded events live there, and a new session ID
makes them moot anyway.
- **Where events go.** The API key comes from the configurator, and events land in whatever project owns
it. Use a scratch project, not a customer's production key.
- **The page's own Amplitude.** The bundle merges itself onto an existing `window.amplitude`, which would
Expand Down
Loading
Loading