npm package: @flagoff/ts-sdk
Description: FlagOff - A comprehensive dual-purpose JavaScript SDK for feature flags with client-side and server-side support
Author: Aaron Welsh
Latest version: 1.2.0

# Flagoff TypeScript SDK

> [!IMPORTANT]
> **Private Early Access**: Flagoff is currently in private early access. You will need an API key to use this SDK. Please email **hello@flagoff.io** to request access.

**Flagoff** is a resilient, privacy-focused feature flagging SDK for TypeScript applications. Built for modern development, it offers:

- **Offline-First Architecture**: Your app keeps working even when the network doesn't.
- **Real-Time Updates**: Toggle flags instantly via WebSockets without page reloads.
- **Privacy by Design**: Flags are evaluated locally on the device, keeping user data secure.
- **Universal Support**: Works seamlessly in **React**, **Node.js**, and vanilla JavaScript.

## Table of Contents

- [Quick Start](#quick-start)
  - [Installation](#1-installation)
  - [Basic Usage](#2-basic-usage)
  - [User Identity Management](#3-user-identity-management)
- [Key Concepts](#key-concepts)
  - [Offline-First & Auto-Recovery](#️-offline-first--auto-recovery)
  - [Local Evaluation & Hashing](#-local-evaluation--hashing)
  - [Real-Time Updates](#-real-time-updates)
- [Framework Guides](#framework-guides)
  - [React](#react)
  - [Node.js (Server-Side)](#nodejs-server-side)
- [API Reference](#api-reference)
- [Configuration](#configuration)
- [Persistent Traits & Targeting](#-persistent-traits--targeting)
- [User Identification & Privacy](#-user-identification--privacy)

---

## Quick Start

### 1. Installation

```bash
npm install @flagoff/ts-sdk
# or
yarn add @flagoff/ts-sdk
```

### 2. Basic Usage

Initialise the client and evaluate a flag.

```typescript
import { FeatureFlagsClient } from '@flagoff/ts-sdk';

const client = new FeatureFlagsClient({
  apiKey: 'YOUR_API_KEY',
  offlineMode: 'cache-only', // Recommended for resilience
});

async function main() {
  // 1. Initialise (connects, caches flags, starts auto-recovery)
  await client.initialize();

  // 2. Evaluate a flag
  const flag = await client.evaluateFlag('new-feature', 'user-123');

  if (flag.enabled) {
    console.log('New feature is enabled!');
  }
}

main();
```

### 3. User Identity Management

Centralised user context management with the `identity()` method. When you switch users, all flags automatically re-evaluate.

```typescript
import { FeatureFlagsClient } from '@flagoff/ts-sdk';

const client = new FeatureFlagsClient({ apiKey: 'YOUR_KEY' });
await client.initialize();

// Set user identity
await client.identity('user-123', { plan: 'pro', region: 'us-east' });

// Evaluate flags (uses current identity automatically)
const flag = await client.evaluateFlag('new-feature');

// Switch to a different user (all flags re-evaluate automatically)
await client.identity('user-456', { plan: 'free' });

// Clear identity and go anonymous
await client.anonymous();
```

**Benefits:**

- ✅ No manual flag iteration needed
- ✅ WebSocket updates use current identity context
- ✅ Identity persists across page reloads
- ✅ Automatic re-evaluation on user switch

---

## Key Concepts

### 🛡️ Offline-First & Auto-Recovery

Flagoff is designed to be indestructible.

- **Cache-Only Mode**: If the SDK cannot connect to the server on startup, it instantly falls back to the last known good configuration from local storage. Your app loads without waiting for a timeout.
- **Auto-Recovery**: In the background, the SDK intelligently polls with exponential backoff until the connection is restored, then silently updates the flags.
- **Smart Retries**: The SDK automatically stops retrying if it encounters a terminal error (like a 401 Unauthorized or 404 Not Found), preventing wasteful network requests.
- **Recovery Events**: You can subscribe to `recoveryRetry` and `recoveryFailed` events to show connection status to your users. You can view the events in the [client offline-demo.ts](./src/examples/client/offline-demo.ts) and [server offline-demo.ts](./src/examples/server/offline-demo.ts).

### ⚡ Local Evaluation & Hashing

Unlike other SDKs that make a network request for every flag check, Flagoff evaluates flags **locally**.

- **Deterministic Hashing**: We use SHA-256 hashing to deterministically map users to flag variants. This ensures consistent experiences across sessions without needing a central server for every decision.
- **Zero Latency**: Flag evaluations happen in microseconds because they are just local math operations.
- **Privacy**: Your user's ID and traits never leave the device during evaluation.

### 🔄 Real-Time Updates

Stop asking users to refresh. Flagoff maintains a lightweight WebSocket connection to push flag changes instantly.

- **Instant Rollouts**: Enable a feature, and it appears on all connected clients immediately.
- **Kill Switches**: Disable a buggy feature globally in milliseconds.

---

## Framework Guides

### React

Wrap your app in the `FeatureFlagsProvider` and use the `useFeatureFlag` hook.

```tsx
// App.tsx
import { FeatureFlagsProvider } from '@flagoff/ts-sdk/react';

const config = { apiKey: 'YOUR_KEY' };

export default function App() {
  return (
    <FeatureFlagsProvider config={config}>
      <MyComponent />
    </FeatureFlagsProvider>
  );
}
```

```tsx
// MyComponent.tsx
import { useFeatureFlag } from '@flagoff/ts-sdk/react';

export function MyComponent() {
  const { enabled, value } = useFeatureFlag('dark-mode', {
    identifier: 'user-123',
  });

  if (enabled) {
    return <div className="dark">Dark Mode Active: {value}</div>;
  }
  return <div>Light Mode</div>;
}
```

### Node.js (Server-Side)

The server SDK shares the same API but is optimized for Node.js environments (using in-memory storage by default).

```typescript
import { FeatureFlagsServer } from '@flagoff/ts-sdk/server';

const client = new FeatureFlagsServer({ apiKey: 'SERVER_KEY' });
await client.initialize();

const flag = await client.evaluateFlag('api-rate-limit', 'tenant-456');
```

```typescript
import { FeatureFlagsServer } from '@flagoff/ts-sdk/server';

const client = new FeatureFlagsServer({ apiKey: 'SERVER_KEY' });
await client.initialize();

// Set identity for long-running processes or tenant-scoped services
await client.identity('tenant-456', { plan: 'enterprise', region: 'eu-west' });

// Evaluate flags using current identity
const flag = await client.evaluateFlag('api-rate-limit');

// Or override for specific evaluation
const flag2 = await client.evaluateFlag('api-rate-limit', 'user-789');
```

---

## API Reference

### `client.initialize()`

Connects to the Flagoff network, loads flags into cache, and sets up real-time listeners. Always await this before evaluating flags.

### `client.identity(userId, traits?)`

Set the user identity and automatically re-evaluate all flags. This centralizes user context management.

- `userId`: Unique user identifier (e.g., `'user-123'`)
- `traits`: Optional object for user attributes. These persist and are merged with global config traits (see [Persistent Traits & Targeting](#persistent-traits--targeting) section).

```typescript
await client.identity('user-123', { plan: 'pro' });
```

### `client.getIdentity()`

Get the current user identity.

```typescript
const identity = client.getIdentity();
// { userId: 'user-123', traits: { plan: 'pro' } }
```

### `client.anonymous()`

Clear the current identity and re-evaluate flags as anonymous.

```typescript
await client.anonymous();
```

### `client.evaluateFlag(key, identifier?, traits?)`

Evaluates a flag for a specific user.

- `key`: The flag identifier (e.g., `'new-checkout'`)
- `identifier`: User ID (optional if identity is set via `client.identity()`)
- `traits`: Optional object for targeting rules (e.g., `{ plan: 'premium' }`)

```typescript
// With identity set
await client.identity('user-123');
const flag = await client.evaluateFlag('new-feature'); // Uses current identity

// Override identity for specific evaluation
const flag2 = await client.evaluateFlag('new-feature', 'user-456', {
  plan: 'free',
});
```

### `client.on(event, callback)`

Listen to lifecycle events.

- `'connected'`: SDK is online
- `'flagsUpdated'`: New flags have arrived (via initial load or WebSocket)
- `'identityChanged'`: User identity has changed

---

## Configuration

| Option            | Default        | Description                                                                                             |
| :---------------- | :------------- | :------------------------------------------------------------------------------------------------------ |
| `apiKey`          | **Required**   | Your project API key.                                                                                   |
| `offlineMode`     | `'cache-only'` | Behavior when network fails on init. `'cache-only'` loads from local storage; `'fail'` throws an error. |
| `enableCaching`   | `true`         | Persist flags to `localStorage` (browser) or memory (server).                                           |
| `enableWebSocket` | `true`         | Receive real-time updates.                                                                              |
| `pollingInterval` | `60000`        | Base interval (ms) for auto-recovery polling.                                                           |
| `traits`          | `{}`           | Global user traits applied to every evaluation. Useful for app version, environment, etc.               |

---

## 🎯 Persistent Traits & Targeting

Flagoff allows you to define user traits at multiple levels. When evaluating a flag, traits are merged in the following order of precedence:

1.  **Evaluation Arguments** (Highest Priority)
2.  **User Identity** (`client.identity()`)
3.  **Global Configuration** (`config.traits`)

### 1. Global Configuration

Set traits that apply to **every** user (e.g., environment, app version).

```typescript
const client = new FeatureFlagsClient({
  apiKey: 'YOUR_KEY',
  traits: {
    appVersion: '1.2.0',
    platform: 'ios',
  },
});
```

### 2. User Identity

Set traits for the current logged-in user. These stick until the user changes or logs out.

```typescript
await client.identity('user-123', {
  plan: 'premium',
  country: 'en-GB',
});

// Automatically checks: environment='production' AND plan='premium' AND country='en-GB'
const flag = await client.evaluateFlag('premium-feature');
```

### 3. Evaluation Override

Override everything for a specific check.

```typescript
// Overrides 'plan' to 'free' just for this check
// Identifier is optional if identity is set via `client.identity()` and is safe to be undefined
const flag = await client.evaluateFlag('premium-feature', undefined, {
  plan: 'free',
});
```

---

## 🆔 User Identification & Privacy

Flagoff is designed to be privacy-first. Your user's ID and traits **never** leave the device during flag evaluation.

### Identifier Precedence

When evaluating a flag, the SDK determines the `userId` using the following order of precedence:

1.  **Provided Identifier**: Passed directly to `evaluateFlag(key, 'user-123')`.
2.  **Current Identity**: Set via `client.identity('user-123')`.
3.  **Default User ID**: Configured via `defaultUserId` in options.
4.  **Anonymous**: Falls back to `'anonymous'` if nothing else is provided.

### Privacy & Targeting Logic

Targeting rules (traits) are defined in the Flagoff dashboard but evaluated **locally** on the client.

1.  **Definition**: You define a rule (e.g., `plan` EQUALS `premium`).
2.  **Sync**: The SDK downloads the targeting _rules_ (not user lists).
3.  **Evaluation**: The SDK checks your local user traits against these rules in memory.
4.  **Security**: User traits (e.g., email, plan, country) are **never sent** to Flagoff servers.