Skip to main content

Solve anonymous-to-known-user identity once, not once per vendor

One canonical anonymous id. One identify() across every provider.

Identity logic gets copy-pasted, not shared

Segment, LaunchDarkly, and FullStory each track their own anonymous id and each implement their own anonymous-to-known transition. Without a shared package, that logic gets reinvented per app: the same toggleProvider.js ended up duplicated verbatim across two separate repos, and some integrations — a LaunchDarkly provider component, for one — shipped with no anonymous id or identity-linking step at all. Different apps drifted to different maturity levels of the exact same pattern, because there was no shared, independently-versioned unit to depend on instead.

One shared package, three independently-versioned layers

identity-core owns the canonical anonymous id and the state machine, and knows nothing about any vendor. Each identity-provider-* package implements a single interface (IdentityProvider) for one vendor. Each identity-adapter-* package wires the framework-idiomatic glue — Next.js middleware plus a React context, Angular module plus an injectable service — and knows nothing about any vendor either.

identity-core <- state machine, zero vendor/framework deps
├─ identity-provider-segment <- implements IdentityProvider for Segment
├─ identity-provider-launchdarkly <- implements IdentityProvider for LaunchDarkly
├─ identity-provider-fullstory <- implements IdentityProvider for FullStory
├─ identity-adapter-nextjs <- depends on identity-core only
└─ identity-adapter-angular <- depends on identity-core only

Adding a new vendor or a new framework is one new package, with zero changes to identity-core or to the others — the direct fix for the duplicated-toggleProvider.js problem above.

Anonymous, until identified

identity-core generates one canonical anonymous id up front and hands it to every registered provider before any vendor SDK loads. A single identify() call fans out to every provider's onIdentify. Logout issues a brand-new anonymous id rather than reusing the old one, so a second person on a shared device never inherits someone else's anonymous history.

anonymous -> identified (login / registration)
identified -> anonymous (logout: a new anonymousId is issued)
createIdentityClient
import { createIdentityClient } from '@idhub/identity-core';
import { segmentProvider } from '@idhub/identity-provider-segment';
import { launchDarklyProvider } from '@idhub/identity-provider-launchdarkly';

const identity = createIdentityClient({
providers: [
segmentProvider({ client: analytics, writeKey: process.env.SEGMENT_WRITE_KEY! }),
launchDarklyProvider({ clientSideId: process.env.LD_CLIENT_ID! }),
],
});

// One call fans out to onIdentify on every registered provider.
identity.identify('user_123', { plan: 'pro' });

identity.subscribe((event, state) => {
if (state.status === 'identified') console.log(state.userId);
});

Next.js: three vendors, one component

Compose the vendor providers you want and mount them once with @idhub/identity-adapter-nextjs's <IdentityProvider> — every child gets useIdentity() for free.

app/providers.tsx
<IdentityProvider
providers={[
segmentProvider({ client: analytics, writeKey: process.env.NEXT_PUBLIC_SEGMENT_WRITE_KEY! }),
launchDarklyProvider({ clientSideId: process.env.NEXT_PUBLIC_LD_CLIENT_ID! }),
fullStoryProvider({ orgId: process.env.NEXT_PUBLIC_FULLSTORY_ORG_ID! }),
]}
>
{children}
</IdentityProvider>

Free & open source

MIT Licensenpm version

@idhub is free and open source, released under the MIT license — every package, no paid tier, no usage limits.

pnpm add @idhub/identity-core