npm package: @revuloop/embed
Description: Embed Revuloop surveys in any website with popup, slider, or inline modes
Author: Revuloop
Latest version: 2.2.0

# @revuloop/embed

Official Revuloop Survey Embed SDK for embedding surveys in any website.

## Features

- **Multiple Display Modes**: Popup modal, slider panel, or inline embedding
- **Token-Based Authentication**: Secure, domain-restricted access with automatic token refresh
- **Smart Survey Support**: AI-powered adaptive surveys with real-time progress tracking
- **Customizable Themes**: Brand your surveys with custom colors, fonts, and styling
- **Advanced Triggers**: Scroll percentage, element click, time delay, exit intent
- **Loading & Error States**: Skeleton loading, error UI with retry
- **Accessibility**: Focus trapping, reduced motion support, ARIA roles
- **Lightweight**: < 30KB gzipped, zero dependencies
- **TypeScript Support**: Full type definitions included
- **Mobile Responsive**: Works great on all devices

## Installation

### NPM / Yarn

```bash
npm install @revuloop/embed
# or
yarn add @revuloop/embed
```

### CDN

```html
<script src="https://cdn.revuloop.com/embed/v2/revuloop.min.js"></script>
```

## Quick Start

### Script Tag (CDN)

```html
<!-- Simple embed with survey ID -->
<script src="https://cdn.revuloop.com/embed/v2/revuloop.min.js?id=YOUR_SURVEY_ID"></script>

<!-- With customization -->
<script
	src="https://cdn.revuloop.com/embed/v2/revuloop.min.js"
	data-survey-id="YOUR_SURVEY_ID"
	data-type="slider"
	data-position="bottom-right"
	data-button-text="Give Feedback"
	data-button-color="#6366f1"
></script>
```

### JavaScript API

```javascript
// Basic initialization
Revuloop.init({
	surveyId: "YOUR_SURVEY_ID",
	type: "slider",
	position: "bottom-right",
	onComplete: (data) => {
		console.log("Survey completed:", data.responseId);
	},
});

// Or create specific widget types
Revuloop.createSlider("YOUR_SURVEY_ID", {
	buttonText: "Feedback",
	buttonColor: "#007bff",
});

Revuloop.createPopup("YOUR_SURVEY_ID", {
	buttonText: "Take Survey",
});
```

### Inline Mode (Checkout, Product Pages)

Embed a survey directly inside any container element — ideal for checkout flows, product pages, and in-app experiences:

```html
<div id="survey-container"></div>

<script>
	Revuloop.createInline("YOUR_SURVEY_ID", "survey-container", {
		token: "rlet_abc123...",
		theme: {
			primaryColor: "#007bff",
		},
		onComplete: (data) => {
			console.log("Survey completed:", data.responseId);
			// Proceed with checkout...
		},
		onLoadError: (error) => {
			console.error("Failed to load survey:", error.message);
		},
	});
</script>
```

The inline widget features:

- **Auto-height**: Adjusts to content automatically (no scrollbars)
- **Skeleton loading**: Shows shimmer animation while loading
- **Error handling**: Displays error UI with retry button on timeout
- **No button**: Survey renders directly in the container

### Token-Based Authentication (Recommended)

For production use, embed tokens provide secure, domain-restricted access:

```javascript
// Generate a token from your server using the Revuloop API
// POST /api/v1/embed/token

Revuloop.initWithToken({
	token: "rlet_abc123...",
	surveyId: "YOUR_SURVEY_ID",
	type: "slider",
	theme: {
		primaryColor: "#007bff",
		backgroundColor: "#ffffff",
	},
	onComplete: (data) => {
		console.log("Completed:", data);
	},
});
```

### With Token Refresh (Long-Running Surveys)

For surveys that may take longer than the token lifetime:

```javascript
Revuloop.initWithToken({
	token: "rlet_abc123...",
	refreshToken: "rlrt_xyz789...",
	tokenExpiresAt: "2024-01-01T12:00:00Z",
	surveyId: "YOUR_SURVEY_ID",
	onTokenRefreshed: (newToken) => {
		console.log("Token refreshed");
	},
});
```

### ES Modules

```typescript
import Revuloop, { WidgetConfig, SurveyCompleteData } from "@revuloop/embed";

const config: WidgetConfig = {
	surveyId: "YOUR_SURVEY_ID",
	type: "slider",
	theme: {
		primaryColor: "#6366f1",
	},
	onComplete: (data: SurveyCompleteData) => {
		console.log("Survey completed:", data);
	},
};

const widget = Revuloop.init(config);

// Later: control the widget
widget?.open();
widget?.close();
widget?.destroy();
```

## Configuration Options

### WidgetConfig

| Option           | Type                                                           | Default                  | Description                       |
| ---------------- | -------------------------------------------------------------- | ------------------------ | --------------------------------- |
| `surveyId`       | `string`                                                       | **required**             | The survey ID to embed            |
| `token`          | `string`                                                       | -                        | Embed token (rlet\_...)           |
| `type`           | `'popup' \| 'slider' \| 'inline'`                              | `'slider'`               | Widget display mode               |
| `position`       | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'`         | Button/widget position            |
| `buttonText`     | `string`                                                       | `'Feedback'`             | Trigger button text               |
| `buttonColor`    | `string`                                                       | `'#6366f1'`              | Trigger button background color   |
| `autoShow`       | `boolean`                                                      | `false`                  | Auto-open widget after delay      |
| `autoShowDelay`  | `number`                                                       | `3000`                   | Delay before auto-show (ms)       |
| `showOnce`       | `boolean`                                                      | `true`                   | Only show once per session        |
| `exitIntent`     | `boolean`                                                      | `false`                  | Show on exit intent (mouse leave) |
| `scrollTrigger`  | `number`                                                       | -                        | Open at scroll % (0-100)          |
| `elementTrigger` | `string`                                                       | -                        | CSS selector — click to open      |
| `timeTrigger`    | `number`                                                       | -                        | Open after delay (ms)             |
| `theme`          | `WidgetTheme`                                                  | -                        | Theme customization               |
| `displayOptions` | `WidgetDisplayOptions`                                         | -                        | Display options                   |
| `containerId`    | `string`                                                       | -                        | Container ID for inline mode      |
| `domain`         | `string`                                                       | `'https://revuloop.com'` | API domain (for self-hosted)      |

### WidgetTheme

| Option            | Type     | Default               | Description             |
| ----------------- | -------- | --------------------- | ----------------------- |
| `primaryColor`    | `string` | `'#6366f1'`           | Primary brand color     |
| `backgroundColor` | `string` | `'#ffffff'`           | Survey background color |
| `fontFamily`      | `string` | `'Inter, sans-serif'` | Font family             |
| `borderRadius`    | `number` | `8`                   | Border radius in pixels |

### WidgetDisplayOptions

| Option         | Type      | Default | Description             |
| -------------- | --------- | ------- | ----------------------- |
| `showProgress` | `boolean` | `true`  | Show progress indicator |
| `showBranding` | `boolean` | `true`  | Show Revuloop branding  |
| `autoFocus`    | `boolean` | `false` | Auto-focus first input  |

## Callbacks

```javascript
Revuloop.init({
	surveyId: "YOUR_SURVEY_ID",

	// Called when widget loads
	onLoad: () => {
		console.log("Widget loaded");
	},

	// Called when widget opens
	onOpen: () => {
		console.log("Widget opened");
	},

	// Called when widget closes
	onClose: () => {
		console.log("Widget closed");
	},

	// Called when survey is completed
	onComplete: (data) => {
		console.log("Survey completed:", data.surveyId, data.responseId);
	},

	// Called on error
	onError: (error) => {
		console.error("Error:", error.code, error.message);
	},

	// Called when iframe fails to load (timeout)
	onLoadError: (error) => {
		console.error("Load failed:", error.code);
	},

	// Smart Survey callbacks (v2.1)
	onSmartSurveyInit: (state) => {
		console.log("Smart survey initialized:", state.sessionId);
	},

	onSmartQuestionChange: (data) => {
		console.log(`Question ${data.questionNumber}/${data.totalQuestions}`);
	},

	onSmartGenerating: (isGenerating) => {
		console.log("AI generating:", isGenerating);
	},
});
```

## API Methods

### Revuloop

| Method                                      | Description                      |
| ------------------------------------------- | -------------------------------- |
| `init(config)`                              | Initialize a widget              |
| `initWithToken(options)`                    | Initialize with embed token      |
| `createPopup(surveyId, opts)`               | Create popup widget              |
| `createSlider(surveyId, opts)`              | Create slider widget             |
| `createInline(surveyId, containerId, opts)` | Create inline widget             |
| `getWidget(surveyId)`                       | Get widget instance by survey ID |
| `destroyWidget(surveyId)`                   | Destroy a specific widget        |
| `destroyAll()`                              | Destroy all widgets              |
| `getVersion()`                              | Get SDK version                  |

### WidgetController

| Method                  | Description                            |
| ----------------------- | -------------------------------------- |
| `open()`                | Open the widget                        |
| `close()`               | Close the widget                       |
| `dismiss()`             | Dismiss permanently (won't show again) |
| `destroy()`             | Destroy and cleanup                    |
| `getState()`            | Get current state                      |
| `getConfig()`           | Get configuration                      |
| `isSmartSurvey()`       | Check if smart survey                  |
| `getSmartSurveyState()` | Get smart survey state                 |

## Smart Surveys

Smart surveys use AI to generate follow-up questions based on responses:

```javascript
Revuloop.initWithToken({
	token: "rlet_...",
	surveyId: "SMART_SURVEY_ID",

	onSmartSurveyInit: (state) => {
		// state.sessionId - unique session ID
		// state.maxQuestions - maximum questions
		// state.status - 'active', 'generating', 'completed'
	},

	onSmartQuestionChange: (data) => {
		// data.questionNumber - current question
		// data.totalQuestions - total expected
		// data.isGenerating - AI is generating next question
	},

	onSmartGenerating: (isGenerating) => {
		// Show/hide loading indicator
	},
});
```

## Browser Support

- Chrome 60+
- Firefox 55+
- Safari 11+
- Edge 79+
- Mobile browsers (iOS Safari, Chrome for Android)

## TypeScript

Full TypeScript support with exported types:

```typescript
import type {
	WidgetConfig,
	WidgetTheme,
	WidgetDisplayOptions,
	SurveyCompleteData,
	WidgetError,
	SmartSurveyState,
	SmartSurveyProgressData,
} from "@revuloop/embed";
```

## License

MIT

## Links

- [Documentation](https://revuloop.com/developers/docs/embed-sdk)
- [API Reference](https://revuloop.com/developers/api-reference)
- [GitHub Issues](https://github.com/revuloop/revuloop/issues)