✳ OMNIVIEWER Share links ← back

How we use Google Analytics without breaking our promise

OmniViewer promises one thing above all: your file never leaves your browser. So how can we also run Google Analytics? Because the two never touch. Analytics here counts which page you landed on/ vs /mp3 vs /json — and literally nothing else. Not the file. Not its name. Not its bytes. Here is exactly how, in code.

On this page The promise Where bytes go (nowhere) The actual code There is no server Offline is a feature

The promise, and the tension

OmniViewer reads your file directly in the browser. Drop a 10 GB log, a private JSON export, an MP3 — the page reads only the bytes it needs to paint the screen, and those bytes are never sent anywhere. That is the whole product.

Analytics is normally the moment a privacy promise quietly dies. The stock Google Analytics snippet reports page_locationthe full URL. And OmniViewer deliberately puts data in the URL: a #payload= share link carries the file's bytes in the fragment, and a ?url= link carries a source address. Ship the full URL to Google and you would ship exactly the thing we swore to keep local.

So we don't. Every hit is pinned to the pathname only — the part before the ? and the #. That pathname is also precisely the signal we actually want (did people land on the MP3 tool or the JSON tool?), so there is no reason to send anything more.

The one-line version. Google Analytics tells us that someone opened /mp3. It never learns which MP3 — because the MP3 is in the fragment, and the fragment never leaves the machine it was dropped on.

Where your bytes go: nowhere

Four things are involved when you use OmniViewer: your browser, the static host the app is downloaded from (Amazon S3 behind CloudFront), Google Analytics, and — the important one — your file. Follow the arrows:

Data-flow diagram for OmniViewer The static app is downloaded once from Amazon S3 into your browser. Your file stays inside the browser and never leaves. Only the bare pathname, such as slash mp3, is sent to Google Analytics — never the file or the URL's query or fragment. Amazon S3 + CloudFront (CDN) static files only HTML + JS (downloaded once) Your browser (your device) OmniViewer app runs entirely here Your file bytes · name · content stays right here ✓ Google Analytics counts landings /mp3 pathname only no bytes cross this line
The app is downloaded once from S3. From then on your file lives only in the browser box. The single thin arrow to Google carries the bare path — /mp3 — and nothing else can cross.

The actual code

No hand-waving — this is the real analytics path from omni-app.js. Two details do all the work: it only initialises on the production host, and every hit is pinned to location.pathname, never location.href.

const GA_ID = 'G-M4FRK95PED';

// Analytics runs on the real domain ONLY. On localhost, in CI, and in the
// test suite this is false, so Google is never even contacted.
const analyticsOn =
  location.hostname === 'omniviewer.org' ||
  location.hostname === 'www.omniviewer.org';

// Pathname only — never the ?query (may hold ?url=) or the #fragment
// (may hold a whole #payload= file). Falls back to '/' for the home route.
const pagePath = () => location.pathname || '/';
const pageLoc  = () => location.origin + pagePath();

function initAnalytics() {
  if (!analyticsOn) return;
  window.dataLayer = window.dataLayer || [];
  window.gtag = function gtag() { window.dataLayer.push(arguments); };
  window.gtag('js', new Date());
  // The page_location we hand GA is rebuilt from the bare path, so the
  // query string and fragment are dropped before anything is sent.
  window.gtag('config', GA_ID, { page_location: pageLoc(), page_path: pagePath() });
}

And when you drop a file and the app rewrites the URL to its format route (/mp3, /json, …), we report that landing ourselves — again, path only:

function trackPage() {
  gtag('event', 'page_view', {
    page_location: pageLoc(),   // origin + pathname, nothing else
    page_path: pagePath(),
    page_title: document.title,
  });
}

Compare with the snippet every tutorial gives you, which sends the whole address:

// The stock snippet — DON'T use this on a page that puts data in the URL.
gtag('config', GA_ID);
// → GA reads location.href by default, i.e. it would send:
//   https://omniviewer.org/mp3#payload=<your entire file, base64>
//   https://omniviewer.org/json?url=https://internal.example/secret.json

That difference — pageLoc() instead of the default location.href — is the entire reason a share link's bytes stay yours. The share-link page keeps its own script-free, analytics-free CSP for the same reason.

Want to check? Open your browser's Network tab and filter for google-analytics. On any OmniViewer page you'll see the outgoing hit — inspect it and the dl / page_location parameter is a bare path like /mp3. Drop a file, add a #payload=, and watch it stay a bare path.

Remember: there is no server

It's worth being blunt about the architecture, because it's what makes the promise structural rather than a pinky-swear. OmniViewer has no backend. There is no application server, no API, no database, no upload endpoint. The entire site is a bundle of static files sitting in an Amazon S3 bucket, handed out by a CDN.

A static host can only do one thing: send you files when you ask. It has no code path that receives your file, because there is no code running anywhere except in your own browser. You can't leak data to a server that was never built. Google Analytics is the single third party in the whole picture, and — as the code above shows — it's fed nothing but the page path.

Offline is always a good thing — trust no one

Here's the cleanest proof of all: OmniViewer works with the network turned off. Load the page once, kill your Wi-Fi, and keep dropping files. Everything still works — the viewer, the hex tab, the format tools — because all of it runs locally. (With the network off, the analytics hit simply never fires, which is fine; it was never load-bearing.)

That's not a party trick, it's a security posture: trust no one. The best way to guarantee your file isn't going somewhere it shouldn't is for there to be no working path off your machine at all. A tool that keeps functioning offline is a tool that provably wasn't depending on phoning home. So a good habit with any viewer that handles sensitive data:

OmniViewer is built for exactly that test. Turn off the network and it keeps its promise, because keeping your bytes local was never a matter of trust — it's the only thing the architecture can do.

Open OmniViewer →Drop any file, any size. It never leaves your browser — try it offline. See share links →20 live examples of files that travel in the URL fragment, never to a server.