<div align="center">

# @playanext/playa-yield-sdk

**Ad Monetization SDK for Chrome Extensions — Manifest V3 Compliant**

A lightweight, fully typed SDK that lets Chrome extension developers display ads and earn revenue through the [PlayaYield](https://www.playayield.com) platform. Built for Manifest V3 with zero remote code execution.

[![npm version](https://img.shields.io/npm/v/@playanext/playa-yield-sdk.svg)](https://www.npmjs.com/package/@playanext/playa-yield-sdk)
[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@playanext/playa-yield-sdk)](https://bundlephobia.com/package/@playanext/playa-yield-sdk)
[![TypeScript](https://img.shields.io/badge/TypeScript-100%25-blue.svg)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

</div>

---

## Why PlayaYield?

|                     |                                                                     |
| ------------------- | ------------------------------------------------------------------- |
| **MV3 Compliant**   | No remote code execution — all code ships bundled in your extension |
| **Lightweight**     | < 35 KB minified, tree-shakeable, zero dependencies                 |
| **Secure**          | All network requests routed through the background service worker   |
| **Type-Safe**       | Full TypeScript definitions with IDE autocomplete                   |
| **Auto-Tracking**   | Viewport-based impression tracking and click tracking — zero setup  |
| **Refreshable Ads** | Built-in auto-rotating ads for long-lived surfaces like side panels |
| **Debug Mode**      | Verbose logging you can toggle at build time or runtime             |

---

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Where Ads Can Appear](#where-ads-can-appear)
- [API Reference](#api-reference)
  - [`initializePlayaYield()`](#initializeplayayieldconfig-sdkconfig-void)
  - [`createAd()`](#createadoptions-adoptions-promiseadresult)
  - [`createManagedRefreshableAd()`](#createmanagedrefreshableadoptions-managedrefreshableadoptions-promisemanagedrefreshableadresult)
- [Use Cases](#use-cases)
- [Test Mode](#test-mode)
- [Chrome Web Store Compliance](#chrome-web-store-compliance)
- [Tracking & Privacy](#tracking--privacy)
- [Error Handling](#error-handling)
- [Build Output](#build-output)
- [Live Example](#live-example)
- [Support](#support)
- [License](#license)

---

## Installation

```bash
npm install @playanext/playa-yield-sdk
```

**Package details:**

- Bundle size: < 35 KB minified
- Zero runtime dependencies
- Tree-shakeable ESM and CJS builds
- Full TypeScript definitions included

---

## Quick Start

### 1. Initialize in Background Service Worker

```typescript
// background.ts
import { initializePlayaYield } from '@playanext/playa-yield-sdk';

initializePlayaYield({
  apiKey: 'your-api-key',
});
```

### 2. Configure manifest.json

```json
{
  "manifest_version": 3,
  "name": "Your Extension",
  "version": "1.0.0",
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["storage"]
}
```

> **No extra permissions required.** PlayaYield works within your existing extension permissions. Users won't see any new permission prompts when you add monetization.

### 3. Display Ads

```typescript
// popup.ts, sidepanel.ts, or content-script.ts
import { createAd } from '@playanext/playa-yield-sdk';

const { element, impressionId } = await createAd({
  placement: 'popup',
  size: { width: 300, height: 250 },
});

document.getElementById('ad-slot')!.appendChild(element);
```

That's it — impression and click tracking are handled automatically.

---

## Where Ads Can Appear

PlayaYield ads are designed to appear within your extension's own UI surfaces by default, with optional support for content scripts controlled by the backend.

**Allowed:**

- Extension Popup (`placement: 'popup'`)
- Side Panel (`placement: 'sidepanel'`)
- Options / Settings Page (`placement: 'internal'`)
- Content Scripts (`placement: 'content_script'`)

**SDK Validation:**

The SDK validates that the requested `placement` matches the actual execution context:

- Calling `createAd()` from a **content script** with `placement: 'popup'` (or `'sidepanel'` / `'internal'`) throws a `ConfigError` — you must use `placement: 'content_script'`.
- Calling `createAd()` from an **extension page** (popup, sidepanel, internal) with `placement: 'content_script'` is allowed, but typically you should match the page type.

**Backend Control:**

- The backend reads the `placement` query param to determine the request origin. When `placement=content_script`, the backend checks your extension's approval status and either serves an ad or returns `403 Forbidden`.
- Unapproved extensions that attempt content script ads will be denied by the backend.

---

## API Reference

### `initializePlayaYield(config: SDKConfig): void`

Initialize the SDK in your background service worker. Call once when your extension starts.

```typescript
interface SDKConfig {
  /** Your PlayaYield API key (required) */
  apiKey: string;
  /** Custom API endpoint URL — only needed for enterprise deployments */
  baseUrl?: string;
  /** Enable verbose SDK logging (default: false) */
  debug?: boolean;
}
```

**Example:**

```typescript
import { initializePlayaYield } from '@playanext/playa-yield-sdk';

initializePlayaYield({
  apiKey: 'your-api-key',
  debug: false,
});
```

> You can also toggle debug logging at runtime without changing code:
>
> ```js
> localStorage.setItem('playayield:debug', 'true');
> ```

---

### `createAd(options: AdOptions): Promise<AdResult>`

Request and render a one-time ad. Returns a ready-to-append `<div>` element.

Must be called from an **extension page** (popup, side panel, or internal page) or from a **content script**. Note that content scripts must use the `'content_script'` placement.

```typescript
interface AdOptions {
  /** Placement identifier — 'popup' | 'sidepanel' | 'internal' | 'content_script' */
  placement: 'popup' | 'sidepanel' | 'internal' | 'content_script';
  /** Ad dimensions. Allowed sizes: 300x250, 320x50, 320x100 */
  size: { width: number; height: number };
  /** Optional targeting categories */
  categories?: string[];
}
```

**Returns:**

```typescript
interface AdResult {
  /** Ready-to-append div element containing the ad */
  element: HTMLDivElement;
  /** Unique impression ID for tracking */
  impressionId: string;
  /** Click-through URL */
  clickUrl?: string;
}
```

**Example:**

```typescript
import { createAd } from '@playanext/playa-yield-sdk';

try {
  const { element, impressionId } = await createAd({
    placement: 'popup',
    size: { width: 300, height: 250 },
    categories: ['tech', 'productivity'],
  });

  document.getElementById('ad-container')!.appendChild(element);
  console.log('Ad displayed:', impressionId);
} catch (error) {
  console.error('Failed to load ad:', error);
}
```

---

### `createManagedRefreshableAd(options: ManagedRefreshableAdOptions): Promise<ManagedRefreshableAdResult>`

Create a fully managed, auto-refreshing ad for long-lived surfaces like side panels. The SDK handles appending, refresh intervals, session impression limits, and cleanup.

Refreshable ads **significantly increase revenue** compared to static placements because they rotate new creatives automatically. Refresh timing and impression caps are controlled dynamically by the backend.

```typescript
interface ManagedRefreshableAdOptions {
  /** Container element or CSS selector where the ad will be appended */
  container: HTMLElement | string;
  /** Placement identifier */
  placement: 'popup' | 'sidepanel' | 'internal';
  /** Ad dimensions. Allowed sizes: 300x250, 320x50, 320x100 */
  size: { width: number; height: number };
  /** Optional targeting categories */
  categories?: string[];
}
```

**Returns:**

```typescript
interface ManagedRefreshableAdResult {
  /** Stop refreshing and clean up resources */
  stop: () => void;
  /** Check if the ad is still actively refreshing */
  isActive: () => boolean;
}
```

**Example — CSS selector:**

```typescript
import { createManagedRefreshableAd } from '@playanext/playa-yield-sdk';

const managed = await createManagedRefreshableAd({
  container: '#ad-slot',
  placement: 'sidepanel',
  size: { width: 300, height: 250 },
});

// Optional: stop when user navigates away
window.addEventListener('beforeunload', () => managed.stop());
```

**Example — element reference:**

```typescript
const managed = await createManagedRefreshableAd({
  container: document.getElementById('ad-slot')!,
  placement: 'sidepanel',
  size: { width: 300, height: 250 },
});
```

> **Auto-cleanup:** The SDK uses a `MutationObserver` to detect when the container is removed from the DOM and stops the refresh cycle automatically.

---

## Use Cases

### Banner Ad in Popup

```typescript
import { createAd } from '@playanext/playa-yield-sdk';

const { element } = await createAd({
  placement: 'popup',
  size: { width: 300, height: 250 },
});

document.getElementById('banner-slot')!.appendChild(element);
```

### Refreshable Ad in Side Panel

```typescript
import { createManagedRefreshableAd } from '@playanext/playa-yield-sdk';

await createManagedRefreshableAd({
  container: '#ad-slot',
  placement: 'sidepanel',
  size: { width: 300, height: 250 },
});
```

### Multiple Ad Placements

```typescript
import { createAd } from '@playanext/playa-yield-sdk';

const [ad1, ad2] = await Promise.all([
  createAd({ placement: 'popup', size: { width: 300, height: 250 } }),
  createAd({ placement: 'popup', size: { width: 320, height: 50 } }),
]);

document.getElementById('ad-slot-1')!.appendChild(ad1.element);
document.getElementById('ad-slot-2')!.appendChild(ad2.element);
```

### Custom Styling

```typescript
import { createAd } from '@playanext/playa-yield-sdk';

const { element } = await createAd({
  placement: 'popup',
  size: { width: 300, height: 250 },
});

element.style.borderRadius = '8px';
element.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
element.style.margin = '20px auto';

document.getElementById('ad-slot')!.appendChild(element);
```

---

## Test Mode

Use test mode during development by using your test API key. The backend determines test vs production based on the API key.

```typescript
initializePlayaYield({
  apiKey: 'dev_test_key',
  // Optional: point to a dedicated staging API
  // baseUrl: 'https://your-test-host.example'
});
```

**What you get in test mode:**

- Requests are tagged as test-mode server-side via the API key
- Safe separation between test and production analytics
- Real integration testing for placement, rendering, and tracking
- Optional custom `baseUrl` for dedicated staging environments

---

## Chrome Web Store Compliance

Extensions integrating the PlayaYield SDK should include disclosure language to ensure Chrome Web Store approval.

### Suggested Store Description Language

```
Our extension provides ads within the extension interface,
including in the popup, the side panel, and/or other
internal extension pages.
```

### Suggested Privacy Policy Language

```
This extension may display advertisements provided by third-party
partners, including PlayaYield. To deliver and measure advertising,
limited technical information such as IP address, device type,
browser type, and interaction data may be transmitted to advertising
partners. For more information, see PlayaYield's Privacy Policy:
https://www.playayield.com/privacy
```

---

## Tracking & Privacy

PlayaYield handles all tracking automatically — no additional code required.

- **Impressions** — Tracked when the ad is at least 50% visible in the viewport via `IntersectionObserver`
- **Clicks** — Tracked automatically before opening the destination URL
- **Session Management** — Refreshable ads enforce per-session impression limits with rolling window tracking

**Privacy:**

- No personal data collection or third-party cookies
- Ads only appear in extension UI (popup, side panel, internal pages)
- No injection into user-facing websites
- Chrome Web Store compliant

---

## Error Handling

The SDK throws typed errors that extend `PlayaYieldError`. Always wrap ad requests in try-catch blocks.

```typescript
import { createAd } from '@playanext/playa-yield-sdk';

try {
  const { element } = await createAd({
    placement: 'popup',
    size: { width: 300, height: 250 },
  });
  document.getElementById('ad-slot')!.appendChild(element);
} catch (error) {
  if (error instanceof Error) {
    switch (error.name) {
      case 'ConfigError':
        // SDK not initialized, missing params, or invalid config
        console.error('Configuration error:', error.message);
        break;
      case 'NetworkError':
        // Request failed, timeout, or server error
        console.error('Network error:', error.message);
        break;
      default:
        console.error('Unexpected error:', error);
    }
  }
}
```

| Error Class       | Code            | Common Causes                                                                                                                                        |
| ----------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ConfigError`     | `CONFIG_ERROR`  | Missing `apiKey`, missing `placement` / `size`, calling `createAd()` before `initializePlayaYield()`, unsupported ad size, unapproved content script |
| `NetworkError`    | `NETWORK_ERROR` | No internet, server timeout, invalid API key                                                                                                         |
| `PlayaYieldError` | _(base class)_  | All SDK errors extend this — includes `code` and optional `details` properties                                                                       |

---

## Build Output

The SDK is built with [tsup](https://tsup.egoist.dev) and outputs:

| Format | File            |
| ------ | --------------- |
| ESM    | `dist/sdk.mjs`  |
| CJS    | `dist/sdk.js`   |
| Types  | `dist/sdk.d.ts` |

All formats are tree-shakeable and optimized for minimal bundle size.

---

## Live Example

See the [`example/`](./example) directory for a complete working Chrome extension that demonstrates background service worker setup, popup ad display, error handling, and modern UI styling.

```bash
cd example
npm install
npm run build
```

Then load the `example/` directory as an unpacked extension in Chrome.

A production example is also available on the Chrome Web Store:

> **[Timezone Freedom](https://chromewebstore.google.com/detail/timezone-freedom/cciepcccllgemmoapkgpdffgjpocdpmj)** — A Chrome Web Store-approved extension using PlayaYield in production.

---

## Support

- **Documentation** — [playayield.com/docs](https://www.playayield.com/docs)
- **Issues** — [GitHub Issues](https://github.com/playanext/playa-yield-sdk/issues)
- **Email** — [support@playanext.com](mailto:support@playanext.com)

---

## License

[MIT](LICENSE) © PlayaYield
