npm package: @pythiasport/betting-widget
Description: TypeScript SDK for embedding Pythia betting widget
Author: pythia sport
Latest version: 1.1.2

# @pythiasport/betting-widget

A modern, framework-agnostic TypeScript SDK for embedding the Pythia betting widget into any web application.

## Features

- ✅ **TypeScript-first** with full type safety
- 🎯 **Framework-agnostic** - works with React, Vue, Angular, or vanilla JS
- 🎨 **Theme support** with dark/light modes
- 📡 **Event-driven architecture** using EventEmitter pattern
- 🔒 **Type-safe APIs** with comprehensive JSDoc documentation
- 📦 **Multiple build formats** - ESM, CommonJS, and UMD/IIFE
- 🎪 **Zero dependencies** in runtime

## Installation

```bash
npm install @pythiasport/betting-widget
```

```bash
yarn add @pythiasport/betting-widget
```

```bash
pnpm add @pythiasport/betting-widget
```

## Quick Start

### 1. Add container to your HTML

```html
<div id="betting-widget-container"></div>
```

### 2. Initialize the SDK

```typescript
import { PythiaSDK } from '@pythiasport/betting-widget';

const sdk = new PythiaSDK();

// Listen to events
sdk.on('betslip:open', () => {
  console.log('Betslip opened!');
});

sdk.on('ready', () => {
  console.log('Widget is ready!');
});

// Initialize with configuration
sdk.init({
  tenantId: 'your-tenant-id',
  jwtToken: 'user-jwt-token',
  userId: 'user-123',
  currencyCode: 'USD',
  currencySymbol: '',
  theme: 'dark',
  style: 'width: 100%; height: 100%; border: none;',
  onLoginRequest: () => {
    // Handle login request
    console.log('User needs to login');
  }
});
```

## Framework Integration

### React

```tsx
import { useEffect, useRef } from 'react';
import { PythiaSDK } from '@pythiasport/betting-widget';

function BettingWidget() {
  const sdkRef = useRef<PythiaSDK | null>(null);

  useEffect(() => {
    const sdk = new PythiaSDK();
    sdkRef.current = sdk;

    sdk.on('betslip:open', () => {
      console.log('Betslip opened');
    });

    sdk.init({
      tenantId: 'your-tenant-id',
      jwtToken: 'user-token',
      userId: 'user-123',
      currencyCode: 'USD',
      currencySymbol: '',
      theme: 'dark',
    });

    return () => {
      sdk.destroy();
    };
  }, []);

  const handleThemeToggle = () => {
    sdkRef.current?.setTheme('light');
  };

  return (
    <div>
      <button onClick={handleThemeToggle}>Toggle Theme</button>
      <div id="betting-widget-container" />
    </div>
  );
}
```

### Vue 3

```vue
<template>
  <div>
    <button @click="toggleTheme">Toggle Theme</button>
    <div id="betting-widget-container"></div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { PythiaSDK } from '@pythiasport/betting-widget';

const sdk = ref<PythiaSDK | null>(null);

onMounted(() => {
  const instance = new PythiaSDK();
  sdk.value = instance;

  instance.on('betslip:open', () => {
    console.log('Betslip opened');
  });

  instance.init({
    tenantId: 'your-tenant-id',
    jwtToken: 'user-token',
    userId: 'user-123',
    currencyCode: 'USD',
    currencySymbol: '',
    theme: 'dark',
  });
});

onUnmounted(() => {
  sdk.value?.destroy();
});

const toggleTheme = () => {
  sdk.value?.setTheme('light');
};
</script>
```

### Angular

```typescript
import { Component, OnInit, OnDestroy } from '@angular/core';
import { PythiaSDK } from '@pythiasport/betting-widget';

@Component({
  selector: 'app-betting-widget',
  template: `
    <div>
      <button (click)="toggleTheme()">Toggle Theme</button>
      <div id="betting-widget-container"></div>
    </div>
  `
})
export class BettingWidgetComponent implements OnInit, OnDestroy {
  private sdk: PythiaSDK | null = null;

  ngOnInit() {
    this.sdk = new PythiaSDK();

    this.sdk.on('betslip:open', () => {
      console.log('Betslip opened');
    });

    this.sdk.init({
      tenantId: 'your-tenant-id',
      jwtToken: 'user-token',
      userId: 'user-123',
      currencyCode: 'USD',
      currencySymbol: '',
      theme: 'dark',
    });
  }

  ngOnDestroy() {
    this.sdk?.destroy();
  }

  toggleTheme() {
    this.sdk?.setTheme('light');
  }
}
```

### Vanilla JavaScript (UMD)

```html
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/@pythiasport/betting-widget@latest/dist/index.global.js"></script>
</head>
<body>
  <div id="betting-widget-container"></div>

  <script>
    const sdk = new PythiaSDK.PythiaSDK();

    sdk.on('betslip:open', function() {
      console.log('Betslip opened');
    });

    sdk.init({
      tenantId: 'your-tenant-id',
      jwtToken: 'user-token',
      userId: 'user-123',
      currencyCode: 'USD',
      currencySymbol: ',
      theme: 'dark'
    });
  </script>
</body>
</html>
```

## API Reference

### Constructor

```typescript
const sdk = new PythiaSDK();
```

### Methods

#### `init(options: PythiaSDKOptions): void`

Initializes the SDK with configuration options.

**Parameters:**

- `tenantId` (string, required) - Unique identifier for the tenant
- `jwtToken` (string, required) - JWT token for authentication
- `userId` (string, required) - User identifier
- `currencyCode` (string, required) - ISO currency code (e.g., 'USD', 'EUR')
- `currencySymbol` (string, required) - Currency symbol (e.g., ', '€')
- `style` (string, optional) - CSS styles for the iframe
- `theme` ('light' | 'dark', optional) - Initial theme (default is 'dark')
- `bettingAppUrl` (string, optional) - URL of the betting application
- `oddsFormat` ('decimal' | 'fractional' | 'moneyline', optional) - a parameter that defines which price format should be used (default is 'decimal').
- `language` (optional) - the interface language, specified as a country/language code (e.g. 'en', 'de'; default is 'en').
- `timeFormat` ('24h' | '12h', optional) - the time display format (default is '24h').
- `userLocation` ('string', optional) - The user’s location in country code format is used to display only the streams available for that specific location. If this parameter is not provided, all streams will be displayed, but not all of them will be playable. (e.g. 'US', 'DE').
- `onLoginRequest` (function, optional) - Callback when login is requested

#### `updateUserInfo(userInfo: UserInfo): void`

Updates user authentication information after login/logout.

```typescript
sdk.updateUserInfo({
  jwtToken: 'new-jwt-token',
  userId: 'user-123',
  currencyCode: 'EUR',
  currencySymbol: '€'
});
```

It can be used to change the price type and the time format.

```typescript
sdk.updateUserInfo({
  timeFormat: '12h',
  oddsFormat: 'moneyline'
});
```

#### `setTheme(theme: 'light' | 'dark'): void`

Changes the widget theme.

```typescript
sdk.setTheme('dark');
```

#### `setLanguage(theme: string): void`

Changes the widget language.

```typescript
sdk.setLanguage('en');
```

#### `notifyScrollChange(scrollY: number, scrollX?: number): void`

Manually notifies the iframe of scroll position changes.

```typescript
sdk.notifyScrollChange(window.scrollY);
```

#### `setBetslipButtonOffset(offset: BetslipButtonOffset): void`

Sets the offset for betslip floating button positioning.

```typescript
sdk.setBetslipButtonOffset({
  top: 80,    // pixels from top
  bottom: 20  // pixels from bottom
});
```

#### `destroy(): void`

Destroys the SDK instance and cleans up all resources.

```typescript
sdk.destroy();
```

#### `getIsInitialized(): boolean`

Checks if the SDK is initialized.

```typescript
if (sdk.getIsInitialized()) {
  // SDK is ready
}
```

### Events

The SDK uses an EventEmitter pattern for all events. Use `on()`, `once()`, and `off()` methods.

#### Event Types

- **`betslip:open`** - Emitted when the betslip is opened
- **`betslip:close`** - Emitted when the betslip is closed
- **`login:request`** - Emitted when the iframe requests login
- **`scroll:up`** - Emitted when the iframe requests scroll to top
- **`height:change`** - Emitted when iframe height changes (receives height string)
- **`ready`** - Emitted when the iframe is loaded and ready

#### Event Methods

```typescript
// Listen to an event
sdk.on('betslip:open', () => {
  console.log('Betslip opened');
});

// Listen once
sdk.once('ready', () => {
  console.log('Widget ready - this will only fire once');
});

// Remove listener
const handler = () => console.log('Betslip closed');
sdk.on('betslip:close', handler);
sdk.off('betslip:close', handler);

// Remove all listeners for an event
sdk.removeAllListeners('betslip:open');

// Remove all listeners
sdk.removeAllListeners();

// Get listener count
const count = sdk.listenerCount('betslip:open');
```

## TypeScript Support

The SDK is written in TypeScript and provides full type definitions:

```typescript
import { PythiaSDK, PythiaSDKOptions, UserInfo, ThemeType } from '@pythiasport/betting-widget';

const options: PythiaSDKOptions = {
  tenantId: 'tenant-123',
  jwtToken: 'token',
  userId: 'user-123',
  currencyCode: 'USD',
  currencySymbol: '',
  theme: 'dark'
};

const sdk = new PythiaSDK();
sdk.init(options);
```

## Legacy Version

For projects that require compatibility with older browsers, a legacy (umd) version of the SDK is available.
To use it you can include it via CDN:

```html
<script src="https://unpkg.com/@pythiasport/betting-widget@latest/dist/index.global.js"></script>
<script>
    window.pythiaSDK.init({
        tenantId: 'your-tenant-id',
        jwtToken: 'user-token',
        currencyCode: 'USD',
        currencySymbol: '$',
        theme: 'dark'
        onLoginRequest: () => {
            // Handle login request
            console.log('User needs to login');
        }
    })
</script>
```
<!-- create a warning that legacy version is deprecated and new features are not supported -->

### # Migration Guide

This guide helps you migrate from the original JavaScript SDK to the new TypeScript version.

#### Two Options

You have **two options** when upgrading to the new SDK:

1. **Use the Legacy API** - Zero code changes, backward compatible
2. **Migrate to Modern API** - Better features, type safety, and developer experience

#### Option 1: Legacy API (Zero Changes) ✨

##### What You Need to Do

Simply replace your script tag with the new legacy build:

**Before:**

```html
<script src="path/to/old-pythia-sdk.js"></script>
```

**After:**

```html
<script src="https://unpkg.com/@pythiasport/betting-widget@latest/dist/legacy.global.js"></script>
```

**That's it!** Your existing code will work without any changes:

```javascript
// All your existing code works exactly as before
window.pythiaSDK.onBetslipOpen = function() {
  console.log('Betslip opened');
};

window.pythiaSDK.init({
  tenantId: 'your-tenant-id',
  jwtToken: 'token',
  userId: 'user-123',
  currencyCode: 'USD',
  currencySymbol: '$',
  theme: 'dark'
});

window.pythiaSDK.setTheme('light');
window.pythiaSDK.updateUserInfo({ /* ... */ });
```

##### What's Different (Behind the Scenes)

- ✅ Modern TypeScript implementation
- ✅ Better performance and optimization
- ✅ Improved error handling
- ✅ Automatic setup of internal listeners
- ⚠️ Some internal methods log deprecation warnings (but still work)

##### Deprecation Warnings

You might see console warnings for these methods (they still work, but are no longer necessary):

- `insertIframe()` - Auto-handled on init()
- `sendAuthDataToIframe()` - Auto-sent on init()
- `listenToIframeEvents()` - Auto-setup on init()
- `sendInitialTheme()` - Auto-sent on init()
- `syncParentScroll()` - Auto-setup on init()
- `syncParentHeight()` - Auto-setup on init()
- `sendIframeTopOffset()` - Auto-sent on init()

You can safely remove calls to these methods if you want to clean up your code.

#### Option 2: Migrate to Modern API 🚀

##### Benefits

- ✅ Multiple event listeners per event
- ✅ Type safety with TypeScript
- ✅ Better IDE autocomplete
- ✅ One-time listeners with `once()`
- ✅ Cleaner event management
- ✅ Tree-shakeable imports
- ✅ Modern module system

##### Step-by-Step Migration

###### Step 1: Install via NPM

```bash
npm install @pythiasport/betting-widget
```

###### Step 2: Import the SDK

**Before (Global Script):**

```html
<script src="path/to/pythia-sdk.js"></script>
```

**After (ES Module):**

```typescript
import { PythiaSDK } from '@pythiasport/betting-widget';
```

###### Step 3: Create Instance

**Before:**

```javascript
// Used global window.pythiaSDK
window.pythiaSDK.init({ /* ... */ });
```

**After:**

```typescript
// Create your own instance
const sdk = new PythiaSDK();
sdk.init({ /* ... */ });
```

###### Step 4: Replace Callback Properties with Events

**Before:**

```javascript
window.pythiaSDK.onBetslipOpen = function() {
  console.log('Opened');
};

window.pythiaSDK.onBetslipClose = function() {
  console.log('Closed');
};
```

**After:**

```typescript
sdk.on('betslip:open', () => {
  console.log('Opened');
});

sdk.on('betslip:close', () => {
  console.log('Closed');
});
```

###### Step 5: Update Method Calls

Most methods stay the same, just called on your instance:

**Before:**

```javascript
window.pythiaSDK.setTheme('light');
window.pythiaSDK.updateUserInfo({ /* ... */ });
window.pythiaSDK.setBetslipButtonOffset({ top: 80 });
window.pythiaSDK.scrollChanged(window.scrollY);
```

**After:**

```typescript
sdk.setTheme('light');
sdk.updateUserInfo({ /* ... */ });
sdk.setBetslipButtonOffset({ top: 80 });
sdk.notifyScrollChange(window.scrollY); // Renamed for clarity
```

###### Step 6: Remove Unnecessary Method Calls

These methods are no longer needed (handled automatically):

**Before:**

```javascript
window.pythiaSDK.init({ /* ... */ });
window.pythiaSDK.insertIframe();           // Remove this
window.pythiaSDK.sendAuthDataToIframe();   // Remove this
window.pythiaSDK.listenToIframeEvents();   // Remove this
window.pythiaSDK.sendInitialTheme();       // Remove this
```

**After:**

```typescript
sdk.init({ /* ... */ });
// That's it! Everything else is automatic
```

###### Step 7: Add Cleanup (Recommended)

**Before:**

```javascript
// No cleanup mechanism
```

**After:**

```typescript
// Clean up when done (e.g., component unmount)
sdk.destroy();
```

##### Complete Example: Before & After

###### Before (Legacy JavaScript)

```html
<!DOCTYPE html>
<html>
<head>
  <script src="pythia-sdk.js"></script>
</head>
<body>
  <div id="betting-widget-container"></div>
  
  <script>
    window.pythiaSDK.onBetslipOpen = function() {
      console.log('Betslip opened');
    };

    window.pythiaSDK.onBetslipClose = function() {
      console.log('Betslip closed');
    };

    window.pythiaSDK.init({
      tenantId: 'tenant-123',
      jwtToken: 'token-abc',
      userId: 'user-456',
      currencyCode: 'USD',
      currencySymbol: '$',
      theme: 'dark',
      onLoginRequest: function() {
        alert('Please login');
      }
    });

    // Later...
    window.pythiaSDK.setTheme('light');
    window.pythiaSDK.scrollChanged(window.scrollY);
  </script>
</body>
</html>
```

###### After (Modern TypeScript)

```typescript
import { PythiaSDK } from '@pythiasport/betting-widget';

// Create instance
const sdk = new PythiaSDK();

// Set up event listeners
sdk.on('betslip:open', () => {
  console.log('Betslip opened');
});

sdk.on('betslip:close', () => {
  console.log('Betslip closed');
});

// Initialize
sdk.init({
  tenantId: 'tenant-123',
  jwtToken: 'token-abc',
  userId: 'user-456',
  currencyCode: 'USD',
  currencySymbol: '$',
  theme: 'dark',
  onLoginRequest: () => {
    alert('Please login');
  }
});

// Later...
sdk.setTheme('light');
sdk.notifyScrollChange(window.scrollY);

// Clean up when done
// sdk.destroy();
```

##### Framework-Specific Examples

###### React

```tsx
import { useEffect, useRef } from 'react';
import { PythiaSDK } from '@pythiasport/betting-widget';

function BettingWidget() {
  const sdkRef = useRef<PythiaSDK | null>(null);

  useEffect(() => {
    const sdk = new PythiaSDK();
    sdkRef.current = sdk;

    // Events
    sdk.on('betslip:open', () => console.log('Opened'));
    sdk.on('betslip:close', () => console.log('Closed'));

    // Initialize
    sdk.init({
      tenantId: 'tenant-123',
      jwtToken: 'token-abc',
      userId: 'user-456',
      currencyCode: 'USD',
      currencySymbol: '$',
      theme: 'dark',
    });

    // Cleanup
    return () => {
      sdk.destroy();
    };
  }, []);

  return <div id="betting-widget-container" />;
}
```

###### Vue 3

```vue
<template>
  <div id="betting-widget-container"></div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { PythiaSDK } from '@pythiasport/betting-widget';

const sdk = ref<PythiaSDK | null>(null);

onMounted(() => {
  const instance = new PythiaSDK();
  sdk.value = instance;

  // Events
  instance.on('betslip:open', () => console.log('Opened'));
  instance.on('betslip:close', () => console.log('Closed'));

  // Initialize
  instance.init({
    tenantId: 'tenant-123',
    jwtToken: 'token-abc',
    userId: 'user-456',
    currencyCode: 'USD',
    currencySymbol: '$',
    theme: 'dark',
  });
});

onUnmounted(() => {
  sdk.value?.destroy();
});
</script>
```

#### API Changes Summary

| Legacy API | Modern API | Notes |
|------------|------------|-------|
| `window.pythiaSDK.onBetslipOpen = fn` | `sdk.on('betslip:open', fn)` | Event-based |
| `window.pythiaSDK.onBetslipClose = fn` | `sdk.on('betslip:close', fn)` | Event-based |
| `window.pythiaSDK.init(opts)` | `sdk.init(opts)` | Same |
| `window.pythiaSDK.setTheme(theme)` | `sdk.setTheme(theme)` | Same |
| `window.pythiaSDK.setLanguage(language)` | `sdk.setLanguage(language)` | Same |
| `window.pythiaSDK.updateUserInfo(info)` | `sdk.updateUserInfo(info)` | Same |
| `window.pythiaSDK.setBetslipButtonOffset(o)` | `sdk.setBetslipButtonOffset(o)` | Same |
| `window.pythiaSDK.scrollChanged(y, x)` | `sdk.notifyScrollChange(y, x)` | Renamed |
| `window.pythiaSDK.getIframe()` | Internal | Not exposed |
| `window.pythiaSDK.insertIframe()` | Auto-handled | Not needed |
| `window.pythiaSDK.config` | Internal | Not exposed |
| `window.pythiaSDK.$iframe` | Internal | Not exposed |
| - | `sdk.destroy()` | New method |
| - | `sdk.on(event, fn)` | New method |
| - | `sdk.once(event, fn)` | New method |
| - | `sdk.off(event, fn)` | New method |

#### Event Names

| Legacy Callback | Modern Event | Description |
|----------------|--------------|-------------|
| `onBetslipOpen` | `'betslip:open'` | Betslip opened |
| `onBetslipClose` | `'betslip:close'` | Betslip closed |
| `onLoginRequest` callback | `'login:request'` | Login requested |
| - | `'scroll:up'` | Scroll to top requested |
| - | `'height:change'` | Iframe height changed |
| - | `'ready'` | Widget loaded |

#### TypeScript Support

Add types to your project:

```typescript
import type { 
  PythiaSDKOptions,
  UserInfo,
  ThemeType,
  BetslipButtonOffset 
} from '@pythiasport/betting-widget';

const options: PythiaSDKOptions = {
  tenantId: 'tenant-123',
  jwtToken: 'token',
  userId: 'user-456',
  currencyCode: 'USD',
  currencySymbol: '$',
  theme: 'dark'
};
```

#### Testing Your Migration

1. **Keep the old implementation running** in parallel for testing
2. **Test all events** - make sure they fire as expected
3. **Test theme switching** - verify dark/light mode works
4. **Test user updates** - ensure auth updates work
5. **Test scroll sync** - verify scroll position updates
6. **Test cleanup** - ensure `destroy()` works properly

#### Rollback Plan

If you need to rollback, you can always switch back to the legacy API:

```html
<!-- Rollback: use legacy build -->
<script src="https://unpkg.com/@pythiasport/betting-widget@latest/dist/legacy.global.js"></script>
```

Your original code will work immediately.

#### Recommended Migration Path

1. **Week 1-2**: Install new SDK, use legacy API (zero changes)
2. **Week 3-4**: Migrate one component/page to modern API
3. **Week 5-6**: Gradually migrate remaining components
4. **Week 7+**: Complete migration, remove legacy API

This gradual approach minimizes risk and allows thorough testing.

## Browser Support

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)

## License

MIT