# Sessionable SDK

Session recording and replay SDK for Sessionable, allowing you to capture user sessions and send them to your Sessionable backend.

## Installation

```bash
npm install sessionable
# or
yarn add sessionable
```

## Quick Start

```javascript
import { Sessionable } from "sessionable";

// Initialize the SDK
const sessionable = new Sessionable({
  apiKey: "your-api-key",
});

// Start recording automatically
sessionable.start();

// Later, stop recording
// sessionable.stop();
```

## React Component

For React applications, we provide a convenient component:

```jsx
import { SessionRecorder, useSessionable } from "sessionable/react";

function App() {
  return (
    <div>
      <SessionRecorder apiKey="your-api-key" autoStart={true}>
        <YourApp />
      </SessionRecorder>
    </div>
  );
}

// In child components, you can access the sessionable instance:
function YourApp() {
  // This hook provides stable access to the Sessionable instance with 
  // optimized rendering - it won't re-render when metrics update
  const { isRecording, identify } = useSessionable();

  const handleUserLogin = async (userId, orgId) => {
    // Identify the user when they log in
    if (isRecording) {
      await identify(userId, orgId);
    }
  };

  return <div>{/* Your app content */}</div>;
}
```

## Configuration Options

The SDK accepts the following configuration options:

| Option             | Type      | Description                                          | Default     |
| ------------------ | --------- | ---------------------------------------------------- | ----------- |
| `apiKey`           | `string`  | Your Sessionable API key                             | Required    |
| `autoStart`        | `boolean` | Start recording automatically                        | `false`     |
| `metadata`         | `Object`  | Custom metadata to include                           | `{}`        |
| `maskInputs`       | `boolean` | Whether to mask input fields (passwords, text, etc.) | `false`     |
| `maskTextSelector` | `string`  | CSS selector for text elements that should be masked | `undefined` |
| `debug`            | `boolean` | Enable debug mode (uses localhost:5173 endpoint)     | `false`     |
| `useDeviceId`      | `boolean` | Use deterministic device ID generation               | `true`      |
| `userId`           | `string`  | Identified user ID to link with sessions             | `undefined` |
| `organizationId`   | `string`  | Organization ID for the identified user              | `undefined` |
| `userAttributes`   | `Object`  | Additional metadata about the identified user        | `{}`        |

## User Identification

You can identify users to link anonymous sessions with known user accounts:

```javascript
// Initialize without identification
const sessionable = new Sessionable({
  apiKey: "your-api-key",
});

// Start recording
sessionable.start();

// Later, identify the user (e.g., after login)
await sessionable.identify(
  "user-123", // User ID (required)
  "org-456", // Organization ID (optional)
  {
    // User attributes (optional)
    email: "user@example.com",
    plan: "premium",
    role: "admin",
  }
);
```

You can also provide identification during initialization:

```javascript
const sessionable = new Sessionable({
  apiKey: "your-api-key",
  userId: "user-123",
  organizationId: "org-456",
  userAttributes: {
    email: "user@example.com",
    plan: "premium",
  },
});
```

## Anonymous User Identification

By default, Sessionable uses device fingerprinting to generate a stable, deterministic ID for anonymous users. This ensures that the same user gets the same ID across sessions, making it easier to track user behavior before they log in.

```javascript
// Enable device-based ID generation (default)
const sessionable = new Sessionable({
  apiKey: "your-api-key",
  useDeviceId: true,
});

// Or disable it to use random IDs
const sessionable = new Sessionable({
  apiKey: "your-api-key",
  useDeviceId: false,
});
```

The device ID is stored in localStorage as `sessionable_anonymous_id`.

## Framework Integration

### Next.js Integration

For Next.js applications, integrate the SessionRecorder component with client-side only rendering:

```tsx
// pages/_app.tsx or app/layout.tsx
import dynamic from "next/dynamic";

// Client-side only import of SessionRecorder
const SessionRecorderClient = dynamic(
  () =>
    import("sessionable/react").then((mod) => ({
      default: mod.SessionRecorder,
    })),
  { ssr: false }
);

function MyApp({ Component, pageProps }) {
  return (
    <>
      <SessionRecorderClient
        apiKey={process.env.NEXT_PUBLIC_SESSIONABLE_API_KEY}
        autoStart={true}
        metadata={{
          environment: process.env.NODE_ENV,
          deployment: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
        }}
        maskInputs={true}
      >
        <Component {...pageProps} />
      </SessionRecorderClient>
    </>
  );
}

export default MyApp;
```

This approach ensures:

- The recorder only runs on the client side
- Sessions are captured across all pages in your application
- Recording persists during client-side navigation

### Remix Integration

For Remix applications, use the client-only module to ensure server-side rendering compatibility:

```tsx
// app/root.tsx
import { ClientOnly } from "remix-utils/client-only";
import { SessionRecorder } from "sessionable/react";

export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        {/* Other meta tags */}
      </head>
      <body>
        <ClientOnly fallback={null}>
          {() => (
            <SessionRecorder
              apiKey={process.env.SESSIONABLE_API_KEY}
              autoStart={true}
              maskInputs={true}
            >
              <Outlet />
            </SessionRecorder>
          )}
        </ClientOnly>

        {/* Remix scripts */}
        <Scripts />
      </body>
    </html>
  );
}
```

Note: Make sure to include your API key in your environment variables, using the appropriate naming convention for your framework (e.g., `NEXT_PUBLIC_` prefix for client-accessible variables in Next.js).

## Debug Mode

For local development and testing, you can enable debug mode to use a local API endpoint:

```javascript
const sessionable = new Sessionable({
  apiKey: "your-api-key",
  debug: true, // Uses http://localhost:5173 instead of production
});
```

## React Hooks API

When using the React integration, you can access the Sessionable instance and metrics using two optimized hooks, which have been designed to prevent unnecessary re-renders in your application:

### useSessionable

Access the Sessionable instance, control methods, and recording state. This hook is optimized to prevent re-renders when metrics change (which can happen frequently). It will only trigger re-renders when the recording state changes:

```jsx
import { useSessionable } from "sessionable/react";

function RecordingControls() {
  const {
    sessionable, // The Sessionable instance
    isRecording, // Current recording state (updates without causing frequent re-renders)
    start, // Start recording
    stop, // Stop recording
    addMetadata, // Add custom metadata
    identify, // Identify user
    getUserId, // Get identified user ID
    getOrganizationId, // Get organization ID
    getUserAttributes, // Get user attributes
  } = useSessionable();

  // This component will only re-render when isRecording changes,
  // not when metrics update every second
  return (
    <div>
      <p>Status: {isRecording ? "Recording" : "Not recording"}</p>

      <button onClick={() => start()} disabled={isRecording}>
        Start Recording
      </button>

      <button onClick={() => stop()} disabled={!isRecording}>
        Stop Recording
      </button>

      <button
        onClick={() => identify("user-123", "org-456")}
        disabled={!isRecording}
      >
        Identify User
      </button>
    </div>
  );
}
```

### useSessionableMetrics

Access only the session metrics. Components using this hook will re-render when metrics change (approximately once per second when metrics are actively updating):

```jsx
import { useSessionableMetrics } from "sessionable/react";

function MetricsDisplay() {
  // This component will re-render when metrics change (every ~1 second during recording)
  const metrics = useSessionableMetrics();

  if (!metrics) return <div>No metrics available</div>;

  return (
    <div>
      <p>Session ID: {metrics.sessionId || "Not recording"}</p>
      <p>Events: {metrics.eventsRecorded || 0}</p>
      <p>Connection: {metrics.connectionStatus || "disconnected"}</p>
      <p>Batches: {metrics.batchesSuccessful}/{metrics.batchesSent}</p>
    </div>
  );
}
```

### Performance Optimized Usage

For optimal performance, follow these patterns:

1. Use `useSessionable` for components that need recording controls but don't need to display metrics
2. Use `useSessionableMetrics` only in components that display metrics data
3. Memoize components to prevent unnecessary re-renders

```jsx
import React, { memo } from "react";
import { useSessionable, useSessionableMetrics } from "sessionable/react";

// This component only re-renders when recording state changes
const ControlButtons = memo(() => {
  const { start, stop, isRecording } = useSessionable();

  return (
    <div>
      <button onClick={start} disabled={isRecording}>
        Start Recording
      </button>
      <button onClick={stop} disabled={!isRecording}>
        Stop Recording
      </button>
    </div>
  );
});

// This component only re-renders when metrics change
const MetricsDisplay = memo(() => {
  const metrics = useSessionableMetrics();
  
  if (!metrics) return <div>No metrics available</div>;
  
  return (
    <div>
      <p>Session ID: {metrics.sessionId || "Not available"}</p>
      <p>Events: {metrics.eventsRecorded}</p>
      <p>Batches: {metrics.batchesSuccessful}/{metrics.batchesSent}</p>
    </div>
  );
});

// Main component that includes both control buttons and metrics
function SessionControls() {
  return (
    <div>
      <h3>Session Controls</h3>
      <ControlButtons />
      
      <h3>Session Details</h3>
      <MetricsDisplay />
    </div>
  );
}
```

This architecture ensures that:
- The control buttons only re-render when recording starts/stops
- The metrics display only re-renders when metrics change
- Components not directly consuming these hooks won't re-render at all

## Examples

### Basic Example

Check out the [basic example](./examples/basic) to see a simple HTML implementation.

### SaaS Demo

For a complete React application example, see the [SaaS Demo](./examples/saas-demo) which shows a healthcare platform with the SDK fully integrated.

## Advanced Usage

### Text Masking with CSS Selectors

You can protect sensitive information on your page by specifying a CSS selector for text that should be masked:

```javascript
const sessionable = new Sessionable({
  apiKey: "your-api-key",
  maskTextSelector: ".sensitive-data, [data-mask], .user-pii",
});
```

With this configuration, any element matching these selectors will have its text content masked in recordings:

```html
<!-- These texts will be masked in recordings -->
<span class="sensitive-data">Account: 1234-5678-9012</span>
<div data-mask>SSN: 123-45-6789</div>
<p class="user-pii">Jane Smith</p>
```

### Adding Custom Metadata

```javascript
// During initialization
const sessionable = new Sessionable({
  apiKey: "your-api-key",
  metadata: {
    userId: "123",
    userType: "premium",
    environment: "production",
  },
});

// Or later during recording
sessionable.addMetadata({
  currentPage: window.location.pathname,
  referrer: document.referrer,
  timestamp: new Date().toISOString(),
});
```

### Getting Session Metrics

You can retrieve session metrics at any time using the `getMetrics` method:

```javascript
const metrics = sessionable.getMetrics();
console.log("Session ID:", metrics.sessionId);
console.log("Events recorded:", metrics.eventsRecorded);
console.log("Connection status:", metrics.connectionStatus);
```

## TypeScript Support

The package includes TypeScript definitions for all public APIs.

## License

All rights reserved. © Sessionable
