npm.io
0.1.2 • Published 15h ago

@opa.sh/analytics

Licence
MIT
Version
0.1.2
Deps
0
Size
100 kB
Vulns
0
Weekly
0

Opa Analytics SDK

First-party, cookieless-friendly browser analytics for Opa — click attribution, identify / track for conversions, and automatic outbound link decoration. Ships as three small packages so you install only what your stack needs.

Package What it is
@opa.sh/analytics Framework-agnostic browser core: createTracker, cookie/localStorage persistence, sendBeacon/fetch transport, outbound link decoration.
@opa.sh/analytics-react React bindings: <OpaProvider>, useOpa(), <OpaAnalytics>.
@opa.sh/analytics-next Next.js (App Router) client bindings — re-exports the React API.

Full docs: https://opa.sh/docs/sdks/conversions

Script tag (no build step)

Drop one tag on any page. On load it reads config from data-* attributes, captures the click id from the URL / first-party cookie, and exposes window.opa:

<script
  src="https://cdn.opa.sh/sdk.js"
  data-key="opa_pub_xxx"
  data-domains='["example.com"]'
  data-attribution-model="last-click"
  async
></script>

<script>
  // Queue calls before the script finishes loading, or call window.opa.* after.
  window.opa.identify({ externalId: "user_123", email: "a@b.com" });
  window.opa.track("signup");
</script>

TypeScript

The CDN bundle is plain JavaScript — it attaches window.opa at runtime and ships no type declarations, so TypeScript doesn't know the global exists. Add a declaration file anywhere your tsconfig.json picks up (any .d.ts under an included path — e.g. src/types/opa.d.ts):

// src/types/opa.d.ts
export {}; // make this file a module so `declare global` augments, not replaces

type OpaIdentifyInput = {
  externalId: string;
  email?: string;
  name?: string;
  avatar?: string;
  [key: string]: unknown;
};

type OpaGlobal = {
  identify: (input: OpaIdentifyInput) => Promise<void>;
  track: (eventName: string, properties?: Record<string, unknown>) => Promise<void>;
  getClickId: () => string | null;
  setConsent: (granted: boolean) => void;
  reset: () => void;
};

declare global {
  interface Window {
    // Present once cdn.opa.sh/sdk.js has loaded. It's optional because the
    // script is async — guard with `window.opa?.track(...)`, or use the
    // pre-load queue: `(window.opa ||= []).push(["track", "signup"])`.
    opa?: OpaGlobal;
  }
}

Then it type-checks with no import and no npm install:

window.opa?.identify({ externalId: "user_123", email: "a@b.com" });
window.opa?.track("signup", { plan: "pro" });

Prefer real imports? Install the npm packages instead (@opa.sh/analytics and friends below) — they bundle their own .d.ts and need no global augmentation.

@opa.sh/analytics (core)

npm i @opa.sh/analytics
import { createTracker } from "@opa.sh/analytics";

const opa = createTracker({
  key: "opa_pub_xxx",
  outboundDomains: ["example.com"],
});

await opa.identify({ externalId: "user_123", email: "a@b.com" });
await opa.track("purchase", { plan: "pro", amount: 4900 });

The tracker posts to POST /v1/track/collect on https://api.opa.sh, sending your public site key via the x-opa-site-key header (or in the JSON body when falling back to navigator.sendBeacon). Every method is SSR-safe and never throws to the caller.

@opa.sh/analytics-react

npm i @opa.sh/analytics @opa.sh/analytics-react
import { OpaProvider, useOpa } from "@opa.sh/analytics-react";

function App() {
  return (
    <OpaProvider config={{ key: "opa_pub_xxx" }}>
      <Checkout />
    </OpaProvider>
  );
}

function Checkout() {
  const opa = useOpa();
  return <button onClick={() => opa.track("purchase")}>Buy</button>;
}

<OpaAnalytics config={...} /> is a zero-render component that boots a single tracker for the page if you do not need the useOpa() context.

@opa.sh/analytics-next

npm i @opa.sh/analytics @opa.sh/analytics-react @opa.sh/analytics-next
// app/layout.tsx
import { OpaProvider } from "@opa.sh/analytics-next";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <OpaProvider config={{ key: "opa_pub_xxx" }}>{children}</OpaProvider>
      </body>
    </html>
  );
}

The Next package is browser-only — it re-exports the client components from @opa.sh/analytics-react with the "use client" boundary already applied.

Development

This is a Bun workspace.

bun install
bun run -F '*' build       # tsup → dist/ (ESM + CJS + .d.ts) for all packages
bun run -F '*' typecheck   # tsc --noEmit
bun run -F '*' test        # bun test

To regenerate the CDN bundle served at /sdk.js:

bun run --cwd packages/analytics build:sdk

Releasing

Publishing is automated by .github/workflows/release.yml, which runs only when a v* tag is pushed. It builds every package and runs npm publish --provenance in dependency order using the NPM_TOKEN secret.

git tag v0.1.0
git push origin v0.1.0

License

MIT Espoca

Keywords