# Xsolla Metrika

[![npm version](https://img.shields.io/npm/v/@xsolla/metrika.svg)](https://www.npmjs.com/package/@xsolla/metrika)
[![License](https://img.shields.io/npm/l/@xsolla/metrika.svg)](LICENSE)
[![Bundle size](https://badgen.net/bundlephobia/minzip/@xsolla/metrika)](https://bundlephobia.com/result?p=@xsolla/metrika)
[![Downloads](https://img.shields.io/npm/dm/@xsolla/metrika.svg)](https://www.npmjs.com/package/@xsolla/metrika)

A lightweight integration library for Xsolla Metrics (XMTS) that simplifies user tracking and analytics setup.
Supports SPA and classic sites, works in all modern browsers, and provides full TypeScript support.

---

## Table of Contents

- [Features](#features)
- [Installation](#installation)
  - [npm / yarn](#npm--yarn)
  - [CDN (jsDelivr / unpkg)](#cdn-jsdelivr--unpkg)
- [Usage](#usage)
  - [ESM](#esm)
  - [CommonJS](#commonjs)
  - [IIFE](#iife)
  - [IIFE (BOOTSTRAP)](#iife-bootstrap)
- [Configuration](#configuration)
  - [Parameters Table](#parameters-table)
- [Sending data](#sending-data)
- [API Reference](#api-reference)
  - [Instance Methods](#instance-methods)
- [Trackers](#trackers)
- [What Data Is Sent](#what-data-is-sent)
- [TypeScript Support](#typescript-support)
- [Migration from v1](#migration-from-v1)
- [Browser Support](#browser-support)
- [License](#license)

---

## Features

- 📦 **Tiny**: Minimal bundle size, no heavy dependencies.
- 🧑‍💻 **TypeScript-first**: Full type definitions out of the box.
- ⚡ **SPA-ready**: Tracks navigation in React, Vue, Angular, etc.
- 🖱️ **Auto-tracking**: Clicks, scrolls, external links, performance, and more.
- 🧬 **Device fingerprinting**.
- 🌍 **Flexible delivery**: Works with `sendBeacon` and `fetch`.
- 🔌 **Custom events**: Send any data, integrate with A/B tests, etc.

---

## Installation

### Bundles

Xsolla Metrika provides several bundle formats for different use cases:

| Format | File                                   | Best For                                                                   |
| ------ | -------------------------------------- | -------------------------------------------------------------------------- |
| IIFE   | `dist/xsolla-metrika.js`               | Direct `<script>` inclusion in browsers                                    |
| IIFE   | `dist/xsolla-metrika.min.js`           | Minified version for browser usage                                         |
| IIFE   | `dist/xsolla-metrika.bootstrap.js`     | Auto-initialization via `<script data-*>`, available as `window.analytics` |
| IIFE   | `dist/xsolla-metrika.bootstrap.min.js` | Minified version of the auto-initialization bundle                         |
| CJS    | `dist/xsolla-metrika.cjs.cjs`          | Node.js/CommonJS (`require`)                                               |
| ESM    | `dist/xsolla-metrika.esm.js`           | Modern bundlers and ESM imports (`import`)                                 |
| Types  | `dist/xsolla-metrika.d.ts`             | TypeScript type definitions                                                |

### npm / yarn

```bash
npm install @xsolla/metrika
# or
yarn add @xsolla/metrika
```

### CDN (jsDelivr / unpkg)

```html
<!-- jsDelivr (IIFE) -->
<script src="https://cdn.jsdelivr.net/npm/@xsolla/metrika@2"></script>
<!-- unpkg (IIFE) -->
<script src="https://unpkg.com/@xsolla/metrika@2"></script>
```

> **Note:** The `@2` version range automatically resolves to the latest `2.x.x` release. This is the recommended approach for production — semver guarantees backward compatibility within a major version.

---

## Usage

### ESM

```ts
import XsollaAnalytics from '@xsolla/metrika';

(async () => {
  const analytics = await XsollaAnalytics.init({
    id: 'YOUR_COUNTER_ID',
    hit: true,
    navigation: true,
    // ...other options
  });

  analytics.hit(window.location.href);
  analytics.elementClick('my-button');
})();
```

### CommonJS

```js
const XsollaAnalytics = require('@xsolla/metrika').default;

XsollaAnalytics.init({ id: 'YOUR_COUNTER_ID', hit: true }).then((analytics) => {
  analytics.hit(window.location.href);
});
```

### IIFE

```html
<script src="https://cdn.jsdelivr.net/npm/@xsolla/metrika@2"></script>
<script>
  window.XsollaAnalytics.init({
    id: 'YOUR_COUNTER_ID',
    hit: true,
    navigation: true,
  }).then(function (analytics) {
    analytics.hit(window.location.href);
  });
</script>
```

### IIFE (BOOTSTRAP)

A special **bootstrap IIFE** bundle: `dist/xsolla-metrika.bootstrap.js` and its minified version `dist/xsolla-metrika.bootstrap.min.js`.
This bundle allows you to specify configuration parameters directly via data-attributes on the `<script>` tag. The analytics instance is then automatically created and becomes available as `window.analytics`.

```html
<script
  src="https://cdn.jsdelivr.net/npm/@xsolla/metrika@2/dist/xsolla-metrika.bootstrap.min.js"
  data-id="123456"
  data-hit="true"
  data-click="true"
  data-ext-link="true"
  data-site-domains='["example.com","sub.example.com"]'
></script>
```

> **Note:**

- All parameters must be specified in kebab-case format.
- All values that require parsing (such as arrays or objects) must be enclosed in single quotes.

---

## Configuration

### Parameters Table

| Name                  | Type                                        | Default                            | Description                                           |
| --------------------- | ------------------------------------------- | ---------------------------------- | ----------------------------------------------------- |
| `id`                  | `string \| number`                          | **required**                       | Counter ID (unique for your project)                  |
| `hit`                 | `boolean`                                   | `false`                            | Track page load (pageview)                            |
| `extLink`             | `boolean`                                   | `false`                            | Track external links (outside `siteDomains`)          |
| `click`               | `boolean`                                   | `false`                            | Track clicks on elements with `data-analytics="true"` |
| `scroll`              | `boolean`                                   | `false`                            | Track scroll depth (10%, 50%, 90%)                    |
| `crossDomainTracking` | `boolean`                                   | `false`                            | Track transitions to allowed domains (`siteDomains`)  |
| `sendLoadMetrics`     | `boolean`                                   | `false`                            | Send performance metrics (LCP, TTFB, etc.)            |
| `navigation`          | `boolean`                                   | `false`                            | Track navigation events (SPA support)                 |
| `siteDomains`         | `string[]`                                  | `[location.host]`                  | List of internal domains for link tracking            |
| `server`              | `string`                                    | `'https://minimetrika.xsolla.com'` | Analytics server base URL                             |
| `hitEndpoint`         | `string`                                    | `'hit'`                            | Endpoint for pageview events                          |
| `externalId`          | `number`                                    | `undefined`                        | External user ID                                      |
| `merchantId`          | `number`                                    | `undefined`                        | Merchant ID                                           |
| `extraValues`         | `Record<string, unknown>`                   | `{}`                               | Additional custom parameters                          |
| `abParams`            | `Record<string, unknown>`                   | `{}`                               | A/B test parameters                                   |
| `useBeacon`           | `boolean`                                   | `true`                             | Use `sendBeacon` API for sending data                 |
| `retryOptions`        | `{ retries?: number, retryDelay?: number }` | `{ retries: 2, retryDelay: 500 }`  | Options for retrying failed fetch requests            |
| `ip`                  | `string`                                    | `undefined`                        | User IP (for override, optional)                      |

---

## Sending Data

By default, Xsolla Metrika sends all events using [sendBeacon](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) for maximum reliability (especially during page unload).

- **Global setting:**  
  If you want to use regular `fetch` instead of `sendBeacon` for manual methods (`hit`, `externalLink`, `elementClick`, etc.), set the `useBeacon: false` parameter during initialization:

  ```js
  XsollaAnalytics.init({
    id: 'YOUR_COUNTER_ID',
    useBeacon: false,
    // ...
  });
  ```

- **Important:**  
  Automatic trackers (such as auto pageview, click, scroll tracking, etc.) always use `sendBeacon` regardless of the global parameter.

- **Override per event:**  
  In any manual method (`hit`, `externalLink`, `elementClick`, etc.), you can explicitly specify the sending method using the `useBeacon` option:

  ```js
  analytics.hit(window.location.href, {}, false); // sends via fetch
  analytics.hit(window.location.href, {}, true); // sends via sendBeacon
  ```

- **Retries with fetch:**  
  If `sendBeacon` is not available or fails, or if you explicitly use `fetch` for sending, the library will automatically retry the request up to **two additional times** (total of 3 attempts) in case of network errors or server errors.

- **Customizing retries:**  
  You can control the number of retry attempts and delay between them using the `retryOptions` parameter during initialization:

  ```js
  XsollaAnalytics.init({
    id: 'YOUR_COUNTER_ID',
    retryOptions: {
      retries: 5, // Number of retry attempts (default: 2)
      retryDelay: 1000, // Delay between retries in ms (default: 500)
    },
  });
  ```

  If `retryOptions` is not specified, the default is 2 retries with a 500ms delay.

This allows you to flexibly control how events are sent depending on your needs.

---

## API Reference

### Instance Methods

| Method                  | Arguments                                                                    | Returns               | Description                                     |
| ----------------------- | ---------------------------------------------------------------------------- | --------------------- | ----------------------------------------------- |
| `hit`                   | `url: string, data?: object, useBeacon?: boolean`                            | `Promise<boolean>`    | Send a pageview event                           |
| `externalLink`          | `url: string, data?: object, useBeacon?: boolean`                            | `Promise<boolean>`    | Track an external link click                    |
| `elementClick`          | `name: string, data?: object, useBeacon?: boolean`                           | `Promise<boolean>`    | Track a named element click                     |
| `formData`              | `name: string, form: object \| FormData, data?: object, useBeacon?: boolean` | `Promise<boolean>`    | Track form submission                           |
| `customEvent`           | `name: string, data?: object, useBeacon?: boolean`                           | `Promise<boolean>`    | Send a custom named event                       |
| `sendCustomData`        | `endpoint: string, data: object, useBeacon?: boolean`                        | `Promise<boolean>`    | Send arbitrary data to a custom analytics route |
| `payStationUserSession` | `data: object, useBeacon?: boolean`                                          | `Promise<boolean>`    | Track Pay Station user session                  |
| `eventForLCP`           | `useBeacon?: boolean`                                                        | `Promise<boolean>`    | Send Largest Contentful Paint event             |
| `setAbParams`           | `abParams: object`                                                           | `void`                | Update A/B test parameters                      |
| `setExtraValues`        | `extraValues: object`                                                        | `void`                | Update extra values                             |
| `getClientId`           | none                                                                         | `string`              | Get the current client ID                       |
| `getVisitorId`          | none                                                                         | `string`              | Get the current visitor ID                      |
| `getFingerprintHash`    | none                                                                         | `string \| undefined` | Get device fingerprint hash                     |
| `getFingerprintData`    | none                                                                         | `object \| undefined` | Get raw device fingerprint data                 |
| `destroy`               | none                                                                         | `void`                | Unsubscribe all trackers and clean up resources |

> **Note:** When sending data using any method (`hit`, `externalLink`, `elementClick`, `formData`, `customEvent`, etc.), you can temporarily extend the global `extraValues` or `abParams` by passing them in the data object:

```ts
analytics.hit(window.location.href, {
  exv: { extraKey_1: 'extraValue_1', extraKey_2: 'extraValue_2' },
  abParams: { abKey_1: 'abValue_1', abKey_2: 'abValue_2' },
});
```

Values passed this way will be added only to the current event and will not modify the global parameters set during initialization.

#### Example

```ts
analytics.hit(window.location.href);
analytics.externalLink('https://external.com');
analytics.elementClick('my-button');
analytics.formData('signup', { email: 'test@example.com' });
analytics.customEvent('my-event', { foo: 1 });
analytics.setAbParams({ experiment: 'A' });
analytics.destroy();
```

---

## List of Event Types

| Type (et)                | Symbolic Name            | Where Used / How to Enable            | Description                                                  |
| ------------------------ | ------------------------ | ------------------------------------- | ------------------------------------------------------------ |
| 1                        | HIT                      | PageLoadTracker, NavigationTracker    | Page view (pageview), SPA navigation                         |
| 2                        | EXTERNAL_LINK            | ExternalLinkTracker, `externalLink()` | Click on an external link (not from `siteDomains`)           |
| 3                        | ELEMENT_CLICK            | ClickTracker, `elementClick()`        | Click on element with `data-analytics="true"` or manual call |
| 4                        | FORM_DATA                | `formData()`                          | Form submission                                              |
| 5                        | CUSTOM_EVENT             | `customEvent()`                       | Custom user event                                            |
| 6                        | LCP                      | PerformanceTracker, `eventForLCP()`   | Largest Contentful Paint metric                              |
| 10                       | SCROLL_TOP               | ScrollTracker                         | Scroll to 10% of the page                                    |
| 11                       | SCROLL_MIDDLE            | ScrollTracker                         | Scroll to 50% of the page                                    |
| 12                       | SCROLL_BOTTOM            | ScrollTracker                         | Scroll to 90% of the page                                    |
| paystation-user-sessions | paystation-user-sessions | `payStationUserSession()`             | PayStation user session event                                |

See [docs/event-types.md](docs/event-types.md) for details.

---

## Trackers

Xsolla Metrika includes several built-in trackers. Enable them via config flags:

| Tracker             | Flag                  | Description                                                                                        |
| ------------------- | --------------------- | -------------------------------------------------------------------------------------------------- |
| PageLoadTracker     | `hit`                 | Tracks page load (pageview)                                                                        |
| ClickTracker        | `click`               | Tracks clicks on `[data-analytics="true"]` elements                                                |
| ExternalLinkTracker | `extLink`             | Tracks clicks on external links                                                                    |
| ScrollTracker       | `scroll`              | Tracks scroll depth (10%, 50%, 90%)                                                                |
| CrossDomainTracker  | `crossDomainTracking` | Appends xsollauid and visitorId to links and forms to allowed domains, cleans URL after extraction |
| PerformanceTracker  | `sendLoadMetrics`     | Tracks web-vitals metrics (LCP)                                                                    |
| NavigationTracker   | `navigation`          | Tracks SPA navigation (history API, hashchange)                                                    |

See [docs/trackers.md](docs/trackers.md) for details.

---

## What Data Is Sent

Xsolla Metrika collects and sends only technical and anonymized data required for analytics and improving user experience.  
**No personal user data is collected or transmitted by default.**

Example of the event data structure

```json
{
  "et": 1, // Event type (e.g., HIT, CLICK, etc.)
  "library_version": "X.Y.Z", // Library version (injected at build time)
  "counter_id": "YOUR_COUNTER_ID", // Your project counter ID
  "dfp": "fingerprint_hash", // Device fingerprint
  "xsollauid": "380884044985925701", // Xsolla user ID
  "eid": 123456, // External ID (if there is)
  "ident": {
    "ntf": 1, // Notifications enabled (1/0)
    "net": "4g", // Network type
    "t": "Page Title", // Document title
    "pc": "UTF-8", // Charset
    "la": "en", // Language
    "tz": -180, // Timezone offset (minutes)
    "wcw": 1920, // Viewport width
    "wch": 1080, // Viewport height
    "sw": 1920, // Screen width
    "sh": 1080, // Screen height
    "j": 0, // Java enabled (1/0)
    "c": 1, // Cookies enabled (1/0)
    "sc": 24, // Color depth
    "ifr": 0, // Is in iframe (1/0)
    "adb": 0, // AdBlock detected (1/0)
    "gacid": "123456789.1234567890", // Google Analytics CID (if there is)
    "clid": "1733820844784899640", // Client ID
    "vid": "1747380405323933016", // Visitor ID,
    "cts": 1709571234567, // Client timestamp (ms since epoch)
    "plt": 356, // Page load time (ms),
    "lcp": 567 // Largest Contentful Paint (ms)
    // ...additional cookies (for example: yid, pv, cv, etc.)
  },
  "user_agent": "...", // User-Agent string
  "browser_major": "125", // Browser major version
  "browser_name": "Chrome", // Browser name
  "title": "Page Title", // Document title
  "utm": {
    "utm_source": null,
    "utm_medium": null,
    "utm_campaign": null,
    "utm_content": null,
    "utm_term": null
  },
  "openstat": {
    "openstat_service": null,
    "openstat_campaign": null,
    "openstat_ad": null,
    "openstat_source": null
  },
  "url": "https://your.site/page", // Current page URL
  "referer": "https://referrer.site/", // Referrer URL
  "exv": {}, // Extra values (if there is)
  "abParams": {} // A/B test params (if there is)
  // ...event-specific fields (for example: value, en, form, metricData, etc.)
}
```

---

## TypeScript Support

- 100% typed API, no need for `@types` packages.
- Works out of the box with TypeScript 5.8+.
- See [docs/ts-support.md](docs/ts-support.md) for troubleshooting and tips.

---

## Migration from v1

- Async initialization: use `await XsollaAnalytics.init(...)`.
- Device Fingerprinting has become more reliable.
- Events are sent via `sendBeacon` by default, with fallback to `fetch`.
- The method `dfpGet` has been renamed to `getFingerprintHash`.
- The method `keyDfpGet` has been renamed to `getFingerprintData`.
- Retry logic for failed fetch requests.
- See [docs/migration/v1_v2.md](docs/migration/v1_v2.md) for a full migration guide.

---

## Browser Support

- All modern browsers (Chrome, Firefox, Edge, Safari, Opera, Samsung Internet, etc.)
- Chrome 80+, Safari 13.1+, Firefox 74+, Edge 80+, iOS Safari 13.4+, Samsung Internet 13+
- See [docs/browser-support.md](docs/browser-support.md) for the full list.

---

## License

[MIT](LICENSE.md) © Xsolla

---
