<p align="center">
  <img src="https://media.licdn.com/dms/image/v2/D560BAQHrjHLOJY--qA/company-logo_200_200/company-logo_200_200/0/1704956117536/vunet_systems_logo?e=2147483647&v=beta&t=nPq8cGYpqCI7yiRuXV1n-Q2Tiz57cVgJzBQ4atS-hOY" alt="Vunet Systems Logo" width="120"/>
</p>

<h1 align="center">Vunet RUM Browser SDK</h1>

<p align="center">
  <strong>Real User Monitoring & Session Replay for Web Applications</strong>
</p>

<p align="center">
  <a href="#license"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
  <img src="https://img.shields.io/badge/OpenTelemetry-Based-blueviolet" alt="OpenTelemetry">
  <img src="https://img.shields.io/badge/Version-0.1.27-green" alt="Version">
  <img src="https://img.shields.io/badge/TypeScript-5.0+-blue" alt="TypeScript">
</p>

<p align="center">
  A comprehensive Real User Monitoring solution built on OpenTelemetry that captures telemetry data from web applications with automatic instrumentation and session replay capabilities.
</p>

## Table of Contents

- [Overview](#overview)
- [What Gets Collected](#what-gets-collected)
- [Quick Start](#quick-start)
- [Installation Modes](#installation-modes)
- [Configuration](#configuration)
- [Public API](#public-api)
- [Telemetry Schema Reference](#telemetry-schema-reference)
- [Trace Context Propagation](#trace-context-propagation)
- [Build and Local Run](#build-and-local-run)
- [Contributor Appendix](#contributor-appendix)
- [Browser Support](#browser-support)
- [License](#license)

## Overview

The SDK loads modules dynamically based on a decide endpoint response:

1. Loader script is added to the page (`vunet-rum-loader.js`).
2. Loader calls decide API and evaluates sampling.
3. Core RUM and/or Session Replay modules are loaded conditionally.
4. Modules initialize and begin telemetry collection/export.

### Bundles

- `dist/vunet-rum-loader.js`: loader/entrypoint
- `dist/vunet-rum-core.js`: tracing + logs instrumentation
- `dist/vunet-rum-session-replay.js`: rrweb-based replay capture
- `dist/vunet-rum.js`: backward-compatible alias of loader

## What Gets Collected

Automatic instrumentation includes:

- Document/page load spans and resource timing
- Fetch and XHR spans
- User interaction spans
- SPA route-change spans
- Long-task spans
- Browser error logs (uncaught exception, unhandled rejection, document errors, console errors, custom errors)
- Session replay events (when enabled by sampling)

## Quick Start

```html
<script
  id="vunet-rum"
  async
  crossorigin="anonymous"
  src="https://cdn.vunet.ai/rum/vunet-rum-loader.js"
  data-collection-source-url="https://collector.example.com/rum"
  data-service-name="my-frontend"
  data-service-namespace="my-app"
></script>
```

> Keep `crossorigin="anonymous"` so browser error reporting includes useful details.

## Installation Modes

### 1) Script tag (recommended)

Use data attributes for auto-init:

```html
<script
  id="vunet-rum"
  async
  crossorigin="anonymous"
  src="https://your-cdn/vunet-rum-loader.js"
  data-collection-source-url="https://collector.example.com/rum"
  data-service-name="my-service"
  data-service-namespace="my-app"
  data-decide-api-endpoint="/vuSmartMaps/api/rum/"
></script>
```

### 2) Programmatic init

```html
<script src="https://your-cdn/vunet-rum-loader.js"></script>
<script>
  window.vunetRum.initialize({
    collectionSourceUrl: 'https://collector.example.com/rum',
    serviceName: 'my-service',
    serviceNamespace: 'my-app',
    decideApiEndpoint: '/vuSmartMaps/api/rum/',
  });
</script>
```

### 3) NPM package

```bash
npm install @vunet/otel-rum
```

```ts
import { initialize } from '@vunet/otel-rum';

initialize({
  collectionSourceUrl: 'https://collector.example.com/rum',
  serviceName: 'my-service',
  serviceNamespace: 'my-app',
});
```

## Configuration

### Important options

| Data Attribute                             | Programmatic Option               | Type                      | Required | Default                 |
| ------------------------------------------ | --------------------------------- | ------------------------- | -------- | ----------------------- |
| `data-collection-source-url`               | `collectionSourceUrl`             | `string`                  | Yes      | -                       |
| `data-decide-api-endpoint`                 | `decideApiEndpoint`               | `string`                  | No       | `/vuSmartMaps/api/rum/` |
| `data-authorization-token`                 | `authorizationToken`              | `string`                  | No       | -                       |
| `data-service-name`                        | `serviceName`                     | `string`                  | No       | `unknown`               |
| `data-service-namespace`                   | `serviceNamespace`                | `string`                  | No       | -                       |
| `data-deployment-environment`              | `deploymentEnvironment`           | `string`                  | No       | -                       |
| `data-default-attributes`                  | `defaultAttributes`               | `Record<string, unknown>` | No       | `{}`                    |
| `data-buffer-max-spans`                    | `bufferMaxSpans`                  | `number`                  | No       | `2048`                  |
| `data-max-export-batch-size`               | `maxExportBatchSize`              | `number`                  | No       | `50`                    |
| `data-buffer-timeout`                      | `bufferTimeout`                   | `number`                  | No       | `2000`                  |
| `data-ignore-urls`                         | `ignoreUrls`                      | `(string \| RegExp)[]`    | No       | `[]`                    |
| `data-propagate-trace-header-cors-urls`    | `propagateTraceHeaderCorsUrls`    | `(string \| RegExp)[]`    | No       | `[]`                    |
| `data-collect-session-id`                  | `collectSessionId`                | `boolean`                 | No       | `true`                  |
| `data-collect-errors`                      | `collectErrors`                   | `boolean`                 | No       | `true`                  |
| `data-drop-single-user-interaction-traces` | `dropSingleUserInteractionTraces` | `boolean`                 | No       | `false`                 |
| `data-drop-short-traces-ms`                | `dropShortTracesMs`               | `number`                  | No       | `0`                     |
| `data-user-interaction-element-name-limit` | `userInteractionElementNameLimit` | `number`                  | No       | `20`                    |
| `data-user-interaction-event-names`        | `userInteractionEventNames`       | `string[]`                | No       | built-in list           |
| `-`                                        | `getOverriddenServiceName`        | `(span) => string`        | No       | -                       |
| `data-form`                                | `form`                            | `object`                  | No       | -                       |
| `data-inject-into-iframe`                  | `injectIntoIframe`                | `boolean`                 | No       | `false`                 |
| `data-rum-logging`                         | `rumLogging`                      | `boolean`                 | No       | `false`                 |

### Decide API response

```json
{
  "sampling_percentage": 100,
  "session_replay_percentage": 50,
  "eventFilter": []
}
```

Static shorthand is also supported:

```txt
sr:50,sp:100
```

Function-based options like `getOverriddenServiceName` must be passed via programmatic `initialize()`.

## Public API

Available via `window.vunetRum` in browser mode:

- `initialize(options)`
- `mapUser(id, userData)`
- `setDefaultAttribute(key, value)`
- `recordError(message, attributes?)`
- `getCurrentSessionId()`
- `onReady(callback)`
- `disableInstrumentations()`
- `registerInstrumentations()`
- `stopProcessing()`
- `tracer` (OpenTelemetry tracer)
- `api` (OpenTelemetry API)

### Example

```js
window.vunetRum.onReady(() => {
  window.vunetRum.setDefaultAttribute('app.version', '1.2.3');
  window.vunetRum.mapUser('user-123', { plan: 'premium' });
});
```

## Telemetry Schema Reference

This section consolidates key schema details from traces and logs.

### Resource attributes (common)

- `service.name`
- `service.namespace`
- `deployment.environment.name`
- `vunet.rum.version`
- `os.name`
- `user_agent.name`
- `vunet.rum.user_identifier` (if mapped)
- `vunet.rum.user_data` (if mapped)

### Trace span categories

- **Document Load**: `documentLoad`, `documentFetch`, `resourceFetch`
- **Network**: `HTTP <METHOD>` spans from fetch/xhr instrumentation
- **User Interaction**: click/input related spans
- **Long Task**: `longtask`
- **Route Change**: `routeChange: <path>`
- **Form Navigation**: `Form Navigation: <id>`

### Log categories

- `browser.error.uncaught_exception`
- `browser.error.unhandled_rejection`
- `browser.error.document`
- `browser.error.console`
- `browser.error.custom`
- Session replay event logs under `@vunet/sessionreplay`

### Common operational attributes

- `session.id`
- `url.full`, `url.path`
- `vunet.document.visibility_state`
- `vunet.root_span.operation`
- `vunet.root_span.url`
- `vunet.http.action_type`

## Trace Context Propagation

Set `propagateTraceHeaderCorsUrls` to enable trace header propagation for selected cross-origin endpoints.

```js
window.vunetRum.initialize({
  collectionSourceUrl: 'https://collector.example.com/rum',
  propagateTraceHeaderCorsUrls: [/^https:\/\/api\.example\.com/],
});
```

Server should allow headers:

```txt
Access-Control-Allow-Headers: traceparent, tracestate
```

## Build and Local Run

### Prerequisites

- Node.js `>=18` (project uses `.nvmrc`)
- npm `>=9`
- Buf CLI (for protobuf generation)

### Setup

```bash
git clone --recurse-submodules git@github.com:vunetsystems/vunet-rum-browser-sdk.git
cd vunet-rum-browser-sdk
npm install
```

If cloned without submodules:

```bash
git submodule update --init --recursive
```

### Build

```bash
npm run build
```

### Tests

```bash
npm test
npm run test:ut
npm run test:e2e
```

### Quality checks

```bash
npm run eslint-fix
npm run prettier-format-all
npm run types
```

### Common troubleshooting

- `buf: not found` -> install Buf and verify with `buf --version`
- submodule issues -> reinit with:

```bash
git submodule deinit --all -f
git submodule update --init --recursive
```

## Contributor Appendix

### Nx workflow

Prefer workspace tasks through Nx:

```bash
npm exec nx run otel-rum:build
npm exec nx run otel-rum:bundle
npm exec nx run-many -t test
```

### Submodule branch sync (maintainer operation)

```bash
git config -f .gitmodules submodule.opentelemetry-js.branch develop
git config -f .gitmodules submodule.opentelemetry-js-contrib.branch develop
git submodule sync --recursive
git submodule update --remote --recursive
```

### TypeScript globals package (optional for script-tag users)

```bash
npm install --save-dev @vunet/rum-types
```

## Browser Support

| Browser | Minimum Version |
| ------- | --------------- |
| Chrome  | 60+             |
| Firefox | 55+             |
| Safari  | 12+             |
| Edge    | 79+             |

Internet Explorer is not supported.

## License

Apache 2.0. See `LICENSE`.
