# @sophi/widget-launcher

Sophi Widget Launcher adds an AI shopping assistant to your storefront. It renders a floating button that opens a full-screen chat modal powered by the Sophi Web SDK.

---

## Table of Contents

- [Quick Start](#quick-start)
  - [Option A — CDN (recommended for GTM)](#option-a--cdn-recommended-for-gtm)
  - [Option B — npm / ESM](#option-b--npm--esm)
- [GTM Setup Guide](#gtm-setup-guide)
- [Configuration](#configuration)
  - [Required fields](#required-fields)
  - [All options](#all-options)
- [Event Handlers](#event-handlers)
- [JavaScript API](#javascript-api)
- [Identifying Users](#identifying-users)
- [Advanced: Disable the Launcher Button](#advanced-disable-the-launcher-button)
- [Advanced: Manual SDK Loading](#advanced-manual-sdk-loading)

---

## Quick Start

### Option A — CDN (recommended for GTM)

Add the following snippet to your page **before `</body>`**. Replace the placeholder values with your actual API key and SDK URL.

```html
<script>
  window.sophiWidgetConfig = {
    apiKey: "YOUR_API_KEY",
    sdk: {
      autoLoad: true,
      url: "https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.js"
    }
  };
</script>
<script
  src="https://cdn.jsdelivr.net/npm/@usesophi/sophi-widget-launcher@latest/dist/sophi-widget-launcher.global.global.js"
  async
></script>
```

---

### Option B — npm / ESM

```bash
npm install @sophi/widget-launcher
```

```ts
import { initSophiWidgetLauncher } from "@sophi/widget-launcher";

initSophiWidgetLauncher({
  apiKey: "YOUR_API_KEY",
  sdk: {
    autoLoad: true,
    url: "https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.js"
  }
});
```

---

## GTM Setup Guide

This section walks you through installing Sophi Widget Launcher via **Google Tag Manager** without touching your site's source code.

### Prerequisites

- Access to your GTM container (Publish permission required)
- Your Sophi API key
- The Sophi Web SDK CDN URL for your environment
- The Sophi Widget Launcher CDN URL

---

### Step 1 — Create the Config Tag

This tag sets up `window.sophiWidgetConfig` **before** the launcher script runs.

1. In GTM, go to **Tags → New**.
2. Click **Tag Configuration → Custom HTML**.
3. Paste the following snippet, replacing the placeholder values:

```html
<script>
  window.sophiWidgetConfig = {
    apiKey: "YOUR_API_KEY",
    environment: "production",

    sdk: {
      autoLoad: true,
      url: "https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.js"
    },

    launcher: {
      enabled: true,
      position: "right-bottom",
      text: "Ask Sophi",
      backgroundColor: "#111827",
      textColor: "#ffffff"
    },

    modal: {
      title: "Sophi"
    },

    handlers: {
      addToCart: function (data) {
        // Replace with your storefront's add-to-cart logic
        console.log("Sophi → addToCart", data);
      },
      sendToCheckout: function (data) {
        // Replace with your storefront's checkout logic
        console.log("Sophi → sendToCheckout", data);
      },
      addToFavorite: function (data) {
        console.log("Sophi → addToFavorite", data);
      }
    }
  };
</script>
```

4. Name the tag **`Sophi - Widget Config`**.
5. Under **Triggering**, select **All Pages** (or a more specific trigger if needed).
6. Save the tag.

---

### Step 2 — Create the Launcher Script Tag

This tag loads the actual launcher bundle from the CDN.

1. In GTM, go to **Tags → New**.
2. Click **Tag Configuration → Custom HTML**.
3. Paste the following:

```html
<script
  src="https://cdn.jsdelivr.net/npm/@usesophi/sophi-widget-launcher@latest/dist/sophi-widget-launcher.global.global.js"
  async
></script>
```

> Replace `x.y.z` with the exact version you want to use (e.g. `1.0.0`). Avoid `@latest` in production.

4. Name the tag **`Sophi - Widget Launcher`**.
5. Under **Triggering**, select **All Pages**.
6. Save the tag.

---

### Step 3 — Set Tag Firing Order

The config tag **must** run before the launcher script. Enforce this with tag sequencing:

1. Open the **`Sophi - Widget Launcher`** tag.
2. Expand **Advanced Settings → Tag Sequencing**.
3. Check **Fire a tag before [this tag] fires**.
4. Select **`Sophi - Widget Config`** from the dropdown.
5. Save.

This guarantees `window.sophiWidgetConfig` is defined before the launcher script executes.

---

### Step 4 — (Optional) Pass the Logged-In User ID

If your site renders the logged-in user ID in the page, you can forward it to Sophi via a Data Layer variable.

**Push the user ID to the data layer** (add this to your site's authentication code or another GTM tag):

```html
<script>
  dataLayer.push({
    event: "userLoggedIn",
    sophiUserId: "{{ YOUR_USER_ID_VARIABLE }}"
  });
</script>
```

Then update the config tag to read from GTM variables:

```html
<script>
  window.sophiWidgetConfig = {
    apiKey: "YOUR_API_KEY",
    userId: "{{DLV - sophiUserId}}",      // Data Layer Variable
    userName: "{{DLV - sophiUserName}}",  // Data Layer Variable (optional)
    sdk: { autoLoad: true, url: "..." }
  };
</script>
```

For dynamic login/logout after page load, call the JavaScript API directly instead (see [Identifying Users](#identifying-users)).

---

### Step 5 — Preview and Test

1. Click **Preview** in GTM to enter debug mode.
2. Navigate to your site — the floating Sophi button should appear.
3. In the GTM debug panel, verify both tags show as **Fired** on page load.
4. Click the Sophi button — the chat modal should open and the widget should initialize.
5. Trigger an add-to-cart action inside the widget and confirm your `addToCart` handler is called (check the browser console).

---

### Step 6 — Publish

Once testing looks good:

1. Click **Submit** in GTM.
2. Add a version name (e.g. `Add Sophi Widget Launcher`).
3. Click **Publish**.

The widget is now live for all visitors.

---

### GTM Tag Summary

| Tag name | Type | Trigger | Fires before |
|---|---|---|---|
| `Sophi - Widget Config` | Custom HTML | All Pages | — |
| `Sophi - Widget Launcher` | Custom HTML | All Pages | `Sophi - Widget Config` |

---

### Consent Mode (Optional)

If your site uses a cookie consent solution (e.g. CookieBot, OneTrust), you should fire the launcher tags **only after consent is granted**.

Instead of **All Pages**, set the trigger to fire on your consent-granted event, for example:

- CookieBot: `cookiebot_accept` event
- OneTrust: `OneTrustGroupsUpdated` event
- Custom: `sophiConsentGranted` — a custom event you push to `dataLayer` after the visitor accepts

This ensures the launcher only loads for visitors who have consented to analytics/marketing cookies.

---

## Configuration

Set `window.sophiWidgetConfig` **before** the launcher script loads, or pass the same object to `initSophiWidgetLauncher()`.

### Required fields

| Field | Type | Description |
|---|---|---|
| `apiKey` | `string` | Your Sophi API key. |

### All options

```ts
window.sophiWidgetConfig = {
  // Required
  apiKey: "YOUR_API_KEY",

  // Optional — identify the current user
  userId: "user-123",
  userName: "Jane Doe",

  // Optional — "production" (default) | "staging" | "development"
  environment: "production",

  // SDK auto-loading
  sdk: {
    autoLoad: true,            // Set true to let the launcher load the SDK script
    url: "https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.js"
  },

  // Floating launcher button
  launcher: {
    enabled: true,             // Set false to hide the button and control via JS API
    position: "right-center",  // "right-center" | "left-center" | "right-bottom" | "left-bottom"
    text: "Start Chat",        // Label shown on hover
    backgroundColor: "#111827",
    textColor: "#ffffff"
  },

  // Chat modal
  modal: {
    title: "Sophi"             // Header text inside the modal
  },

  // Widget container (advanced — only change if you embed the widget elsewhere)
  widget: {
    containerSelector: "#sophi-widget",
    width: "100%",
    height: "100%"
  },

  // Event handlers (see Event Handlers section below)
  handlers: {
    addToCart: (data) => { /* ... */ },
    sendToCheckout: (data) => { /* ... */ },
    addToFavorite: (data) => { /* ... */ },
    error: (error) => { /* ... */ }
  }
};
```

---

## Event Handlers

Sophi emits commerce events that your storefront must handle. You can register handlers in `sophiWidgetConfig.handlers` (preferred) or as global window functions.

| Event | Config handler | Global fallback |
|---|---|---|
| User adds a product to cart | `handlers.addToCart(data)` | `window.sophiAddToCart(data)` |
| User proceeds to checkout | `handlers.sendToCheckout(data)` | `window.sophiSendToCheckout(data)` |
| User adds a product to favourites | `handlers.addToFavorite(data)` | `window.sophiAddToFavorite(data)` |
| Widget error | `handlers.error(error)` | — |

**Example using config handlers:**

```html
<script>
  window.sophiWidgetConfig = {
    apiKey: "YOUR_API_KEY",
    sdk: { autoLoad: true, url: "..." },
    handlers: {
      addToCart: function (data) {
        console.log("Add to cart:", data);
        // call your storefront cart API here
      },
      sendToCheckout: function (data) {
        console.log("Send to checkout:", data);
        // redirect or open checkout here
      }
    }
  };
</script>
```

**Example using global window functions (alternative):**

```html
<script>
  window.sophiAddToCart = function (data) {
    // call your storefront cart API here
  };

  window.sophiSendToCheckout = function (data) {
    // redirect or open checkout here
  };
</script>
```

---

## JavaScript API

After the script loads, `window.sophiWidget` exposes the following methods. You can call these from any JavaScript on the page.

```js
// Open the chat modal (initializes the widget on first call)
window.sophiWidget.open();

// Close the chat modal
window.sophiWidget.close();

// Manually initialize the widget without opening the modal
window.sophiWidget.init();

// Destroy the widget instance (e.g. on logout)
window.sophiWidget.destroy();

// Check whether the widget has been initialized
window.sophiWidget.isInitialized(); // → boolean

// Check whether the modal is currently open
window.sophiWidget.isOpen(); // → boolean

// Get the underlying SDK instance for advanced use
window.sophiWidget.getInstance();

// Set or update the current user ID
window.sophiWidget.setUserId("user-123");

// Set or update both user ID and display name in one call
window.sophiWidget.setUser("user-123", "Jane Doe");

// Subscribe to SDK events
window.sophiWidget.on("add_to_cart", (data) => { /* ... */ });

// Unsubscribe from SDK events
window.sophiWidget.off("add_to_cart", handler);
```

> The widget is **not** initialized automatically on page load. It initializes on the first `open()` or `init()` call.

---

## Identifying Users

Pass `userId` and optionally `userName` in the config to associate sessions with a known user:

```html
<script>
  window.sophiWidgetConfig = {
    apiKey: "YOUR_API_KEY",
    userId: "user-123",    // logged-in user ID
    userName: "Jane Doe",  // display name (optional)
    sdk: { autoLoad: true, url: "..." }
  };
</script>
```

To update the user after the page has loaded (e.g. after login or logout):

```js
// After login — set both ID and display name
window.sophiWidget.setUser("user-456", "Jane Doe");

// After login — set only the user ID (userName unchanged)
window.sophiWidget.setUserId("user-456");

// After logout — pass null or undefined to clear the user
window.sophiWidget.setUser(null);
```

Both `setUser` and `setUserId` handle the session safely:

- If the widget has not been opened yet, the new values will be used on next open.
- If the modal is open, the widget is restarted immediately with the new values.
- If the modal is closed, the widget is destroyed and will restart with the new values on the next open.

---

## Advanced: Disable the Launcher Button

Set `launcher.enabled: false` to hide the floating button and control the widget entirely through the JavaScript API. This is useful when you want to trigger Sophi from your own button or link.

```html
<script>
  window.sophiWidgetConfig = {
    apiKey: "YOUR_API_KEY",
    sdk: { autoLoad: true, url: "..." },
    launcher: { enabled: false }
  };
</script>

<!-- Your own trigger -->
<button onclick="window.sophiWidget.open()">Ask Sophi</button>
```

---

## Advanced: Manual SDK Loading

If you load the Sophi Web SDK yourself (e.g. it is already bundled in your app), omit `sdk.autoLoad`. The launcher will use `window.SophiWebSDK` directly.

```html
<!-- Load the SDK first -->
<script src="https://cdn.jsdelivr.net/npm/@usesophi/sophi-web-sdk@latest/dist/index.umd.js"></script>

<!-- Then load the launcher without autoLoad -->
<script>
  window.sophiWidgetConfig = {
    apiKey: "YOUR_API_KEY"
    // sdk.autoLoad is false by default
  };
</script>
<script src="https://cdn.jsdelivr.net/npm/@usesophi/sophi-widget-launcher@latest/dist/sophi-widget-launcher.global.global.js" async></script>
```
