Unified Tracking — a project by Ahsan Mahmood
Zero-runtime-dependency TypeScript package giving web, React, and Capacitor apps one API for analytics, user identity, revenue, consent gating, and error…
The consent check happens before any provider sees an event.
That reads like an implementation detail. It is the entire design. Most instrumentation puts consent at the edges: a banner sets a flag, each SDK is initialised or not, and every call site is trusted to have asked the question first.
One forgotten call site and a vendor holds a record it should never have received.
You cannot take it back. A deletion request to a third party is a slower, weaker and far less certain thing than never having sent the event, which is why the gate belongs somewhere a developer cannot walk past it.
So it sits on the dispatch path itself.
An event is checked, policy-excluded properties are stripped, and only then does anything reach a provider. A call site cannot skip that step, because there is no route to a vendor that goes around it.
Sixteen providers sit behind the interface: eight for analytics and eight for error tracking.
Initialisation, event tracking, user identity, screen views, revenue and error capture all arrive through one API rather than through eight vendor SDKs, each with its own vocabulary for the same three ideas.
On the React side there is no context wrapper and no higher-order component. You import a hook and call it, which means instrumentation can go into one screen without touching anything above it in the tree.
The second decision is a trade rather than a win.
The package carries zero runtime dependencies. Each vendor SDK is fetched from that vendor's own CDN when the provider is switched on. The bundle stays small, and a product using two providers never downloads the other fourteen.
That is right for a website and wrong for a browser extension.
An extension may load no remote code at all, and a store treats a CDN script as grounds for rejection rather than as a review note. So an extension bundles its analytics or ships with none, and this package's central efficiency is the exact thing that disqualifies it there.
I would rather write that sentence than a compatibility table that implies otherwise.
Event buffering is the third piece, and it exists because of a failure that hides well. A vendor SDK's initialisation call returns before its destination plugins have attached, so events fired in that window are dropped with one console line nobody is reading at the time.
That failure is intermittent.
A clean test run proves nothing about a gap that opens on one load in many. So buffering removes the window instead of narrowing it, and the fix becomes something you verify by reading the code.
What it cost was a dispatch path that has to stay boring. Every provider wants a special case, and each special case is another chance for the gate to be walked around by something that arrived looking like a convenience.
One limit is worth naming. This package routes events; it does not tell you whether the numbers arriving on the other side are true. A key left blank in a deployed build, or an SDK that never took over its own global, produces a silence that looks exactly like a quiet week, and the only way to tell those two states apart is to go and confirm that an event genuinely landed on the other side.
It runs in a browser and inside a Capacitor WebView. There is no iOS release behind this work, because there is no Apple Developer account here.
MIT, on npm as unified-tracking, with the source at github.com/aoneahsan/unified-tracking.
An analytics layer earns its place on the day you switch a provider off and nothing else breaks.
What it does, and what that costs to build
- One API for 16 analytics & error providers
- Zero runtime dependencies (CDN-loaded SDKs)
- Provider-free React hooks
- Consent gating + data minimization on dispatch
- Pre-init event buffering
- Revenue + screen + user-identity tracking
- Tree-shakeable ESM with .d.ts types
Built with
- TypeScript
- React 19
- CapacitorJS
- Vitest
Worth knowing
- npm
- package
- analytics
- tracking
- react
- zero-dependency
Who built Unified Tracking?
Ahsan Mahmood wrote it, including the consent gate that sits in front of every provider. It is on npm as unified-tracking under MIT, with the source at github.com/aoneahsan/unified-tracking. The provider list runs long because his own projects report to several destinations at once rather than to a single vendor, so a layer treating sixteen of them as interchangeable was cheaper to build once than to rebuild per product.
What stops an event reaching a provider the user did not consent to?
The gate sits on the dispatch path, so nothing reaches a provider without passing through it. Consent is checked and policy-excluded properties are stripped in one place, before any vendor SDK is handed the event, rather than at each of the dozens of call sites that fire one. That distinction is the difference between a policy and a hope. A call site cannot skip the check by forgetting to ask, because there is no route to a provider that goes around the gate.
Does Unified Tracking work on iPhone?
It runs in any browser, an iPhone browser included, because the analytics half of this is web code rather than platform code. There is no iOS release behind this work: no Apple Developer account exists here, and nothing I build has shipped to the App Store. Inside a Capacitor shell the package runs in the WebView exactly as it runs on the open web, and Android is the shell that has actually been exercised. Nothing here should be read as a claim about an Apple build.
What happens to events fired before the SDK finishes initialising?
They are buffered and dispatched once initialisation genuinely completes. That matters more than it sounds, because a vendor SDK's init call returns before its destination plugins have attached, and events fired inside that window are dropped with a console line nobody reads. The failure is intermittent, so a clean test run proves nothing about it. Buffering removes the window rather than narrowing it, and that is the only version of the fix you can verify by reading code instead of by getting lucky.
Where do the vendor SDKs come from?
From each vendor's own CDN, fetched only when that provider is enabled, which is how the package holds zero runtime dependencies. The consequence deserves stating plainly: this design is right for a website and wrong for a browser extension. An extension may load no remote code, and a store treats a CDN script as grounds for rejection rather than as a review note, so an extension bundles its analytics or ships without any. If extensions are your target, this is the wrong package, and saying so is more useful than a compatibility table.
https://aoneahsan.com/projects/com.aoneahsan.unifiedtracking