npm package: @firstlook-uat/sdk
Description: FirstLook — UAT testing SDK for web applications
Author: awatatgr
Latest version: 0.12.0

# @firstlook/sdk

A framework-agnostic UAT (User Acceptance Testing) SDK for web applications. Embed quest-driven test flows, session recording, voice annotations, and shake-to-report bug reporting into any web app with a single script tag or npm import.

## Installation

```bash
# npm
npm install @firstlook/sdk

# yarn
yarn add @firstlook/sdk

# pnpm
pnpm add @firstlook/sdk
```

## Quick Start

### npm / ES Module

```ts
import { FirstLookSDK } from "@firstlook/sdk";

const sdk = new FirstLookSDK();

await sdk.init({
  projectId: "proj_xxx",
  apiKey: "flk_xxx",
  userId: "tester@example.com",
});

sdk.activate();

await sdk.startSession([
  { id: "q1", title: "Add to Cart", description: "Add any item to the cart", order: 1 },
  { id: "q2", title: "Checkout", description: "Complete the checkout flow", order: 2, blocking: true },
]);
```

### CDN / UMD

```html
<script src="https://unpkg.com/@firstlook/sdk/dist/firstlook.umd.js"></script>
<script>
  const sdk = new FirstLook.FirstLookSDK();
  sdk.init({
    projectId: "proj_xxx",
    apiKey: "flk_xxx",
    userId: "tester@example.com",
  }).then(() => sdk.activate());
</script>
```

## Configuration

All options are passed to `sdk.init()`. Only `projectId`, `apiKey`, and `userId` are required.

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `projectId` | `string` | *required* | Project ID from the FirstLook dashboard |
| `apiKey` | `string` | *required* | API key for authentication |
| `userId` | `string` | *required* | Unique identifier for the current tester |
| `role` | `string` | `undefined` | Optional role label (e.g. `"admin"`, `"tester"`) |
| `context` | `Record<string, unknown>` | `{}` | Arbitrary metadata attached to the session |
| `endpoint` | `string` | Production URL | Custom endpoint for session data upload |
| `triggers.tapCount` | `number` | `5` | Number of taps to open the debug menu |
| `triggers.deepLink` | `boolean` | `true` | Enable deep link activation (`?firstlook=1`) |
| `triggers.shake` | `boolean` | `true` | Enable shake-to-activate on mobile |
| `triggers.customCheck` | `() => boolean` | `undefined` | Custom function to auto-activate on init |
| `security.watermark` | `boolean` | `true` | Show dynamic watermark during recording |
| `security.maskSelectors` | `string[]` | `['input[type="password"]', '[data-sensitive]', '[data-mask]', '.uat-mask']` | CSS selectors for fields to mask |
| `recording.domSnapshot` | `boolean` | `true` | Enable DOM mutation recording |
| `recording.voice` | `boolean` | `true` | Enable voice recording |
| `recording.maxDuration` | `number` | `600` | Max recording duration in seconds |
| `recording.snapshotInterval` | `number` | `1000` | DOM snapshot interval in milliseconds |

## API Reference

### `init(config: FirstLookConfig): Promise<void>`

Initialize the SDK. Sets up triggers, IndexedDB storage, and Shadow DOM host. Does **not** activate the UI.

### `activate(): void`

Manually activate the SDK UI, bypassing configured triggers. Must be called after `init()`.

### `startSession(quests: Quest[]): Promise<string>`

Start a UAT session with a list of quests. Returns the generated session ID. The SDK must be in the `active` state.

### `startSessionFromRemote(questGroupId: string): Promise<string>`

Fetch quest definitions from the API by quest group ID, then start a session. Returns the session ID.

### `endSession(): Promise<SessionData | null>`

End the current recording session. Stops all recorders, persists data to IndexedDB, and enqueues the session for upload. Returns the session data or `null` if no session was active.

### `on(type: SDKEventType | "*", listener: SDKEventListener): () => void`

Subscribe to SDK events. Returns an unsubscribe function.

Available event types: `sdk:initialized`, `sdk:activated`, `sdk:deactivated`, `session:started`, `session:ended`, `quest:started`, `quest:completed`, `quest:failed`, `quest:blocked`, `recording:started`, `recording:stopped`, `report:submitted`, `error`.

```ts
const unsubscribe = sdk.on("session:ended", (event) => {
  console.log("Session ended:", event.sessionId);
});

// Later...
unsubscribe();
```

### `getState(): SDKState`

Returns the current SDK state: `"idle"` | `"initialized"` | `"active"` | `"recording"` | `"finished"`.

### `destroy(): void`

Completely destroy the SDK instance. Removes all DOM elements, stops recorders, clears timers, and resets state to `idle`.

## Quest Format

Each quest in the `quests` array must conform to:

```ts
interface Quest {
  id: string;          // Unique quest identifier
  title: string;       // Short display title
  description: string; // Instructions for the tester
  order: number;       // Sequence order (1-based)
  blocking?: boolean;  // If true, failure blocks subsequent quests
}
```

## License

MIT