# Welcome

StorySDK is a tool to integrate stories and onboardings to your iOS, Android or Web app. This is open-source SDK with no upfront costs.

<figure><img src="/files/XmUuxsC911I0iG2PDbzS" alt=""><figcaption></figcaption></figure>

### Easy to integrate

1. Create a Story in powerful editor. Add photos, videos, interactive elements, and GIFs
2. Just install StorySDK package for the required platform and integrate it with the SDK token.
3. Publish Your Stories
4. Enjoy the aesthetics and share your amazing products with new customers
5. Analyze results. Gather engagement metrics at a granular level on the analytics dashboard

## Get started

* [ ] Register in [StorySDK](https://storysdk.com/ru), add your app and get SDK token
* [ ] Integrate SDK in your app
* [ ] Add group&#x20;
* [ ] Create stories in editor
* [ ] Publish stories and setup group display settings

Now our SDK available for [iOS](/sdk-integrations/ios-sdk) and [Web](https://docs.storysdk.com/getting-started/sdk-integration/web). Coming soon Android and React Native.


# How to get SDK token

To get SDK token go to app settings page and copy SDK token:

<figure><img src="/files/MxqCJMjfVYlEbzRaMsi8" alt=""><figcaption></figcaption></figure>


# Web SDK

This guide describes how to add and use StorySDK to your site or web app.

## SDK token

To get SDK token go to app settings page and copy SDK token:

<figure><img src="/files/IBAKihgYqotN4tsw8y8i" alt=""><figcaption></figcaption></figure>

### Installation

#### NPM

```bash
npm install @storysdk/core
```

#### Yarn

```bash
yarn add @storysdk/core
```

### Basic Usage

> **Please note:** StorySDK Core is built on React and requires it to be present in your project. React is NOT bundled with the library, including the CDN version.

#### Dependencies

StorySDK will not work without React. It relies on React for rendering components and uses React hooks internally.

```bash
# Install React if it's not already installed in your project
npm install react react-dom

# Recommended versions: React 16.8.0 and above
# Minimum supported React version: 16.8.0 (with hooks support)
```

#### React

To integrate StorySDK in a React application:

```jsx
import { Story } from "@storysdk/core"; 
import "@storysdk/core/dist/bundle.css";
import { useRef, useEffect } from "react";

function StoryComponent() {
  const ref = useRef(null);

  useEffect(() => {
    const story = new Story("<APP_TOKEN_HERE>");

    const element = ref.current;
    story.renderGroups(element);
    
    // Cleanup function
    return () => {
      story.destroy();
    };
  }, []);

  return <div ref={ref} style={{ minHeight: "100px" }}></div>;
}

export default StoryComponent;
```

#### Next.js

When using StorySDK with Next.js, you need to load the component dynamically without server-side rendering:

```jsx
// In your page or component file
import { useRef, useEffect } from 'react';
import dynamic from 'next/dynamic';

// Import CSS statically
import '@storysdk/core/dist/bundle.css';

// Dynamically import the StoryComponent with SSR disabled
const StoryComponent = dynamic(
  () => import('../components/StoryComponent'),
  { ssr: false }
);

function HomePage() {
  return (
    <div>
      <h1>My Next.js App</h1>
      <StoryComponent token="<APP_TOKEN_HERE>" />
    </div>
  );
}

export default HomePage;
```

Then in your component file (`components/StoryComponent.js`):

```jsx
import { useRef, useEffect } from 'react';

function StoryComponent({ token, options = {} }) {
  const ref = useRef(null);

  useEffect(() => {
    // Only import and initialize the Story SDK on the client side
    const { Story } = require('@storysdk/core');
    const story = new Story(token, options);
    
    const element = ref.current;
    if (element) {
      story.renderGroups(element);
    }
    
    return () => {
      story.destroy();
    };
  }, [token, options]);

  return <div ref={ref} style={{ minHeight: "100px" }}></div>;
}

export default StoryComponent;
```

#### JavaScript (ES6)

For vanilla JavaScript applications:

```javascript
// First import React (if using npm/yarn)
import React from 'react';
import ReactDOM from 'react-dom';

import { Story } from "@storysdk/core"; 
import "@storysdk/core/dist/bundle.css";

document.addEventListener("DOMContentLoaded", () => {
  const story = new Story("<APP_TOKEN_HERE>");

  const element = document.querySelector("<SELECTOR_HERE>");
  story.renderGroups(element);
});
```

#### Static HTML

> **Important:** React is NOT included in the CDN bundle. You need to include React and ReactDOM separately before loading StorySDK.

For static HTML pages:

```html
<head>
  <!-- First include React -->
  <script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
  
  <!-- Then include StorySDK -->
  <script src="https://cdn.jsdelivr.net/npm/@storysdk/core@latest/dist/bundle.umd.js"></script>
  <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@storysdk/core@latest/dist/bundle.css">
</head>
<body>
  <div 
    data-storysdk-token="<APP_TOKEN_HERE>" 
    style="min-height: 100px;" 
    id="storysdk"
  ></div>
  
  <script>
    document.addEventListener("DOMContentLoaded", function() {
      // The SDK will automatically initialize using the data-storysdk-token attribute
      // The SDK instance is automatically created and available globally as window.storysdk
      
      // You can access the story instance methods directly:
      console.log(window.storysdk); // Access the Story instance
      
      // Example: Subscribe to events using the global instance
      window.storysdk.on('storyOpen', function(event) {
        console.log('Story opened:', event);
      });
    });
  </script>
</body>
```

### Shopify (Liquid)

StorySDK can be easily integrated into your Shopify store using theme sections. Follow these steps:

1. Add the following code to the `<head>` tag of your Shopify theme:

```html
<!-- First include React -->
<script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>

<!-- Then include StorySDK -->
<script src="https://cdn.jsdelivr.net/npm/@storysdk/core@latest/dist/bundle.umd.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@storysdk/core@latest/dist/bundle.css"/>
```

2. Create a section for StorySDK integration in your theme customizer:

```liquid
{% schema %}
{
  "name": "StorySDK Stories",
  "settings": [
    {
      "type": "text",
      "id": "sdk_token",
      "label": "StorySDK Token",
      "default": "<SDK_TOKEN_HERE>"
    },
    {
      "type": "number",
      "id": "container_height",
      "label": "Container Height (px)",
      "default": 100
    }
  ],
  "presets": [
    {
      "name": "StorySDK Stories",
      "category": "Interactive"
    }
  ]
}
{% endschema %}

<!-- StorySDK container -->
<div
  data-storysdk-token="{{ section.settings.sdk_token }}"
  style="min-height: {{ section.settings.container_height }}px;"
  id="storysdk"
></div>
```

This implementation allows you to:

* Add StorySDK to your Shopify theme through the theme customizer
* Configure your StorySDK token and container height directly from the Shopify admin
* Place the StorySDK container anywhere in your store through the theme editor

### Available Parameters

By specifying the following data attributes in the HTML tag, you can control the appearance, behavior, and functionality of the SDK directly from HTML without additional JavaScript configuration.

| Parameter                                  | Description                                                                                                                                                              |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data-storysdk-group-image-width`          | Specifies the width of the group image in pixels, controlling how large the group's main image will appear.                                                              |
| `data-storysdk-group-image-height`         | Sets the height of the group image, allowing you to control the image's vertical display size.                                                                           |
| `data-storysdk-group-title-size`           | Adjusts the font size of the group title, so you can set the desired emphasis for the group title text.                                                                  |
| `data-storysdk-group-class-name`           | Allows a custom CSS class to be applied to the individual group elements. Use this to apply unique styling to each group.                                                |
| `data-storysdk-groups-class-name`          | Adds a custom CSS class to the container for multiple groups. This is useful for controlling overall styling for the collection of groups.                               |
| `data-storysdk-autoplay`                   | Enables autoplay for stories if set to `"true"`. The stories will play automatically without requiring user interaction.                                                 |
| `data-storysdk-group-id`                   | Identifies a specific group ID to load within the SDK, allowing targeted display of group content when autoplay is enabled.                                              |
| `data-storysdk-start-story-id`             | Sets the initial story to display when the SDK opens, letting you direct users to a specific story.                                                                      |
| `data-storysdk-forbid-close`               | Prevents users from closing the SDK interface if set to `"true"`. Useful for mandatory viewing scenarios.                                                                |
| `data-storysdk-is-show-mockup`             | Displays the story content within a device mockup if set to `"true"`, providing a preview within a simulated device frame.                                               |
| `data-storysdk-is-status-bar-active`       | Activates a status bar at the top of the story if set to `"true"`, helping indicate story progress.                                                                      |
| `data-storysdk-open-in-external-modal`     | Opens stories within an external modal if set to `"true"`. This is useful if you want to display content in a standalone modal overlay.                                  |
| `data-storysdk-groups-outline-color`       | Specifies a custom outline color for groups. This is useful for visually highlighting or differentiating specific groups within the interface.                           |
| `data-storysdk-active-group-outline-color` | Specifies a custom outline color for the active group or for the group when hovered over. This helps emphasize the currently selected or focused group in the interface. |

### Example Implementation

```html
<div
  id="storysdk"
  data-storysdk-token="YOUR_SDK_TOKEN"
  data-storysdk-group-image-width="150"
  data-storysdk-group-image-height="150"
  data-storysdk-group-title-size="18"
  data-storysdk-group-class-name="custom-group"
  data-storysdk-groups-class-name="custom-groups"
  data-storysdk-autoplay="true"
  data-storysdk-group-id="12345"
  data-storysdk-start-story-id="67890"
  data-storysdk-forbid-close="false"
  data-storysdk-is-show-mockup="true"
  data-storysdk-is-status-bar-active="true"
  data-storysdk-open-in-external-modal="false"
  data-storysdk-groups-outline-color="#e9e6e9"
  data-storysdk-active-group-outline-color="#fd19cc">
  <!-- Stories content goes here -->
</div>
```

⚡️ Please note that in order for stories to be displayed in the SDK, they need to be published.

### API Reference

#### `Story` Class

The main class for interacting with the StorySDK.

**Constructor**

```javascript
const story = new Story(token, options);
```

**Parameters:**

* `token` (string, required): Your application token provided by StorySDK
* `options` (object, optional): Configuration options for StorySDK

**Options**

```typescript
{
  // Appearance options
  groupImageWidth?: number;           // Width of group thumbnail images
  groupImageHeight?: number;          // Height of group thumbnail images
  groupTitleSize?: number;            // Font size for group titles
  activeGroupOutlineColor?: string;   // Color of the outline for active group
  groupsOutlineColor?: string;        // Color of the outline for inactive groups
  arrowsColor?: string;               // Color of navigation arrows
  backgroundColor?: string;           // Background color
  
  // Layout options
  storyWidth?: number;                // Width of story viewer (only 360 is supported)
  storyHeight?: number;               // Height of story viewer (only 640 or 780 are supported)
  isShowMockup?: boolean;             // Show device mockup around story
  isShowLabel?: boolean;              // Show labels
  isStatusBarActive?: boolean;        // Show status bar
  
  // Behavior options
  autoplay?: boolean;                 // Automatically play stories
  forbidClose?: boolean;              // Prevent user from closing the story
  openInExternalModal?: boolean;      // Open stories in a modal
  
  // Selection options
  groupId?: string;                   // Initial group ID to display
  startStoryId?: string;              // Initial story ID to display
  
  // CSS classes
  groupClassName?: string;            // Custom CSS class for individual groups
  groupsClassName?: string;           // Custom CSS class for the groups container
  
  // Development options
  isDebugMode?: boolean;              // Enable debug mode
}
```

**Methods**

**`renderGroups(container)`**

Renders story groups in the specified element.

**Parameters:**

* `container` (HTMLElement, optional): The DOM element to render stories in. If not provided, the container specified during initialization will be used.

**Returns:** void

**`destroy()`**

Cleans up resources used by the Story instance, unmounting React components.

**Returns:** void

**`on<T = any>(eventName, listener)`**

Subscribes to a story event.

**Parameters:**

* `eventName` (StoryEventTypes): Name of the event to subscribe to
* `listener` (function): Callback function to execute when the event occurs

**Returns:** Function to unsubscribe from the event

**`off<T = any>(eventName, listener)`**

Removes a specific event listener.

**Parameters:**

* `eventName` (StoryEventTypes): Name of the event
* `listener` (function): The listener function to remove

**Returns:** void

**`once<T = any>(eventName, listener)`**

Subscribes to an event for one time only. The listener automatically unsubscribes after being called once.

**Parameters:**

* `eventName` (StoryEventTypes): Name of the event to subscribe to
* `listener` (function): Callback function to execute when the event occurs

**Returns:** Function to unsubscribe from the event

### Event Handling

StorySDK uses a TypeScript-based event system for handling interactions with stories. You can subscribe to these events using the `on` method:

```typescript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Subscribe to widget click events
story.on(StoryEventTypes.WIDGET_CLICK, (event) => {
  console.log("Widget clicked:", event);
});

// Subscribe to story open events - using once for one-time handling
story.once(StoryEventTypes.STORY_OPEN, (event) => {
  console.log("Story opened (will only log once):", event);
});

// You can also store the unsubscribe function
const unsubscribe = story.on(StoryEventTypes.STORY_NEXT, (event) => {
  console.log("Next story:", event);
});

// Later, you can unsubscribe
unsubscribe();

// Alternatively, use the off method directly
const onPrevHandler = (event) => {
  console.log("Previous story:", event);
};
story.on(StoryEventTypes.STORY_PREV, onPrevHandler);
// Later, remove the handler
story.off(StoryEventTypes.STORY_PREV, onPrevHandler);
```

#### Available Events

StorySDK provides the following event types:

```typescript
enum StoryEventTypes {
  GROUP_CLOSE = 'groupClose',
  GROUP_OPEN = 'groupOpen',
  STORY_CLOSE = 'storyClose',
  STORY_OPEN = 'storyOpen',
  STORY_NEXT = 'storyNext',
  STORY_PREV = 'storyPrev',
  WINDGET_ANSWER = 'widgetAnswer',
  WIDGET_CLICK = 'widgetClick'
}
```

* `groupClose`: When a story group is closed (provides group ID, user ID, viewing duration in seconds, and language)
* `groupOpen`: When a story group is opened (provides user ID, group ID, start time, and language)
* `storyClose`: When a story is closed (provides group ID, story ID, user ID, viewing duration, and language)
* `storyOpen`: When a specific story is opened (provides group ID, story ID, user ID, and language)
* `storyNext`: When navigating to the next story (provides group ID, story ID, user ID, and language)
* `storyPrev`: When navigating to the previous story (provides group ID, story ID, user ID, and language)
* `widgetAnswer`: When a user responds to an interactive widget (polls, quizzes, etc.)
* `widgetClick`: When a widget within a story is clicked (buttons, links, swipe up actions)

#### Widget Click Event

The `widgetClick` event is fired when a user interacts with clickable elements in a story. The event provides detailed information about the interaction through its payload.

**Event Structure**

```typescript
interface WidgetClickEvent {
  detail: {
    widget: 'button' | 'link' | 'swipe_up';  // Type of widget that was clicked
    actionType?: string;                     // Present for button widgets, indicates the action type
    userId: string;                          // Unique user identifier
    storyId: string;                         // ID of the story containing the widget
    widgetId: string;                        // ID of the clicked widget
    data: {
      url?: string;                          // URL to navigate to (if applicable)
      storyId?: string;                      // Target story ID (for navigation between stories)
      customFields?: Record<string, any>;    // Additional custom data (for buttons only)
    }
  }
}
```

**Example Usage**

```javascript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Listen for widget click events
story.on(StoryEventTypes.WIDGET_CLICK, (event) => {
  console.log("Widget type:", event.detail.widget);
  
  // Handle different widget types
  switch(event.detail.widget) {
    case 'button':
      console.log("Button clicked:", event.detail.widgetId);
      console.log("Action type:", event.detail.actionType);
      console.log("Custom fields:", event.detail.data.customFields);
      break;
    
    case 'link':
      console.log("Link clicked:", event.detail.widgetId);
      console.log("URL:", event.detail.data.url);
      break;
      
    case 'swipe_up':
      console.log("Swipe up action triggered");
      console.log("URL:", event.detail.data.url);
      break;
  }
  
  // You can also track these events in your analytics system
  trackWidgetInteraction(event.detail);
});
```

**Implementation Notes**

* Button widgets include an `actionType` field and may contain `customFields` for additional context
* Link widgets provide the target URL in the `data.url` field
* Swipe up actions are similar to links but represent a different user interaction pattern
* All widget events include user, story, and widget identifiers for comprehensive tracking

#### Widget Answer Event

The `widgetAnswer` event is fired when a user responds to an interactive widget. This event provides data about the user's response.

**Supported Widget Types**

The `widgetAnswer` event is available for the following widget types:

```typescript
enum WidgetTypes {
  SLIDER = 'slider',
  QUESTION = 'question',
  TALK_ABOUT = 'talk_about',
  EMOJI_REACTION = 'emoji_reaction',
  CHOOSE_ANSWER = 'choose_answer',
  QUIZ_ONE_ANSWER = 'quiz_one_answer',
  QUIZ_MULTIPLE_ANSWERS = 'quiz_multiple_answers',
  QUIZ_OPEN_ANSWER = 'quiz_open_answer',
  QUIZ_MULTIPLE_ANSWER_WITH_IMAGE = 'quiz_one_multiple_with_image',
  QUIZ_RATE = 'quiz_rate'
}
```

**Event Structure**

```typescript
interface WidgetAnswerEvent {
  detail: {
    widget: WidgetTypes;           // Type of interactive widget from the enum above
    userId: string;                // Unique user identifier
    storyId: string;               // ID of the story containing the widget
    widgetId: string;              // ID of the widget that received the answer
    data: {
      answer: any;                 // The user's response/selection
    }
  }
}
```

**Example Usage**

```javascript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Listen for widget answer events
story.on(StoryEventTypes.WINDGET_ANSWER, (event) => {
  console.log("Widget type:", event.detail.widget);
  console.log("User's answer:", event.detail.data.answer);
  
  // You can handle different widget types
  switch(event.detail.widget) {
    case 'slider':
      console.log("Slider value selected:", event.detail.data.answer);
      break;
      
    case 'quiz_one_answer':
      console.log("Quiz answer submitted:", event.detail.data.answer);
      // Check if answer is correct and provide feedback
      break;
      
    case 'emoji_reaction':
      console.log("Emoji reaction:", event.detail.data.answer);
      break;
      
    // Handle other interactive widget types
  }
  
  // Store user response for analytics or personalization
  saveUserResponse(event.detail.userId, event.detail.widgetId, event.detail.data.answer);
});
```

**Implementation Notes**

* The `widget` field identifies the specific type of interactive element from the `WidgetTypes` enum
* The `answer` field can contain various data types depending on the widget (string, number, object, array)
* This event is useful for:
  * Collecting user feedback
  * Building personalization features
  * Creating dynamic, interactive story experiences
  * Analyzing user engagement with interactive elements

#### Group Open Event

The `groupOpen` event is fired when a user opens a story group. This event provides information about which group was opened and by whom.

**Event Structure**

```typescript
interface GroupOpenEvent {
  detail: {
    uniqUserId: string;          // Unique identifier for the user
    groupId: string;             // ID of the story group that was opened
    startTime: number;           // Timestamp when the group was opened
    language: string;            // Language setting for the content
  }
}
```

**Example Usage**

```javascript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Listen for group open events
story.on(StoryEventTypes.GROUP_OPEN, (event) => {
  console.log("Group opened:", event.detail.groupId);
  console.log("User:", event.detail.uniqUserId);
  console.log("Time:", new Date(event.detail.startTime).toLocaleString());
  console.log("Language:", event.detail.language);
  
  // You can use this event to:
  
  // 1. Track user engagement
  trackGroupView(event.detail.uniqUserId, event.detail.groupId);
  
  // 2. Calculate viewing session duration (when combined with GROUP_CLOSE)
  startViewingSession(event.detail.groupId, event.detail.startTime);
  
  // 3. Adapt content based on language
  if (event.detail.language !== userPreferredLanguage) {
    // Suggest language change or record language preference
  }
});
```

**Implementation Notes**

* The `startTime` is provided as a numeric timestamp which can be converted to a Date object
* The `language` field can be used for analytics or to ensure proper localization
* This event is typically paired with `groupClose` to track complete interaction sessions
* This event is useful for:
  * Monitoring which story groups are most popular
  * Analyzing user behavior patterns
  * Building recommendation engines based on user preferences

#### Group Close Event

The `groupClose` event is fired when a user closes a story group. This event provides information about which group was closed and how long the user interacted with it.

**Event Structure**

```typescript
interface GroupCloseEvent {
  detail: {
    groupId: string;             // ID of the story group that was closed
    uniqUserId: string;          // Unique identifier for the user
    duration: number;            // Duration in seconds that the group was viewed
    language: string;            // Language setting for the content
  }
}
```

**Example Usage**

```javascript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Listen for group close events
story.on(StoryEventTypes.GROUP_CLOSE, (event) => {
  console.log("Group closed:", event.detail.groupId);
  console.log("User:", event.detail.uniqUserId);
  console.log("Viewing duration (seconds):", event.detail.duration);
  console.log("Language:", event.detail.language);
  
  // You can use this event to:
  
  // 1. Track engagement metrics
  updateEngagementMetrics(
    event.detail.groupId, 
    event.detail.uniqUserId, 
    event.detail.duration
  );
  
  // 2. Identify popular content
  if (event.detail.duration > 30) {
    markAsHighEngagement(event.detail.groupId);
  }
  
  // 3. Complete user session tracking (when combined with GROUP_OPEN)
  completeViewingSession(
    event.detail.groupId, 
    event.detail.uniqUserId, 
    event.detail.duration
  );
});
```

**Implementation Notes**

* The `duration` is provided in seconds, useful for calculating engagement metrics
* This event complements the `groupOpen` event for complete session analysis
* Comparing duration across different groups can help identify the most engaging content
* This event is useful for:
  * Measuring content effectiveness
  * Identifying drop-off points in user flows
  * Optimizing story sequences based on engagement patterns
  * Building analytics dashboards for content performance

#### Story Open Event

The `storyOpen` event is fired when a user opens an individual story within a group. This event provides information about which specific story was opened.

**Event Structure**

```typescript
interface StoryOpenEvent {
  detail: {
    groupId: string;             // ID of the parent story group
    storyId: string;             // ID of the specific story that was opened
    uniqUserId: string;          // Unique identifier for the user
    language: string;            // Language setting for the content
  }
}
```

**Example Usage**

```javascript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Listen for story open events
story.on(StoryEventTypes.STORY_OPEN, (event) => {
  console.log("Story opened:", event.detail.storyId);
  console.log("In group:", event.detail.groupId);
  console.log("User:", event.detail.uniqUserId);
  console.log("Language:", event.detail.language);
  
  // You can use this event to:
  
  // 1. Track individual story views
  trackStoryView(
    event.detail.storyId, 
    event.detail.groupId, 
    event.detail.uniqUserId
  );
  
  // 2. Record story sequence progression
  updateUserProgress(
    event.detail.uniqUserId,
    event.detail.groupId,
    event.detail.storyId
  );
  
  // 3. Trigger external integrations based on specific story views
  if (isKeyStory(event.detail.storyId)) {
    triggerExternalEvent(event.detail.storyId, event.detail.uniqUserId);
  }
});
```

**Implementation Notes**

* This event is fired at the individual story level, as opposed to the group level
* It contains both the story ID and its parent group ID for hierarchical tracking
* A single user session will typically trigger multiple story open events as the user progresses
* This event is useful for:
  * Analyzing navigation patterns within story groups
  * Building progression funnels to identify drop-off points
  * Tracking which individual stories drive user engagement
  * Creating personalized experiences based on story viewing history

#### Story Close Event

The `storyClose` event is fired when a user finishes viewing an individual story. This event provides information about which story was viewed and for how long.

**Event Structure**

```typescript
interface StoryCloseEvent {
  detail: {
    groupId: string;             // ID of the parent story group
    storyId: string;             // ID of the story that was closed
    uniqUserId: string;          // Unique identifier for the user
    duration: number;            // Duration in seconds that the story was viewed
    language: string;            // Language setting for the content
  }
}
```

**Example Usage**

```javascript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Listen for story close events
story.on(StoryEventTypes.STORY_CLOSE, (event) => {
  console.log("Story closed:", event.detail.storyId);
  console.log("In group:", event.detail.groupId);
  console.log("User:", event.detail.uniqUserId);
  console.log("Viewing duration (seconds):", event.detail.duration);
  console.log("Language:", event.detail.language);
  
  // You can use this event to:
  
  // 1. Track individual story engagement
  trackStoryEngagement(
    event.detail.storyId,
    event.detail.duration,
    event.detail.uniqUserId
  );
  
  // 2. Identify stories with high completion rates
  if (event.detail.duration >= getExpectedDuration(event.detail.storyId)) {
    markAsFullyViewed(event.detail.storyId, event.detail.uniqUserId);
  }
  
  // 3. Complete story view tracking (when combined with STORY_OPEN)
  completeStoryViewSession(
    event.detail.storyId, 
    event.detail.uniqUserId, 
    event.detail.duration
  );
});
```

**Implementation Notes**

* The `duration` field indicates how long the user viewed the story in seconds
* This event complements the `storyOpen` event for complete story viewing analysis
* Short durations may indicate skipped or unengaging content
* This event is useful for:
  * Determining which stories hold user attention the longest
  * Calculating completion rates for individual stories
  * Refining content based on viewing patterns
  * Building detailed analytics for story-level engagement

#### Story Next Event

The `storyNext` event is fired when a user navigates to the next story in a sequence. This event helps track user navigation patterns.

**Event Structure**

```typescript
interface StoryNextEvent {
  detail: {
    groupId: string;             // ID of the parent story group
    storyId: string;             // ID of the story being navigated to
    uniqUserId: string;          // Unique identifier for the user
    language: string;            // Language setting for the content
  }
}
```

**Example Usage**

```javascript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Listen for story next navigation events
story.on(StoryEventTypes.STORY_NEXT, (event) => {
  console.log("Navigated to next story:", event.detail.storyId);
  console.log("In group:", event.detail.groupId);
  console.log("User:", event.detail.uniqUserId);
  console.log("Language:", event.detail.language);
  
  // You can use this event to:
  
  // 1. Track forward navigation patterns
  trackForwardNavigation(
    event.detail.groupId,
    event.detail.storyId,
    event.detail.uniqUserId
  );
  
  // 2. Analyze user flow through stories
  updateUserFlowAnalytics(
    event.detail.uniqUserId,
    'next',
    event.detail.storyId
  );
  
  // 3. Log sequential story viewing behavior
  logSequentialProgress(event.detail.uniqUserId, event.detail.storyId);
});
```

#### Story Previous Event

The `storyPrev` event is fired when a user navigates to the previous story in a sequence. This event helps identify when users revisit content.

**Event Structure**

```typescript
interface StoryPrevEvent {
  detail: {
    groupId: string;             // ID of the parent story group
    storyId: string;             // ID of the story being navigated to
    uniqUserId: string;          // Unique identifier for the user
    language: string;            // Language setting for the content
  }
}
```

**Example Usage**

```javascript
import { Story, StoryEventTypes } from "@storysdk/core";

const story = new Story("<APP_TOKEN_HERE>");

// Listen for story previous navigation events
story.on(StoryEventTypes.STORY_PREV, (event) => {
  console.log("Navigated to previous story:", event.detail.storyId);
  console.log("In group:", event.detail.groupId);
  console.log("User:", event.detail.uniqUserId);
  console.log("Language:", event.detail.language);
  
  // You can use this event to:
  
  // 1. Track backward navigation patterns
  trackBackwardNavigation(
    event.detail.groupId,
    event.detail.storyId,
    event.detail.uniqUserId
  );
  
  // 2. Identify potentially confusing content
  if (isHighBackwardNavigationRate(event.detail.storyId)) {
    flagForContentReview(event.detail.storyId);
  }
  
  // 3. Analyze user review behavior
  updateUserFlowAnalytics(
    event.detail.uniqUserId,
    'previous',
    event.detail.storyId
  );
});
```

**Implementation Notes for Navigation Events**

* Both `storyNext` and `storyPrev` events have identical structures but represent different navigation actions
* The `storyId` in these events refers to the story being navigated TO (not from)
* High rates of backward navigation may indicate confusing content or users reviewing important information
* These events are useful for:
  * Creating flow diagrams of user navigation patterns
  * Identifying content that users frequently revisit
  * Optimizing story sequences based on navigation behavior
  * Understanding how users interact with story sequences

### Styling & Customization

#### HTML Data Attributes

You can configure StorySDK using HTML data attributes in static HTML implementations:

```html
<div 
  data-storysdk-token="<APP_TOKEN_HERE>"
  data-storysdk-group-image-width="60"
  data-storysdk-group-image-height="60" 
  data-storysdk-group-title-size="12"
  data-storysdk-active-group-outline-color="#FF5500"
  data-storysdk-groups-outline-color="#CCCCCC"
  data-storysdk-group-class-name="custom-group"
  data-storysdk-groups-class-name="custom-groups"
  data-storysdk-autoplay="true"
  data-storysdk-arrows-color="#000000"
  data-storysdk-background-color="#FFFFFF"
></div>
```

#### Custom CSS Classes

Apply custom styling using the provided class name options:

```javascript
const story = new Story("<APP_TOKEN_HERE>", {
  groupClassName: "my-custom-group",
  groupsClassName: "my-custom-groups-container"
});
```

Then in your CSS:

```css
.my-custom-group {
  margin: 0 5px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.my-custom-groups-container {
  padding: 10px;
  background-color: #f9f9f9;
}
```

### Troubleshooting

#### Debug Mode

Enable debug mode to see detailed logging of API requests and responses:

```javascript
const story = new Story("<APP_TOKEN_HERE>", {
  isDebugMode: true
});
```

With debug mode enabled:

* API requests and responses will be logged to the console
* If a `#storysdk-debug` element exists in your DOM, debug information will be appended there

#### Common Issues

1. **Stories not appearing**
   * Verify your app token is correct
   * Ensure the target element has sufficient height (min-height: 100px recommended)
   * Check browser console for errors
   * Make sure you've imported the CSS: `import "@storysdk/core/dist/bundle.css";`
2. **Initialization issues**
   * When using the static HTML approach, make sure the `data-storysdk-token` attribute is correctly set
   * If manually initializing, ensure the container element exists in the DOM before calling `renderGroups()`
3. **Cleanup issues**
   * Always call `destroy()` when unmounting your component to prevent memory leaks

#### Browser Support

StorySDK supports all modern browsers:

* Chrome (latest versions)
* Firefox (latest versions)
* Safari (latest versions)
* Edge (latest versions)


# React Native

React Native components for StorySDK using WebView to display stories.

## SDK token

To get SDK token go to app settings page and copy SDK token:

<figure><img src="/files/IBAKihgYqotN4tsw8y8i" alt=""><figcaption></figcaption></figure>

### **Installation**

**NPM**

```bash
npm install @storysdk/react-native react-native-webview
```

**Yarn**

```bash
yarn add @storysdk/react-native react-native-webview
```

### Usage

#### StoryGroups

Component for displaying a list of story groups:

```tsx
import { StoryGroups } from '@storysdk/react-native';

// In your component
<StoryGroups
  token="YOUR_TOKEN"
  onGroupClick={(groupId) => {
    // Handle group click
    setSelectedGroupId(groupId);
  }}
  groupImageWidth={80}
  groupImageHeight={80}
  groupTitleSize={14}
/>
```

#### StoryModal

Component for displaying stories in a modal window:

```tsx
import { StoryModal } from '@storysdk/react-native';

// In your component
<StoryModal
  token="YOUR_TOKEN"
  groupId={selectedGroupId}
  onClose={() => setSelectedGroupId(null)}
  storyWidth={300}
  storyHeight={600}
/>
```

### Onboarding Implementation

For onboarding flows, use the `StoryModal` component with an Onboarding ID instead of a regular groupId:

```tsx
import { StoryModal } from '@storysdk/react-native';

// For onboarding implementation
<StoryModal
  token="YOUR_TOKEN"
  groupId="ONBOARDING_ID" // Use your Onboarding ID here
  onClose={() => setOnboardingComplete(true)}
/>
```

**Important**: When an Onboarding ID is specified, the modal window will open automatically. Only use the `StoryModal` component for onboarding implementations.

### Props

#### StoryGroups

* `token` (required) - SDK token for accessing StorySDK
* `onGroupClick` - Handler for group click events
* `groupImageWidth` - Width of group image in pixels
* `groupImageHeight` - Height of group image in pixels
* `groupTitleSize` - Font size of group title in pixels
* `groupClassName` - CSS class for styling individual group
* `groupsClassName` - CSS class for styling groups container
* `activeGroupOutlineColor` - Outline color for active group
* `groupsOutlineColor` - Outline color for all groups
* `arrowsColor` - Color of navigation arrows
* `backgroundColor` - Background color of the component
* `onError` - Error handler callback that receives error details
* `onEvent` - Event handler callback that receives event type and associated data

#### StoryModal

* `token` (required) - SDK token for accessing StorySDK
* `groupId` - Group ID to display (or Onboarding ID for onboarding flows)
* `onClose` - Handler for modal close event
* `storyWidth` - Width of story in pixels
* `storyHeight` - Height of story in pixels
* `isShowMockup` - Whether to show device mockup around stories
* `isShowLabel` - Whether to show labels
* `isStatusBarActive` - Whether status bar is active
* `autoplay` - Automatically play through stories
* `arrowsColor` - Color of navigation arrows
* `backgroundColor` - Background color of the component
* `forbidClose` - Prevent modal from being closed (useful for critical onboarding flows)
* `onError` - Error handler callback that receives error details
* `onEvent` - Event handler callback that receives event type and associated data

### SDK Events

`StoryGroups` and `StoryModal` components can handle the following events through the `onEvent` prop:

* `groupClose` - Group of stories closed
* `groupOpen` - Group of stories opened
* `storyClose` - Story closed
* `storyOpen` - Story opened
* `storyNext` - Navigation to next story
* `storyPrev` - Navigation to previous story
* `widgetAnswer` - User response to a widget
* `widgetClick` - Widget click
* `storyModalOpen` - Modal window opened
* `storyModalClose` - Modal window closed
* `groupClick` - Story group clicked

#### onEvent Usage Example

```tsx
import React, { useState } from 'react';
import { View } from 'react-native';
import { StoryGroups, StoryModal } from '@storysdk/react-native';

const App = () => {
  const [selectedGroupId, setSelectedGroupId] = useState(null);

  const handleEvent = (eventType, eventData) => {
    console.log(`Event: ${eventType}`, eventData);
    
    // Example of handling a specific event
    if (eventType === 'widgetClick') {
      console.log('User clicked on a widget:', eventData);
    }
  };

  return (
    <View style={{ flex: 1 }}>
      <StoryGroups
        token="YOUR_TOKEN"
        onGroupClick={setSelectedGroupId}
        onEvent={handleEvent}
      />
      <StoryModal
        token="YOUR_TOKEN"
        groupId={selectedGroupId}
        onClose={() => setSelectedGroupId(null)}
        onEvent={handleEvent}
      />
    </View>
  );
};

export default App;
```

### Usage Example

#### Standard Story Implementation

```tsx
import React, { useState } from 'react';
import { View } from 'react-native';
import { StoryGroups, StoryModal } from '@storysdk/react-native';

const App = () => {
  const [selectedGroupId, setSelectedGroupId] = useState(null);

  return (
    <View style={{ flex: 1 }}>
      <StoryGroups
        token="YOUR_TOKEN"
        onGroupClick={setSelectedGroupId}
      />
      <StoryModal
        token="YOUR_TOKEN"
        groupId={selectedGroupId}
        onClose={() => setSelectedGroupId(null)}
      />
    </View>
  );
};

export default App;
```

#### Onboarding Implementation

```tsx
import React, { useState, useEffect } from 'react';
import { View } from 'react-native';
import { StoryModal } from '@storysdk/react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

const App = () => {
  const [showOnboarding, setShowOnboarding] = useState(false);
  
  // Check if user has completed onboarding
  useEffect(() => {
    const checkOnboardingStatus = async () => {
      try {
        const onboardingCompleted = await AsyncStorage.getItem('onboardingCompleted');
        if (onboardingCompleted !== 'true') {
          setShowOnboarding(true);
        }
      } catch (error) {
        console.error('Error checking onboarding status:', error);
      }
    };
    
    checkOnboardingStatus();
  }, []);
  
  const handleOnboardingComplete = async () => {
    try {
      await AsyncStorage.setItem('onboardingCompleted', 'true');
      setShowOnboarding(false);
    } catch (error) {
      console.error('Error saving onboarding status:', error);
    }
  };

  return (
    <View style={{ flex: 1 }}>
      {/* Your app content */}
      
      {/* Onboarding modal */}
      <StoryModal
        token="YOUR_TOKEN"
        groupId={showOnboarding ? "ONBOARDING_ID" : null}
        onClose={handleOnboardingComplete}
        forbidClose={false} // Set to true if onboarding must be completed
      />
    </View>
  );
};

export default App;
```

### Media Background Playback Permissions

For proper background media playback (audio/video), you need to configure additional permissions in your project:

#### iOS

Add the following to your iOS project's `Info.plist` file:

```xml
<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>
```

If you encounter the error `ProcessAssertion::acquireSync Failed to acquire RBS assertion 'WebKit Media Playback'`, you may need to add additional entitlements to your project:

1. Create or edit the `.entitlements` file in your iOS project root
2. Add the following entitlements:

```xml
<key>com.apple.runningboard.assertions.webkit</key>
<true/>
<key>com.apple.multitasking.systemappassertions</key>
<true/>
```

3. In Xcode, go to project settings > Signing & Capabilities and add the "Background Modes" capability, then enable the "Audio, AirPlay, and Picture in Picture" option

#### Android

Add the following to your Android project's `AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
```

These permissions are necessary to ensure continuous media playback even when the app is minimized or the screen is locked.


# iOS SDK

This guide describes how to add and use StorySDK to your iOS app.

### Installation

#### Swift Package Manager

To install StorySDK using Swift Package Manager, follow these steps:

1. Open your project in Xcode and go to `File > Swift Packages > Add Package Dependency`.
2. In the search field, enter `https://github.com/StorySDK/ios-sdk.git` and click Next.
3. Select the version rule "Up to Next Major" and enter "1.0.0" in the text field.
4. Click Next and then Finish.

#### CocoaPods

To install StorySDK using CocoaPods, add the following to your Podfile:

```ruby
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '13.0'
use_frameworks!

target 'MyApp' do
  pod 'StorySDK', '~> 1.0'
end
```

Then, run the following command:

```bash
$ pod install
```

**Carthage**

To install StorySDK using Carthage, add the following to your Cartfile:

```
github "StorySDK/ios-sdk" ~> 1.0
```

Then, run the following command:

```bash
$ carthage update
```

### Usage

Make sure to import the project wherever you may use it:

```swift
import StorySDK
```

### Setup Story Witget

<figure><img src="/files/XmUuxsC911I0iG2PDbzS" alt=""><figcaption></figcaption></figure>

To use the SDK, you need to obtain a token from the StorySDK dashboard. You can find your token in the Settings section of the dashboard at <https://app.storysdk.com/dashboard/>.

```swift
var config = SRConfiguration(sdkId: "[YOUR_SDK_ID]")
StorySDK.shared.configuration = config
```

Further we consider that

```swift
storySdk = StorySDK.shared
```

#### Integration

Define Groups Widget in your UIViewController

```swift
private var widget: SRStoryWidget!
```

Get information about the SDK application:

```swift
storySdk.getApps { result in
    switch result {
    case .success(let app):
        print(app)
    case .failure(let error):
        print("Error:", error.localizedDescription)
    }
}
```

Get the groups of the app and then call `widget.load()`:

```swift
storySdk.getGroups { result in
    switch result {
    case .success(let groups):
        print(groups)
        // Convenient place to call widget loading
        widget.load()
    case .failure(let error):
        print("Error:", error.localizedDescription)
    }
}
```

You can use the Groups Widget to display groups of stories in your app. Create and add the widget to your view hierarchy:

```swift
widget = SRStoryWidget()
widget.delegate = self
widget.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(widget)
```

Layout widget:

```swift
NSLayoutConstraint.activate([
    widget.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
    widget.widthAnchor.constraint(equalTo: view.widthAnchor),
    widget.heightAnchor.constraint(equalToConstant: 120.0),
])
```

Conform `SRStoryWidgetDelegate` protocol and implement openStories action:

```swift
func openStories(index: Int, groups: [SRStoryGroup], in vc: UIViewController,
                 delegate: SRStoryWidgetDelegate?, animated: Bool) {
    // SRNavigationController allows you to open specific group of stories
    let controller = SRNavigationController(index: index, groups: groups,
                                            backgroundColor: UIColor.gray)
    vc.present(controller, animated: animated)
}

// You can handle errors and taps on a group by conforming the `SRStoryWidgetDelegate` protocol
extension YourViewController: SRStoryWidgetDelegate {
    func onWidgetErrorReceived(_ error: Error, widget: SRStoryWidget) {}
    func onWidgetGroupPresent(index: Int, groups: [SRStoryGroup], widget: SRStoryWidget) {
        guard groups.count > index else { return }
        
        openStories(index: index, groups: groups, in: self, delegate: self, animated: true)
    }
    
    func onWidgetGroupsLoaded(groups: [SRStoryGroup]) {}
    func onWidgetGroupClose() {}
    func onWidgetMethodCall(_ selectorName: String?) {}
    func onWidgetLoading() {}
    func onWidgetLoaded() {}
}
```

When your app is ready to load groups, call `widget.load()`.

<mark style="background-color:purple;">⚡️ Please note that in order for stories to be displayed in the SDK, they need to be published.</mark>

### Onboarding

<figure><img src="/files/8vAGvdcG73dCIWqocdJh" alt=""><figcaption></figcaption></figure>

To add onboarding to your app, follow these steps:

1. First, create an onboarding in the Onboarding section of the dashboard at <https://app.storysdk.com/dashboard/> and copy your Onboarding ID from Onboarding Settings
2. Then conform `SRStoryWidgetDelegate` protocol and fill some methods such implementation (here we've added `dismiss` func that call paywall screen and `rate` func that request rate the app on second onboarding screen, this is a standard approach, you can implement similar or completely different behavior as you wish)

```swift
import StorySDK
import StoreKit

extension YourViewController: SRStoryWidgetDelegate {
    func onWidgetErrorReceived(_ error: Error, widget: SRStoryWidget) {}
    func onWidgetGroupPresent(index: Int, groups: [SRStoryGroup], widget: SRStoryWidget) {
        guard groups.count > index else { return }
        let group = groups[index]
        
        let controller = SRStoriesViewController(group, asOnboarding: true)
        controller.view.backgroundColor = UIColor.gray
        controller.delegate = self
        
        present(controller, animated: false)
    }
    
    func onWidgetGroupsLoaded(groups: [SRStoryGroup]) {
        widget?.openAsOnboarding(groupId: Constants.onboardingGroup)
    }

    func onWidgetGroupClose() {
        dismiss()
    }

    func onWidgetMethodCall(_ selectorName: String?) {
        guard let selectorName = selectorName else { return }
        
        let sel = NSSelectorFromString(selectorName)
        if canPerformAction(sel, withSender: self) {
            performSelector(onMainThread: sel, with: nil, waitUntilDone: true)
        }
    }

    func onWidgetLoading() {}
    func onWidgetLoaded() {}

    @objc func dismiss() {
        let vc = YourPaywallViewController(paywallID: "YOUR_PAYWALL_ID")
        let pvc = presentedViewController ?? self
        pvc.present(vc, animated: false)
    }
    
    @objc func scrollNext() {
        // NB: this method is called every time you move to the next story in onboarding
        // add a condition so that rate is called only once
        rate()
    }

    private func rate() {
        guard let scene = UIApplication.shared.connectedScenes
            .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene else {
            return
        }
        
        SKStoreReviewController.requestReview(in: scene)
    }
}
```

3. Caching (optional but useful step)

Often, to load stories faster, for example, to build onboarding in an app especially if you have video files in your stories, you may need to get them without waiting for loading from the server. In this case, caching will help you:

Caching is provided using a couple of lanes fastlane and consists of a few simple steps:

Install *fastlane* first (by running the *fastlane* command in the root of the project) and then follow the step above.

After run

```bash
fastlane prepare_story_cache group:"groupId"
```

where groupId is the id of the group that contains the media files you want to cache. If successful, you will see your media files in the "fastlane/cached" directory.

Now it’s enough to add these files to the Xcode project, like resources via *Add Files to...* action so that they end up in the bundle of your app, that’s all - in this form `StorySDK` can already see them. Caching is now complete.

#### Direct API

To show the stories of a selected group using the top view controller:

```swift
storySdk.getStories(group) { [weak self] result in
    switch result {
    case .success(let stories):
        guard !stories.isEmpty else { break } // No active stories
        // Present stories
    case .failure(let error):
         print("Error:", error.localizedDescription)
    }
}
```

**Configuration**

a) Set language

```swift
storySdk.configuration.language = "en"
```

b) Set full screen on / off

```swift
storySdk.configuration.needFullScreen = true / false
```

c) Show title on / off

```swift
storySdk.configuration.needShowTitle = true / false
```

d) Filter (hide) onboarding on / off

```swift
storySdk.configuration.onboardingFilter = true / false
```

e) Set show time duration for each story

```swift
storySdk.configuration.storyDuration = 10 // 10 seconds
```

f) Set progress color

```swift
storySdk.configuration.progressColor = .green
```

**Advanced**

StorySDK has a nice default loader. If you prefer to replace it with another one, that it also possible. Ensure your custom loader confirms `SRLoader` protocol:

```swift
public protocol SRLoadingIndicator: AnyObject {
    func startAnimating()
    func stopAnimating()
}

public protocol SRLoader: SRLoadingIndicator where Self: UIView {}
```

You can just remove the loader if you don't need it:

```swift
storySdk.configuration.loader = nil
```

Or use the your own custom loaders, here are some examples:

<details>

<summary>NVExtentedActivityIndicatorView</summary>

```swift
    import UIKit
    import StorySDK
    import NVActivityIndicatorView

    class NVExtentedActivityIndicatorView: UIView, SRLoader {
        var indicator: NVActivityIndicatorView = NVActivityIndicatorView.ballSpin()
        
        init() {
            super.init(frame: CGRect(x: 0, y: 0, width: 72, height: 72))
            addSubview(indicator)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func startAnimating() {
            indicator.startAnimating()
        }
        func stopAnimating() {
            indicator.stopAnimating()
        }
    }
```

</details>

<details>

<summary>Lottie</summary>

```swift
import UIKit
import StorySDK
import Lottie

class LottieLoadingIndicatorView: UIView, SRLoader {
    var indicator: LottieAnimationView!
    
    init() {
        super.init(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
        
        indicator = LottieAnimationView(name: "equalizer-icon")
        addSubview(indicator)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func startAnimating() {
        indicator.play()
    }
    
    func stopAnimating() {
        indicator.stop()
    }
}
```

</details>

or even

<details>

<summary>Rive</summary>

```swift
import UIKit
import StorySDK
import RiveRuntime

class RiveLoadingIndicatorView: UIView, SRLoader {
    var model = RiveViewModel(fileName: "Screencut_Logo_Loader")
    var indicator: RiveView!
    
    init() {
        super.init(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
        
        indicator = model.createRiveView()
        addSubview(indicator)
        indicator.frame = bounds
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func startAnimating() {
        model.play()
    }
    
    func stopAnimating() {
        model.stop()
    }
}
```

</details>

***

In additional you also can handle custom method defined in dashboard (using `onWidgetMethodCall` from `SRStoryWidgetDelegate` protocol) for instance define action on onboarding close event or request rate your during onboarding. Like this:

```swift
    func onWidgetMethodCall(_ selectorName: String?) {
        guard let selectorName else { return }
        
        switch selectorName {
        case "onboarding-finished":
            setupFinished()
        case "scrollNext":
            if !onRateDisplayed {
                onRateDisplayed = true
                rate()
            }
        default:
            break
        }
    }
```

### License

StorySDK is available under the MIT license. See the LICENSE file for more info.

### Example project

You can see an example of usage [here](https://github.com/StorySDK/story-ios-sdk-example).


# Cordova

This guide describes how to add and use StorySDK to Cordova app

## SDK token

To get SDK token go to app settings page and copy SDK token:

<figure><img src="/files/IBAKihgYqotN4tsw8y8i" alt=""><figcaption></figcaption></figure>

Go to Settings, Setup section.&#x20;

<figure><img src="/files/sDL6ZvDYbVobX4bx5EVT" alt=""><figcaption></figcaption></figure>

## Setup

Choose Custom solution and copy the code. Add the following code to the **`<head>`** section of your HTML page to include the necessary script and styles.

```html
<script src="https://cdn.jsdelivr.net/npm/@storysdk/core@latest/dist/bundle.umd.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@storysdk/core@latest/dist/bundle.css">
```

Add an HTML element to the page and place the SDK token in the **`data-storysdk-token`** attribute.

<pre class="language-html"><code class="lang-html">&#x3C;!DOCTYPE html>
&#x3C;html lang="en">
&#x3C;head>
    &#x3C;meta charset="UTF-8">
    &#x3C;meta name="viewport" content="width=device-width, initial-scale=1.0">
    &#x3C;title>Your Page Title&#x3C;/title>
    &#x3C;script src="https://cdn.jsdelivr.net/npm/@storysdk/core@latest/dist/bundle.umd.js">&#x3C;/script>
<strong>    &#x3C;link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@storysdk/core@latest/dist/bundle.css">
</strong>&#x3C;/head>
&#x3C;body>

    &#x3C;!-- Place your HTML element with SDK token here -->
    &#x3C;div id="storysdk" data-storysdk-token="YOUR_SDK_TOKEN">&#x3C;/div>

    &#x3C;!-- Other content on your page -->

&#x3C;/body>
&#x3C;/html>

</code></pre>

Make sure that the Content-Security-Policy allows requests to <https://api.storysdk.com> and Google servers for analytics tracking. For example, add the following code to the **`<head>`** section:

```html
 <meta 
  http-equiv="Content-Security-Policy" 
  content="
    default-src 'self';
    script-src 'self' https://www.googletagmanager.com;
    connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com https://api.storysdk.com;
  ">
```

Enable Background Modes in Xcode (for iOS)

1. **Open Your Project in Xcode**
   * Navigate to your app in Xcode and select your app target from the project navigator.
2. **Add Background Modes**
   * Go to the **Signing & Capabilities** tab.
3. **Configure Background Modes**
   * Click the "+ Capability" button and select "Background Modes".
   * Check the "Audio, AirPlay, and Picture in Picture" option

<figure><img src="/files/VGlOolTa66CLwJz4rovo" alt=""><figcaption></figcaption></figure>

Run the application build and test the stories in the simulator.

<figure><img src="/files/knrq4FZW21jrpOA6CDf6" alt=""><figcaption></figcaption></figure>

⚡️ For the interface elements to work correctly, you need to add the following parameters to the config.xml file in the Cordova project

```xml
<preference name="AllowInlineMediaPlayback" value="true" />
<platform name="ios">
    <config-file parent="UIBackgroundModes" target="*-Info.plist">
        <array>
            <string>audio</string>
        </array>
    </config-file>
</platform>
```

<mark style="background-color:purple;">⚡️ Please note that in order for stories to be displayed in the SDK, they need to be published.</mark>

## Customisation

By specifying the following data attributes in the HTML tag, you can control the appearance, behavior, and functionality of the SDK directly from HTML without additional JavaScript configuration.

* **`data-storysdk-group-image-width`**\
  Specifies the width of the group image in pixels, controlling how large the group’s main image will appear.
* **`data-storysdk-group-image-height`**\
  Sets the height of the group image, allowing you to control the image’s vertical display size.
* **`data-storysdk-group-title-size`**\
  Adjusts the font size of the group title, so you can set the desired emphasis for the group title text.
* **`data-storysdk-group-class-name`**\
  Allows a custom CSS class to be applied to the individual group elements. Use this to apply unique styling to each group.
* **`data-storysdk-groups-class-name`**\
  Adds a custom CSS class to the container for multiple groups. This is useful for controlling overall styling for the collection of groups.
* **`data-storysdk-autoplay`**\
  Enables autoplay for stories if set to `"true"`. The stories will play automatically without requiring user interaction.
* **`data-storysdk-group-id`**\
  Identifies a specific group ID to load within the SDK, allowing targeted display of group content when autoplay is enabled.
* **`data-storysdk-start-story-id`**\
  Sets the initial story to display when the SDK opens, letting you direct users to a specific story.
* **`data-storysdk-forbid-close`**\
  Prevents users from closing the SDK interface if set to `"true"`. Useful for mandatory viewing scenarios.
* **`data-storysdk-is-show-mockup`**\
  Displays the story content within a device mockup if set to `"true"`, providing a preview within a simulated device frame.
* **`data-storysdk-is-status-bar-active`**\
  Activates a status bar at the top of the story if set to `"true"`, helping indicate story progress.
* **`data-storysdk-open-in-external-modal`**\
  Opens stories within an external modal if set to `"true"`. This is useful if you want to display content in a standalone modal overlay.
* **`data-storysdk-groups-outline-color`**\
  Specifies a custom outline color for groups. This is useful for visually highlighting or differentiating specific groups within the interface.
* **`data-storysdk-active-group-outline-color`**\
  Specifies a custom outline color for the active group or for the group when hovered over. This helps emphasize the currently selected or focused group in the interface.

Example of usage:

```html
<div
  id="storysdk"
  data-storysdk-token="YOUR_SDK_TOKEN"
  data-storysdk-group-image-width="150"
  data-storysdk-group-image-height="150"
  data-storysdk-group-title-size="18"
  data-storysdk-group-class-name="custom-group"
  data-storysdk-groups-class-name="custom-groups"
  data-storysdk-autoplay="true"
  data-storysdk-group-id="12345"
  data-storysdk-start-story-id="67890"
  data-storysdk-forbid-close="false"
  data-storysdk-is-show-mockup="true"
  data-storysdk-is-status-bar-active="true"
  data-storysdk-open-in-external-modal="false"
  data-storysdk-groups-outline-color="#e9e6e9"
  data-storysdk-active-group-outline-color="#fd19cc">
  <!-- Stories content goes here -->
</div>
```


# Create account

Description of all the steps to create a StorySDK account

**1) Registration**

To access all the features of StorySDK, you need to create an account. There are two registration options available:

* Register via email
* Register via Google

<figure><img src="/files/WhKsUf3btlKiOc2Wn3yE" alt=""><figcaption></figcaption></figure>

After entering your information, you'll need to verify your email.

**2) Create first app**

Next, you need to create your first application by entering the application name.

<figure><img src="/files/iPyDC9eC09zyNibhe0vw" alt=""><figcaption></figcaption></figure>

**3) Choosing Stories or Onboarding**

What do you want to create? Stories widget or onboarding for a mobile app?

<figure><img src="/files/n6mpRB3qlSQPCCKqelFs" alt=""><figcaption></figcaption></figure>

**4) Create first group**

Next, you need to create the first group of stories. You can upload own avatar and change the name.

<figure><img src="/files/WCBLAEDzUdCwne5Tk7Jr" alt=""><figcaption></figcaption></figure>

**5) Choose templates**

The next step is to choose ready-made story templates. You can preview them before choosing.

<figure><img src="/files/iPmpwk2afXgqDL3P6yTb" alt=""><figcaption></figcaption></figure>

**Congrats! All done!**&#x20;

Your account is ready to work!


# Stories Witget

Setup Stories Widget for website or mobile app

**Story Widget** is a tool for publishing content in the Instagram Stories format within apps and websites. Each widget consists of story groups, with each group having a title and an image. Each group can contain an unlimited number of stories.

<figure><img src="/files/kGMmnS8MDgu3UnlFReTp" alt=""><figcaption></figcaption></figure>

To configure content within the story widget, go to the Stories section. Story groups are displayed at the top, and the stories themselves are listed below.

<figure><img src="/files/F2AWgfdj4r0Np92T2qqe" alt=""><figcaption></figcaption></figure>

For each group, you can upload a custom image and add a title.

<figure><img src="/files/p2N9EvtILGgKMqEj4njN" alt=""><figcaption></figcaption></figure>

In the **Settings > Style** section, you can change the widget's style. We offer support for four different widget styles.

<figure><img src="/files/ZXTscK6bWb1cHzfqi9qI" alt=""><figcaption></figcaption></figure>


# Adding New App

This guide describes how to add your app to StorySDK.

To add a new app, click on "*Add new app"* at the top:

![](/files/JGSR9wsNr635dDrqwYFV)

Type app name and click "Add app":

![](/files/KGldLHJLsowf6dl81BKQ)


# Adding New Stories Group

This guide describes how to add new stories group to your app.

To add a new stories group, click on *"Add group"*:

![](/files/WtG7Q35EV2xAdGVD14W2)

Choose group image and type group name. Then click ''Add group":

![](/files/D7XcJVOaKdZXqiEogFgg)


# Adding New Story

This guide describes how to add new story to stories group.

To add new stories, select Stories group and then click on *"Add new story":*

![](/files/4VOLOyYiUNujQFzoueZt)

Create content for your story in the editor:

![](/files/Vh5X2qe5PyokDJ8Jq3tm)

After that click *"Save"* on the top right to save changes:

![](/files/d0CA1EwhxsxhfU0rSBm9)

If you want to publish this story, go back to Dashboard and click *"Publish"* on the Story:

![](/files/vjrjtZnAuDvfiyVLbKBd)


# Editor

Overview of the Editor’s Main Features

In the editor, you can customize the design of each story. You can set the story’s background (color, gradient, image, or video) and add overlay layers (text, photos, videos, links, buttons, GIFs, and interactive elements).

<figure><img src="/files/cwHW4eWuU0vgIwPqiY5N" alt=""><figcaption></figcaption></figure>

## Templates

After creating the first story, you’ll need to add a background to it or start by using a ready-made template. Templates are grouped by category in the left panel.

<figure><img src="/files/99THZy8LYDdGMbtNTWdn" alt=""><figcaption></figcaption></figure>

## Background settings

To change the story background, click on "Background" and select the desired background type in the color picker.

<figure><img src="/files/tut7OPxqT9oDZ2sLD6js" alt=""><figcaption></figcaption></figure>

## Add overlay

Once the background is set, you can proceed with the design customization. In the example below, we’ve added a logo (an image widget) and text. The text style and font can be customized in the right panel.

<figure><img src="/files/TgzqOiZGyshs757IOWgg" alt=""><figcaption></figcaption></figure>

## Interactive widgets

You can also add interactive widgets to collect feedback from your clients. Each interactive widget can be customized in the right panel.

<figure><img src="/files/LTKEUBT6gM8CgiaiLZQY" alt=""><figcaption></figcaption></figure>

## Сhange the order of layers

You can change the order of layers by right-clicking on any widget.

<figure><img src="/files/Y3C3ajF3NcKPlFMU7FKZ" alt=""><figcaption></figcaption></figure>


# Widgets


# Button

A button widget that can be used for navigating stories, as a link, or for implementing a custom scenario within your application.

<figure><img src="/files/NFrjL3SntgbyDESbWveU" alt=""><figcaption></figcaption></figure>

## Actions

### Custom action

To add custom action select "**Custom"** in the **"Link\&action"** dropdown menu.

<figure><img src="/files/TWFcN77whrQhNoxD7jFi" alt=""><figcaption></figcaption></figure>

#### Web

To use a custom action in the WebSDK, provide the necessary information in the **Web** field. For example, you can enter details such as the navigation path or screen name. Then, handle the button click event to capture this data.

To handle the event, get a container with the following HTML selector -`#storysdk`:

```javascript
const container = document.querySelector('#storysdk');
```

Add event listener to listen `'storysdk_custom_click'` event:&#x20;

```javascript
container.addEventListener('storysdk_custom_click', (event) => {
    console.log('my custom data', event.detail.data);
    // Wtrite your code here
});

```

To retrieve the data you entered, use the `event.detail.data` field inside the event listener.

<figure><img src="/files/3bCtoBmTMNw6Ai1blvoA" alt=""><figcaption></figcaption></figure>


# Stories size

StorySDK editor is built on a vector grid principle with a base size of 360x640.

The StorySDK editor is built on a **vector grid principle** with a **base size of 360x640**. This ensures full compatibility with popular design tools like Figma and Sketch. By using this approach, designers no longer need to manually calculate button sizes and spacing, streamlining workflows and simplifying the creation process.

<figure><img src="/files/OGtbinYYufDF4E8tWlpA" alt=""><figcaption></figcaption></figure>

For story player, StorySDK employs vector scaling. This method dynamically adjusts the story size to fit the user's screen without compromising quality, ensuring sharp and visually appealing results across all devices.

When working with raster images, we recommend using a resolution of **1080x1920** — the standard adopted by Instagram. This format strikes the perfect balance between high-quality visuals and fast loading speeds. Additionally, like Instagram, StorySDK automatically centers stories on larger screens, ensuring a comfortable viewing experience.

With StorySDK, you get a seamless blend of design efficiency and top-tier content quality for users.

<figure><img src="/files/NNb9eALeYZvOQNTlSUmM" alt=""><figcaption></figcaption></figure>


# Dashboard


# Stories

This guide describes how you can manage your stories and groups.

You can add unlimited numbers of story groups. In each group, you can create an unlimited number of stories. To manage a group, select it in the list of groups:

## Stories tab

On the Stories tab, you can see stories in Draft, Active, and Expired statuses. When a story is created, it is assigned the Draft status. This means that the story has not yet been published and is not visible to users. To publish a story, simply hover over it and click publish. To edit the story, click Open in editor:

You can also unpublish a story by clicking Remove to draft:

You can completely delete the story by clicking on the trash can icon.

## Group Settings tab

On the Group Settings tab, you can manage group settings. Click *"Save"* to save changes:

### General settings

You can change the group name and image in the general settings section. To change the image - click on the current image of the group:

### Display period

Here you can specify the start date and end date of the group display. After the end of the display period, the group will not be displayed to users:

You can manually disable the publication of the group:

You can also delete a group. In this case, all stories will also be deleted:


# Analytics

This guide describes what types of analytics are available to you.

## Story groups

This section displays analytical data by groups.

You can filter data by period. To do this, click on the period field and select the start date and end date:

&#x20;The data will display the summed indicators for the selected period.

The following metrics are available for groups:

### Open

Displays the number of times a group has been opened by unique users

### Impression

Displays the number of times a group has been viewed over 1 second

### Duration

Total group browsing time in seconds

### Clicks

The total number of clicks on link widgets within the group.

## Interactions

This tab contains information about interactions with stories and widgets.

You can filter data by period. To do this, click on the period field and select the start date and end date:

Two metrics are displayed for each story: Interactions and Statistics.

Interactions display the total number of actions (clicks, answers to questions, reactions) performed by users within story.

The statistics show the number of views of the story.

To see detailed statistics for a story, hover over a story and click *"More"*.

### Detailed story statistic&#x20;

Detailed information is presented in the popup:

Popup contains two sections: Interactions and Statistics.

The Interactions section displays information about story widgets. To switch the widget, click on the arrows:

The Statistics section displays advanced metrics related to story.


# Settings

This guide describes how you can manage application settings.

## App Settings

This tab contains the general application settings. You can edit the name or delete the application. If deleted, all groups and stories will also be deleted.

This tab also contains the SDK token, which is used to integrate the SDK into your applications.

## Style

On this tab, you can control the style of story groups. There are 4 types of group icons available for selection. Style settings apply for each platform separately (iOS, Android, Web). To select a style, click on the desired platform name, then click on your chosen example and click "*Save settings*" at the top:

You can also control the style of groups by setting the pixel size and CSS styles. [Read more](broken://pages/Oad4ot6lh6gFwlytAdm7)

## Localization

Here you can configure the available locales for your application. To add a new locale, click *"Add new locale"* and select the required locale from the list:

To remove or set the localization as default, click on the kebab menu icon:

The default localization will be used when creating and editing groups, stories, and viewing analytics.


# Integrations

You can integrate StorySDK with various marketing services. To do this, go to the Integrations section.

<figure><img src="/files/flzCrdeKfU3sQSClr84k" alt=""><figcaption></figcaption></figure>


# Google Analytics

You can integrate StorySDK with Google Analytics to receive all user events.

For this, you will need a Measurement ID. To find the Measurement ID in your Google Analytics account, follow these steps:

1. **Log in to Google Analytics**: Go to [Google Analytics](https://analytics.google.com) and sign in with your account.
2. **Select the Account and Property**: From the dashboard, choose the account and property for which you want to retrieve the Measurement ID.
3. **Go to Admin Settings**: Click on the **Admin** option in the lower-left corner of the interface.
4. **Access Data Streams**: Under the **Property** column, select **Data Streams**.

<figure><img src="/files/82NabkpcgO26YbhMNhxF" alt=""><figcaption></figcaption></figure>

5. **Choose Your Data Stream**: Click on the data stream associated with your website or app.

<figure><img src="/files/7XWA92uZrFHCNeFopnCE" alt=""><figcaption></figcaption></figure>

6. **Find the Measurement ID**: In the data stream details, you will see the Measurement ID at the top of the page. It starts with "G-" followed by a unique series of numbers and letters.

<figure><img src="/files/t56MT4sNn7Sm40O1X65Q" alt=""><figcaption></figcaption></figure>

Make sure to copy this ID for use in your integrations.

7. **Go to the Integrations tab** in the StorySDK dashboard.

<figure><img src="/files/jLOIHSwlJOfhPd91Zz7Q" alt=""><figcaption></figcaption></figure>

8. **Find the Google Analytics card** and **click the "Activate" button**. In the modal window that opens, paste the obtained Measurement ID and click the "Save" button.

<figure><img src="/files/tvjThM0sKgR6nq1SXbaW" alt=""><figcaption></figcaption></figure>

9. After that, the indicator on the card will turn green, and the label will change to "Active".

<figure><img src="/files/FkOF5XQe8ZfjrdWD14Qs" alt=""><figcaption></figcaption></figure>

You can change the Measurement ID at any time by clicking the "Configure" button on the card. To delete the integration, simply leave the field empty and click "Save."

## StorySDK Google Analytics Events

* **`storysdk_group_duration`**: This event tracks the duration of time a user spends on a specific group of stories or content within the StorySDK.
* **`storysdk_group_open`**: This event is triggered when a user opens a group of stories within the StorySDK.
* **`storysdk_group_close`**: This event is triggered when a user closes or exits a group of stories  within the StorySDK.
* **`storysdk_story_duration`**: This event tracks the duration of time in seconds a user spends on a single story within the StorySDK.
* **`storysdk_story_impression`**: This event is triggered when a story or content is shown more then one second.
* **`storysdk_story_open`**: This event is triggered when a user opens or starts viewing a particular story within the StorySDK.
* **`storysdk_story_close`**: This event is triggered when a user closes or finishes viewing a particular story within the StorySDK.
* **`storysdk_story_next`**: This event is triggered when a user navigates to the next story  in a sequence within the StorySDK.
* **`storysdk_story_back`**: This event is triggered when a user navigates to the previous story  in a sequence within the StorySDK.
* **`storysdk_widget_answer`**: This event is triggered when a user provides an answer or interacts with a widget within a story  in the StorySDK.
* **`storysdk_widget_click`**: This event is triggered when a user clicks on a widget or interactive element within a story in the StorySDK.
* **`storysdk_quiz_start`**: This event is triggered when a user starts a quiz or interactive questionnaire within the StorySDK.
* **`storysdk_quiz_finish`**: This event is triggered when a user finishes a quiz or interactive questionnaire within the StorySDK.


