npm package: @sygnl/identity-manager
Description: Lightweight identity and session management with cookie + localStorage fallback
Author: Edge Foundry, Inc.
Latest version: 1.0.3

# @sygnl/identity-manager

> Lightweight identity and session management with cookie + localStorage fallback

[![npm version](https://img.shields.io/npm/v/@sygnl/identity-manager.svg)](https://www.npmjs.com/package/@sygnl/identity-manager)
[![License](https://img.shields.io/npm/l/@sygnl/identity-manager.svg)](https://github.com/sygnl/identity-manager/blob/main/LICENSE)

## Features

- 🎯 **Zero Dependencies** - Fully self-contained
- 🔒 **Privacy-First** - Anonymous user identification without PII
- 💾 **Dual Storage** - Cookie + localStorage fallback for maximum persistence
- 🌐 **SSR-Safe** - Works in both browser and Node.js environments
- ⚙️ **Fully Configurable** - Customize every aspect of cookie/storage behavior
- 🧪 **Well Tested** - Comprehensive test suite with >90% coverage
- 📦 **TypeScript Native** - Full type definitions included
- 🚀 **Tiny Bundle** - ~2KB minified + gzipped

## Installation

```bash
npm install @sygnl/identity-manager
```

```bash
yarn add @sygnl/identity-manager
```

```bash
pnpm add @sygnl/identity-manager
```

## Quick Start

```typescript
import { IdentityManager } from '@sygnl/identity-manager';

// Create instance with default settings
const identity = new IdentityManager();

// Get or create anonymous user ID (365-day persistence)
const userId = identity.ensureAnonymousId();
// → "a3f2b8c9-4d5e-4f6a-8b9c-1d2e3f4a5b6c"

// Get or create session ID (1-day persistence)
const sessionId = identity.ensureSessionId();
// → "sess_1704067200000"
```

## Use Cases

### Analytics & Tracking

Track user behavior across sessions without requiring login:

```typescript
const identity = new IdentityManager();

// Track page view with persistent user ID
analytics.track('page_view', {
  userId: identity.ensureAnonymousId(),
  sessionId: identity.ensureSessionId(),
  page: window.location.pathname,
});
```

### A/B Testing

Consistently assign users to experiment groups:

```typescript
const identity = new IdentityManager();
const userId = identity.ensureAnonymousId();

// User always gets same variant across sessions
const variant = hashUserId(userId) % 2 === 0 ? 'A' : 'B';
```

### Shopping Cart Persistence

Associate cart with anonymous user before login:

```typescript
const identity = new IdentityManager();

async function addToCart(productId: string) {
  await fetch('/api/cart', {
    method: 'POST',
    body: JSON.stringify({
      anonymousId: identity.ensureAnonymousId(),
      productId,
    }),
  });
}
```

### Form Progress Tracking

Resume incomplete forms across sessions:

```typescript
const identity = new IdentityManager();
const formKey = `form_${identity.ensureAnonymousId()}`;

// Save progress
localStorage.setItem(formKey, JSON.stringify(formData));

// Resume later
const savedData = localStorage.getItem(formKey);
```

## API Reference

### Constructor

```typescript
new IdentityManager(options?: PartialIdentityManagerOptions)
```

Creates a new IdentityManager instance with optional configuration.

#### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `anonymousIdCookieName` | `string` | `'_stid'` | Cookie name for anonymous ID |
| `sessionIdCookieName` | `string` | `'_session_id'` | Cookie name for session ID |
| `anonymousIdTTL` | `number` | `365` | Anonymous ID TTL in days |
| `sessionIdTTL` | `number` | `1` | Session ID TTL in days |
| `cookieDomain` | `string?` | `undefined` | Cookie domain (e.g., `.example.com`) |
| `cookiePath` | `string` | `'/'` | Cookie path |
| `cookieSameSite` | `'Strict' \| 'Lax' \| 'None'` | `'Lax'` | Cookie SameSite attribute |
| `cookieSecure` | `boolean` | `true` | Cookie Secure flag (requires HTTPS) |
| `useLocalStorage` | `boolean` | `true` | Enable localStorage fallback |
| `debug` | `boolean` | `false` | Enable debug logging |

### Methods

#### `ensureAnonymousId(): string`

Gets existing anonymous ID or creates a new one. Always returns a value.

```typescript
const userId = identity.ensureAnonymousId();
// → "a3f2b8c9-4d5e-4f6a-8b9c-1d2e3f4a5b6c"
```

**Persistence Strategy:**
1. Check cookie
2. Check localStorage (if enabled)
3. Generate new UUID v4
4. Save to both cookie and localStorage

#### `getAnonymousId(): string | null`

Gets existing anonymous ID without creating a new one.

```typescript
const userId = identity.getAnonymousId();
// → "a3f2b8c9-..." or null
```

#### `ensureSessionId(): string`

Gets existing session ID or creates a new one. Always returns a value.

```typescript
const sessionId = identity.ensureSessionId();
// → "sess_1704067200000"
```

**Format:** `sess_{timestamp}`

#### `getSessionId(): string | null`

Gets existing session ID without creating a new one.

```typescript
const sessionId = identity.getSessionId();
// → "sess_1704067200000" or null
```

#### `getCookie(name: string): string | null`

Low-level cookie getter.

```typescript
const value = identity.getCookie('my_cookie');
```

#### `setCookie(name: string, value: string, days: number): void`

Low-level cookie setter.

```typescript
identity.setCookie('my_cookie', 'my_value', 30);
```

#### `configure(options: PartialIdentityManagerOptions): void`

Updates configuration at runtime.

```typescript
identity.configure({
  debug: true,
  anonymousIdTTL: 180,
});
```

## Advanced Usage

### Custom Cookie Names

Use custom cookie names to avoid conflicts:

```typescript
const identity = new IdentityManager({
  anonymousIdCookieName: '_my_user_id',
  sessionIdCookieName: '_my_session',
});
```

### Subdomain Sharing

Share identity across subdomains:

```typescript
const identity = new IdentityManager({
  cookieDomain: '.example.com', // Shares across *.example.com
});
```

### Extended Anonymous ID Persistence

Keep anonymous ID for longer:

```typescript
const identity = new IdentityManager({
  anonymousIdTTL: 730, // 2 years
});
```

### Longer Sessions

Extend session duration:

```typescript
const identity = new IdentityManager({
  sessionIdTTL: 7, // 1 week
});
```

### Debug Mode

Enable logging for troubleshooting:

```typescript
const identity = new IdentityManager({
  debug: true,
});

// Logs to console:
// [IdentityManager] ensureAnonymousId: generated new ID { anonymousId: "..." }
// [IdentityManager] setCookie: success { name: "_stid", value: "...", ... }
```

### Disable localStorage Fallback

Use cookies only:

```typescript
const identity = new IdentityManager({
  useLocalStorage: false,
});
```

### SameSite Configuration

For cross-site tracking (requires Secure):

```typescript
const identity = new IdentityManager({
  cookieSameSite: 'None',
  cookieSecure: true, // Required for SameSite=None
});
```

### SSR/Node.js Usage

The library is SSR-safe and won't throw in Node.js:

```typescript
// Server-side rendering
const identity = new IdentityManager();
const userId = identity.ensureAnonymousId(); // Returns null in SSR
```

## Edge Cases Handled

### ✅ Cookie Blocking

When cookies are blocked by browser settings:
- Falls back to localStorage
- Gracefully returns null if both fail
- No errors thrown

### ✅ Private Browsing Mode

When localStorage throws errors:
- Catches and handles silently
- Falls back to cookies only
- Continues working

### ✅ Storage Quota Exceeded

When localStorage is full:
- Catches quota errors
- Uses cookie as primary storage
- No functionality loss

### ✅ SSR/Node.js Environment

When `document`/`window` are undefined:
- Detects environment
- Returns null gracefully
- No errors thrown

### ✅ Cookie Expiration

When cookies expire but localStorage persists:
- Automatically restores from localStorage
- Syncs back to cookie
- Seamless recovery

## Migration Guide

### From Custom Implementation

**Before:**
```javascript
function getAnonymousId() {
  let id = getCookie('_user_id');
  if (!id) {
    id = generateUUID();
    setCookie('_user_id', id, 365);
  }
  return id;
}
```

**After:**
```typescript
import { IdentityManager } from '@sygnl/identity-manager';

const identity = new IdentityManager({
  anonymousIdCookieName: '_user_id', // Match your existing cookie name
});

const id = identity.ensureAnonymousId();
```

### From pixel.source.js (Sygnl)

This package extracts and enhances these functions from `pixel.source.js`:
- `getCookie()` → `identity.getCookie()`
- `setCookie()` → `identity.setCookie()`
- `generateUUID()` → Internal (automatic)
- `ensureAnonymousId()` → `identity.ensureAnonymousId()`
- `ensureSessionId()` → `identity.ensureSessionId()`

**Benefits of migration:**
- ✅ TypeScript support
- ✅ Better error handling
- ✅ Configurable options
- ✅ Comprehensive tests
- ✅ SSR safety
- ✅ Maintained package

## Browser Compatibility

- ✅ Chrome/Edge 90+
- ✅ Firefox 88+
- ✅ Safari 14+
- ✅ Node.js 18+ (SSR-safe)

**UUID Generation:**
- Uses `crypto.randomUUID()` in modern browsers
- Falls back to `Math.random()` for older browsers
- Both implementations are RFC4122 compliant

## Performance

- **Bundle Size:** ~2KB minified + gzipped
- **Runtime:** <1ms for ID generation
- **Memory:** <1KB per instance
- **Zero dependencies**

## Testing

Run the test suite:

```bash
npm test
```

Run with coverage:

```bash
npm run test:coverage
```

Watch mode for development:

```bash
npm run test:watch
```

**Test Coverage:**
- 60+ test cases
- >90% code coverage
- Edge cases covered
- Integration tests included

## TypeScript

Full TypeScript support included:

```typescript
import { 
  IdentityManager,
  IdentityManagerOptions,
  PartialIdentityManagerOptions,
  DEFAULT_OPTIONS
} from '@sygnl/identity-manager';

// Type-safe configuration
const options: PartialIdentityManagerOptions = {
  debug: true,
  anonymousIdTTL: 365,
};

const identity = new IdentityManager(options);
```

## Contributing

Contributions welcome! Please read our [Contributing Guide](CONTRIBUTING.md) first.

## License

Apache-2.0

Copyright 2026 Edge Foundry, Inc.

## Support

- 📧 Email: support@sygnl.com
- 🐛 Issues: [GitHub Issues](https://github.com/sygnl/identity-manager/issues)
- 💬 Discussions: [GitHub Discussions](https://github.com/sygnl/identity-manager/discussions)

## Related Packages

- [@sygnl/event-transport](#) - Analytics event transport layer
- [@sygnl/page-engagement](#) - Page engagement tracking
- [@sygnl/event-schema](#) - Event schema builder

---

Made with ❤️ by [Sygnl](https://sygnl.com)